一個合格的Webpack4配置工程師素養:第三部分

小諾哥發表於2019-01-27

前兩篇

webpack配置分層

之前有提過webpack根據不同的環境我們會載入不同的配置。我們只需要提取出三部分。

- base: 公共的部分
- dev: 開發環境部分
- prod: 生產環境部分
複製程式碼
npm i -D webpack-merge
複製程式碼

我們這裡現在簡單分層:正式專案最好建立一個config/webpack目錄管理。

webpack目錄

下面是原始碼。

"scripts": {
    "dev": "cross-env NODE_ENV=development npx webpack --progress --config webpack.dev.config.js",
    "build": "cross-env NODE_ENV=production npx webpack --progress --config webpack.prod.config.js"
 },
複製程式碼
// webapck.base.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackplugin = require('clean-webpack-plugin')

module.exports = {
    entry: './src/index.js',
    module: {
        rules: [
            // 處理字型
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                loader: 'url-loader',
                options: {
                    // 檔案大小小於limit引數,url-loader將會把檔案轉為DataUR
                    limit: 10000,
                    name: '[name]-[hash:5].[ext]',
                    output: 'fonts/',
                    // publicPath: '', 多用於CDN
                }
            },
            // 處理檔案
            {
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                use: [
                    // 轉base64
                    {
                        loader: 'url-loader',
                        options: {
                            // 具體配置見外掛官網
                            limit: 10000,
                            name: '[name]-[hash:5].[ext]',
                            outputPath: 'img/', // outputPath所設定的路徑,是相對於 webpack 的輸出目錄。
                            // publicPath 選項則被許多webpack的外掛用於在生產模式下更新內嵌到css、html檔案內的 url , 如CDN地址
                        },
                    },
                    {
                        loader: 'image-webpack-loader',
                        options: {
                          mozjpeg: {
                            progressive: true,
                            quality: 65
                          },
                          // optipng.enabled: false will disable optipng
                          optipng: {
                            enabled: false,
                          },
                          pngquant: {
                            quality: '65-90',
                            speed: 4
                          },
                          gifsicle: {
                            interlaced: false,
                          },
                          // the webp option will enable WEBP
                          webp: {
                            quality: 75
                          }
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        // 打包模板
        new HtmlWebpackPlugin({
            inject: true,
            hash: true,
            cache: true,
            chunksSortMode: 'none',
            title: 'Webapck4-demo', // 可以由外面傳入
            filename: 'index.html', // 預設index.html
            template: path.resolve(__dirname, 'index.html'),
            minify: {
                collapseWhitespace: true,
                removeComments: true,
                removeRedundantAttributes: true,
                removeScriptTypeAttributes: true,
                removeStyleLinkTypeAttributes: true
            }
        }),
        // 清理dist目錄
        new CleanWebpackplugin(['dist'])
    ]
}

複製程式碼
// webpack.dev.config.js

const path = require('path')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.config.js')


module.exports = merge(baseWebpackConfig, {
    mode: 'development',
    output: {
        filename: 'bound.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            // 處理css/scss/sass
            {
                test: /\.(sc|sa|c)ss$/,
                use: [
                    {
                        loader: 'style-loader',
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true
                        }
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            sourceMap: true,
                            plugins: (loader) => [
                                require('autoprefixer')({
                                    browsers: [
                                        'last 10 Chrome versions',
                                        'last 5 Firefox versions',
                                        'Safari >= 6',
                                        'ie > 8'
                                    ]
                                })
                            ]
                        }
                    },
                    {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: true
                        }
                    }
                ]
            }
        ]
    }
})

複製程式碼
// webapck.prod.config.js

const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssertsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.config.js')
const devMode = process.env.NODE_ENV !== 'production'

