快速打造簡易高效的webpack配置

ssssyoki發表於2017-07-04

webpack給前端開發帶來了毋庸置疑的改變,它把JS,圖片,css都作為模組處理,同時具有開發便捷,自動化,相容AMD寫法等等諸多無須贅述的優點,更令人稱道的是其外掛社群非常強大,對於不同的業務需求和技術需求社群都有大量外掛可供使用。

凡事都具有兩面性,許多人說:前端開發再也不能只需新建HTML檔案和JS檔案就可以開始寫程式碼了。webpack帶來了更高階更規範的前端開發模式,由於其本身也在不斷完善中,從1到2再到釋出不久的webpack3,頻繁的修改給新手帶來了許多困惑。而且網路上各種教程魚目混雜,經常出現別人的教程程式碼copy下來在自己的環境卻跑不通的蛋疼問題。就拿devtool配置項來說,官方文件提供了多達7種的配置方法,連react核心團隊成員Pete Hunt都在twitter上調侃:我分不清webpack的許多配置之間的區別。所以今天我們拋開那些琳琅滿目的外掛和令人煩躁的配置項,筆者和大家一起5分鐘從零搭建一個簡易高效的webpack開發環境。

首先我們明確一下需求:

  • 打包除錯
  • 提取公共程式碼
  • 壓縮
  • 熱替換

1.打包除錯

第一步,我們在目標資料夾下安裝webpack(假設已有package.json)
npm i webpack@ -g
cnpm i webpack@ --save-dev
(這裡推薦大家安裝穩定的2.x版本)

專案結構如圖:


我們將編寫的js程式碼和樣式檔案放置在app資料夾內(正常專案開發需要js檔案和less檔案更規範的組織檔案結構,此處僅為演示方便)。

第二步,我們在目標資料夾下新建webpack.config.js

module.exports = {
    entry:{
        main:__dirname + '/app/main.js',
    },
    output:{
        path:__dirname + '/public',
        filename:'[name].[id].js',//此格式寫法後續會提到為什麼
        publicPath:'/public/'
    }
}複製程式碼

我們已經完成了webpack最基礎的部分:新增了檔案的輸入和輸出。入口是app資料夾內的main.js檔案,出口為public資料夾。接下來我們來處理各種檔案的解析,就是大名鼎鼎的loader的舞臺了。假設我們使用es6less開發,那麼我們需要:
npm i babel-loader babel-core babel-preset-es2015 babel-preset-stage-0 --save-dev

npm i less less-loader css-loader style-loader --save-dev

接下來我們只需要在modules欄位下把這些loader加進去:

module.exports = {
    devtool:'cheap-module-eval-source-map',//多種選擇,選擇最適合自己的
    entry:{
        main:__dirname + '/app/main.js',
    },
    output:{
        path:__dirname + '/public',
        filename:'[name].[id].js',
        publicPath:'/public/'
    },
    module:{
        loaders:[
            {
                test:/\.js$/,  //解析檔案型別
                exclude:/node_modules/,  //排除node_modules檔案
                loader:'babel-loader', //使用哪種loader解析
                query:{
                    presets:['es2015','stage-0']//loader的配置項,解析es6
                }
            },
            {
                test:/\.less$/,
                exclude:/node_modules/,
                loader:'style-loader!css-loader!less-loader'//順序為從右向左
            }
        ]
    },
}複製程式碼

大功告成!

如果你在全域性安裝有webpack的話,可以在終端敲入webpack並回車,幾秒鐘後,main.js檔案已經在public打包出來了!

之後我們在index.html中引入main.0.js檔案,再開啟index.html就可以看到效果了。

以上步驟,我們已經實現了檔案的打包除錯,但是現在有個問題擺在我們面前:第三方庫程式碼和業務程式碼打包到了同一個檔案main.0.js內,每次更新程式碼都要更新整個檔案。那麼接下來我們對程式碼進行拆分。

2.提取公共程式碼

引入CommonsChunkPlugin外掛,在webpack.config.js新增如下內容:

