7.webpack-optimise

203 阅读8分钟

缩小范围

    1. extensions: 指定extensions之后可以不用在require或者import的时候加文件拓展名,会依次尝试添加拓展名进行匹配
    1. alias: 配置别名可以加快webpack查找模块的速度
    • 如每当引入bootstrap模块的时候,会直接引入bootstrap,而不需要从node_modules文件夹中按照模块查找规则查找
    1. modules
    • 对于直接声明依赖名的模块(如react),webpack会类似node.js一样进行路径搜索,搜索node_modules目录
    • 这个目录就是使用resolve.modules字段进行配置的默认设置
    1. mainFields
    • 默认情况下package.json 文件则按照文件中 main 字段的文件名来查找文件
    1. mainFiles
    • 当目录下没有 package.json 文件时,我们说会默认使用目录下的 index.js 这个文件,其实这个也是可以配置的
    1. resolveLoader
    • resolve.resolveLoader用于配置解析 loader 时的 resolve 配置,默认的配置如下
const bootstrap = path.resolve(__dirname,'node_modules/_bootstrap@3.3.7@bootstrap/dist/css/bootstrap.css');
resolve: {
    extensions: [".js", ".jsx", ".json", ".css"],
    alias: {
        "bootstrap": bootstrap
    },
    // modules: ['node_modules'], // 默认配置
    modules: [path.resolve(__dirname, 'node_modules')],
    // 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是:
    mainFields: ['browser', 'module', 'main'],
    // target 的值为其他时,mainFields 默认值为:
    mainFields: ["module", "main"],
    mainFiles: ['index'], // 你可以添加其他默认使用的文件名
}
resolveLoader: {
    modules: [ 'node_modules' ],
    extensions: [ '.js', '.json' ],
    mainFields: [ 'loader', 'main' ]
}

noParse

  • module.noParse 字段,可以用于配置哪些模块文件的内容不需要进行解析
  • 不需要解析依赖(即无依赖) 的第三方大型类库等,可以通过这个字段来配置,以提高整体的构建速度
  • 使用 noParse 进行忽略的模块文件中不能使用 import、require、define 等导入机制
module.exports = {
    module: {
        noParse: /jquery|lodash/, // 正则表达式
        // 或者使用函数
        noParse(content) {
          return /jquery|lodash/.test(content)
        },
    }
}

DefinePlugin 配置全局常量

IgnorePlugin 忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去

// 方式1:忽略掉全部语言包 需要哪一个再手动引入
import moment from 'moment';
import 'moment/locale/zh-cn';

new webpack.IgnorePlugin({
  //A RegExp to test the context (directory) against.
  contextRegExp: /moment$/,
  //A RegExp to test the request against.
  resourceRegExp: /^\.\/locale/
})


// 方式2:通过正则匹配忽略掉不是中文的语言包
  /*  new webpack.IgnorePlugin( {
     //A filter function for resource and context.
     checkResource: (resource, context) => {
         if(/moment$/.test(context)){
             if(/^\.\/locale/.test(resource)){
                 if(!(/zh-cn/.test(resource))){
                     return true;
                 }
             }
         }
         return false;
     } 
 })*/

日志优化

- 日志太多太少都不美观
- 可以修改stats
- friendly-errors-webpack-plugin
/*
errors-only 只在错误时输出
minimal 发生错误和新的编译时输出
none 没有输出
normal 标准输出
verbose 全部输出
*/ 
+ stats:'verbose',
  plugins:[
+   new FriendlyErrorsWebpackPlugin()
  ]

日志输出

"scripts": {
    "build": "webpack",
+    "build:stats":"webpack --json > stats.json",
    "dev": "webpack-dev-server --open"
  },
  
const webpack = require('webpack');
const config = require('./webpack.config.js');
webpack(config,(err,stats)=>{
    if(err){
      console.log(err);
    }
    if(stats.hasErrors()){
      return console.error(stats.toString("errors-only"));
    }
    console.log(stats);
});

费时分析

const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
module.exports =smw.wrap({
});

libraryTarget 和 library

  • 当用 Webpack 去构建一个可以被其他模块导入使用的库时需要用到它们
  • output.library 配置导出库的名称
  • output.libraryExport 配置要导出的模块中哪些子模块需要被导出。 它只有在 output.libraryTarget 被设置成 commonjs 或者 commonjs2 时使用才有意义
  • output.libraryTarget 配置以何种方式导出库,是字符串的枚举类型,支持以下配置 libraryTarget
    var => 以全局变量的形式提供使用 commonjs => require('npm-name')['calculate'].add() commonjs2 => require('npm-name').add() this => 使用:this.calculate.add() window => window.calculate.add() global => global.calculate.add() umd => 同时支持commonjs xommonjs2 amd script

实现css文件的提取和压缩

  • purgecss-webpack-plugin
  • 可以去除未使用的css, 一般和glob/glob-all配合使用
  • 必须和mini-css-extract-plugin配合使用
  • paths路径是绝对路径 npm i purgecss-webpack-plugin mini-css-extract-plugin css-loader glob -D
const path = require("path");
+const glob = require("glob");
+const PurgecssPlugin = require("purgecss-webpack-plugin");
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PATHS = {
  src: path.join(__dirname, 'src')
}
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  module: {
    rules: [
      {
        test: /\.js/,
        include: path.resolve(__dirname, "src"),
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-env", "@babel/preset-react"],
            },
          },
        ],
      },
+      {
+        test: /\.css$/,
+        include: path.resolve(__dirname, "src"),
+        exclude: /node_modules/,
+        use: [
+          {
+            loader: MiniCssExtractPlugin.loader,
+          },
+          "css-loader",
+        ],
+      },
    ],
  },
  plugins: [
+    new MiniCssExtractPlugin({
+      filename: "[name].css",
+    }),
+    new PurgecssPlugin({
+      paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),//true表示处理文件不处理目录
+    })
  ],
};

DLL --webpack5中已经废弃

  • .dll为后缀的文件成为动态链接库,在一个动态链接库中可以包含给其他模块调用的函数和数据
  • 把基础模块独立出来打包到单独的动态链接库里
  • 当需要导入的模块在动态链接库里的时候,模块不用再次被打包,而是去动态链接库里获取
  • 定义dll
    • DllPlugin插件:用于打包出一个个动态连接库
    • DllReferencePlugin: 在配置文件中引入DllPlugin插件打包好的动态连结库

thread-loader 多进程打包

  • 把这个 loader 放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行
  • thread-loader并不一定能提高打包速度,项目比较小时没必要开启多进程,可能反而会更慢
  • happypack也可以实现多进程,但已废弃
rules: [
      {
        test: /\.js/,
        include: path.resolve(__dirname, "src"),
        use: [
+          {
+            loader:'thread-loader',
+            options:{
+              workers:3
+            }
+          },
          {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-env", "@babel/preset-react"],
            },
          },
        ],
      },
  ]

CDN

  • CDN 又叫内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。
  • html不缓存
  • 资源文件(js css 图片)文件加上hash 长期缓存
  • 为了并行加载 把静态资源分散到不同的域名上去
  • 域名解析 dns-prefetch 进行域名的预解析
    • 在 HTML HEAD 标签中 加入<link rel="dns-prefetch" href="http://img.zhufengpeixun.cn">去预解析域名,以降低域名解析带来的延迟
  • 启用CDN之后 相对路径,都变成了绝对的指向 CDN 服务的 URL 地址
  • 接入cdn
{
        output: {
        path: path.resolve(__dirname, 'dist'),
+       filename: '[name]_[hash:8].js',
+       publicPath: 'http://img.zhufengpeixun.cn'
    },
}

  • hash/chunkhash/contenthash

tree shaking

  • 用到的方法才打包
  • 原理是利用es6模块的特点,只能作为模块顶层语句出现 import的模块名只能是字符串常量 webpack默认支持,在.babelrc里设置modules:false即可在production mode下默认开启 "sideEffects": false 所有的代码都没有副作用(都可以进行 tree shaking) 可能会把 css / @babel/polyfill文件干掉 可以设置"sideEffects":["*.css"]
{
    loader: "babel-loader",
    options: {
+              presets: [["@babel/preset-env",{"modules":false}], "@babel/preset-react"],
    },
  },
  • modules:false

代码先经过babel-loader的处理,调用babel进行编译。modules:false不会默认换import和export语法,把这个语法给webpack,webapck才能去进一步处理; modules:true 会转换import export变成commonjs的require、module.exports 这样就实现不了tree shaking了; 实现tree-shaking的前提是es module 而不能是commonjs