module.exports = merge(baseWebpackConfig, {
    mode: 'production',
    output: {
        filename: 'bound.[hash:5].js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            // 處理css/scss/sass
            {
                test: /\.(sc|sa|c)ss$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            plugins: (loader) => [
                                require('autoprefixer')({
                                    browsers: [
                                        'last 10 Chrome versions',
                                        'last 5 Firefox versions',
                                        'Safari >= 6',
                                        'ie > 8'
                                    ]
                                })
                            ]
                        }
                    },
                    {
                        loader: 'sass-loader'
                    }
                ]
            }
        ]
    },
    plugins: [
        // 提取CSS
        new MiniCssExtractPlugin({
            filename: devMode ? '[name].css' : '[name].[hash:5].css', // 設定輸出的檔名
            chunkFilename: devMode ? '[id].css': '[id].[hash:5].css'
        })
    ],
    optimization: {
        minimizer: [
            // 壓縮JS
            new TerserPlugin({
                cache: true,
                parallel: true,
                sourceMap: true,
                // 等等詳細配置見官網
            }),
            // 壓縮CSS
            new OptimizeCSSAssertsPlugin({})
        ]
    }
})

複製程式碼

webpack配置js使用sourceMap

在webpack4使用inline-source-map選項就可以啟動錯誤的堆疊跟蹤, 只用於開發環境

devtool: 'inline-source-map'
複製程式碼

監控檔案變化自動編譯

簡單的方法就是啟動watch模式: 如

"dev": "cross-env NODE_ENV=development npx webpack --progress --config webpack.dev.config.js --watch"
複製程式碼

webpack開啟熱更新和代理配置

很明顯上面watch模式效率不高而且很不方便, 編譯完還需要重新整理頁面, webpack可以開啟熱更新模式,大大加速開大效率。

npm i -D webpack-dev-server
複製程式碼

修改script指令碼。

"dev": "cross-env NODE_ENV=development npx webpack-dev-server --progress --config webpack.dev.config.js"
複製程式碼

修改配置檔案

const webpack = require('webpack')


plugins: [
    new webpack.NamedModulesPlugin(), // 更方便檢視patch的依賴
    new webpack.HotModuleReplacementPlugin() // HMR
],
devServer: {
    clientLogLevel: 'warning', // 輸出日誌級別
    hot: true, // 啟用熱更新
    contentBase: path.resolve(__dirname, 'dist'), // 告訴伺服器從哪裡提供內容
    publicPath: '/', // 此路徑下的打包檔案可在瀏覽器下訪問
    compress: true, // 啟用gzip壓縮
    // publicPath: './',
    disableHostCheck: true,
    host: '0.0.0.0',
    port: 9999,
    open: true, // 自動開啟瀏覽器
    overlay: { // 出現錯誤或者警告時候是否覆蓋頁面線上錯誤資訊
        warnings: true,
        errors: true
    },
    quiet: true,
    proxy: { // 設定代理
        '/dev': {
            target: 'http://dev.xxxx.com.cn',
            changeOrigin: true,
            pathRewrite: {
                '^/dev': ''
            }
            /**
             * 如果你的配置是
             * pathRewrite: {
                '^/dev': '/order/api'
                }
                即本地請求 /dev/getOrder   =>  實際上是  http://dev.xxxx.com.cn/order/api/getOrder
            */
        },
        '/test': {
            target: 'http://test.xxxx.com.cn',
            changeOrigin: true,
            pathRewrite: {
                '^/test': ''
            }
        },
        '/prod': {
            target: 'http://prod.xxxx.com.cn',
            changeOrigin: true,
            pathRewrite: {
                '^/prod': ''
            }
        }
    },
    watchOptions: { // 監控檔案相關配置
        poll: true,
        ignored: /node_modules/, // 忽略監控的資料夾, 正則
        aggregateTimeout: 300, // 預設值, 當你連續改動時候, webpack可以設定構建延遲時間(防抖)
    }
}
複製程式碼

webpack啟用babel轉碼

npm i -D  babel-loader @babel/core @babel/preset-env @babel/runtime @babel/plugin-transform-runtime
複製程式碼

修改配置檔案

// 編譯js
{
    test: /\.js$/,
    exclude: /(node_modules|bower_components)/,
    use: {
        loader: 'babel-loader?cacheDirectory', // 通過cacheDirectory選項開啟支援快取
        options: {
          presets: ['@babel/preset-env']
        }
    }
},
複製程式碼

增加.babelrc配置檔案

// 僅供參考

{
    "presets": [
      [
        "@babel/preset-env"
      ]
    ],
    "plugins": [
      [
        "@babel/plugin-transform-runtime",
        {
          "corejs": false,
          "helpers": true,
          "regenerator": true,
          "useESModules": false,
          "absoluteRuntime": "@babel/runtime"
        }
      ]
    ]
  }

複製程式碼

webpack配置eslint校驗

npm i -D eslint eslint-loader

// 校驗規則
npm i -D babel-eslint standard
複製程式碼

修改webpack配置檔案

// 編譯js
  {
    test: /\.js$/,
    exclude: /(node_modules|bower_components)/,
    use: [
      {
        loader: 'babel-loader?cacheDirectory', // 通過cacheDirectory選項開啟支援快取
        options: {
          presets: ['@babel/preset-env']
        }
      },
      {
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      }
    ]
  },
複製程式碼

增加.eslintrc.js檔案

/*
 * ESLint的JSON檔案是允許JavaScript註釋的,但在gist裡顯示效果不好,所以我把.json檔案字尾改為了.js
 */

/*
 * ESLint 配置檔案優先順序:
 * .eslintrc.js(輸出一個配置物件)
 * .eslintrc.yaml
 * .eslintrc.yml
 * .eslintrc.json(ESLint的JSON檔案允許JavaScript風格的註釋)
 * .eslintrc(可以是JSON也可以是YAML)
 *  package.json(在package.json裡建立一個eslintConfig屬性,在那裡定義你的配置)
 */

/*
 * 你可以通過在專案根目錄建立一個.eslintignore檔案告訴ESLint去忽略特定的檔案和目錄
 * .eslintignore檔案是一個純文字檔案,其中的每一行都是一個glob模式表明哪些路徑應該忽略檢測
 */

module.exports = {
  //ESLint預設使用Espree作為其解析器
  //同時babel-eslint也是用得最多的解析器
  //parser解析程式碼時的引數
  "parser": "babel-eslint",
  "parserOptions": {
    //指定要使用的ECMAScript版本,預設值5
    "ecmaVersion": 6
    //設定為script(預設)或module(如果你的程式碼是ECMAScript模組)
    // "sourceType": "script",
    //這是個物件,表示你想使用的額外的語言特性,所有選項預設都是false
    // "ecmafeatures": {
    //   //允許在全域性作用域下使用return語句
    //   "globalReturn": false,
    //   //啟用全域性strict模式(嚴格模式)
    //   "impliedStrict": false,
    //   //啟用JSX
    //   "jsx": false,
    //   //啟用對實驗性的objectRest/spreadProperties的支援
    //   "experimentalObjectRestSpread": false
    // }
  },
  //指定環境,每個環境都有自己預定義的全域性變數,可以同時指定多個環境,不矛盾
  "env": {
    //效果同配置項ecmaVersion一樣
    "es6": true,
    "browser": true,
    "node": true,
    "commonjs": false,
    "mocha": true,
    "jquery": true,
    //如果你想使用來自某個外掛的環境時,確保在plugins陣列裡指定外掛名
    //格式為:外掛名/環境名稱(外掛名不帶字首)
    // "react/node": true
  },

  //指定環境為我們提供了預置的全域性變數
  //對於那些我們自定義的全域性變數,可以用globals指定
  //設定每個變數等於true允許變數被重寫,或false不允許被重寫
  "globals": {
    "globalVar1": true,
    "globalVar2": false
  },

  //ESLint支援使用第三方外掛
  //在使用外掛之前,你必須使用npm安裝它
  //全域性安裝的ESLint只能使用全域性安裝的外掛
  //本地安裝的ESLint不僅可以使用本地安裝的外掛還可以使用全域性安裝的外掛
  //plugin與extend的區別:extend提供的是eslint現有規則的一系列預設
  //而plugin則提供了除預設之外的自定義規則,當你在eslint的規則裡找不到合適的的時候
  //就可以借用外掛來實現了
  "plugins": [
    // "eslint-plugin-airbnb",
    //外掛名稱可以省略eslint-plugin-字首
    // "react"
  ],

  //具體規則配置
  //off或0--關閉規則
  //warn或1--開啟規則,警告級別(不會導致程式退出)
  //error或2--開啟規則,錯誤級別(當被觸發的時候,程式會退出)
  "rules": {
    "eqeqeq": "warn",
    //你也可以使用對應的數字定義規則嚴重程度
    "curly": 2,
    //如果某條規則有額外的選項,你可以使用陣列字面量指定它們
    //選項可以是字串,也可以是物件
    "quotes": ["error", "double"],
    "one-var": ["error", {
      "var": "always",
      "let": "never",
      "const": "never"
    }],
    //配置外掛提供的自定義規則的時候,格式為:不帶字首外掛名/規則ID
    // "react/curly": "error"
  },

  //ESLint支援在配置檔案新增共享設定
  //你可以新增settings物件到配置檔案,它將提供給每一個將被執行的規則
  //如果你想新增的自定義規則而且使它們可以訪問到相同的資訊,這將會很有用,並且很容易配置
  "settings": {
    "sharedData": "Hello"
  },

  //Eslint檢測配置檔案步驟:
  //1.在要檢測的檔案同一目錄裡尋找.eslintrc.*和package.json
  //2.緊接著在父級目錄裡尋找,一直到檔案系統的根目錄
  //3.如果在前兩步發現有root:true的配置,停止在父級目錄中尋找.eslintrc
  //4.如果以上步驟都沒有找到,則回退到使用者主目錄~/.eslintrc中自定義的預設配置
  "root": true,

  //extends屬性值可以是一個字串或字串陣列
  //陣列中每個配置項繼承它前面的配置
  //可選的配置項如下
  //1.字串eslint:recommended,該配置項啟用一系列核心規則,這些規則報告一些常見問題,即在(規則頁面)中打勾的規則
  //2.一個可以輸出配置物件的可共享配置包,如eslint-config-standard
    //可共享配置包是一個匯出配置物件的簡單的npm包,包名稱以eslint-config-開頭,使用前要安裝
    //extends屬性值可以省略包名的字首eslint-config-
  //3.一個輸出配置規則的外掛包,如eslint-plugin-react
    //一些外掛也可以輸出一個或多個命名的配置
    //extends屬性值為,plugin:包名/配置名稱
  //4.一個指向配置檔案的相對路徑或絕對路徑
  //5.字串eslint:all,啟用當前安裝的ESLint中所有的核心規則
    //該配置不推薦在產品中使用,因為它隨著ESLint版本進行更改。使用的話,請自己承擔風險
  "extends": [
    "standard"
  ]
}

複製程式碼

增加.eslintignore檔案

/dist/
/node_modules/
/*.js

複製程式碼

webpack配置resolve模組解析

配置alias方便路徑的檢索效率。 配置檔案預設副檔名,import時候自動匹配。

resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      '@': path.resolve(__dirname, 'src/')
    }
},
複製程式碼

webpack配置外部擴充套件externals

externals選項可以提供排除打包某些依賴到boundle的方法.例如我們想通過CDN引入jQuery而不是把jQuery打包到boudle。

這裡以jQuery為例:

修改配置檔案

// 配置外部依賴不會打包到boudle
externals: {
    jquery: 'jQuery'
},
複製程式碼

在模板檔案引入CDN

// index.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="google" value="notranslate">
    <meta http-equiv="Cache-Control" content="no-siteapp">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
    <meta name="format-detection" content="telephone=no">
    <script
    src="https://code.jquery.com/jquery-3.3.1.js"
    integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60="
    crossorigin="anonymous"></script>
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>

<body>
    <div id="app"></div>
    <div class="logo"></div>
    <div class="man"></div>
</body>

</html>

複製程式碼

在index.js使用jquery

import $ from 'jquery'

// 測試外部擴充套件配置
$(function () {
  $('.logo').click(function () {
    console.log('click')
  })
})
複製程式碼

webpack打包報表分析以及優化

npm i -D webpack-bundle-analyzer
複製程式碼

我單獨配置了一個命令進行打包分析:

"build:report": "cross-env NODE_ENV=production npx webpack --progress --config webpack.analysis.config.js"
複製程式碼

當然你其實完全可以通過傳引數配置整合到prod那個配置檔案如:

"build:report": "npm run build --report"
複製程式碼

然後在prod配置環境中如果引數判斷:

// 虛擬碼
if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
複製程式碼

這裡給出我的方案

const merge = require('webpack-merge')
const prodWebpackConfig = require('./webpack.prod.config.js')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = merge(prodWebpackConfig, {
  plugins: [
    new BundleAnalyzerPlugin() // 打包分析
  ]
})

複製程式碼

倉庫地址

Rp地址

有用的話點個star, 有問題歡迎提issues。

相關文章