module.exports = {
    devtool:'cheap-module-eval-source-map',
    entry:{
        main:__dirname + '/app/main.js',
        vendor:'moment'
    },
    output:{
        path:__dirname + '/public',
        filename:'[name].[id].js',
        publicPath:'/public/'
    },
    module:{
        loaders:[
            {
                test:/\.js$/,
                exclude:/node_modules/,
                loader:'babel-loader',
                query:{
                    presets:['es2015','stage-0']
                }
            },
            {
                test:/\.less$/,
                exclude:/node_modules/,
                loader:'style-loader!css-loader!less-loader'
            }
        ]
    },
    plugins:[
            new webpack.optimize.CommonsChunkPlugin({
                names:['vendor','manifest']
         })
     ]
}複製程式碼

我們看到向外掛的建構函式傳入了兩個引數vendormanifest,以及我們在entry也加入了新的入口momentmoment是常用的時間處理的第三方庫,我們可以通過npm i moment --save-dev進行安裝。而entry處的vendor將成為output欄位filename[name]的值,也就是說將打包出main.x.jsvendor.x.js兩個檔案,main.x.js檔案將儲存我們的業務程式碼,vendor.x.js將儲存moment的程式碼,這樣我們將公共程式碼和業務程式碼進行了初步分離。

在新新增的CommonmChunkPlugin外掛中,我們新增了manifest值,這是為什麼呢?如果你不新增這個值,你在打包時會發現,main.x.js有更新,vendor.x.js還是有更新,並未真正實現"分離"。官方文件對此的解釋是:

The issue here is that on every build, webpack generates some webpack runtime code, which helps webpack do it’s job. When there is a single bundle, the runtime code resides in it. But when multiple bundles are generated, the runtime code is extracted into the common module, here the vendor file.

大致的意思就是說,webpack每次編譯時執行的程式碼會影響到hash值的變化,當只有一個打包檔案時這部分程式碼會塞進去,當有多個打包檔案時,這部分程式碼會進入公共的vendor。所以解決辦法是使用manifest欄位把這部分程式碼從vendor中作為一個公共模組抽出來,從而不會影響vendor

將以上的配置寫入webpack.config.js,執行webpack命令,我們發現業務程式碼和公共庫程式碼成功分離,改寫main.1.js檔案的內容,再次打包,發現vendor檔案並沒有變化,成功!

當我們再進行打包時,發現又會多出了新的main.x.js等檔案,打包三次就會出現三個main.x.js檔案,此時該怎麼辦呢?我們可以使用clean-webpack-plugin外掛:

npm i clean-webpack-plugin --save-dev

然後在webpack.config.js中引入:

    var CleanWebpackPlugin = require('clean-webpack-plugin');
    new CleanWebpackPlugin(
            ['public/main.*.js','public/manifest.*.js'],//要刪除的檔案目錄匹配
            {
                root:__dirname,
                verbose:true,
                dry:false
            }
        ),複製程式碼

這樣我們每次在打包新的程式碼時,舊檔案就會刪除,不會再出現同一份檔案存在多份的情況。

3.壓縮

在webpack中,圖片,css,js等等其他資源皆可壓縮,本文僅以壓縮js為例。
安裝外掛:
npm i uglifyjs-webpack-plugin --save-dev

webpack.config.js中引入:

var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
new UglifyJsPlugin({
    beautify:true,
    exclude:['/node_modules/'],
    compress:{
        warnings:false
    },
    output:{
        comments:false
    }
})複製程式碼

我們指定了壓縮的方法,排除了不需要壓縮的node_modules部分,同時我們去除了comments部分(comments為@license等註釋,是可觀的壓縮空間)。再次在終端輸入打包命令,可見js打包後的體積有令人滿意的減小。

4.熱替換

webpack總是繞不開熱替換的話題。熱替換的功能配置和原理是一大話題,三天三夜也說不完,也並非本文重點,本文只提供簡易高效的配置方法。

熱替換存在兩種使用方式,clinodecli方式無需新增新的熱替換外掛,且無需在入口處新增webpack-dev-server等入口,故本文采用cli使用方式。

webpack.config.js中新增devServer欄位,加入如下程式碼:

devServer:{
    inline:true,
    hot:true
},複製程式碼

