webpack4配置到優化到原理(上)

前言

  • 前端繁榮發展,工程化已經成爲高級前端工程師的必不可少的條件之一,打包構建的發展從gruntfisgluprollupwebpackParcel,技術手段變幻無窮,javascript

  • 但其實不論任何一項技術或工具,都有五個階段,css

    1. 簡單使用(菜鳥)
    2. 熟練掌握(老鳥)
    3. 弄清原理(高手)
    4. 改造優化(大牛)
    5. 創新超越(大神)

這五個階段越日後是越艱難,可是你越是日後深刻就越能透過表象看清它的本質,以在這快速變化的技術手段中站穩,以不變應萬變html

webpack如今是前端打包構建最流行的工具,那麼咱們就來好好了解一下它(webpack ^4.42.1)前端

首先梳理下本文要講到的內容vue

  1. 核心概念java

    • entry(打包入口)
    • output(打包後文件的處理)
    • loader(對各類資源的處理)
    • plugin (利用插件對webpack進行擴展和加強)
    • mode(針對開發環境和生成環境的區分處理)
  2. 其餘經常使用配置node

    • devServer(熱更新)
    • resolve(模塊解析)
    • optimization(優化)
    • devtool(源碼調試)
  3. 優化手段react

    • stats分析
    • 速度分析
    • 體積分析
    • tree-shaking
    • scope-hoisting
    • 多進程構建
    • 構建中斷處理
    • 並行壓縮
    • 預編譯資源模塊
    • 提高二次構建速度
    • css的tree-shaking
    • webpack圖片壓縮
    • 動態polufill
  4. 配置總結webpack

  5. webpack原理css3

  6. loader編寫

  7. plugin編寫

下面逐個介紹

一. 核心概念

1.entry(打包入口)

定義打包的入口

  • webpack是一個模塊打包器,他會把一切資源都看成是模塊,模塊之間存在依賴關係
  • 入口指示使用哪些模塊,根據依賴關係,構成了依賴樹,以下圖所示
  • 對依賴樹進行遍歷,最終生成打包後資源

使用示例

// 簡寫
module.exports = {
    entry: './src/index.js',
}
// 多入口
module.exports = {
    entry: {
        index: './src/index.js',
        list: './src/index.js',
    },
}
複製代碼

2. output(輸出)

編譯後文件輸出到磁盤的相關配置

// 簡寫
module.exports = {
    output: {
        filename: '[name]_[chunkhash:8].js' //單個文件名可直接指定,多入口利用佔位符保證文件名統一
        path: path.join(__dirname, '../dist') // 寫入文件磁盤路徑
        publicPath: 'http://cdn.example.com/assets/' 
        //資源使用 CDN ,給全部文件引入模版文件時加上路徑前綴
    },
}
複製代碼

佔位符

  1. [name]
  • 入口名稱
  1. [id]
  • 內部chunk id,例如0,1,2
  1. [hash]
  • 全部文件哈希值相同,只要改變內容跟以前的不一致,全部哈希值都改變
  1. [chunkhash]
  • 不一樣的entry生成不一樣的chunkhash
  • 同一個模塊,就算將js和css分離,其哈希值也是相同的,修改一處,js和css哈希值都會變
  1. [contenthash]
  • 文件內容不同,產生的哈希值就不同
  1. [hash:8]
  • 默認生成20位hash,可自定義截取位數

3. loader(資源解析轉換)

webpack 原生只支持js 和json,利用loader,對不一樣文件類型支持,轉換成有效的模塊 簡單示例

module.exports = {
    module: {
        rules:[
            {
                test: /\/.txt$/, // 指定匹配規則
                use: 'babel-loader' // 指定使用的loader名稱
            }
        ]
    }
}
複製代碼

下面介紹幾種文件類型的處理以及經常使用的loader

  1. 解析ES6和JSX
  • babel-loader:js默認是不支持es6 和jsx語法的,
  • .babelrc文件: 設置具體支持的屬性方法
{
    test: /\.(j|t)sx?$/,
    use: 'babel-loader',
    exclude: /node_modules/                
},
複製代碼
  1. 解析CSS 項目裏使用css文件
  • style-loader:將樣式經過style 標籤插入模版文件的head當中
  • css-loader: 用於加載.css文件 而且轉換成commonjs 對象
{
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader',
        ],
      },
複製代碼
  1. 解析less
  • less-loader:less轉換成css,
  • 其餘同上
{
        test: /.css$/,
        use: [
            'style-loader',
            'css-loader',
            {
                loader: 'less-loader',
                options: { // 可配置屬性,修改變量的值,通常利用來修改UI庫的主題樣式,如下是antd主題樣式配置
                    modifyVars: {
                        '@primary-color': '#ec7259',
                    },
                    javascriptEnabled: true,
                },
            },
        ],
      },
複製代碼
  1. 圖片和字體解析
  • file-loader:解析圖片, 字體等
  • url-loader:也可處理圖片和字體,,並可設置較小資源自動base64
{
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
              name: 'static/img/[name].[hash:8].[ext]',// [ext] 文件的後綴名
            },
          },

        ],
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 8192,
          name: 'static/fonts/[name].[hash:8].[ext]',
        },
      },
複製代碼
  1. 移動端適配
  • px2rem-loader: 把px轉換成rem,配合lib-flexible使用
{
        loader: 'px2rem-loader',
        options: {
            remUnit: 75, // 1rem=多少像素
            remPrecision: 8, // rem的小數點後位數
        }
    }
複製代碼
  1. css前綴補齊
  • postcss-loader:用於瀏覽器適配,某些css3屬性瀏覽器不支持須要加前綴,它會自動針對不一樣瀏覽器加不一樣的屬性前綴
{
        loader: 'postcss-loader',
        options: {
            plugins: () => [autoprefixer()],
        },
    },
複製代碼

4. plugin (利用插件對webpack進行擴展和加強)

  • 解決loader沒法完成的事
  • 由於插件能夠攜帶參數/選項,因此要向 plugins屬性傳入new實例 簡單示例
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    plugin: {
        new webpack.ProgressPlugin(),
        new HtmlWebpackPlugin({template: './src/index.html'}) 
    }
}
複製代碼

下面介紹幾種經常使用的plugin

  1. 頁面打包
new HtmlWebpackPlugin({
      filename: '../dist/template/index.html', // 指定生成的模版文件名及路徑
      template: path.join(__dirname, '../src/template/index.html'), // 指定要使用的模版文件
      inject: true, // 指定的chunk會自動注入html文件中
      chunks: ['index'], //指定生成的html要使用的chunk
      minify: { // 代碼的最小化輸出
        collapseWhitespace: true, // 刪除空格,可是不會刪除SCRIPT、style和textarea中的空格
        preserveLineBreaks: false, // 是否保留換行符
        minifyCSS: true, // css壓縮
        minifyJS: true, // js壓縮
        removeComments: true, // 刪除註釋,可是會保留script和style中的註釋
      },
    }),
複製代碼
  1. 文件清理
  • 每次打包文件到dist,首先要清理dist內部文件,或直接刪除dist文件夾,防止文件重複 咱們可利用rimraf dist
  • 但此方式不太優雅,咱們可使用clean-webpack-plugin,清理dist內部文件
new CleanWebpackPlugin(),
複製代碼
  1. css剝離
  • css代碼默認打包在js文件中,但有時候css變了,js沒變,或者相反,這是不利於緩存的,
  • 咱們能夠把css剝離出來單獨生成文件,去作緩存處理
new MiniCssExtractPlugin({
            filename: '[name]_[contenthash:8].css'
        }),
複製代碼
  1. css壓縮
  • 既然css單獨剝離出來,就要作壓縮,
new OptimizeCssAsssetePlugin({
    assetNameRegExp: /\.css$/g, //文件匹配
    cssProcessor: require('cssnano') // cssnano 壓縮和優化的css插件 
}),
複製代碼
  1. 基礎庫分離
  • 咱們常把一些不太變化的靜態資源放在cdn上,而後在模版文件裏引入
  • 在webpack也提供了插件支持,可直接配置插入
  • 假如咱們分離react和react-dom

舉例:

