你的網站或許不須要前端構建

本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或從新修改使用,但須要註明來源。 署名 4.0 國際 (CC BY 4.0)css

本文做者: 蘇洋html

建立時間: 2019年05月27日 統計字數: 8760字 閱讀時間: 18分鐘閱讀 本文連接: soulteary.com/2019/05/27/…前端


你的網站或許不須要前端構建

自從幾年前 Webpack 替換掉了 GulpGrunt 後,咱們能夠明顯看到前端項目的工程複雜度愈來愈高,前端技術迭代速度也愈來愈快。vue

大廠也好、培訓班也罷,都針對 Webpack、Babel 、ESLint 前端工程工具三巨頭貢獻出了數不勝數分享和案例。html5

可是隨之而來的是,前端項目幾乎沒有了往日的「簡單愉快」,想用流行框架寫一個項目,通常得先整一個腳手架,若是你寫的程序沒有「經歷前端構建」,整的你都很差意思和同行打招呼。webpack

這篇文章會以兩個簡單的例子來講明,即便不配置腳手架、使用一些「老傢伙」同樣能夠開發出高性能的網站。git

額外說明

本篇文章並不徹底適用十幾人乃至幾十人以上團隊規模的複雜、須要高密度的協做項目,僅針對中小型項目,諸如簡單的後臺、流程配置、甚至是 Demo。github

碎碎唸了這麼多,讓咱們正式開始迴歸愉快的前端開發web

從一個簡單的「單頁」應用開始

不管是使用 ReactVue 仍是使用更有年代感的 jQuery ,作一個簡單的頁面,不外乎分別完成 「頁面結構」、「頁面風格」、「頁面功能」 三個部分的編寫。算法

