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


webpack

構建流程

  • 1、初始化參數:配置文件和shell語句合並參數,得到最終參數
  • 2、開始編譯:初始化Compiler編譯對象,加載插件,執行run開始編譯
  • 3、確定入口:根據entry找到入口文件
  • 4、編譯模塊:用loader進行翻譯后,找出對應依賴模塊
  • 5、完成編譯:確定了翻譯的內容和依賴關系
  • 6、輸出准備:根據入口和模塊的依賴關系,組裝成包含多個模塊的chunk,每個chunk轉成一個文件加載到輸出列表。
  • 7、執行輸出:根據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服務。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM