webpack4經常使用配置

webpack4 特性

webpack4 經過一系列默認配置,將 webpack3 經常使用的 plugin 都默認引入了,相對簡化了配置項。實際上,通常項目 webpack4 與 webpack3 在基本配置上差異並非很大,主要有如下不一樣:javascript

  • webpack4 須要配合 webpack-cli 一塊兒使用
  • webpack4 增長了 mode 屬性,設置爲 development / production

development:css

  1. process.env.NODE_ENV 的值設爲 development
  2. 默認開啓如下插件,充分利用了持久化緩存。參考基於 webpack 的持久化緩存方案
  • NamedChunksPlugin :以名稱固化 chunk id
  • NamedModulesPlugin :以名稱固化 module id

production:html

  1. process.env.NODE_ENV 的值設爲 production
  2. 默認開啓如下插件,其中 SideEffectsFlagPluginUglifyJsPlugin 用於 tree-shaking
  • FlagDependencyUsagePlugin :編譯時標記依賴
  • FlagIncludedChunksPlugin :標記子chunks,防子chunks屢次加載
  • ModuleConcatenationPlugin :做用域提高(scope hosting),預編譯功能,提高或者預編譯全部模塊到一個閉包中,提高代碼在瀏覽器中的執行速度
  • NoEmitOnErrorsPlugin :在輸出階段時,遇到編譯錯誤跳過
  • OccurrenceOrderPlugin :給常用的ids更短的值
  • SideEffectsFlagPlugin :識別 package.json 或者 module.rules 的 sideEffects 標誌(純的 ES2015 模塊),安全地刪除未用到的 export 導出
  • UglifyJsPlugin :刪除未引用代碼,並壓縮
  • webpack4 刪除了 CommonsChunkPlugin 插件,改用 optimization 屬性,重點是 splitChunks (自定義公用代碼提取—vendor) 和 runtimeChunk (webpack運行代碼提取—manifest)
  • webpack4 增長了 WebAssembly 的支持,能夠直接 import/export wasm 模塊,也能夠經過編寫 loaders 直接 import C++/C/Rust

項目目錄結構

一個項目通常有開發環境和生產環境,因此相應的項目基本結構大體以下 :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配置
複製代碼

代碼示例

webpack.base.conf.js

首先是 webpack4 的基礎配置 webpack.base.conf.js,集合了開發和生產環境的通用配置,結構以下 :node

module.exports = {
    entry: {},
    output: {},
    resolve: {},
    module: {},
    plugins: {},
    optimization: {}
}
複製代碼

除去 optimization 其餘的都是很熟悉的webpack3的配置,不一一介紹,示例代碼以下 :webpack

  • entry
entry: {
    index: './src/index.js',
    // main: './src/main.js' //多頁面設置直接添加便可,同時plugins須要加上一個新的HtmlWebpackPlugin
}
複製代碼
  • output
output: {
    filename: '[name].js',                       //打包後名稱
    path: path.resolve(__dirname, '../dist'),    //打包後路徑
}
複製代碼
  • resolve
resolve: {
    mainFields: ['jsnext:main', 'browser', 'main'], //配合tree-shaking,優先使用es6模塊化入口(import)
    extensions: ['.js', '.json', '.css'],           //可省後綴
    alias: {
        '@': path.resolve(__dirname, '../src')      //別名
    }
}
複製代碼
  • module
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
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'
    })
]
複製代碼
  • externals 該屬性同時須要在模板html 裏插入 cdn 的 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 都須要在 HtmlWebpackPluginchunks 里加上。es6

webpack.dev.conf.js

開發環境下,經過 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]"      //指定生成的名稱
    }
}
複製代碼

webpack.prod.conf.js

生產環境下,一樣須要的相似於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;
}
複製代碼

參考

  1. webpack4-demo
  2. webpack4-用之初體驗,一塊兒敲它十一遍
  3. Webpack(含 4)配置詳解——關注細節
相關文章
相關標籤/搜索