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服务。

猜你喜欢

转载自www.cnblogs.com/Joe-and-Joan/p/12701767.html