基於webpack4的VUE多頁腳手架

連接

——寫在前面

一、爲何要本身手寫一個腳手架?

在寫這個腳手架以前我也深深的問過本身,在我工做的項目中須要去從新寫一個腳手架嗎?或者說有那麼多已經寫好的腳手架爲什麼不採用?html

事情的通過是這樣的,在很早很早之前,我嘗試使用過VUE-CLI 2進行過項目開發,當時並不怎麼熟悉webpack以及一些打包編譯的相關知識,隨着頁面的增長!項目的體積的增大,致使總體build出來的包很是之大,公共文件也會隨之增大,加載速度也會隨之下降。後續的結果我就不作闡述了!vue

那麼!後來... vue-Cli 3.0誕生了,首次使用簡直是個救世的主,不管從速度仍是編譯過程體驗都很是好,並且還能夠經過vue.config.js自定義不少的配置,基本上徹底能夠自定義了,固然!也是隨着頁面的不斷增長核項目的增大,在這個時候。我開始發現我本身對於webpack或者說打包編譯的相關知識已經不能支撐我繼續自定義的開發下去了。發現了一些潛在的問題,可是並無實際的解決思路的時候,就能夠追述到一些基礎知識的欠缺。node

隨着項目的逐漸增大,尤爲是多頁應用的支持以及一些文件模塊化的拆分,包括一些tree-shaking的運用。儘管vue-cli3.0支持configureWebpack 這樣強大的API。可是仔細想一想,要想從事情的本質或根本上解決問題,首先自身要相對的熟悉,並在此基礎之上運用和操做,得以充分的發揮;因此仍是決定本身去了解以便更好的開發。webpack

二、如何去思考遇到的問題?

在項目的開發中,尤爲是在寫腳手架這種工具性的東西的時候,須要考慮到的場景和實際運用的時候,更多的是不能沉浸在本身的思惟之中,參考並學習別人的經驗是有必要的,從而得出一套符合本身的思路。ios

從最開始的目錄結構,以及模塊化的一些思考,如何更好的作到性能的優化等等,都是值得思考的問題所在,如何處理好本身的業務邏輯,針對不一樣的項目以及兼容性的考慮等等。git

——正文

在此以前咱們須要對webpack4的一些文檔或者API進行充分的瞭解,能夠參考官方文檔或者參考印記中文的webpack文檔,可是針對於webpack4的文檔原本介紹的不是很全面,在不少的API上面仍是以前的介紹,因此,有不少小夥伴在看文檔的時候發現並不能正常的進行操做,這時候能夠結合兩個不一樣版本的文檔進行研究,固然時間的消耗成本也是比較高的。github

三、一些基本的構建思路!
在此以前我將控制業務邏輯的代碼進行分離,腳手架是單獨存在的。二者目錄結構相互獨立,業務邏輯的代碼永遠不會干涉到腳手架的

對於一些最基礎的配置我就不一一講述了:web

webpack.config.js

module.exports = {
    mode:'development',
    entry: './***.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    module:{
        //...
    },
    plugins:[
        //...
    ]
};

以上是一些基本的配置方式,固然咱們能夠經過package.json文件中的scripts選項自定義一些基於命令行的配置:vue-router

package.josn

 "scripts": {
    "dev": "APP_ENV=dev webpack-dev-server --config core/dev.js",
    "prd": "APP_ENV=build webpack --config core/build.js",
    "build": "APP_ENV=build webpack --config core/build.js",
    "lint": "eslint --fix */*.vue *.js",
    "test:whale": "jest tests/*.js && npm run build"
  },

以上的一些配置僅供參考,分別能夠經過npm run dev | npm run prd | ...來進行相關的操做。關於更多的npm script 詳細參考

四、node_modules的一些介紹

關於node_modules這塊其實很是的龐大,這也是npm的一個巨大的社區。與其說是學習,不如說是抽一部分優秀的包來使用,要知道一個node_modules包是在作什麼事情,能夠經過npm搜索包進行瀏覽其詳細介紹,在對應的github地址進行相應的瞭解。

關於如何學習node_modules包並不在個人介紹範圍內,可是我會介紹一些經常使用的一些比較好用的包進行分享。並且在構建腳手架以前,必需要對不少的包進行相關的學習,不須要知道每一個包的源碼是什麼,可是至少須要知道一些包的做用和用法,好比.vue文件須要使用vue-loader進行解析,使用到一些語法校驗的時候須要用到eslint,基於webpack進行生成html頁面的時候須要用到html-webpack-plugin;ES6ES5須要用到一系列的@babel/xxx插件,等等..

我這裏不作一一的介紹,可是會在後續用到每個包的時候作相對的介紹便可。

——多入口的輸入和編譯的輸出

多入口的輸入相對比較簡單,能夠直接參考官方文檔
固然entry能夠接受一個對象進行多頁面的輸入,若是隻是起步階段建議使用一個入口文件進行編寫,例:

module.exports = {
    entry: index:'xxx/xxx/index.js'
}
////或
module.exports = {
    mode: isProd ? 'production' : 'development',
    entry: {
        index:'xxx/xxx/index.js'
    }
}

輸出能夠根據本身的須要配置output參數:

let path = require('path');
output: {
    filename: './js/[name].js',
    chunkFilename: './js/[name].js',
    path: path.resolve(__dirname, '../build/')
},
關於 module | plugins | optimization | ...等模塊的配置我就不詳細的說了,可是主要仍是要說一下各個模塊之間的配合使用。
五、module模塊的優化

首先module.noParse是一個必備的參數,能夠忽略一下大型的已經構建過的模塊,從而提升構建的性能,這裏放一個案例:

webpack.config.js

module:{
    noParse:/^(vue|vue-router|vuex|vuex-router-sync|lodash|echarts|axios)$/,
}

以上的案例忽略了vue | vue-router | vuex | ...等大型額已經構建過的模塊。(通俗易懂的說一下,webpack在打包時會將全部用到的模塊進行打包編譯,在這裏只是忽略了從新構建的過程,可是對於chunk的時候依然仍是會在內存堆裏面使用並打包。)

六、比較複雜的module.rules

其實相對來講,在webapck 4module.rules仍是和以前同樣的使用,針對不一樣的後綴的文件進行不一樣的處理,在這裏主要說一下藉助了happypack進行多線程處理,固然你也能夠從npm網站詳細瞭解。與此同時我也選擇放棄使用DllPlugin | DLLReferencePlugin插件,下面我就說一下,如何選擇?爲什麼放棄?

咱們都知道webpack在打包編譯時是單線程的運做,可是咱們的電腦已經很是強大,單線程的處理,第一是隨着項目的增大時間會增加,第二是有些空閒的cpu等硬件設備得不到充分的利用,這是有咱們選擇開啓多線程的操做,在node中是能夠開啓多線程的,只是咱們藉助了happypack這樣的工具來進行多線程的控制和使用,在webpack中,咱們可能須要處理到.js文件,也可能會用到.css文件,那麼在打包的時候會將全部的字符匯聚到內存中,進行大量的密集型運算,並提取拆分紅不一樣的塊,這時候咱們藉助多線程來處理便可,使用方法以下:

首先在當前項目中執行命令行npm i happypack -D在開發環境中安裝,引入:

const webpack = require('webpack');

例如咱們再處理.js文件時候須要使用cache-loaderbabel-loader時,只須要配置:

{
    test: /\.js$/,
    use: ['happypack/loader?id=babel' ],
    exclude: /node_modules/
}

並在plugins選項中配置:

new HappyPack({
    id: 'babel',
    cache: true,
    threads: require('os').cpus().length, //開幾個線程去處理 
    loaders: [ 'cache-loader','babel-loader?cacheDirectory' ]
    verbose: true,         //容許 HappyPack 輸出日誌 ,默認true
    //threadPool: happyThreadPool,
})

須要注意的只是rules中調用的HappyPackidplugins中實例化的id相同便可。

最後說一下爲什麼要放棄DllPlugin,這個插件是生成一個動態的連接文件,也就是你把你認爲不須要屢次重複編譯的文件經過DllPlugin插件如生成一個.js.json文件,當你第二次進行打包編譯的時候再經過DllReferencePlugin進行引入使用,這樣就會大大減小了編譯的數量,好處是能夠多一些固定的模塊包進行減小處理,可是後來我發現,在項目中只有模塊拆分的足夠細緻時候這個確實有很多做用,不然徒增一些步驟,由於每次在拆分塊的時候,不少的模塊是會進行從新組裝的。(以上只是我的觀點,僅供參考~)