咱們使用如今比較流行的 Vue 舉個例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>簡單的頁面</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ant-design-vue@1.3.8/dist/antd.min.css">
    <!-- 你也能夠選擇保存在本地使用, 腳本資源也是同樣 -->
    <!-- <link rel="stylesheet" href="assets/common/antd-v1.3.8.min.css"> -->
    <style>body{color:#2c3e50}#header{height:50px;background:#fff;border-bottom:1px solid #eceef1}#header-nav{float:left;height:50px}#header-search{float:right;width:180px;margin:4px}#header-button{float:right;height:50px;overflow:hidden;line-height:50px}#has-team-news{top:-7px;left:-3px}.logo{width:120px;height:100%;line-height:50px;font-weight:bold;background:rgba(255,255,255,.2);float:left}#left-menu{margin-top:10px}#left-menu-wrap{padding-left:10px;margin-left:10px}#top-switch{margin-top:10px;overflow:hidden}#top-switch-2{float:right;overflow:hidden;width:100px;height:20px;line-height:20px;margin-top:10px}#top-switch-2 a{font-size:12px}#top-switch-2 a.grey{color:gray}#top-divider{margin:10px}#post-container{margin:10px}.slick-slide{text-align:center;height:160px;line-height:160px;background:#364d79;overflow:hidden}.slick-slide h3{color:#fff}#carousel{margin:10px}.demo-loadmore-list{min-height:350px}.post-meta{display:inline-block;font-size:13px;line-height:13px;height:13px;overflow:hidden;font-style:italic;margin-right:4px}.desc{margin:14px 0;font-size:16px}#tag-list .ant-tag{margin-bottom:8px}.item-people{margin:10px 0}#ranking .ant-tabs-top-bar{margin-bottom:0}#car-list,#cars-list{border-top:none}</style>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/ant-design-vue@1.3.8/dist/antd.min.js"></script>
</head>
<body>

    <div id="app">
        <a-layout>
           <a-layout-header id="header">
              <div class="logo">博客Logo擺放</div>
              <a-menu mode="horizontal" :defaultSelectedKeys="['1']" id="header-nav">
                 <a-menu-item key="1">首頁</a-menu-item>
                 <a-menu-item key="2">
                    團隊
                    <a-badge id="has-team-news" status="success"></a-badge>
                 </a-menu-item>
                 <a-menu-item key="3">標籤</a-menu-item>
              </a-menu>
              <a-button-group id="header-button">
                 <a-button icon="file-text"></a-button>
                 <a-button icon="star"></a-button>
                 <a-button icon="user"></a-button>
              </a-button-group>
              <a-input-search id="header-search" placeholder="你不知道啥?問我鴨" />
           </a-layout-header>
           <a-layout-content>
              <a-row type="flex">
                 <a-col :span="4">
                    <div id="left-menu-wrap">
                       <a-menu id="left-menu" mode="inline" :openKeys="openKeys" @openChange="onMenuOpenChange">
                          <a-sub-menu key="sub0">
                             <span slot="title">
                                <a-icon type="home"></a-icon>
                                <span>推薦</span>
                             </span>
                             <a-menu-item key="1">牛逼的比賽</a-menu-item>
                             <a-menu-item key="2">犀利的觀點</a-menu-item>
                             <a-menu-item key="3">給力的事件</a-menu-item>
                             <a-menu-item key="4">特別的曝光</a-menu-item>
                          </a-sub-menu>
                          <a-sub-menu key="sub1">
                             <span slot="title">
                                <a-icon type="html5"></a-icon>
                                <span>前端</span>
                             </span>
                             <a-menu-item key="1">最佳實踐</a-menu-item>
                             <a-menu-item key="2">基礎知識</a-menu-item>
                             <a-menu-item key="3">多彩樣式</a-menu-item>
                             <a-menu-item key="4">有趣腳本</a-menu-item>
                          </a-sub-menu>
                          <a-sub-menu key="sub2">
                             <span slot="title">
                                <a-icon type="codepen"></a-icon>
                                <span>後端</span>
                             </span>
                             <a-menu-item key="5">Option 5</a-menu-item>
                             <a-menu-item key="6">Option 6</a-menu-item>
                             <a-sub-menu key="sub3" title="Submenu">
                                <a-menu-item key="7">Option 7</a-menu-item>
                                <a-menu-item key="8">Option 8</a-menu-item>
                             </a-sub-menu>
                          </a-sub-menu>
                          <a-sub-menu key="sub3">
                             <span slot="title">
                                <a-icon type="appstore"></a-icon>
                                <span>運維</span>
                             </span>
                             <a-menu-item key="9">Option 9</a-menu-item>
                             <a-menu-item key="10">Option 10</a-menu-item>
                             <a-menu-item key="11">Option 11</a-menu-item>
                             <a-menu-item key="12">Option 12</a-menu-item>
                          </a-sub-menu>
                          <a-sub-menu key="sub4">
                             <span slot="title">
                                <a-icon type="html5"></a-icon>
                                <span>算法</span>
                             </span>
                             <a-menu-item key="9">Option 9</a-menu-item>
                             <a-menu-item key="10">Option 10</a-menu-item>
                             <a-menu-item key="11">Option 11</a-menu-item>
                             <a-menu-item key="12">Option 12</a-menu-item>
                          </a-sub-menu>
                          <a-sub-menu key="sub5">
                             <span slot="title">
                                <a-icon type="html5"></a-icon>
                                <span>分類</span>
                             </span>
                             <a-menu-item key="9">Option 9</a-menu-item>
                             <a-menu-item key="10">Option 10</a-menu-item>
                             <a-menu-item key="11">Option 11</a-menu-item>
                             <a-menu-item key="12">Option 12</a-menu-item>
                          </a-sub-menu>
                          <a-sub-menu key="sub6">
                             <span slot="title">
                                <a-icon type="html5"></a-icon>
                                <span>分類</span>
                             </span>
                             <a-menu-item key="9">Option 9</a-menu-item>
                             <a-menu-item key="10">Option 10</a-menu-item>
                             <a-menu-item key="11">Option 11</a-menu-item>
                             <a-menu-item key="12">Option 12</a-menu-item>
                          </a-sub-menu>
                       </a-menu>
                    </div>
                 </a-col>
                 <a-col :span="14">
                    <a-carousel id="carousel" autoplay>
                       <div>
                          <h3>涼風有幸 1</h3>
                       </div>
                       <div>
                          <h3>秋月無邊 2</h3>
                       </div>
                       <div>
                          <h3>啦啦啦啦 3</h3>
                       </div>
                       <div>
                          <h3>置頂精選 4</h3>
                       </div>
                    </a-carousel>
                    <div id="top-switch">
                       <a-dropdown>
                          <a-menu slot="overlay" @click="handleTopMenuClick">
                             <a-menu-item key="1">
                                <a-icon type="user"></a-icon>
                                編輯精選
                             </a-menu-item>
                             <a-menu-item key="2">
                                <a-icon type="user"></a-icon>
                                最新發布
                             </a-menu-item>
                          </a-menu>
                          <a-button style="margin-left: 8px">
                             編輯精選
                             <a-icon type="down" />
                          </a-button>
                       </a-dropdown>
                       <div id="top-switch-2">
                          <a href="#">熱門</a>
                          <a-divider type="vertical"></a-divider>
                          <a href="#" class="grey">最新</a>
                       </div>
                       <a-divider id="top-divider"></a-divider>
                    </div>
                    <div id="post-container">
                       <a-list class="demo-loadmore-list" :pagination="pagination" :loading="loading"
                          itemLayout="horizontal" :dataSource="postDataSource" :locale="{emptyText: '暫無數據'}">
                          <div v-if="showLoadingMore" slot="loadMore"
                             :style="{ textAlign: 'center', marginTop: '12px', height: '32px', lineHeight: '32px' }">
                             <a-spin v-if="loadingMore" />
                             <a-button v-else @click="onLoadMore">loading more</a-button>
                          </div>
                          <a-list-item v-for="(item, index) in postDataSource">
                             <a-card style="width:100%">
                                <h2>這是一篇博客的標題,可能很長很長很長很長</h2>
                                <div class="post-meta">
                                   <a-icon type="user"></a-icon>
                                   @nickname
                                </div>
                                <div class="post-meta">
                                   <a-icon type="clock-circle"></a-icon>
                                   10分鐘前
                                </div>
                                </a-avatar>
                                <p class="desc">
                                   簡單的內容描述。
                                </p>
                                <div style="float:left">
                                   <a-tag>前端</a-tag>
                                   <a-tag>工程工具</a-tag>
                                   <a-tag>方法論</a-tag>
                                </div>
                                <div class="post-meta" style="float:right">
                                   <span>
                                      <a-icon type="like" style="margin-right: 8px"></a-icon>
                                   </span>
                                   <span>
                                      <a-icon type="star" style="margin-right: 8px"></a-icon>
                                   </span>
                                   <span>
                                      <a-icon type="message" style="margin-right: 8px"></a-icon>
                                   </span>
                                </div>
                             </a-card>
                          </a-list-item>
                       </a-list>
                       <a-pagination :defaultCurrent="6" :total="500" />
                    </div>
                 </a-col>
                 <a-col :span="6">
                    <a-card style="margin: 10px; border-color: #42b983;">
                       <h3 style="color:#42b983">
                          <a-icon type="notification" style="margin-right: 8px"></a-icon>
                          這裏是一個公告標題
                       </h3>
                       <p>這裏有一個描述性詞彙描述性詞彙描述性詞彙描述性詞彙描述性詞彙</p>
                       <a-button style="width:100%;background:#42b983;border-color: #42b983;color: #fff;">開始瀏覽
                       </a-button>
                    </a-card>
                    <a-card id="tag-list" style="margin: 10px;">
                       <h3>
                          <a-icon type="tag" style="margin-right: 8px"></a-icon>
                          熱門標籤
                       </h3>
                       <a-divider></a-divider>
                       <a-tag>前端</a-tag>
                       <a-tag>工程工具</a-tag>
                       <a-tag>方法論</a-tag>
                       <a-tag>工程工具</a-tag>
                       <a-tag>方法論</a-tag>
                       <a-tag>前端</a-tag>
                       <a-tag>方法論</a-tag>
                       <a-tag>工程工具</a-tag>
                       <a-tag>方法論</a-tag>
                       <a-tag>前端</a-tag>
                    </a-card>
                    <a-tabs defaultActiveKey="2" style="margin: 10px;" type="card" id="ranking">
                       <a-tab-pane tab="月度優秀做者" key="1">
                          <a-card id="car-list">
                             <div class="item-people">
                                <a-avatar style="margin-right:4px">1</a-avatar>
                                <a href="#">做者 - 簡單描述</a>
                                <a-badge style="zoom:0.8" count="12"
                                   :numberStyle="{backgroundColor: '#8bc34a'} " />
                             </div>
                             <div class="item-people">
                                <a-avatar style="margin-right:4px">2</a-avatar>
                                <a href="#">做者 - 簡單描述</a>
                                <a-badge style="zoom:0.8" count="10"
                                   :numberStyle="{backgroundColor: '#8bc34a'} " />
                             </div>
                             <div class="item-people">
                                <a-avatar style="margin-right:4px">1</a-avatar>
                                <a href="#">做者 - 簡單描述</a>
                                <a-badge style="zoom:0.8" count="9"
                                   :numberStyle="{backgroundColor: '#8bc34a'} " />
                             </div>
                             <div class="item-people">
                                <a-avatar style="margin-right:4px">4</a-avatar>
                                <a href="#">做者 - 簡單描述</a>
                                <a-badge style="zoom:0.8" count="7"
                                   :numberStyle="{backgroundColor: '#8bc34a'} " />
                             </div>
                             <div class="item-people">
                                <a-avatar style="margin-right:4px">5</a-avatar>
                                <a href="#">做者 - 簡單描述</a>
                                <a-badge style="zoom:0.8" count="6"
                                   :numberStyle="{backgroundColor: '#8bc34a'} " />
                             </div>
                             <div class="item-people">
                                <a-avatar style="margin-right:4px">6</a-avatar>
                                <a href="#">做者 - 簡單描述</a>
                                <a-badge style="zoom:0.8" count="2"
                                   :numberStyle="{backgroundColor: '#8bc34a'} " />
                             </div>
                          </a-card>
                       </a-tab-pane>
                       <a-tab-pane tab="月度優秀做者" key="2">
                          <a-card id="cars-list">
                             <div class="item-people">
                                <a-avatar style="margin-right:4px">1</a-avatar>
                                <a href="#">做者</a>
                                <a-badge style="zoom:0.8" count="109"
                                   :numberStyle="{backgroundColor: '#8bc34a'} " />
                             </div>
                             <div class="item-people">
                                <a-avatar style="margin-right:4px">1</a-avatar>
                                <a href="#">做者</a>
                                <a-badge style="zoom:0.8" count="109"
                                   :numberStyle="{backgroundColor: '#8bc34a'} " />
                             </div>
                             <div class="item-people">
                                <a-avatar style="margin-right:4px">1</a-avatar>
                                <a href="#">做者</a>
                                <a-badge style="zoom:0.8" count="109"
                                   :numberStyle="{backgroundColor: '#8bc34a'} " />
                             </div>
                          </a-card>
                       </a-tab-pane>
                    </a-tabs>
                 </a-col>
              </a-row>
           </a-layout-content>
           <a-layout-footer>
              <div>
                 <a-divider>-EOF-</a-divider>
                 <a-divider type="vertical"></a-divider>
                 <a href="#">投稿</a>
                 <a-divider type="vertical"></a-divider>
                 <a href="#">關於</a>
              </div>
           </a-layout-footer>
        </a-layout>
    </div>

<script>
Vue.use(antd);

const posts = [[],[],[],[],[],[],]

var app = new Vue({
    el: '#app',
    data() {
        return {
            rootSubmenuKeys: ['sub0', 'sub1', 'sub2', 'sub3', 'sub4', 'sub5', 'sub6'],
            openKeys: ['sub0'],

            loading: true,
            loadingMore: false,
            showLoadingMore: true,
            postDataSource: posts,

            pagination: {
                onChange: (page) => {
                    console.log('Change Page', page);
                },
                pageSize: 3,
            },

        }
    },
    mounted() {
        this.getData((res) => {
            this.loading = false
            this.postDataSource = res
        })
    },
    methods: {
        onMenuOpenChange(openKeys) {
            const latestOpenKey = openKeys.find(key => this.openKeys.indexOf(key) === -1)
            if (this.rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
                this.openKeys = openKeys
            } else {
                this.openKeys = latestOpenKey ? [latestOpenKey] : []
            }
        },

        handleButtonClick(e) {
            console.log('Button Clicked', e);
        },
        handleTopMenuClick(e) {
            console.log('Top Menu Clicked', e);
        },

        getData(callback) {
            setTimeout(() => {
                callback(posts)
            }, 300)
        },
        onLoadMore() {
            this.loadingMore = true
            this.getData((res) => {
                this.postDataSource = this.postDataSource.concat(res)
                this.loadingMore = false
                this.$nextTick(() => {
                    window.dispatchEvent(new Event('resize'))
                })
            })
        },

    },
})
</script>

</body>
</html>
複製代碼

將上面的三百來行代碼保存爲 index.html, 使用瀏覽器直接打開,不出意外你將看到下面的界面。

一個簡單的單頁 Demo

簡單把玩以後,你必定會說,這個示例頁面沒有什麼複雜交互,並且這不就是官方的推薦用法之一嘛。

是的,但但願你可以看到,像上面這樣作一個樣子還說的過去的頁面,真的不是必須把構建工具也「摻和」進來,即便你把組件交互的部分填充完畢。

而開發過程,就能夠迴歸經典的「邊改邊刷新」,所見即所得了。

接下來,咱們來聊聊如何將上面的程序拆分爲模塊使用,讓多個頁面之間能夠複用模塊,固然仍是在不使用構建工具的前提下。

拆分功能模塊

將單一職責的功能抽象模塊化,能夠說是工程師們的平常。這樣作除了提升了可維護性、潛在的提高了頁面性能、讓軟件構建更靈活以外、最大的收益即是增長了功能模塊的可複用性。

咱們平常使用 webpack 的時候,必定有看到過被分割爲一堆名爲 chunk 文件的腳本,或者名稱可能叫作 vendorappcomponent 的文件。這些即是構建程序幫咱們切割的軟件模塊了,甚至是上面例子中引入的 *.min.js. 也是如此。

若是咱們不使用構建工具進行模塊拆分,該怎麼作呢?這裏面常見的坑有哪些呢?

  1. 拆分爲多個模塊以後,會涉及到額外的網絡資源獲取和解析處理。
  2. 拆分爲多個模塊以後,可能會涉及到額外的模塊依賴管理。
  3. 拆分爲多個模塊以後,會涉及到數據、狀態同步管理。

想要解決前兩個問題,能夠經過使用 Require.js 之類的資源加載器,來控制拆分後多出來的資源文件的加載和對模塊進行依賴管理,想了解這個老傢伙的細節,能夠瀏覽它的官方網站

而拆分後的模塊,想要保持書寫上的簡單明快,這裏選擇使用 Vue 的 Component 語法進行模塊保存,因此須要額外引入一個模塊解析器,原理很簡單,經過 XHR 方法將資源獲取後,使用正則將內容分別抽取爲「樣式」、「腳本」、「模版」,而後在合適的時機在瀏覽器環境執行。爲了簡化操做,我在 requirejs-vue 的基礎上進行了刪減,有興趣能夠圍觀源碼

至於第三個問題,不管是使用單例共享數據源、亦或者使用發佈訂閱模式傳遞數據、或者使用觀察者模式均可以,解決的手段還有不少,就不擴展了,本文暫且略過,你能夠挑一個你以爲順手的使用。

以上面的單頁程序爲例,咱們先編寫頁面框架。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>將模版分離</title>
    <link rel="stylesheet" href="assets/common/antd-v1.3.8.min.css">

    <script src="assets/common/vue-v2.6.10.min.js"></script>
    <script src="assets/common/moment-v2.24.0.min.js"></script>
    <script src="assets/common/antd-v1.3.8.min.js"></script>

    <script src="assets/common/require-v2.3.6.min.js"></script>

    <script>
        Vue.use(antd);
        requirejs && requirejs.config({
            baseUrl: './assets',
            paths: { 'vue': 'common/require-vue' },
            config: { 'vue': { 'css': 'inject', 'templateVar': '__template__' } }
        });
    </script>
</head>
<body>

    <div id="app">
        <a-layout>
            <a-layout-header id="header"></a-layout-header>

            <a-layout-content>
                <a-row type="flex">
                    <a-col id="navbar" :span="4"></a-col>
                    <a-col id="main" :span="14"></a-col>
                    <a-col id="sidebar" :span="6"></a-col>
                </a-row>
            </a-layout-content>

            <a-layout-footer id="footer"></a-layout-footer>
        </a-layout>
    </div>

    <script>
        requirejs([
            'vue!template/header.html',
            'vue!template/footer.html',
            'vue!template/navbar.html',
            'vue!template/sidebar.html',
            'vue!template/carousel.html',
            'vue!template/feed.html',
        ], function (header, footer, navbar, sidebar, carousel, feed) {

            var appInst = new Vue({ el: '#app' });

            var headerInst = new Vue({ el: '#header' });
            header.$mount();
            headerInst.$el.appendChild(header.$el);

            var footerInst = new Vue({ el: '#footer' });
            footer.$mount();
            footerInst.$el.appendChild(footer.$el);

            var navbarInst = new Vue({ el: '#navbar' });
            navbar.$mount();
            navbarInst.$el.appendChild(navbar.$el);

            var sidebarInst = new Vue({ el: '#sidebar' });
            sidebar.$mount();
            sidebarInst.$el.appendChild(sidebar.$el);

            var mainInst = new Vue({ el: '#main' });
            carousel.$mount();
            mainInst.$el.appendChild(carousel.$el);
            feed.$mount();
            mainInst.$el.appendChild(feed.$el);
        });
    </script>
</body>
</html>
複製代碼

相比較上一小節三百來行混雜了細節邏輯的代碼,這個長度只有不到一百行的代碼是否是邏輯清晰許多呢。

剛剛提到了模塊複用,其實也很簡單,好比咱們想實現一個「列表頁面」,能夠這麼寫:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>複用模塊的例子</title>
    <link rel="stylesheet" href="assets/common/antd-v1.3.8.min.css">

    <script src="assets/common/vue-v2.6.10.min.js"></script>
    <script src="assets/common/moment-v2.24.0.min.js"></script>
    <script src="assets/common/antd-v1.3.8.min.js"></script>

    <script src="assets/common/require-v2.3.6.min.js"></script>

    <script>
        Vue.use(antd);
        requirejs && requirejs.config({
            baseUrl: './assets',
            paths: { 'vue': 'common/require-vue' },
            config: { 'vue': { 'css': 'inject', 'templateVar': '__template__' } }
        });
    </script>
</head>
<body>

    <div id="app">
        <a-layout>
            <a-layout-header id="header"></a-layout-header>
            <a-layout-content>
                <a-row type="flex">
                    <a-col :pull="5" :push="5" :span="14" id="main"></a-col>
                </a-row>
            </a-layout-content>
            <a-layout-footer id="footer"></a-layout-footer>
        </a-layout>
    </div>

    <script>
        requirejs([
            'vue!template/header.html',
            'vue!template/footer.html',
            'vue!template/list.html',
        ], function (header, footer, submit) {
            var appInst = new Vue({ el: '#app' });

            var headerInst = new Vue({ el: '#header' });
            header.$mount();
            headerInst.$el.appendChild(header.$el);

            var footerInst = new Vue({ el: '#footer' });
            footer.$mount();
            footerInst.$el.appendChild(footer.$el);

            var mainInst = new Vue({ el: '#main' });
            submit.$mount();
            mainInst.$el.appendChild(submit.$el);

        });
    </script>
</body>
</html>
複製代碼

聊完頁面框架後,咱們接着來看看拆分出的模塊怎麼寫,以一個簡單的 header 模塊舉例:

<script>
define([], function() {
    return new Vue({
        template: __template__,
        data() {
            return {}
        },
        mounted() {},
        methods: {},
    })
});
</script>

<style>
#header{height:50px;background:#fff;border-bottom:1px solid #eceef1}#header-nav{float:left;height:50px}#header-search{float:right;width:180px;margin:4px}#header-button{float:right;height:50px;overflow:hidden;line-height:50px}#has-team-news{top:-7px;left:-3px}.logo{width:120px;height:100%;line-height:50px;font-weight:bold;background:rgba(255, 255, 255, .2);float:left}
</style>

<template>
    <div>
        <div class="logo">博客Logo擺放</div>
        <a-menu mode="horizontal" :defaultSelectedKeys="['1']" id="header-nav">
           <a-menu-item key="1">
              首頁
           </a-menu-item>
           <a-menu-item key="2">
              團隊
              <a-badge id="has-team-news" status="success"></a-badge>
           </a-menu-item>
           <a-menu-item key="3">
              標籤
           </a-menu-item>
        </a-menu>
        <a-button-group id="header-button">
           <a-button icon="file-text"></a-button>
           <a-button icon="star"></a-button>
           <a-button icon="user"></a-button>
        </a-button-group>
        <a-input-search id="header-search" placeholder="你不知道啥?問我鴨"></a-input-search>
     </div>
</template>
複製代碼

能夠看到,這徹底就是普通的 Vue 組件模版嘛。

其餘模塊的代碼能夠在這裏找到,拆分方式大同小異,在此就不進行贅述。

和上一小節不一樣的是,由於咱們使用了 XHR 的方式獲取資源,因此使用瀏覽器直接打開 HTML 頁面的方法來預覽效果,會獲得相似下面的報錯而沒法獲得想要的結果。

Access to XMLHttpRequest at 'file:///Users/soulteary/You-Dont-Need-Webpack/src/assets/template/header.html' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https. 
複製代碼

這裏解決的方法很簡單,將頁面扔到一個可以提供 HTTP 服務的程序裏就行了,可使用 Node(HTTP Server、Express、KOA)等方案、也可使用 Apache、Nginx、Caddy… 選擇你順手的工具就好啦。

在 GitHub 倉庫中,我提供了一個 docker-compose.yml 編排文件,若是你本地有安裝 Docker 的話,只須要 Clone 下來項目,接着執行 docker-compose up ,打開 localhost:10240/split.html 就能看到預覽結果了。

不使用構建工具拆分頁面模塊的例子

體驗加強

若是你想獲取和 Webpack 實時刷新頁面的開發體驗,能夠考慮全局安裝 browsersync 這個工具,除了根據文件是否修改來刷新頁面以外,這個老傢伙還能同步不一樣設備上,當前調試頁面的滾動、點擊事件等交互操做。介紹這個工具的具體細節,不在本文範疇,有興趣的小夥伴能夠訪問它的官方網站: www.browsersync.io/

在本例中,咱們將模塊拆分爲多個 .html 文件,雖然請求數多了,沒法像傳統腳本、樣式資源同樣享受服務端 combo 的能力。

可是由於使用了 HTML、又沒有通過構建壓縮混淆,配合 CMS 實時更新一些配置,改變頁面功能反而變得更容易進行操做了。畢竟上線後毋需構建發版。(能夠了解淘寶 TMS 模塊化方案)

看起來變多的請求

另外,若是實在對請求數敏感,能夠針對模塊加載器進行優化,實現相似 lsloader 之類的本地強緩存+資源版本管理的功能,減小請求獲取。不過已經 2019 年,這點請求數對於多路複用的 HTTP2,隨處可見的大帶寬徹底不是問題。

即便不使用 HTTPS(HTTP2)的方式打開頁面,進行模塊化拆分的頁面首屏體驗也優於未拆分頁面。

未拆分模塊的頁面

未進行模塊化拆分的頁面刷新後,會明顯出現頁面白屏抖動。

進行了模塊拆分的頁面

而拆分模塊的頁面,展現則會「順滑」許多,固然若是你追求極致,還能夠添加骨架屏。

若是上面的動圖還不夠清楚的話,能夠看兩種狀況下的性能測試。

未拆分模塊的頁面

未拆分頁面首幀雖然快,可是隨着業務腳本的複雜,Evaluate Script 的時間也會愈來愈長,致使 DOM Content Loaded 被無限滯後,在用戶體驗上會帶來卡頓感。

進行了模塊拆分的頁面

而進行拆分了頁面模塊拆分後,DOM Content Loaded 時機被極大的提早,雖然說總體腳本複雜度不變,可是單一模塊複雜度變低,伴隨 DCL 時間提早,模塊腳本的解析完畢時間也提早了。

最後

再次重申,本篇文章不是說咱們開發項目不進行腳手架配置、徹底不使用 Webpack 等前端優秀工具。

重點是在擁有搭建開發環境的能力後,在適合的場景下,咱們應該適當靈活變通,使用更簡單輕快的方案進行開發,騰出配置環境、安裝模塊的時間去作更有意思的事情。

本文示例中的界面,參考了 https://love2.io/掘金 的設計,感謝設計師們的辛苦付出。

—EOF


我如今有一個小小的折騰羣,裏面彙集了一些喜歡折騰的小夥伴。

在不發廣告的狀況下,咱們在裏面會一塊兒聊聊軟件、HomeLab、編程上的一些問題,也會在羣裏不按期的分享一些技術沙龍的資料。

喜歡折騰的小夥伴歡迎掃碼添加好友。(請註明來源和目的,不然不會經過審覈)

關於折騰羣入羣的那些事

相關文章
相關標籤/搜索