• webpack使用 三 优化环境配置


    webpack 性能优化

    • 开发环境性能优化
    • 生产环境性能优化

    开发环境性能优化

    • 优化打包构建速度
      • HMR 热模块替换
        • 构建时,只有一个模块发生变化,那么只会重新构建这一个模块,
          而其他模块都会从缓存中读取,不再重新构建
        • 针对 css/js/html 文件,html文件一般只有一个,不需要此操作;而对于css文件,style-loader是默认可以进行此操作,
          js 默认不支持,需要自己进行配置
    • 优化代码调试
      • source-map 提供源代码到构建后代码映射的技术
        • 要清除开发环境和生产环境的推荐使用
        • 开发环境推荐: eval-source-map / eval-cheap-module-source-map
        • 生产环境推荐: source-map / cheap-module-source-map

    生产环境优化

    • 优化打包构建速度

      • oneOf
        • 找到处理文件对应的loader时就停止继续查询,而不是每一个loader全部都过一遍
        • 注意: 当一个文件需要两个或者多个loader处理时,将其中一个放在oneOf里面,其余放在外面
      • babel 缓存
        • 第一次构建时,会缓存babel构建的结果,再次构建时,直接从缓存中读取
        • 通过babel-loader优化对js文件的处理
    • 优化代码运行的性能

      • 缓存 (进行强制缓存时,若文件发生改变时,为了识别到变化与否而进行的操作)
        • hash 无论文件内容是否变化,当重构建时,都会重置哈希值,导致重新构建没有发生变化的文件
        • chunkhash 来自同一个chunk(打包时来自于同一个入口的文件共享同一个哈希值),样式文件和js文件哈希值相同,
          一旦样式文件发生变化而js文件不变,js文件哈希值也要发生变化;同理,只改变js文件而不改变样式文件,样式文件哈希值也变
        • contenthash 根据文件内容生成哈希值,文件内容不同,哈希值一定不同
      • tree shaking
        • 去除掉应用程序中我们没有使用到的代码 (满足两个前提,自动开启tree shaking)
      • code split 将一个大的js文件在构建时拆分成多个js文件,然后可以并行加载,节约时间
        • 单入口文件
        • 多入口文件

    HMR : hot module replacement 热模块替换/模块热替换

    • 作用: 一个模块发生变化,只会重新打包这一个模块(而不是重新打包所有),提升构建速度
    • 样式文件: 可以使用HMR功能:因为style-loader内部实现了
    • js文件: 默认不能使用HMR功能
    • 解决: 修改js代码,添加支持HMR功能的代码
    • 一旦module.hot为true,说明开启了HMR功能 —> 让HMR功能功能生效
    if(module.hot){
        // 此方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建,会执行后面的回到函数
        module.hot.accept('./print.js',function () {
            print();
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 注意: HMR功能对js的处理,只能处理非入口js文件
    • html文件: 默认不能使用HMR功能,同时会导致问题:html文件不能热更新了(不用做HMR功能)
    • 解决: 修改entry入口,将html文件引入
     entry: ['./src/js/index.js','./src/index.html'],
    
    • 1

    source-map:

    一种提供源代码到构建后代码映射关系的技术(如果构建后代码出错了,通过映射关系可以追踪源代码错误)

    • [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
     devtool:'eval-source-map',
    
    • 1

    inline-source-map 内联

    • 只生成了一个 source-map
    • 错误代码准确信息 和 源代码的错误位置

    hidden-source-map 外部

    • 错误代码 和 错误原因, 但是没有错误位置
    • 不能提示源代码错误,只能提示到构建后代码的错误位置

    eval-source-map 内联

    • 每一个文件都生成了对应的 source-map,在built.js内部生成

    nosourcrs-source-map 外部

    • 错误代码准确信息 但是没有任何源代码的错误信息

    cheap-source-map

    • 错误代码的准确信息 和 源代码的错误位置
    • 只能精确到行,但不能精确到列

    cheap-module-source-map

    • 错误代码准确信息 和 源代码的错误位置
    • module会将loader的source map 加入

    内联 和 外部的区别:

      1. 外部生成了文件,内联没有 2. 内联构建速度跟快
    • 开发环境: 速度快,调试更友好
    • 速度(eval > inline > cheap…)
    • eval-cheap-source-map
    • eval-source-map‘
    • 调试更友好
    • source-map
    • cheap-module-source-map
    • cheap-source-map
    • —> eval-source-map / eval-cheap-module-source-map
    • 生产环境: 源代码是否隐藏? 调试要不要更友好
    • 内联会让代码体积变大,所以生产环境下不用内联
    • nosources-source-map 全部隐藏
    • hidden-source-map 只隐藏源代码,会提示构建后代码错误
    • —> source-map / cheap-module-source-map

    oneOf

    正常来说,一个文件会被所有的loader过滤处理一遍,如果我有100个loader配置,那么我一个文件就要被100个loader匹配,而使用oneOf后,而如果放在oneOf中的loader规则有一个匹配到了,oneOf中的其他规则就不会再对这文件进行匹配

    注意:oneOf中不能有两个loader规则配置处理同一种文件,否则只能生效一个 例如:对于js进行eslint检测后再进行babel转换

    ​ 解决:将eslint抽出到外部,然后优先执行,这样在外部检测完后oneOf内部配置就会再进行检测匹配
    以下loader只会匹配一个
    注意: 不能有两个配置处理同一个文件

    oneOf:[
                        {
                            test:/\.css$/,
                            use:[
                                // 此loader会将css代码以标签的形式整合进js代码中
                                // 'style-loader',
                                ...commonCssLoader
                            ]
                        },
                        {
                            test:/\.less$/,
                            use:[
                                ...commonCssLoader,
                                'less-loader'
                            ]
                        },
                        /**
                         * 正常来讲,一个文件只能被一个loader处理。
                         * 当一个文件要被多个loader处理时,那么一定要指定loader的先后顺序
                         * 先执行 eslint,再执行babel
                         */
                        // 在package.json中的eslintConfig ---> airbnb规则
                        // 对js做兼容性处理
                        {
                            test:/\.js$/,
                            exclude:/node_module/,
                            loader:'babel-loader',
                            options:{
                                presets:[
                                    // 转译新的es6语法
                                    '@babel/preset-env',
                                    {
                                        useBuiltIns:'usage',
                                        corejs:{version:3},
                                        targets:{
                                            chrome:'60',
                                            firefox:'50'
                                        }
                                    }
                                ]
                            },
                        },
                        {
                            test:/\.(jpg|png|gif|)$/,
                            loader: 'url-loader',
                            options: {
                                limit:8*1024,
                                name:'[hash:10].[ext]',
                                outputPath:'imgs',
                                esModule:false,
                            }
                        },
                        {
                            tset:/\.html$/,
                            loader:'html-loader'
                        },
                        {
                            exclude:/\.(js|html|less|jpg|png|gif|css)/,
                            loader: 'file-loader',
                            options: {
                                outputPath: 'media',
                            }
                        }
                    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    缓存

    babel缓存

      cacheDirectory: true
        让第二次打包构建速度更快
    
    • 1
    • 2

    文件资源缓存

      hash: 每次wepack构建时会生成一个唯一的hash值。
        问题: 因为js和css同时使用一个hash值。
          (可能我却只改动一个文件)如果重新打包,会导致所有缓存失效。
    
    • 1
    • 2
    • 3

    chunkhash:代码块缓存

       根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
        问题: js和css的hash值还是一样的
          因为css是在js中被引入的,所以同属于一个chunk
          chunk:从一个入口文件引入的其他依赖和其他文件,都属于同一个chunk文件
    
    • 1
    • 2
    • 3
    • 4

    contenthash

    根据文件的内容生成hash值。不同文件hash值一定不一样
    让代码上线运行缓存更好使用

    treeShaking

    指的就是当我引入一个模块的时候,我不引入这个模块的所有代码,我只引入我需要的代码,这就需要借助 webpack 里面自带的 Tree Shaking 这个功能来帮我们实现。

    官方有标准的说法:Tree-shaking的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination)

    在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 Tree-Shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。

    在production 模式下不用在webpack.config.js中配置

    前提: 1. 必须使用ES6模块化 2. 开启production环境
    满足这两个前提会自动开启 tree shaking
    作用: 减少代码体积

     在package.json中配置
     "sideEffects": false 即所有代码都没有副作用(都可以进行tree shaking)
     问题: 可能会将css/ @babel/ployfill (副作用)文件干掉
     "sideEffects":["*.css"] 将css资源标记为不会进行tree shaking的资源
    
    • 1
    • 2
    • 3
    • 4

    代码分割

    将打包输出的一个 chunk 分割成多个 chunk,加载时候可以并行加载等等,加快加载速度,还可实现按需加载等等
    主要是对js代码进行分割

    方法1 根据入口文件进行代码分割

      // 单入口 一般对应单页面应用
      // entry: './src/js/index.js',
      entry: {
        // 多入口:有一个入口,最终输出就有一个bundle
        index: './src/js/index.js',
        test: './src/js/test.js'
      },
      output: {
        // [name]:取文件名
        filename: 'js/[name].[contenthash:10].js',
        path: resolve(__dirname, 'build')
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    方法2 optimization配置项进行优化

    1. 对于单入口文件,可以将node_modules中的代码单独打包成一个chunk,最终输出
    2. 对于多入口文件,会自动分析多入口chunk中,有没有公共的文件.如果有会将相同的公共文件打包成一个单独的chunk
    optimization:{
        splitChunks:{
          chunks:'all'
        }
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5

    方法三 通过js代码,让某个文件被单独打包成一个chunk

    // ES10 的 import语法
    //通过注释,可以让js生成的打包文件带上这个名字
    import(/* webpackChunkName: 'test' */'./test')
      .then(({ mul, count }) => {
        // 文件加载成功~
        // eslint-disable-next-line
        console.log(mul(2, 5));
      })
      .catch(() => {
        // eslint-disable-next-line
        console.log('文件加载失败~');
      });
    // eslint-disable-next-line
    console.log(sum(1, 2, 3, 4));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    懒加载 (lazy loading) 和预加载

    应用场景:当我们模块很多时,导入的js太多,或者说有的js只有使用的时候才有用,而我一开始便加载,就可能造成一些不必要的性能浪费

    1、懒加载:当文件需要使用时才加载

    ​ 可能的问题:当用户第一次使用时,如果js文件过大,可能造成加载时间过长(有延迟),但是第二次就不会了,因为懒加载第二次是从缓存中读取文件

    2、预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载

    ​ 正常加载可以认为时并行加载(同一时间加载多个文件,但是同一时间有上限)

    ​ 就例如下面例子,有预加载的代码运行效果,是页面刷新后,但是还未进行使用时,该文件其实已经加载好了

    注意:预加载虽然性能很不错,但是需要浏览器版本较高,兼容性较差,慎用预加载

    console.log('index.js文件被加载了~');
    // import { mul } from './test';
    //懒加载
    document.getElementById('btn').onclick = function() {
        //懒加载其实也是需要前面Ⅵ代码分割功能,将我的需要加载的文件打包成单独文件
      import(/* webpackChunkName: 'test'*/'./test').then(({ mul }) => {
        console.log(mul(4, 5));
      });
    };
    //预加载
    //在注释参数上添加 webpackPrefetch: true 
      import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
        console.log(mul(4, 5));
      });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    PWA (离线访问)

    PWA: 渐进式网络开发应用程序(离线可访问) workbox -->下载依赖: workbox-webpack-plugin

    1、在配置中使用该插件 :① 帮助serviceworker快速启动 ② 删除旧的 serviceworker

    2、在入口文件js中添加代码

    3、eslint不认识 window、navigator全局变量

    解决:需要修改package.json中eslintConfig配置

    4、代码必须运行在服务器上才有效果

    ① node.js

    ② npm i serve -g -->serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去

    webpack.config.js新增配置

     const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
    plugins: [
        new WorkboxWebpackPlugin.GenerateSW({
          /*生成一个 serviceworker 配置文件~*/
          //1. 帮助serviceworker快速启动
          clientsClaim: true,
          //2. 删除旧的 serviceworker
          skipWaiting: true
        })
      ],
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    入口文件js -->index.js

    // 注册serviceWorker
    // 处理兼容性问题
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker
          .register('/service-worker.js')
          .then(() => {
            console.log('sw注册成功了~');
          })
          .catch(() => {
            console.log('sw注册失败了~');
          });
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    package.json新增配置

     "eslintConfig": {
        "extends": "airbnb-base",
        "env": {
          "browser": true //开启为eslint支持浏览器端的bian'l,比如 window 
        }
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    多线程打包

    1、下载thread-loader依赖

    2、使用loader: 'thread-loader’开启多线程打包

    注意点:进程启动大约为600ms,进程通信也有开销,只有工作消耗时间较长,才需要多进程打包 比如:babel转换可以使用多线程

    const { resolve } = require('path');
    module.exports = {
      module: {
        rules: [
            oneOf: [
              {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                  /* 
                    开启多进程打包。 
                    进程启动大概为600ms,进程通信也有开销。
                    只有工作消耗时间比较长,才需要多进程打包
                  */
                  {
                    loader: 'thread-loader',
                    options: {
                      workers: 2 //设置 进程2个
                    }
                  },
                  {
                    loader: 'babel-loader',
                    options: {
                      presets: [
                        [
                          '@babel/preset-env',
                          {
                            useBuiltIns: 'usage',
                            corejs: { version: 3 },
                            targets: {
                              chrome: '60',
                              firefox: '50'
                            }
                          }
                        ]
                      ],
                      // 开启babel缓存
                      // 第二次构建时,会读取之前的缓存
                      cacheDirectory: true
                    }
                  }
                ]
              }
            ]
          }
        ]
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    externals

    当你使用外部引入代码时:如CDN引入,不想他将我引入的模块也打包,就需要添加这个配置

    即:声明哪些库是不进行打包的
    –>externals: {}

    const { resolve } = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      entry: './src/js/index.js',
      output: {
        filename: 'js/built.js',
        path: resolve(__dirname, 'build')
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html'
        })
      ],
      mode: 'production',
      externals: {
        // 拒绝jQuery被打包进来
        jquery: 'jQuery'
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    dll

    使用dll技术,对某些库(第三方库:jquery、react、vue…)进行单独打包

    作用:如果不是cdn引入,而是使用第三方库,想要打包后暴露出去,使用该方法

    1、首先你需要写一个新的配置文件,因为使用dll技术,所以命名为webpack.dll.js

    当你运行 webpack 时,默认查找 webpack.config.js 配置文件 需求:需要先运行 webpack.dll.js 文件

    –> webpack --config webpack.dll.js 在这个文件中进行对某些库的单独打包

    2、在webpack.config.js中,需要告诉webpack哪些库不需要再次打包(即在dll.js中打包后生成的文件)

    3、这里需要使用到add-asset-html-webpack-plugin与webpack插件

    4、运行webpack.dll.js对第三方库进行单独打包后,除非你要加新的库,不然不用再重新打包这个,直接webpack打包其他的即可

    webpack.dll.js配置文件

    const { resolve } = require('path');
    const webpack = require('webpack');
    
    module.exports = {
      entry: {
        // 最终打包生成的[name] --> jquery
        // ['jquery'] --> 要打包的库是jquery
        jquery: ['jquery'],
       //  react:['react','react-dom' ]
      },
      output: {
        filename: '[name].js',
        path: resolve(__dirname, 'dll'),
        library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
      },
      plugins: [
        // 打包生成一个 manifest.json --> 提供和jquery映射
        new webpack.DllPlugin({
          name: '[name]_[hash]', // 映射库的暴露的内容名称
          path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
        })
      ],
      mode: 'production'
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    webpack.config.js配置文件

    const { resolve } = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const webpack = require('webpack');
    const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
    
    module.exports = {
      entry: './src/index.js',
      output: {
        filename: 'built.js',
        path: resolve(__dirname, 'build')
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html'
        }),
        // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
        new webpack.DllReferencePlugin({
          manifest: resolve(__dirname, 'dll/manifest.json')
        }),
        // 将某个文件打包输出去,并在html中自动引入该资源
        new AddAssetHtmlWebpackPlugin({
          filepath: resolve(__dirname, 'dll/jquery.js')
        })
      ],
      mode: 'production'
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    Linux常用指令总结
    Seatunnel系列之:深入理解Seatunnel,快速应用Seatunnel实现数据同步
    操作系统学习笔记3-同步互斥问题
    docker搭建rabbitmq集群
    Java抽象类
    尚硅谷Flume(仅有基础)
    InnoDB引擎之flush脏页
    Xcode 异常图片导致ipa包增大问题
    629. K个逆序对数组--(每日一难phase2--days11)
    【C语言】文件操作(二)
  • 原文地址:https://blog.csdn.net/csdssdn/article/details/126180585