七、介紹幾個簡單的plugins用法

若是是在開發環境,咱們須要對頁面進行熱更新,咱們能夠開啓:

plugins:[
    new webpack.NamedModulesPlugin(),
    new webpack.HotModuleReplacementPlugin()
]

NamedModulesPlugin是現實熱更新的模塊的名稱,HotModuleReplacementPlugin啓用HRM官方有介紹
若是你是使用VUE開發項目,那麼new VueLoaderPlugin()是必不可少的了。
注入一些全局變量使用參考:

new webpack.DefinePlugin({
    //...
}),

重點的說一下html-webpack-plugin這個插件,用於生成過一些頁面的小夥伴應該都有所瞭解,那麼在生成多個頁面的時候,咱們就須要new HtmlWebpackPlugin({ ... })多個實例便可,一般狀況下,咱們會經過循環入口文件進行循環注入一個數組中便可。
另外備註一部分參數的說明:

new HtmlWebpackPlugin({
    title: PAGES[k].title || 'title',
    chunks: chunks,
    filename:`${k}.html`,
    minify: {
        removeComments: true,       //Strip HTML comments
        collapseWhitespace: true,   //摺疊有助於文檔樹中文本節點的空白區域
    },                    //對html進行壓縮,默認false
    hash: PAGES[k].hash === true ? true : false,      //默認false
    template: PAGES[k].template,
    excludeChunks: excludeChunks,
    favicon:PAGES[k].favicon || ''
    // chunksSortMode:"dependency"
    /**
     * 'dependency' 按照不一樣文件的依賴關係來排序。
     * 'auto' 默認值,插件的內置的排序方式,具體順序我也不太清楚...
     * 'none' 無序? 不太清楚...
     * 'function' 提供一個函數!!複雜...
     */
})

上面的代碼只是截取的部分代碼片斷,有一些值是須要作一些相應的處理,關於HtmlWebpackPlugin插件的詳細參考

這裏須要注意的是,當咱們對項目包中的公共代碼作了不一樣的 splitChunks(下面會講解這個模塊)時候,好比像 chunks默認會所有注入進入頁面,因此我麼你可能須要手動進行一些處理,或者使用 excludeChunks對一些塊進行排除,其排除的是你最終生成的代碼文件名稱。 template是指對應的模版。更加詳細的參考github 文檔等。
八、敲黑板、講重點的optimization.splitChunks

固然目前這部分的文檔在官網還不是很全,因此這裏咱們參考了印記中文webpack的說明文檔,optimization指優化模塊。splitChunks能夠翻譯爲拆分塊,默認的配置參數參考官方文檔便可,重要的說一下optimization.splitChunks.cacheGroups,很是強大的一個API,先說何時會用到這個功能,這就對應了咱們最前面所說的,vue-cli3腳手架不太方便的地方,當項目包逐漸增大的同時,一般狀況下,會爲咱們提供一個公共文件,在vue-cli3腳手架中,爲咱們提取了 vendor-chunks.js爲全部文件的公共文件,可是若是咱們有一個場景,其中的某一個頁面根本不須要依賴一些包的,且這個包相對較大的同時,咱們另可多發一次請求,也不須要去加載這些多餘的文件,咱們就能夠經過optimization.splitChunks.cacheGroups將這部分公共的提取出來,在對制定的頁面在HtmlWebpackPlugin插件中將它排除便可(或者採用注入指定的chunk的形式),舉一個例子,咱們能夠吧全部頁面中的vue相關的源碼包提取到一個單獨的文件,咱們能夠採用以下的配置:

optimization: {
    splitChunks: {
        minSize: 30000,
        //緩存組
        cacheGroups: {
            vue: {
                test: /([\/]node_modules[\/]vue)/,  // <- window | mac -> /node_modules/vue/
                name: 'vue-vendor',                 //拆分塊的名稱
                chunks: 'initial',                  //initial(初始塊)、async(按需加載塊)、all(所有塊),默認爲all;
                priority: 100,                      // 該配置項是設置處理的優先級,數值越大越優先處理
                enforce: true,                      // 若是cacheGroup中沒有設置minSize,則據此判斷是否使用上層的minSize,true:則使用0,false:使用上層minSize
                //minSize: 1024*10,                 //表示在壓縮前的最小模塊大小,默認爲0;
                //minChunks: 1,                     //表示被引用次數,默認爲1;
                //maxAsyncRequests:                 //最大的按需(異步)加載次數,默認爲1;
                //maxInitialRequests:               //最大的初始化加載次數,默認爲1;
                //reuseExistingChunk: true          //表示可使用已經存在的塊,即若是知足條件的塊已經存在就使用已有的,再也不建立一個新的塊。
            }
        }
    },

}