代码分割

  • 方式
    • 1.入口分割
    • 2.import动态导入
  • preload/prefetch
    • blog.csdn.net/vivo_tech/a…
    • preload 预加载
    • 因为异步/延迟/插入的脚本(无论在什么位置)在网路优先级都是low
    • preload-webpack-plugin
    • prefetch(预先拉取)
      • prefetch 跟 preload 不同,它的作用是告诉浏览器未来可能会使用到的某个资源,浏览器就会在闲时去加载对应的资源,若能预测到用户的行为,比如懒加载,点击到其它页面等则相当于提前预加载了需要的资源
    • preload vs prefetch
      • preload告诉浏览器马上要用到这个资源 浏览器会提升加载优先级为high
      • prefetch告诉浏览器未来可能会用到这个资源 浏览器会以非常低的优先级lowest 在自己空闲的时候加载这个资源
      • preload不要轻易用 只有最急需的资源才会用
      • preload魔法注释需要preload-webpack-plugin生成link标签
      • prefetch不需要插件配合,默认支持生成link标签
import(
  `./utils.js`
  /* webpackPreload: true */
  /* webpackChunkName: "utils" */
)
// <link rel="preload" as="script" href="utils.js">
button.addEventListener('click', () => {
  import(
    `./utils.js`
    /* webpackPrefetch: true */
    /* webpackChunkName: "utils" */
  ).then(result => {
    result.default.log('hello');
  })
});
// <link rel="prefetch" href="utils.js" as="script">
  • 提取公共代码
    • 为什么要提去公共代码
      • 相同的资源被重复的加载,浪费用户的流量和服务器的成本;
      • 每个页面需要加载的资源太大,导致页面首屏加载缓慢 影响用户体验
      • 如果能把公共代码抽离成单独文件进行加载能进行优化,可以减少网络传输流量,降低服务器成本
    • 如何提取
      • 基础类库 方便长期缓存
      • 页面之间的共用代码
      • 各个页面单独生成文件
    • 概念
      • module: 每一个文件都是一个模块
      • chunk: 代码块 有三种情况
        • 项目入口文件
        • 通过import()动态引入的代码
        • 通过splitchunks拆分出来的代码
      • bundle: bundle是webpack打包之后的各种文件,一般就是和chunk一对一关系,bundle就是对chunk进行编译压缩打包等处理之后的产出
optimization: {
    splitChunks: {...},
    runtimeChunk: true, //将运行时的工具代码提取出去
}

开启 Scope Hoisting

  • 自带功能
  • Scope Hoisting 可以让 Webpack 打包出来的代码文件更小、运行的更快, 它又译作 "作用域提升",是在 Webpack3 中新推出的功能。
  • 初webpack转换后的模块会包裹上一层函数,import会转换成require
  • 代码体积更小,因为函数申明语句会产生大量代码
  • 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小
  • 大量作用域包裹代码会导致体积增大
  • 运行时创建的函数作用域变多,内存开销增大
  • scope hoisting的原理是将所有的模块按照引用顺序放在一个函数作用域里,然后适当地重命名一些变量以防止命名冲突
  • 这个功能在mode为production下默认开启,开发环境要用 webpack.optimize.ModuleConcatenationPlugin插件
  • 也要使用ES6 Module,CJS不支持

利用缓存

  • webpack中利用缓存一般有两种
    • babel-loader开启缓存
    • 使用cache-loader
  • babel-loader
    • babel在转译js文件的时候很消耗性能,将babel-loader执行的结果缓存起来,当重新打包构建时会尝试读取缓存,来提高打包构建速度,降低消耗
{
    test: /\.js$/,
    exclude: /node_modules/,
    use: [{
      loader: "babel-loader",
      options: {
+        cacheDirectory: true
      }
    }]
  },
  • cache-loader
    • 在一些性能开销较大的 loader 之前添加此 loader,以将结果缓存到磁盘里
    • 存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
+          'cache-loader',
          ...loaders
        ],
        include: path.resolve('src')
      }
    ]
  }
}
  • oneOf
    • 每个文件对于rules中的所有规则都会遍历一遍,如果使用oneOf就可以解决该问题,只要能匹配一个即可退出。(注意:在oneOf中不能两个配置处理同一种类型文件)