webpack如何提升打包速度和工程優化

webpack

構建流程

  • 一、初始化參數:配置文件和shell語句合併參數,獲得最終參數
  • 二、開始編譯:初始化Compiler編譯對象,加載插件,執行run開始編譯
  • 三、肯定入口:根據entry找到入口文件
  • 四、編譯模塊:用loader進行翻譯後,找出對應依賴模塊
  • 五、完成編譯:肯定了翻譯的內容和依賴關係
  • 六、輸出準備:根據入口和模塊的依賴關係,組裝成包含多個模塊的chunk,每一個chunk轉成一個文件加載到輸出列表。
  • 七、執行輸出:根據output路徑和文件名,寫入文件系統。

bundle,chunk,module分別指什麼

  • Entry:做爲構建依賴圖的入口文件
  • Output:輸出建立的bundle到指定文件夾
  • bundle(包):webpack打包出來的文件
  • chunk(代碼塊):一個 Chunk 由多個模塊組合而成,用於代碼合併與分割。
  • module(模塊):Webpack 裏一切皆模塊(圖片、ES6模塊)、一個模塊對應一個文件。Webpack 會從配置的 Entry 開始遞歸找出全部依賴的模塊。

Loader和Plugin的區別

  • Loader(加載器)
    • 用於文件轉換
    • webpack原生只能解析js,loader使webpack能夠加載和解析非js文件(css、圖片)
    • 用法:module.rules配置,數組裏面每項都是object,描述了{ test針對類型、loader使用什麼加載、options使用的參數 }
      module: {
          rules: [
              {
                  test: /\.vue$/,
                  loader: 'vue-loader',
                  options: vueLoaderConfig
              },
              {
                  test: /\.scss$/,
                  loaders: ['style-loader', 'css-loader', 'sass-loader']
              },
          ]
      }
  • 常見Loader
    • url-loader:小文件以 base64 的方式把文件內容注入到代碼中去
    • css-loader:加載 CSS,支持模塊化、壓縮、文件導入等特性
    • style-loader:把外部 CSS 代碼注入到 html 中,經過 DOM 操做去加載 CSS。
    • sass-loader: sass語法轉換
    • babel-loader:把 ES6 轉換成 ES5
    • eslint-loader: ESLint 檢查 JavaScript 代碼
  • Plugin(插件)
    • 用於擴展webpack的功能(如打包優化、壓縮)
    • 在webpack打包過程廣播不少事件,Plugin監聽事件並在合適的時機使用webpack的api改變輸出結果。
    • 用法:plugins中單獨配置,數組裏每項都是一個plugin實例,參數由構造函數傳入。
      plugins: [
          new HtmlWebpackPlugin(),
          new ProgressBarPlugin(),
          new webpack.LoaderOptionsPlugin({
              minimize: true
          }),
          new VueLoaderPlugin(),
      ]
  • 常見Plugin
    • define-plugin:定義環境變量
    • html-webpack-plugin:簡化html文件建立,設置loading
    • uglifyjs-webpack-plugin:經過UglifyES壓縮ES6代碼
    • webpack-parallel-uglify-plugin: 多核壓縮,提升壓縮速度
    • webpack-bundle-analyzer: 可視化webpack輸出文件的體積

HMR熱更新原理(hot module replacement)

  • Webpack服務端檢測到代碼或者文件有改動,對模塊從新編譯打包,保存內存中。
  • webpack-dev-middleware和webpack交互來監控代碼,服務端和瀏覽器創建websocket長鏈接,傳遞新模塊的hash給瀏覽器
  • HMR.runtime收到新模塊hash,Jsonp.runtime向服務端發送ajax請求,獲取json更新列表(包含全部要更新模塊的最新hash)再經過jsonp獲取最新hash對應的模塊代碼。
  • HotModulePlugin進行模塊對比,是否要進行模塊替換及更新依賴引用。
  • HMR失敗,經過瀏覽器刷新獲取最新代碼