const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
    plugins: [
        new HtmlWebpackExternalsPlugin({
            externals:[
                {
                    module: 'react',
                    entry: 'https://cdn.cn/16.8.0/react.min.js',
                    global: 'React',
                },
                {
                    module: 'react-dom',
                    entry: 'https://cdn.cn/16.8.0/react-dom.min.js',
                    global: 'ReactDOM',
                }
            ]
        }),
複製代碼

5. mode(針對開發環境和生成環境的區分處理)

  1. mode:對應三個屬性,
  • development: 開發模式,
  • production: 生產模式,
  • none: 無

webpack會針對不一樣環境直接作一些優化工做,例如production模式下會進行,tree-shakingscope-hosting 下面優化會詳細介紹

二. 經常使用配置

6. devServer(熱更新)

  1. 簡介
  • 遠古時期,咱們作前端開發時,寫一個html文件,在瀏覽器打開它查看效果,修改時,需手動更新,

  • 後來咱們使用熱更新,webpack低版本,不提供熱更新的支持,咱們使用插件http-proxy-middlewarewebpack-hot-middleware,實現熱更新,配置比較麻煩

  • 最後webpack把熱更新集成在內部,就成了devServer

簡單配置以下

devServer: {
    historyApiFallback: true, // 單頁面程序 刷新瀏覽器會出現404,緣由是它經過這個路徑(好比: /search/list)來訪問後臺,因此會出現404,而把historyApiFallback設置爲true那麼全部的路徑都執行index.html

    host: '127.0.0.1', // 域名
    open: true, //支持自動打開瀏覽器
    hot: true, // 模塊熱替換,在前端代碼變更的時候無需整個刷新頁面,只把變化的部分替換掉
    inline: false, // inline選項會爲入口頁面添加「熱加載」功能,即代碼改變後從新加載頁面
    port: 8080, // 端口
    proxy: proxyConfig._proxy, // 代理後端服務,舉例:可本地調試測試接口
    before(app) { // 其餘中間件以前, 提供執行自定義中間件
      apiMocker(app, path.resolve('./mocks/mock.js'), // 舉例:可用來作mock數據
        proxyConfig._proxy);
    },
  },
複製代碼
  1. 原理

首先來看下簡單的流程示意圖

webpack熱更新流程示意圖

  • webpack compile 將JS編譯成bundle.js
  • HMR server 將熱更新文件輸出給HMR Runtime,HMR -> HotModuleReplacement(熱模塊替換)
  • Bundle server 提供文件在瀏覽器的訪問
  • HMR Runtime 注入瀏覽器,更新文件的變化,使瀏覽器 和 服務器創建一個連接(websocket)
  • bundle.js 構建輸出的文件

啓動階段

  • 1 -> 2 -> 3

  • 初始代碼通過webpack compiler編譯進行打包

  • 編譯好的文件傳輸給bundle server, 它就至關於一個服務器,它使文件以server的方式讓瀏覽器訪問

  • files -> webpack Compiler -> Bundle Sever -> bundle.js

更新階段

  • 1 -> 4 -> 5 -> 6

  • file文件發生變化,通過webpack compiler編譯

  • 編譯好的文件傳輸給HMR server,通知瀏覽器端的HMR Runtime(一般以JSON形式傳輸)

  • HMR Runtime 更新代碼,實現無刷新改變頁面內容

  • files -> webpack Compiler -> HMR server -> HMR Runtime -> code

7. resolve(模塊解析)

  • 設置模塊如何被解析

介紹幾個經常使用的屬性用法

  1. alias 建立 import 或 require 的別名,來確保模塊引入變得更簡單
  2. extensions 自動解析肯定的擴展
  3. mainFileds 當從 npm 包中導入模塊時,決定在 package.json 中使用哪一個字段導入模塊
  4. moudles 告訴 webpack 解析模塊時應該搜索的目錄

舉例:

// webpack 配置文件
resolve: {
    alias: {
        Util: path.resolve(__dirname, 'src/util/'),
    },
    mainFileds: ['main'],
    extensions: ['.js', '.jsx', '.json'],
    moudles: [path.resolve(__dirname, 'node_modules')]
  },

//業務文件 component.js
import Utility from '../../util/utility.js';
// 簡化寫法(不用寫文件路徑前綴,也不用寫引用文件的擴展名)
import Utility from 'Util/utility';
複製代碼

8. optimization(優化)

  • webpack 4 開始,會根據你選擇的 mode 來執行不一樣的優化,不過全部的優化仍是能夠手動配置和重寫 下面介紹幾種經常使用的優化
  1. 提取公共資源
  • 項目多頁面的時候,大多數頁面使用的基礎庫或依賴都是同樣的,這時每一個頁面都單獨打包一份,對資源是一種浪費,打包後體積較大,頁面加載時間長,
  • 因此咱們能夠把公共資源提取出來單獨打包,訪問多頁面時利用緩存機制,只加載一次,達到優化目的
  • splitChunks,代替以前的CommonsChunkPlugin,公共資源分離vendors

chunks屬性特別說明

  • async 對異步引入的文件分離(默認)
  • initial 對同步引入的文件分離
  • all 對全部匹配的文件分離 不管是同步仍是異步咱們都但願分離出來,因此推薦使用 all
  1. 公共文件分離
  • 一些公共的工具函數類文件,咱們能夠經過限制被調用的次數來決定是否把它分離出來
  • 利用splitChunks 公共文件分離commons
  1. 提取webpack的模塊化信息清單
  • 模塊信息清單在每次有模塊變動(hash 變動)時都會變動, 因此把這部分代碼單獨打包出來, 配合後端緩存策略,
  • 避免某個模塊的變化致使包含在模塊化信息中的模塊緩存失效
  • 具體使用runtimeChunk

舉例:

optimization: {
    runtimeChunk: {
      name: 'manifest',
    },
    splitChunks: {
        minSize: 50000 // 分離的包的體積大小
      cacheGroups: {
        vendors: {
          test:  /(react|react-dom)/, //正則匹配要分離的文件
          name: 'vendors',
          chunks: 'all', // 肯定對何種引入方式的文件進行分離
          minChunks: 1, // 最小使用的次數
          priority: 10, // 多個緩存組時,須要有優先級排列,優先使用哪一個進行分離
        },
        commons: { // 分離公共文件
          name: 'commons',
          chunks: 'all',
          minChunks: 2,
          priority: 5,
        },
      },
    },
  },
複製代碼

9. devtool(源碼調試)

  • 控制是否生成,以及如何生成 source map
  • 咱們要進行一個配置以方便咱們在測試環境進行問題定位,源碼調試的加強

關鍵字定義

  • eval 模塊都使用 eval() 包裹執行,而且都有 //@ sourceURL(指向的是原文件index.js,調試的時候,根據sourceUrl找到的index.js文件的)
  • source map 產生.map文件(這個map文件會和原始文件作一個映射,調試的時候,就是經過這個.map文件去定位原來的代碼位置的 )
  • cheap 不包含列的信息,(假如代碼運行出現了錯誤,控制檯報出了,error,咱們點擊定位到具體源碼的時候,就只能定位到行,而不能定位到具體的列)
  • inline .map文件做爲dataUrl嵌入到打包文件,而不單獨生成
  • moudle 包含loader的sourcemap(調試的代碼不會被轉換,會保留原始代碼語法)

幾種關鍵字進行組合就造成了具體的用法

不一樣用法對構建速度是有影響的,基本狀況你越清晰容易的看到原始的代碼,構建速度就越慢,調試的方便性和構建速度上你們能夠本身權衡一下

共有13種用法,詳細的請看官方文檔

舉例

// 開發環境
devtool: 'cheap-module-eval-source-map' // 原始源代碼(僅限行)

// 生產環境,通常不進行設置

複製代碼

三. 優化手段

1. 優化分析 stats

構建統計信息

使用舉例

// 構建完成後會生成json文件,顯示構建的一些信息,時間,各模塊的體積等
scripts: {
'build: stats': 'webpack --config build/webpack.prod.config.js --json > stats.json',
}

複製代碼

2. 速度分析

  • 分析每一個插件和loader的耗時狀況 使用舉例
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasureWebpackPlugin();
module.exports = smp.wrap(merge(webpackConfigBase, webpackConfigProd));
複製代碼

速度分析示意圖

圖中咱們看到了每一個插件和loader的耗時狀況, 若是耗時較長,會以紅字提示,咱們就能夠具體分析那個地方爲何時間長,能夠用別的插件替換之類的去作構建速度優化

3. 體積分析

  • 以圖形大小的形式,更加直觀的看到各個模塊插件所佔用的體積
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
  plugins: [ 
    new BundleAnalyzerPlugin({ 
      analyzerPort: 8919 //打包構建後體積分析圖展現的窗口
      }),
  ],
複製代碼

運行打包命令,體積分析示意圖會自動打開在8919窗口

體積分析示意圖

圖中咱們能夠看到moment插件佔用的空間很大,咱們能夠對它進行優化

  1. 減少體積(忽略語言包)
  • 咱們能夠看到爲了支持國際化,moment裏包含了不少語言包,咱們能夠利用webpack內置的插件忽略它,在須要的時候按需引入
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
// compoent
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
複製代碼
  1. 替換插件
  • 咱們能夠用更爲輕量的dayjs插件進行替換,它的大小僅爲2k

4. tree-shaking

  • 字面意思是搖晃樹,就是把樹上壞掉的葉子搖下來,就是死碼清除(即沒有被用到的代碼)
  • 某個模塊或文件,的某個方法被用到了,整個模塊都會被打包都bundle文件中去,tree-shaking會把沒有用到的方法去除,在uglify階段清除
  • 僅支持es6語法 webpack 4 中設置 production 默認開啓了此項優化

Dead Code(什麼是死碼呢?)

  1. 代碼不會被執行
  2. 執行結果不會被用到
  3. 代碼只會影響死變量

ES6 module 特色:

  • 只能做爲模塊頂層的語句出現
  • import 的模塊名只能是字符串常量
  • import binding 是 immutable的

ES6模塊依賴關係是肯定的,和運行時的狀態無關,能夠進行可靠的靜態分析,這就是tree-shaking的基礎,讓死碼清除成爲了可能

靜態分析就是不執行代碼,僅僅從字面的意思上對代碼進行分析,ES6以前的模塊化,好比咱們能夠動態require一個模塊,只有執行後才知道引用的什麼模塊,這個就不能經過靜態分析去作優化,特別說明import()動態引入也是不支持的

5. scope-hoisting

  1. 問題 webpack構建後存在大量的閉包代碼
  • 大量函數閉包包裹代碼,致使體積增大
  • 運行代碼時函數做用域變多,消耗更多的內存
  1. 舉例

引用的文件tools.js

export default 'Hello World';
複製代碼

入口文件index.js

import str from './tools';
console.log(str);
複製代碼

未開啓scope-hoisting編譯後文件

/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
console.log(_tools__WEBPACK_IMPORTED_MODULE_0__["default"]);
/***/ }),

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ('Hello World');
/***/ })

