zoukankan      html  css  js  c++  java
  • 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服务。
  • 相关阅读:
    Transformer详解
    PAT 1012
    PAT 1011
    PAT 1010
    Jordan Lecture Note-3: 梯度投影法
    PAT 1009
    PAT 1008
    Jordan Lecture Note-2: Maximal Margin Classifier
    PAT 1007
    PAT 1006
  • 原文地址:https://www.cnblogs.com/Joe-and-Joan/p/12701767.html
Copyright © 2011-2022 走看看