webpack在vue cli3的使用

  • 默認splitChunks和minimize
    • 代碼就會自動分割、壓縮、優化,
    • 可單獨拆包配置,如elementUI
    • 同時 webpack 也會自動幫你 Scope hoisting(變量提高) 和 Tree-shaking
    splitChunks: {
    // ...
        cacheGroups: {    
            elementUI: {
                name: "chunk-elementUI", // 單獨將 elementUI 拆包
                priority: 15, // 權重需大於其它緩存組
                test: /[\/]node_modules[\/]element-ui[\/]/
            }
        }
    }
  • 默認CSS壓縮:mini-css-extract-plugin
    • 升級:將原先內聯寫在每個 js chunk bundle的 css,單獨拆成了一個個 css 文件。
    • css 獨立拆包最大的好處就是 js 和 css 的改動,不會影響對方,致使緩存失效。
    • 配合optimization.splitChunks去拆開打包獨立的css文件
  • 默認Tree-Shaking:將代碼中永遠不會走到的片斷刪除掉。
    //index.js
    import {add, minus} from './math';
    add(2,3);//minus不會參與構建
    • 原理:基於ES6 modules 的靜態特性檢測,就是import 語法,來找出未使用的代碼
    • webpack 4 : package.json 文件中設置 sideEffects: false表示該項目或模塊是 pure 的,能夠進行無用模塊刪除。
    • webpack2: .babelrc 裏設置 modules: false ,避免module被轉換commonjs
    • 真正生效:引入資源時,僅僅引入須要的組件,
  • 配置configureWebpack選項,可爲對象或函數(基於環境有條件地配置), 合併入最終的 webpack 配置
    // vue.config.js
    module.exports = {
      configureWebpack: {
        plugins: [
          new MyAwesomeWebpackPlugin()
        ]
      }
    }
    // vue.config.js
    module.exports = {
      configureWebpack: config => {
        if (process.env.NODE_ENV === 'production') {
          // 爲生產環境修改配置...
        } else {
          // 爲開發環境修改配置...
        }
      }
    }
  • 鏈式操做,修改/新增/替換Loader,更細粒度的控制其內部配置
    // vue.config.js
    module.exports = {
      chainWebpack: config => {
        config.module
          .rule('vue')
          .use('vue-loader')
            .loader('vue-loader')
            .tap(options => {
              // 修改它的選項...
              return options
            })
      }
    }