複製代碼

能夠看到

  • 0表示index模塊
  • 1表示tools模塊
  • 兩個模塊就存在兩塊函數閉包代碼,真實的場景會有更多模塊

開啓scope-hoisting編譯後文件

/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ var tools = ('Hello World');
console.log(tools);

/***/ })
複製代碼

能夠看到

  • 函數聲明由兩個變成了一個,tools.js 中定義的內容被直接注入到了 index.js 對應的模塊中
  1. 優勢 使用scope-hoisting
  • 代碼體積更小,由於函數申明語句會產生大量代碼;
  • 代碼在運行時由於建立的函數做用域更少了,內存開銷也隨之變小。
  1. 原理
  • 將全部模塊的代碼按照引用順序,放在一個函數做用域內,
  • 重命名一些變量防止命名衝突
  1. 小結
  • webapack4 中mode設置production後也是默認開啓的此項優化
  • 使用非 ES6 模塊或使用異步 import() 也不會把模塊放到同一個函數做用域中去
  • 還要注意得是,只合並被引用了一次的模塊,引用屢次的仍是分紅多個閉包,減小代碼的冗餘度

6. 多進程構建

  • webpack構建是一個涉及文件的讀寫的過程,若是項目很是複雜,構建時間就會加長,
  • 而webpack運行在nodejs上是單線程模型,同一時間只能處理一個任務
  • 咱們是否可讓webpack同時進行多任務處理呢 happypackthread loader給咱們提供了方案