固然你能夠根據本身的須要進行多個的配置,名稱能夠自定義,test是過濾的方式,這裏核心要說明的是priority(優先權),固然是數字越大優先權越高,什麼意思,當咱們在進行webpack打包的同時,會將咱們全部用到的代碼所有加載在內存中,進行進行轉換和編譯等操做,拆分塊的核心在於,將一些公共的模塊拆分紅多個模塊,按照優先級進行提取出去,生成一個文件,而後再去查找下一個優先級的進行提取。這裏如何進行包拆分能夠藉助webpack-bundle-analyzer插件進行可視化的進行操做。其用法是直接npm i webpack-bundle-analyzer

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

webpack中配置plugin選項新增便可:

new BundleAnalyzerPlugin({
    defaultSizes:'gzip',
    logLevel:'warn'
}),

在執行項目的時候會進行包依賴的詳細分析,此時咱們能夠經過可視化的方式進行更好的拆分。

splitChunks也是在webpack4鼎力打造的一個功能,在於理解其中的用法,拆分的代碼塊更多的時候是要咱們在html-webpack-plugin的時候更好的生成每個獨立的頁面,有一些框架中採用了每增長一個文件就回去自動添加一個新的頁面的方式,固然這樣的額缺點是必須按照對應的目錄進行生成對應的頁面,這裏咱們採用了手動配置的方式生成新的頁面,每生成一個頁面,會默認的注入一些拆分出來的代碼塊。
雖然html-webpack-plugin沒生成一個新的html頁面必需要實例化一個新的對象。因此咱們能夠經過配置文件的循環進行生成對應的配置:

new HtmlWebpackPlugin({
            title: PAGES[k].title || 'title',
            chunks: chunks,
            filename:`${k}.html`,
            minify: {
                removeComments: true,       //Strip HTML comments
                collapseWhitespace: true,   //摺疊有助於文檔樹中文本節點的空白區域
            },    //對html進行壓縮,默認false
            hash: PAGES[k].hash === true ? true : false,      //默認false
            template: PAGES[k].template,
            excludeChunks: excludeChunks,
            favicon:PAGES[k].favicon || ''
            // chunksSortMode:"dependency"
            /**
             * 'dependency' 按照不一樣文件的依賴關係來排序。
             * 'auto' 默認值,插件的內置的排序方式,具體順序我也不太清楚...
             * 'none' 無序? 不太清楚...
             * 'function' 提供一個函數!!複雜...
             */
        })

上面是一些腳手架中的源代碼,在一些簡單的頁面,能夠經過排除的方式省去一些文件的加載,而不是通用的加載一個較大的文件包。在優化的過程當中咱們能夠減小http請求,可是咱們也能夠減小請求包的大小。

放棄使用DllPlugin | DLLReferencePlugin

通過一段時間的考量,我發現關於DllPlugin & DLLReferencePlugin這兩個插件的使用只有在一部分狀況下才比較適合,當你的項目包中用一個不須要從新構建的模塊的時候你再使用這個插件是比較合適的,然而不少時候,咱們每次的構建幾乎都會從新編譯咱們的代碼,固然他們的使用方式是先經過DllPlugin去打包好不不須要從新構建的文件,同時生成manifest.json文件,在下次編譯的同時經過DLLReferencePlugin進行載入便可,減小了一些包的重複編譯。

總結

寫在最後,這一塊所包含的信息量相對較多,同時須要對項目構建有必定程度的瞭解,在不少的過程當中是須要去思考一個問題的解決方法和方式,而不只僅是追求使用,每個工具都會提供強大的API和功能以適合衆多的業務場景。對於一些經過不一樣的方式獲得一樣結果的問題就仁者見仁吧!

相關文章
相關標籤/搜索