webpack打包加速優化

  • 提升熱更新速度:
    • 提升熱更新速度,上百頁 2000ms內搞定,10幾頁面區別不大
    //在.env.development環境變量配置
     VUE_CLI_BABEL_TRANSPILE_MODULES:true
    • 原理:利用插件,在開發環境中將異步組件變爲同步引入,也就是import()轉化爲require())
    • 通常頁面到達幾十上百,熱更新慢的狀況下須要用到。
    • webpack5 即將發佈,大幅提升了打包和編譯速度
  • 分析打包時長:
    • webpack-bundle-analyzer 分析打包後的模塊文件大小
    npm run build -- --report
    npm install --save-dev speed-measure-webpack-plugin
    //vue.config.js
    //導入速度分析插件
    const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
    //實例化插件
    const smp = new SpeedMeasurePlugin();
    
    module.exports = {
    configureWebpack: smp.wrap({
            plugins: [
                // 這裏是本身項目裏須要使用到的其餘插件
                new yourOtherPlugin()
            ]
        })
    }
  • 較耗時:代碼的編譯或壓縮(轉化 AST樹 -> 遍歷AST樹 -> 轉回JS代碼)
    • 編譯 JS、CSS 的 Loader
    • 壓縮 JS、CSS 的 Plugin
  • 緩存:讓二次構建時,不須要再去作重複的工做[沒有變化的直接使用緩存,速度更快]
    • 開啓Loader、壓縮插件的cache配置【如babel-loader的cacheDirectory:true】,uglifyjs-webpack-plugin【如cache: true】,構建完將緩存存放在node_modules/.cache/..。
    • cache-loader:將 loader 的編譯結果寫入硬盤緩存,再次構建若是文件沒有發生變化則會直接拉取緩存,添加在時間長的 loader 的最前面。
    module: {
    rules: [
      {
        test: /\.ext$/,
        use: ['cache-loader', ...loaders],
        include: path.resolve('src'),
      },
    ],
    },
  • 多核:充分利用了硬件自己的優點
    • happypack:開啓系統CPU最大線程,經過插件將loader包裝,暴露id,直接module.rules引用該id。
    //安裝:npm install happypack -D
    //引入:const Happypack = require('happypack');
    exports.plugins = [
      new Happypack({
        id: 'jsx',
        threads: 4,
        loaders: [ 'babel-loader' ]
      }),
    
      new Happypack({
        id: 'styles',
        threads: 2,
        loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
      })
    ];
    
    exports.module.rules = [
      {
        test: /\.js$/,
        use: 'Happypack/loader?id=jsx'
      },
    
      {
        test: /\.less$/,
        use: 'Happypack/loader?id=styles'
      },
    ]
    • thread-loader:添加在此loader後面的放入單獨的 worker 池裏運行,配置簡單
    //安裝:npm install thread-loader -D
    module.exports = {
        module: {
                //個人項目中,babel-loader耗時比較長,因此我給它配置 thread-loader
                rules: [
                    {
                        test: /\.jsx?$/,
                        use: ['thread-loader', 'cache-loader', 'babel-loader']
                    }
                ]
        }
    }
    • 默認的TerserWebpackPlugin:開啓了多進程和緩存,緩存文件 node_modules/.cache/terser-webpack-plugin
    • 其餘並行壓縮插件:
      • webpack-parallel-uglify-plugin:子進程併發執行把結果送回主進程,多核並行壓縮來提高壓縮速度
      • uglifyjs-webpack-plugin自帶的parallel:【如parallel: true】配置項開啓多核編譯
  • 抽離:Vue全家桶、echarts、element-ui、工具庫lodash不常變動的依賴 【幾十秒】
    • 內置webpack的 DllPlugin 和 DllReferencePlugin 引入dll, 經過DllPlugin來對那些咱們引用可是絕對不會修改的npm包來進行預編譯,再經過DllReferencePlugin將預編譯的模塊加載進來,避免反覆編譯浪費時間。新建一個webpack.dll.config.js 的配置文件,通常不變化,若是變了,新的dll文件名便會加上新的hash
    // webpack.config.dll.js
    const webpack = require('webpack');
    const path = require('path');
    
    module.exports = {
        entry: {
            react: ['react', 'react-dom']
        },
        mode: 'production',
        output: {
            filename: '[name].dll.[hash:6].js',
            path: path.resolve(__dirname, 'dist', 'dll'),
            library: '[name]_dll' //暴露給外部使用
            //libraryTarget 指定如何暴露內容,缺省時就是 var
        },
        plugins: [
            new webpack.DllPlugin({
                //name和library一致
                name: '[name]_dll', 
                path: path.resolve(__dirname, 'dist', 'dll', 'manifest.json') //manifest.json的生成路徑
            })
        ]
    }
    
    // package.json 中新增 dll 命令
    {
        "scripts": {
            "build:dll": "webpack --config webpack.config.dll.js"
        },
    }
    
    // npm run build:dll 後,會生成 
    dist
        └── dll
            ├── manifest.json
            └── react.dll.9dcd9d.js
    
    // manifest.json 用於讓 DLLReferencePlugin 映射到相關依賴上。至此 dll 準備工做完成,接下來在 webpack 中引用便可。
    
    // webpack.config.js
    const webpack = require('webpack');
    const path = require('path');
    module.exports = {
        //...
        devServer: {
            contentBase: path.resolve(__dirname, 'dist')
        },
        plugins: [
            new webpack.DllReferencePlugin({
                manifest: path.resolve(__dirname, 'dist', 'dll', 'manifest.json')
            }),
            new CleanWebpackPlugin({
                cleanOnceBeforeBuildPatterns: ['**/*', '!dll', '!dll/**'] //不刪除dll目錄
            }),
            //...
        ]
    }
    // 使用 npm run build 構建,能夠看到 bundle.js 的體積大大減小。
    // 修改 public/index.html 文件,在其中引入 react.dll.js
    <script src="/dll/react.dll.9dcd9d.js"></script>
    • 配置Externals(推薦):外部引入,將不須要打包的靜態資源從構建邏輯中剔除,使用 CDN 的方式去引用。
      • 步驟:在externals中配置key[包名]+value[CDN全局變量名],而後在HTML中引入CDN的script 標籤。就能實現import引入了。
      //webpack.config.js
          module.exports = {
              //...
              externals: {
                  //jquery經過script引入以後,全局中即有了 jQuery 變量
                  'jquery': 'jQuery'
              }
          }
      • 常見CDN連接由host域名+包名+版本號+路徑
      <script src="https://cdn.bootcss.com/react/16.9.0/umd/react.production.min.js"></script>
      • 有些 CDN 服務不穩定,儘可能選擇成熟的CDN服務。
相關文章
相關標籤/搜索