因爲happypack做者再也不維護此項目,同時二者原理大體一致,咱們就主要介紹thread loader

  • thread loader由官方提供
  • thread loader放在最上面,就會在最後執行,以前的loader會在一個單獨的worker池中運行,
  • 每一個 worker 都是一個單獨的有 600ms 限制的 node.js 進程,從而實現了多進程的構建,下降構建時間 舉例
rules: [
    {
      test: /.js$/,
      use: [
        {
          loader: 'thread-loader',
          options: {
            workers: 3, // 產生的 worker 的數量,默認是 cpu 的核心數
          }
        }
      ]
  },
]

複製代碼

注意:thread loader 只有在項目龐大複雜的時候才能顯著的凸顯效果,若是是中小型項目沒有必要使用

日誌上報

7. 構建異常,中斷處理

  • 構建過程當中,有時會出現構建異常報錯的狀況,咱們能夠經過某些方法捕獲,以及自定義一些邏輯
plugins: [
    // 主動捕獲構建錯誤
    function () {
      this.hooks.done.tap('done', (stats) => {
        if (stats.compilation.errors
          && process.argv.indexOf('--watch' == -1)) {
          console.log('error', stats.compilation.errors);
          // 能夠作一個構建系統的日誌,在此處上報錯誤緣由
          process.exit(12); // 自定義錯誤code碼
        }
      });
    },
  ],
