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();
    })
}
  • 注意: HMR功能对js的处理,只能处理非入口js文件
  • html文件: 默认不能使用HMR功能,同时会导致问题:html文件不能热更新了(不用做HMR功能)
  • 解决: 修改entry入口,将html文件引入
 entry: ['./src/js/index.js','./src/index.html'],

source-map:

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

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

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',
                        }
                    }
                ]

缓存

babel缓存

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

文件资源缓存

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

chunkhash:代码块缓存

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

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的资源

代码分割

将打包输出的一个 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')
  },

方法2 optimization配置项进行优化

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

方法三 通过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));

懒加载 (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));
  });

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
    })
  ],

入口文件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注册失败了~');
      });
  });
}

package.json新增配置

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

多线程打包

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
                }
              }
            ]
          }
        ]
      }
    ]
  }
};

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'
  }
};

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'
};

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'
};

猜你喜欢

转载自blog.csdn.net/csdssdn/article/details/126180585