儲存後執行webpack-dev-server --inline --hot --progress,再修改下main.less檔案的樣式,會發現瀏覽器並沒有重新整理,但頁面已經發生了變化,我們的熱替換功能也成功加入了!

tips:

在實際專案打包時,可以將filename欄位的值換為[name].[chunkhash].js,其中[chunkhash]為webpack每次打包後給每個模組的標識值,這個值每次打包後都會更換。為什麼在此處我們使用[id]呢,因為chunkhash與熱替換存在衝突,終端會有報錯,那麼使用id可以算作一個解決方案。這就引申出另一話題,我們可以使用兩套webpack配置分別用於生產環境和開發環境,通過webpack指定config來進行打包。例如我們在開發環境使用id,在生產環境去掉熱替換並使用hash的方式。而且,一些壓縮外掛也沒必要在開發環境過度使用,兩套配置能讓webpack發揮最大的威力。

另外,chunkhashhash有區別,chunkhash顧名思義是模組的標識,而hash是webpack每次編譯的標識值,不同的資源如js和css存在chunkhash解耦的問題,此處不進行過多討論。

關於熱替換的更多細節和原理,參考文章:www.cnblogs.com/wonyun/p/70…

5.執行

我們知道,每次打包後,都會有新的main.x.js檔案生成,其hash值每次打包後都會發生變化,難道我們的index.html檔案需要每次打包後都手動修改main.x.js的路徑嗎?還好社群提供了html-webpack-plugin外掛,可以在已有html模板的條件下自動為我們生成帶有最新程式碼的html檔案:

npm i html-webpack-plugin --save-dev

webpack.config.js中引入:

var HtmlWebpackPlugin = require('html-webpack-plugin');
new HtmlWebpackPlugin({
    title:'demo',
    template:'index.html'
}),複製程式碼

在終端執行打包命令,我們看到public資料夾下生成了新的index.html檔案:

以後我們再進行除錯時,以本文為例,則需要開啟localhost:8080/public/index.html,因為每次webpack的HtmlWebpackPlugin都會把新的js檔案加入到這個html檔案內。在開發全部完成後,我們可以將js路徑寫死,新增到原有的index.html檔案中。

以下是我們webpack.config.js全部的配置;

var webpack = require('webpack');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
    devtool:'cheap-module-eval-source-map',
    entry:{
        main:__dirname + '/app/main.js',
        vendor:'moment'
    },
    output:{
        path:__dirname + '/public',
        filename:'[name].[id].js',
        publicPath:'/public/'
    },
    devServer:{
        inline:true,
        hot:true
    },
    module:{
        loaders:[
            {
                test:/\.js$/,
                exclude:/node_modules/,
                loader:'babel-loader',
                query:{
                    presets:['es2015','stage-0']
                }
            },
            {
                test:/\.less$/,
                exclude:/node_modules/,
                loader:'style-loader!css-loader!less-loader'
            }
        ]
    },
    plugins:[
        new CleanWebpackPlugin(
            ['public/main.*.js','public/manifest.*.js'],
            {
                root:__dirname,
                verbose:true,
                dry:false
            }
        ),
        new webpack.optimize.CommonsChunkPlugin({
            names:['vendor','manifest']
        }),
        new HtmlWebpackPlugin({
            title:'demo',
            template:'index.html'
        }),
        new UglifyJsPlugin({
            beautify:true,
            exclude:['/node_modules/'],
            compress:{
                warnings:false
            },
            output:{
                comments:false
            }
        })
    ]
}複製程式碼

整個專案,我們在app檔案下的main.js內寫業務程式碼,main.less寫樣式,在public/index.html下使用熱替換進行除錯,打包後的壓縮檔案在public資料夾下,並且對業務程式碼,第三方程式碼進行了清晰地區分。

使用這份webpack配置,我們實現了:

  • 工程的打包除錯
  • 公共程式碼提取,提高開發效率
  • 資源壓縮
  • 熱替換

這份配置麻雀雖小,五臟俱全。本文還有許多不完善之處,比如一些外掛的使用方法,原理沒有與大家講清楚,但webpack實在太龐大了,一個外掛的使用方法和原理都可以寫上千字的文章了,學習不可淺嘗輒止,但也不能太鑽牛角尖,與大家共勉~

相關文章