複製代碼

8. 並行壓縮

  • webpack4 推薦使用 terser-webpack-plugin 開啓 parallel
    • uglify-webpack-plugin也支持並行壓縮,但不支持es6,不作具體介紹,有興趣的同窗自行查詢
optimization: {
        minimizer: [
            new TerserPluginWebpack({
                parallel: 4, // 開啓 不主動指定的話,默認數值是當前電腦cpu數量的2倍減1
            })
        ],
    }
複製代碼

9. 分包,預編譯資源模塊

  1. 問題 以前的分包存在問題
  • externals 分包會打出太多的script標籤
  • splitchunk 分包須要一個分析的時間
  1. 解決
  • 預編譯資源模塊
  • 將react,react-dom redux 等基礎包和業務基礎包打包成一個文件
  1. 方法
  • dll-plugin進行分包,
  • dllreferenceplugin 對mainfest.json(對分離包的描述)引用,將預編譯的模塊加載進來
  • 利用add-asset-html-webpack-plugin把生成文件插入模版

舉例

// package.json
  "scripts": {
    "dll": "webpack --config build/webpack.dll.js" // 打包前只需執行一次,只要基礎包不變則不須要執行
  },
// webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
    entry: { // 指定要分離的包
        library: [ // 基礎包
            'react',
            'react-dom',
        ],
    },
    output: {
        filename: "[name]_[hash].dll.js",
        path: path.resolve(__dirname, './library'),
        library: "[name]_[hash]", //包名稱 注意此名需和下面的DllPlugin,name名一致
    },
    plugins: [
        new webpack.DllPlugin({
            name: "[name]_[hash]",
            path: path.resolve(__dirname, './library/[name].json')
        })
    ]
}
// webpack.prod.js
plugins: [
  ...
  // 將給定的 JS文件添加到 webpack 配置的文件中,並插入到模版文件中
  new AddAssetHtmlPlugin({
      filepath: path.resolve(__dirname, '../build/library/*.dll.js'),
    }),
  // 
  new webpack.DllReferencePlugin({
      manifest: require('../build/library/library.json')
    }),
]
複製代碼
  1. 總結 【1】使用範圍
  • 引用可是不會修改的npm包 【2】優勢:
  • 多個包打在了一塊兒
  • DllPlugin 將包含大量複用模塊且不會頻繁更新的庫進行編譯,只須要編譯一次,提高打包速度

10. 提高二次構建之後的速度

  • 緩存插件 hard-source-webpack-plugin
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
  plugins: [
    ...
    new HardSourceWebpackPlugin() 
  ]
]}
複製代碼

總結

  • dll 雖然提高打包速度,可是配置複雜
  • 而且vue-clicreate-react-app中並無使用dll
  • 因此若是隻是爲了提高打包速度,可使用hard-source-webpack-plugin替換dll
  • 但若是想把將多個npm包打成一個公共包,dll仍是有點用的

