webpack4 經過一系列默認配置,將 webpack3 經常使用的 plugin 都默認引入了,相對簡化了配置項。實際上,通常項目 webpack4 與 webpack3 在基本配置上差異並非很大,主要有如下不一樣:javascript
mode
屬性,設置爲 development / production
development
:css
- process.env.NODE_ENV 的值設爲 development
- 默認開啓如下插件,充分利用了持久化緩存。參考基於 webpack 的持久化緩存方案
NamedChunksPlugin
:以名稱固化 chunk idNamedModulesPlugin
:以名稱固化 module id
production
:html
- process.env.NODE_ENV 的值設爲 production
- 默認開啓如下插件,其中
SideEffectsFlagPlugin
和UglifyJsPlugin
用於 tree-shaking
FlagDependencyUsagePlugin
:編譯時標記依賴FlagIncludedChunksPlugin
:標記子chunks,防子chunks屢次加載ModuleConcatenationPlugin
:做用域提高(scope hosting),預編譯功能,提高或者預編譯全部模塊到一個閉包中,提高代碼在瀏覽器中的執行速度NoEmitOnErrorsPlugin
:在輸出階段時,遇到編譯錯誤跳過OccurrenceOrderPlugin
:給常用的ids更短的值SideEffectsFlagPlugin
:識別 package.json 或者 module.rules 的 sideEffects 標誌(純的 ES2015 模塊),安全地刪除未用到的 export 導出UglifyJsPlugin
:刪除未引用代碼,並壓縮
CommonsChunkPlugin
插件,改用 optimization 屬性,重點是 splitChunks
(自定義公用代碼提取—vendor) 和 runtimeChunk
(webpack運行代碼提取—manifest)一個項目通常有開發環境和生產環境,因此相應的項目基本結構大體以下 :java
├─ config
│ ├─ webpack.base.conf.js //webpack基礎配置
│ ├─ webpack.dev.conf.js //webpack開發配置
│ └─ webpack.prod.conf.js //webpack生產配置
├─ src
│ ├─ css //css文件
│ │ └─ common.css
│ ├─ js //js文件
│ │ └─ common.js
│ ├─ index.html //html模板
│ └─ index.js //入口js
├─ .babelrc //babel配置
├─ package.json //package.json
└─ postcss.config.js //postcss配置
複製代碼
首先是 webpack4 的基礎配置 webpack.base.conf.js
,集合了開發和生產環境的通用配置,結構以下 :node
module.exports = {
entry: {},
output: {},
resolve: {},
module: {},
plugins: {},
optimization: {}
}
複製代碼
除去 optimization
其餘的都是很熟悉的webpack3的配置,不一一介紹,示例代碼以下 :webpack
entry: {
index: './src/index.js',
// main: './src/main.js' //多頁面設置直接添加便可,同時plugins須要加上一個新的HtmlWebpackPlugin
}
複製代碼
output: {
filename: '[name].js', //打包後名稱
path: path.resolve(__dirname, '../dist'), //打包後路徑
}
複製代碼
resolve: {
mainFields: ['jsnext:main', 'browser', 'main'], //配合tree-shaking,優先使用es6模塊化入口(import)
extensions: ['.js', '.json', '.css'], //可省後綴
alias: {
'@': path.resolve(__dirname, '../src') //別名
}
}
複製代碼
module: {
noParse: /three\.js/, //這些庫都是不依賴其它庫的庫 不須要解析他們能夠加快編譯速度
rules: [{
test: /\.js$/,
use: 'babel-loader?cacheDirectory=true',//babel-loader的cacheDirectory表示緩存轉換結果,提升webpack下次編譯效率
// include: /src/, //只轉化src目錄下js
exclude: /node_modules/ //不轉化node_modules目錄下js
},
{
test: /\.(html|htm)$/,
use: 'html-withimg-loader' //html下的img路徑
},
{
test: /\.(eot|ttf|woff|svg|woff2)$/,
use: 'file-loader'
},
{
test: /\.(jpe?g|png|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
outputPath: 'images/', //打包目錄
name: '[name].[hash:7].[ext]'
}
}]
}]
}
複製代碼
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html', //目標文件
template: './src/index.html', //模板文件
chunks: ['manifest', 'vendor', 'utils', 'index'] //對應關係,index.js對應的是index.html
}),
new webpack.ProvidePlugin({ //自動加載模塊,而沒必要處處 import 或 require
'THREE': 'three'
})
]
複製代碼
script
(注 :本例並無使用 cdn 來引入 three.js ,這裏僅是參考)externals: {
three:'THREE' //屬性是three,即排除 import 'three' 中的 three 模塊,'THREE'則用於檢索一個全局 THREE 變量
}
複製代碼
而後是 optimization
,替代了原來的 CommonsChunkPlugin
公共代碼抽離 :git
optimization: {
splitChunks: {
chunks: 'all', //'all'|'async'|'initial'(所有|按需加載|初始加載)的chunks
// maxAsyncRequests: 1, // 最大異步請求數, 默認1
// maxInitialRequests: 1, // 最大初始化請求書,默認1
cacheGroups: {
// 抽離第三方插件
vendor: {
test: /node_modules/, //指定是node_modules下的第三方包
chunks: 'all',
name: 'vendor', //打包後的文件名,任意命名
priority: 10, //設置優先級,防止和自定義公共代碼提取時被覆蓋,不進行打包
},
// 抽離本身寫的公共代碼,utils這個名字能夠隨意起
utils: {
chunks: 'all',
name: 'utils',
minSize: 0, //只要超出0字節就生成一個新包
minChunks: 2, //至少兩個chucks用到
// maxAsyncRequests: 1, // 最大異步請求數, 默認1
maxInitialRequests: 5, // 最大初始化請求書,默認1
}
}
},
//提取webpack運行時的代碼
runtimeChunk: {
name: 'manifest'
}
}
複製代碼
一樣 manifest, vendor, utils
都須要在 HtmlWebpackPlugin
的 chunks
里加上。es6
開發環境下,經過 webpack-merge
添加上須要的更多開發配置,示例以下:github
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require("path");
const base = require('./webpack.base.conf');
// const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin"); //更好的錯誤輸出
module.exports = merge(base, {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(), //熱更新,還需在index.js裏配置
// new FriendlyErrorsWebpackPlugin() //優化 webpack 輸出信息
],
devtool: '#source-map', //方便斷點調試
// devtool: '#cheap-module-eval-source-map', //構建速度快,採用eval執行
devServer: {
contentBase: path.resolve(__dirname, '../dist'), //服務路徑,存在於緩存中
host: 'localhost', // 默認是localhost
port: 8080, // 端口
open: true, // 自動打開瀏覽器
hot: true, // 開啓熱更新,只監聽js文件,因此css假如被抽取後,就監聽不到了
// inline: true, //inline模式開啓服務器(默認開啓)
// proxy: xxx //接口代理配置
// quiet: true //和friendly-errors-webpack-plugin配合,但webpack自身的錯誤或警告在控制檯不可見。
clientLogLevel: "none", //阻止打印那種搞亂七八糟的控制檯信息
},
mode: 'development' //開發環境
})
複製代碼
假如啓用 css-modules
須要額外在 css-loader 的 options 裏配置,使用方法可參考①,②web
{
loader:"css-loader",
options:{
modules: true, //使用css-modules
minimize: true, //壓縮css
importLoaders: 1,
localIdentName: "[name]__[local]__[hash:base64:5]" //指定生成的名稱
}
}
複製代碼
生產環境下,一樣須要的相似於hash,等配置,一種示例以下:
const webpack = require('webpack');
const base = require('./webpack.base.conf');
const merge = require('webpack-merge');
const path = require("path");
const CleanWebpackPlugin = require('clean-webpack-plugin'); //每次都清空 dist 文件夾
// 1. webpack-bundle-analyzer 可視化定位體積大的模塊
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// 2. happypack 開啓多個子進程,加快 webpack 構建/打包速度。因爲其對`file-loader`, `url-loader` 支持的不友好,不建議對這兩 loader 使用(本例只是示例對css使用,實際能夠加上js的構建,代碼相似)
const Happypack = require('happypack');
const os = require('os');
const happyThreadPool = Happypack.ThreadPool({ size: os.cpus().length }); //cpu 核數
// 3. extract-text-webpack-plugin 拆分css,會把css文件放到dist目錄下的css/[name].[md5:contenthash:hex:20].css,以link的方式引入css
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
let styleCss = new ExtractTextWebpackPlugin({
filename: 'css/[name].[md5:contenthash:hex:20].css',
allChunks: true
});
module.exports = merge(base, {
output: {
filename: '[name].[chunkhash].js', //chunkhash:根據自身的內容計算而來
},
module: {
rules: [{
test: /\.(css|scss|sass)$/,
use: styleCss.extract({
fallback: 'style-loader', // 樣式沒有被抽取時,使用style-loader
use: 'happypack/loader?id=css', // 將css用link的方式引入就再也不須要style-loader了,loader採用happypack
publicPath: '../' //與url-loader裏的outputPath對應,這樣能夠根據相對路徑引用圖片資源
})
}]
},
plugins: [
styleCss,
new CleanWebpackPlugin('dist', {
root: path.resolve(__dirname, '../'),
verbose: true
}),
new Happypack({
id: "css", //id與module.rules裏loader裏的id一致
loaders: [ //至關於module.rules裏loader
{ loader: 'css-loader', options: { importLoaders: 1, minimize: true } },
'postcss-loader',
'sass-loader'
],
threadPool: happyThreadPool,
verbose: true
}),
new webpack.HashedModuleIdsPlugin(), //固化module id
// new BundleAnalyzerPlugin() // 使用默認配置,啓動127.0.0.1:8888
],
mode: 'production'
})
複製代碼
當我覺得這樣就寫完了的時候,坑爹的事情來了。可能你已經發現,爲什麼 extract-text-webpack-plugin 使用了 [md5:contenthash:hex:20]
而不是 [contenthash]?
緣由就是 extract-text-webpack-plugin 即將棄用,bata 版目前只能在 Webpack 4.2.0 如下可用,這也致使了在最新版 webpack 中,假如使用 [contenthash] ,則會報錯: Error: Path variable [contenthash] not implemented in this context: css/[name].[contenthash].css
一種過渡方案就是使用 [md5:contenthash:hex:20]
,另一種就是使用官方推薦的 mini-css-extract-plugin
。好了,那 mini-css-extract-plugin 該怎麼改寫呢?
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//...
module: {
rules: [{
test: /\.(css|scss|sass)$/,
use: [{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../' //同extract-text-webpack-plugin同樣,與url-loader裏的outputPath對應
}
}, {
loader: 'happypack/loader?id=css'
}]
}]
},
//...
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
chunkFilename: 'css/[name].[contenthash].css',
})
],
複製代碼
更多配置能夠參考mini-css-extract-plugin,經過配合 optimization
,能夠實現自定義的css壓縮;多個 css chunk 合併。目前依然有坑,好比可能會多加 61bytes 的js(我好像沒遇到...不過貌似是 webpack 的問題,即將解決了),能夠關注其 issues 。
webpack5 將對 css 的處理直接集成,期待可以解決 css 的痛點。
optimization
抽離代碼很是有用,其中匹配用的 test
屬性除了正則,還能夠用 function ,參數就是每一個 module,實際使用還得查 webpack 的 test 才能知道這些內置的方法,下面的註釋意思可能有一點偏差。
test: module => module.nameForCondition &&
/\.css$/.test(module.nameForCondition()) && //module.nameForCondition() 獲得的應該是module的路徑
!/^javascript/.test(module.type) //module.type 是實際類型,打印下來會有 javascript/auto 還有 mini-css-extract-plugin 注入的
//若是 module 在 a 或者 b chunk 被引入,而且 module 的路徑包含 node\_modules ,那這個 module 就應該被打包到這個 vendor 中
test: module => {
for (const chunk of module.chunksIterable) { //全部chunks的迭代
if (chunk.name && /(a|b)/.test(chunk.name)) { //chunk的名稱
if (module.nameForCondition() && /[\\/]node_modules[\\/]/.test(module.nameForCondition())) {
return true;
}
}
}
return false;
}
複製代碼