11. css的tree-shaking(Remove unused CSS)

  • 清除 css無用代碼
  • 早期PurifyCSSPlugin,它主要的做用是能夠去除沒有用到的CSS代碼,相似JS的Tree Shaking,現已再也不維護
  • 如今使用purgecss-webpack-plugin,配合mini-css-extract-plugin使用

舉例

const PATHS = {
    src: path.join(__dirname, '../src')
  }
plugin: [
          new PurgecssPlugin({	
            paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),	// 注意是絕對路徑匹配
          }),
]
複製代碼

12. webpack 圖片壓縮

  • 使圖片壓縮更加自動化
  • 基於node庫的imagemin
  1. 優勢
  • 支持定製選項,
  • 可引入第三方插件
  • 支持多個圖片格式
  1. 具體用image-webpack-loader 舉例
{
             test: /.(png|jpg|gif|jpeg)$/,
             use: [
                {
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        name:'[name]_[hash:8].[ext]'
                    }
                 },
                 {
                    loader: 'image-webpack-loader',
                    options: {
                      mozjpeg: {
                        progressive: true,
                        quality: 65
                      },
                      optipng: {
                        enabled: false,
                      },
                      pngquant: {
                        quality: [0.65, 0.90],
                        speed: 4
                      },
                      gifsicle: {
                        interlaced: false,
                      },
                      webp: {
                        quality: 75
                      }
                    }
                  },
             ],
         },
複製代碼

13 動態polyfill服務

  1. 問題
  • babel-polyfill 打包時佔資源比重較大
  • 可否按需加載呢
  1. polyfill-service
  • 獲取瀏覽器的useragent判斷支持狀況,
  • 動態的返回瀏覽器不支持的新特性
  • 官方提供了cdnhttps://polyfill.io/v3/polyfill.min.js
  • 也能夠基於官方自建polyfill服務,更加自由的配置屬性(好比,指定只判斷promise的支持程度) 例如 https://polyfill.alicdn.com/polyfill.min.js?features=Promise
  1. 注意
  • 國內瀏覽器衆多複雜,某些瀏覽器私自修改了useragent,致使判斷不許確
  • 咱們能夠判斷執行錯誤時加載回所有polyfill,進行一個降級處理

四. 配置總結

咱們以實際目標爲導向收集整理一下經常使用webpack配置要作什麼事情

1. 基礎配置

  1. 解析js
  2. 解析css
  3. 解析less
  4. 解析圖片
  5. 解析字體
  6. 前綴補齊
  7. 移動端適配
  8. 目錄清理
  9. 頁面打包
  10. css抽離
  11. 異常主動捕獲

2.提升構建速度

  1. resolve縮小文件的搜索範圍
  2. 使用DllPlugin減小基礎模塊編譯次數
  3. thread loader 多進程構建
  4. terser並行壓縮
  5. hard-source-webpack-plugin提高二次構建速度
  6. dll分包,預編譯資源模塊

3.減少構建體積

  1. 公共資源分離
  2. Tree Shaking
  3. scope-hoisting
  4. js,css,字體,圖片壓縮
  5. 動態polyfill
  6. css的tree-shaking
  7. 代碼分割,按需引入,import動態加載

4.提高開發體驗

  1. sourcemap源碼調試
  2. devserver熱更新
  3. 友好錯誤提示

5. 保證穩定安全

  1. 代碼壓縮混淆

小結

雖說了不少關於webpack的配置和優化,但咱們仍是要根據項目的複雜程度,公司&項目的具體狀況,處理遺留代碼的難度,來選擇性的處理, 有時若是強行使用反而得不償失

總結

固然最後要記住一切的技術都只是工具,都要以賦能業務,價值產出爲目標,打包工具的目標就是

  • 提升工做效率,
  • 提高開發體驗,
  • 提高用戶體驗,
  • 保證穩定安全

關於webpack原理loader&plugin請看 webpack4配置到優化到原理(下)

參考

  1. 玩轉webpack
  2. webpack文檔
  3. hard-source-webpack-plugin,webpack DllPlugin配置的代替方案
相關文章
相關標籤/搜索