关于webpack:长文玩转-webpack-学习笔记

2次阅读

共计 20440 个字符,预计需要花费 52 分钟才能阅读完成。

写在最后面

最近工作中 webpack 用的比拟多,想零碎的学习一下,找到了极客工夫的教程,跟着视频上讲的学了一遍,播种挺大的。尽管视频教程是 2019 年出的,有点老,有些常识曾经过期了,但并不障碍学习。那些过期的常识全当理解,能够搜寻寻找代替计划。学习资源:玩转 webpack,上面是我的学习笔记。

构建工具

前端技术一直倒退,比方:ReactjsxVueAngular指令,CSS预处理器,最新的 ES 语法等,浏览器都不能辨认。

构建工具的作用就是将这些浏览器不能辨认的语法(高级语法)转换成了浏览器能辨认的语法(低级语法)。

还有一个作用是将代码压缩混同,在压缩代码体积的同时也让代码不易浏览。

webpack是当初前端风行的构建工具。

初识webpack

配置 script 脚本

装置好 webpack 后,在命令行中应用 webpack 时,有两种形式:

  1. 指定门路./node_modules/.bin/webpack
  2. 应用 npx 工具,npx webpack

这两种办法应用挺麻烦的。有种简略的形式应用 package.json 中的 scripts,它可能读到node_modules/.bin 目录下的命令。

在命令行中执行 npm run build 即可。

"scripts": {"build": "webpack"}

配置文件

webpack的默认配置文件 webpack.config.js,能够通过webpack --config 来指定配置文件。

比方,生产环境的配置文件webpack.config.prod.js,开发环境的配置文件是webpack.config.dev.js

"scripts": {
  "build": "webpack --config webpack.config.prod.js",
  "build:dev": "webpack --config webpack.config.dev.js"
}

外围概念

webpack有 5 个外围概念:entryoutputmodeloadersplugins

entry

打包时的入口文件,默认是./src/index.js

entry是有两种形式:单入口、多入口。

单入口用于单页面利用,entry是个字符串

module.exports = {entry: "./src/index.js",};

多入口用于多页面利用,entry是对象模式

module.exports = {
  entry: {
    index: "./src/index.js",
    app: "./src/app.js",
  },
};

output

打包后的输入文件,默认是./dist/main.js

entry是单入口,output可通过批改参数 pathfilename

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {path: path.resolve(__dirname, "dist"),
    filename: "index.js",
  },
};

entry是多入口,outputfilename 须要用 [name] 占位符,用来指定打包后的名称,对应的是 entry 中的key

const path = require("path");

module.exports = {
  entry: {
    index: "./src/index.js",
    app: "./src/app.js",
  },
  output: {path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  },
};

mode

可设置为developmentproductionnone,默认是production

  • development:开发环境,设置 process.env.NODE_ENV 的值为 development。开启NamedChunksPluginNamedModulesPlugin
  • production:生产环境,设置 process.env.NODE_ENV 的值为 production。开启FlagDependencyUsagePluginFlagIncludedChunksPluginModuleConcatenationPluginNoEmitonErrorsPluginOccurrenceOrderPluginSideEffectsFlagPluginTerserPlugin
  • none:不开启任何优化选项

ps: development模式下开启的两个插件在代码热更新阶段,会在控制台打印出哪个模块产生了热更新,这个模块的门路是啥。production模式下开启的插件会在压缩代码。

loaders

webpack只反对 jsjson两种文件类型,loader的作用就是用来解决其余的文件,并将它们增加到依赖图中。

loader是个函数,接管源文件作为参数,返回转换后的后果。

个别 loader 命名的形式都是以 -loader 为后缀,比方css-loaderbabel-loader

test是指定匹配的规定,use是指定应用 loader 的名称

module.exports = {
  module: {
    rules: [
      {
        test: /.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
    ],
  },
};

一个 loader 个别只做一件事,在解析 less 时,须要用 less-loaderless转成 css,因为webpack 不能辨认 css,又须要用css-loadercss转换成 commonjs 对象放到 js 中,最初须要用 style-loadercss插入到页面的 style 中。

ps: loader的组合通常由两种形式,一种是从左到右(相似 unixpipe),另一种是从右到左(compose)。webpack抉择的是compose,从右到左一次执行loader

plugins

任何 loader 没法做的事件,都能够用 plugin 解决,它次要用于文件优化、资源管理、环境变量注入,作用于整个构建过程。

个别 plugin 命名的形式是以 -webpack-plugin 为后缀结尾,也有是以 -plugin 为后缀结尾的。

const CleanWebpackPlugin = require("clean-webpack-plugin");

module.exports = {plugins: [new CleanWebpackPlugin()],
};

webpack资源解析

解析es6

须要装置@babel/core@babel/preset-envbabel-loader

在根目录上面新建 .babelrc 文件,将 @babel/preset-env 增加到 presets

{"presets": ["@babel/preset-env"]
}

webpack中配置babel-loader

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        use: "babel-loader",
      },
    ],
  },
};

还有一种配置的形式,不应用 .babelrc 文件,将它配置在 use 的参数 options

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        use: {
          loader: "babel-loader",
          options: {presets: ["@babel/preset-env"],
          },
        },
      },
    ],
  },
};

解析css

须要装置 css-loaderstyle-loader

module.exports = {
  moudle: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};

解析less

须要装置less-loadercss-loaderstyle-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
    ],
  },
};

解析图片和字体

须要装置file-loader

module.exports = {
  module: {
    rules: [
      {test: /.(png|jpeg|jpg|gif)$/,
        use: ["file-loader"],
      },
      {test: /.(woff|woff2|eot|ttf|otf)$/,
        use: ["file-loader"],
      },
    ],
  },
};

url-loader

url-loader能够将较小的图片转换成base64

module.exports = {
  module: {
    rules: [
      {test: /.(png|jpeg|jpg|gif)$/,
        use: {
          loader: "url-loader",
          options: {limit: 10240, // 小于 10k 图片,webpack 在打包时主动转成 base64},
        },
      },
    ],
  },
};

配置vue

配置 vue 开发环境,须要装置vuevue-loadervue-template-compiler

const {VueLoaderPlugin} = require("vue-loader");

module.export = {plugins: [new VueLoaderPlugin()],
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: "vue-loader",
      },
    ],
  },
};

ps: 这里应用的 vue-loader15.x版本,我装置最新的版本 16.x 有问题,始终没有解决。

配置react

配置 react 开发环境须要装置reactreact-dom@babel/preset-react

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: "babel-loader",
          options: {presets: ["@babel/preset-env", "@babel/preset-react"],
          },
        },
      },
    ],
  },
};

webpack文件监听及热更新

文件监听

每次批改代码后,都须要手动构建,影响开发效率。webpack提供了文件监听的作用。开启监听时,webpack会调用 nodefs模块来判断文件是否发生变化,如果产生了变动,主动从新构建输入新的文件。

webpack开启监听模式,有两种形式:(须要手动刷新浏览器)

  • 启动 webpack 命令时,带上 --watch 参数
"scripts": {"watch": "webpack --watch"}
  • webpack.config.js中设置watch: true
module.exports = {watch: true,};

文件监听剖析

webpack文件监听判断根据是看文件的最初编辑工夫是否发生变化。

它会将批改的工夫保存起来,当文件批改时,它会和上一次的批改工夫进行比对。

如果发现不统一,它并不会马上通知监听者,而是先把文件的批改缓存起来,期待一段时间,如果这段时间内还有其余文件发生变化,它就会把这段时间内的文件列表一起构建。这个期待的工夫就是aggregateTimeout

module.exports = {
  watch: true,
  watchOptions: {
    ignored: /node_modules/,
    aggregateTimeout:300,
    poll: 1000,
  }
}
  • watch:默认为false
  • watchOptions:只有 watchtrue时,才失效

    • ignored:须要疏忽监听的文件或者文件夹,默认为空,疏忽 node——modules 性能会有所晋升。
    • aggregateTimeout:监听到文件变动后,期待的工夫,默认300ms
    • poll:轮询工夫,1s一次

热更新

热更新须要 webpack-dev-serverHotModuleReplacementPlugin两个插件背地应用。

watch 相比,它不输入文件,间接形式内存中,所以它的构建熟读更快。

热更新在开发模式中才会应用。

const webpack = require("webpack");
const path = require("path");

module.exports = {
  mode: "development",
  plugins: [new webpack.HotModuleReplacementPlugin()],
  devServer: {contentBase: path.join(__dirname, "dist"),
    hot: ture,
  },
};

package.json 中配置命令

webpack4.x

"scripts": {"dev": "webpack-dev-server --open"}

webpack5.x

"scripts": {"dev": "webpack server"}

ps: webpack5.xwebpack-dev-server 有抵触,不能应用 --open 关上浏览器。

HotModuleReplacementPlugin

热更新的外围是 HMR ServerHMR Runtime

  • HMR Server:是服务端,用来将变动的 js 模块通过 websocket 的音讯告诉给浏览器端
  • HMR Runtime:是浏览器端,用于接管 HMR Server 传递过去的模块数据,浏览器端能够看到 .hot-update.json 文件

hot-module-replacement-plugin的作用:webpack自身构建进去的 bundle.js 自身是不具备热更新的,HotModuleReplacementPlugin的作用就是 HMR Runtime 注入到 bundle.js,使得bundle.js 能够和 HMR Server 建设 websocket 的通信能力。一旦磁盘里的文件产生批改,那么 HMR Server 将有批改的 js 模块通过 websocket 发送给 HMR Runtime,而后HMR Runtime 去部分更新页面的代码,这种办法不会刷新浏览器。

webpack-dev-server的作用:提供 bundle server 的能力,就是生成的 bundle.js 能够通过 localhost://xxx 的形式去拜访,另外也提供livereload(浏览器主动刷新)。

文件指纹

什么是文件指纹

文件指纹是指打包后输入的文件名的后缀。比方:index_56d51795.js

通常用于用于版本治理

文件指纹类型

  • hash: 和我的项目的构建相干,只有我的项目文件扭转,构建我的项目的 hash 值就会扭转。采纳 hash 计算的话,每一次构建后的哈希值都不一样,如果说文件内容没有发生变化,那这样子是没法实现缓存的。
  • chunkhash:和 webpack 打包的 chunk 无关,不同的 entry 会生成不同的 chunkhash。生产环境会将一些公共库和源码离开来,独自用chunkhash 构建,只有不扭转公共库的代码,它的哈希值就不会变,就能够实现缓存。
  • contenthash:依据文件内容来定义 hash,文件内容不变,则contenthash 不变。个别用于 css 资源,如果 css 资源应用 chunkhash,那么批改了jscss 资源就会变动,缓存就会生效,所以 css 应用contenthash

ps:

  1. 应用 hash 的场景要联合 mode 来思考,如果 modedevelopment,在应用 HMR 状况下,应用 chunkhash 是不适宜的,应该应用 hash。而modeproduction时,应该用chunkhash
  2. js应用 chunkhash 是便于寻找资源,因为 js 的资源关联度更高。css应用 contenthash 是因为 css 个别是依据不同的页面书写的,css资源之间的关联度不高,也就不必在其余资源批改时,从新更新css

js文件指纹

设置 outputfilename,应用[chunkhash]

const path = require("path");

module.exports = {
  output: {path: path.join(__dirname, "dist"),
    filename: "[name]_[chunkhash:8].js", // 取 hash 的前 8 位
  },
};

css文件指纹

须要装置mini-css-extract-plugin

style-loader是将 css 插入到页面的 head 中,mini-css-extract-plugin是提取为独自的文件,它们之间是互斥的。

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({filename: "[name]_[contenthash:8].css",
    }),
  ],
};

图片文件指纹

须要装置file-loader

占位符名称 含意
[ext] 资源后缀名
[name] 文件名称
[path] 文件的相对路径
[folder] 文件所在的文件夹
[contenthash] 文件内容的hash,默认是md5
[hash] 文件内容的hash,默认是md5
[emoji] 一个随机的指代文件内容的emoji

图片的 hashcss/jshash 概念不一样,图片的 hash 是有图片的内容决定的。

module.exports = {
  module: {
    rules: [
      {test: /.(png|jpeg|jpg|gif)$/,
        use: {
          loader: "file-loader",
          options: {filename: "[folder]/[name]_[hash:8].[ext]",
          },
        },
      },
      {test: /.(woff|woff2|eot|ttf|otf)$/,
        use: [
          {
            loader: "file-loader",
            options: {filename: "[name]_[hash:8].[ext]",
            },
          },
        ],
      },
    ],
  },
};

代码压缩

代码压缩次要分为 js 压缩,css压缩,html压缩。

js压缩

webpack内置了 uglifyjs-webpack-plugin 插件,默认打包后的文件都是压缩过的,这里无需额定配置。

能够手动装置这个插件,能够额定设置一下配置,比方开启并行压缩,须要将 parallel 设置为true

const UglifyjsWebpackPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
  optimization: {
    minimizer: [
      new UglifyjsWebpackPlugin({parallel: true,}),
    ],
  },
};

css压缩

装置 optimize-css-webpack-plugin,同时装置css 预处理器cssnano

const OptimizeCssWebpackPlugin = require("optimize-css-webpack-plugin")
module.exports = {
  plugins: [
    new OptimizeCssWebpackPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require("cssnano)
    })
  ]
}

html压缩

须要装置html-webpack-plugin,通过设置压缩参数。

ps: 多页面利用须要写多个new HtmlWebpackPlugin({...})

const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({template: path.join(__dirname, "src/search.html"),    //html 模版所在的地位,模版外面能够应用 ejs 的语法
      filename: "search.html",              // 打包进去后的 html 名称
      chunks: ["search"],           // 打包进去后的 html 要应用那些 chunk
      inject: true,                // 设置为 true,打包后的 js css 会主动注入到 HTML 中
      minify: {
        html5: true,
        collapseWhitespace: true,
        preserveLineBreaks: false,
        minifyCSS: true,
        minifyJS: true,
        removeComments: false
    }),
    new HtmlWebpackPlugin({...})
  ],
};

HtmlWebpackPlugin参数 minify 外面的 minifyJSminifyCSS的作用是用于压缩一开始就内联在 html 里的 js(不能应用ES6 语法)和css

chunks对应的是 entry 中的 key。你心愿哪个chunk 主动注入,就将哪个 chunk 写到chunks

chunkbundlemodule区别:

  • chunk:每个 chunk 是又多个 module 组成,能够通过代码宰割成多个chunk
  • bundle:打包生成的最终文件
  • modulewebpack中的模块(jscss、图片)

主动清理构建目录

每次在构建的时候,会产生新的文件,造成输入目录 output 文件越来越多。

最常见的清理办法用命令 rm 去删除,在打包之前先执行 rm -rf 命令将 dist 目录删除,在进行打包。

"scripts": {"build": "rm -rf ./dist && webpack"}

另一种是应用 rimraf 进行删除。

装置 rimraf,应用时也是先在打包前先将dist 目录进行删除,而后在打包。

"scripts": {"build": "rimraf ./dist && webpack"}

这两种计划尽管都能将 dist 目录清空,但不太优雅。

webpack提供 clean-webpack-plugin,它会主动清理output.path 下的文件。

const {CleanWebpackPlugin} = require("clean-webpack-plugin");

module.exports = {plugins: [new CleanWebpackPlugin()],
};

主动补齐款式前缀

不同的浏览器厂商在实现 css 个性的规范没有齐全对立,比方 display: flex,在webkit 内核中要写成display: -webkit-box

在开发的时候一个个加上内核,将是一个微小的工程。

webpack 中能够用 loader 去解决主动补齐 css 前缀的问题。

装置postcss-loader,以及它的插件autoprefixer

autoprefixer是依据 can i use 这个网站提供的 css 兼容性进行不全前缀的。

autoprefixer是后置处理器,它和预处理器不同,预处理器是在打包的时候解决,而 autoprefixer 是在代码解决好之后,款式曾经生成了再进行解决。

webpack4.x 中装置postcss-loader@3.0.0autoprefixer@9.5.1

形式一: 间接在 webpack.config.js 中配置

webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "less-loader",
          {
            loader: "postcss-loader",
            options: {plugins: () => [require("autoprefixer")({overrideBrowserslist: ["last 2 version", ">1%", "ios 7"], // 最新的两个版本,用户大于 1%,且兼容到 ios7
                }),
              ],
            },
          },
        ],
      },
    ],
  },
};

形式二: 利用 postcss 配置文件 postcss.config.jswebpack.config.js 间接写 postcss-loader 即可。

webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "less-loader",
          "postcss-loader",
        ],
      },
    ],
  },
};

postcss.config.js

module.exports = {
  plugins: [require("autoprefixer")({overrideBrowserslist: ["last 2 version", ">1%", "ios 7"],
    }),
  ],
};

办法三: 浏览器的兼容性能够写 package.json 中,postcss.config.js中只需加载 autofixer 即可。

webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "less-loader",
          "postcss-loader",
        ],
      },
    ],
  },
};

postcss.config.js

module.exports = {plugins: [require("autofixer")],
};

package.json

"browserslist": {
  "last 2 version",
  "> 1%",
  "ios 7"
}

资源内联

在一些场景中须要应用到资源内联,常见的有:

  1. 页面框架的初始化脚本
  2. 缩小 http 网络申请,将一些小的图片变成 base64 内容到代码中,较少申请
  3. css内联减少页面体验
  4. 尽早执行的 js,比方REM 计划

html内联

在多页面我的项目中,head中有很多专用的标签,比方meta,想要晋升维护性,会将它提取成一个模版,而后援用进来。

装置raw-loader@0.5.1

<head>
  ${require("raw-loader!./meta.html")}
</head>

js内联

REM 计划中,须要尽早的 html 标签的 font-size,那么这段js 就要尽早的加载、执行。

<head>
  <script>
    ${require('raw-loader!babel-loader!./calc-root-fontsize.js')}
  </script>
</head>

css内联

为了更好的体验,防止页面闪动,须要将打包好的 css 内联到 head 中。

装置 html-inline-css-webpack-plugin,这个插件须要放在html-webpack-plugin 前面。

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlInlineCssWebpackPlugin = require("html-inline-css-webpack-plugin");

module.exports = {
  module: {rules: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
  },
  plugins: [
    new MiniCssExtractPlugin({filename: "[name]_[contenthash:8].css",
    }),
    new HtmlWebpackPlugin(),
    new HtmlInlineCssWebpackPlugin(),],
};

须要先将 css 提取成独自的文件才行。

ps: style-laoderhtml-inline-css-webpack-plugin 的区别是:

  • style-loader:插入款式是一个动静的过程,打包后的源码不会有 style 标签,在执行的时候通过 js 动静插入 style 标签
  • html-inline-css-webpack-plugin:是在构建的时候将 css 插入到页面的 style 标签中

多页面利用

每个页面对应一个 entry,同时对应plugins 中一个html-webpack-plugin

这种形式有个毛病,每个减少一个 entry 就要减少一个html-webpack-plguin

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: {
    index: "./src/index.js",
    search: "./src/search.js",
  },
  plugins: [new HtmlWebpackPlugin({ template: "./src/index.html"}),
    new HtmlWebpackPlugin({template: "./src/search.html"}),
  ],
};

借助 glob 能够实现通用化的配置形式。

装置 glob,同时所有的页面都要放在src 上面,并且入口文件都要叫做index.js

const path = require("path");
const glob = require("glob");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const setMPA = () => {const entry = {};
  const htmlWebpackPlugins = [];
  const entryFiles = glob.sync(path.join(__dirname, "./src/*/index.js"));

  entryFiles.forEach((entryFile) => {const match = entryFile.match(/src\/(.*)\/index\.js/);
    const pageName = match && match[1];

    entry[pageName] = entryFile;
    htmlWebpackPlugins.push(
      new HtmlWebpackPlugin({template: path.join(__dirname, `src/${pageName}/index.html`),
        filename: `${pageName}.html`,
        chunks: [pageName],
        inject: true,
        minify: {
          html5: true,
          collapseWhitespace: true,
          preserveLineBreaks: false,
          minifyCSS: true,
          minifyJS: true,
          removeComments: false,
        },
      })
    );
  });

  return {
    entry,
    htmlWebpackPlugins,
  };
};

const {entry, htmlWebpackPlugins} = setMPA();
module.exports = {
  entry,
  plugins: [].concat(htmlWebpackPlugins),
};

sourceMap应用办法

公布到生成环境中的代码,要将代码进行压缩混同,然而在压缩混同之后,看代码就像在看天书一样。

sourceMap提供了压缩后的代码和源代码之间的映射关系。

sourceMap在开发环境开启,线上环境中敞开,在线上排查问题时将 source map 上传到谬误监控零碎。

source map关键字

  • eval:应用 eval 包裹模块代码
  • source map:产生 map 文件
  • cheap:不蕴含列信息
  • inline:将 .map 作为 DataURI 嵌入,不独自生成 .map 文件
  • module:蕴含 loadersourcemap

source map类型

devtool 首次构建 二次构建 是否适宜生产环境 能够定位的代码
(none) +++ +++ yes 最终输入的代码
eval +++ +++ no webpack生成的代码(一个个的模块)
cheap-eval-source-map + ++ no 通过 loader 转换后的代码(只能看到行)
cheap-module-eval-source-map o ++ no 源代码(只能看到行)
eval-source-map + no 源代码
cheap-source-map + o yes 通过 loader 转换后的代码只能看到行)
cheap-module-source-map o yes 源代码(只能看到行)
inline-cheap-source-map + no 通过 loader 转换后的代码(只能看到行)
inline-cheap-module-source-map o no 源代码(只能看到行)
source-map yes 源代码
Inline-source-map no 源代码
hidden-source- map yes 源代码

提取页面公共资源

在我的项目中,多个页面中都会应用到一些根底库,比方reactreact-dom,还有一写公共的代码,当在打包时这些都会被打包进最终的代码中,这是比拟节约的,而且打包后的体积也比拟大。

webpack4内置了 SplitChunksPlugin 插件。

chunks参数阐明:

  • async异步引入的库进行拆散(默认)
  • initial同步引入的库进行拆散
  • all所有引入的库进行拆散(举荐)

抽离出根底库的名称 cacheGroups 里中的 filename 放到 html-webpack-pluginchunks中,会主动导入:

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({chunks: ["vendors", "commons", "index"], // 打包进去后的 html 要应用那些 chunk
      })
    );
  ],
  optimization: {
    splitChunks: {
      chunks: "async",
      minSize: 30000, // 抽离的公共包最小的大小,单位字节
      maxSize: 0, // 最大的大小
      minChunks: 1, // 资源应用的次数(在多个页面应用到),大于 1,最小应用次数
      maxAsyncRequests: 5, // 并发申请的数量
      maxInitialRequests: 3, // 入口文件做代码宰割最多能分成 3 个 js 文件
      automaticNameDelimiter: "~", // 文件生成时的连接符
      automaticNameMaxLength: 30, // 主动主动命名最大长度
      name: true, // 让 cacheGroups 里设置的名字无效
      cacheGroups: {
        // 当打包同步代码时, 下面的参数失效
        vendors: {test: /[\\/]node_modules[\\/]/, // 检测引入的库是否在 node_modlues 目录下的
          priority: -10, // 值越大, 优先级越高. 模块先打包到优先级高的组里
          filename: "vendors.js", // 把所有的库都打包到一个叫 vendors.js 的文件里
        },
        default: {
          minChunks: 2, // 下面有
          priority: -20, // 下面有
          reuseExistingChunk: true, // 如果一个模块曾经被打包过了, 那么再打包时就疏忽这个上模块
        },
      },
    },
  },
};

tree shaking(摇树优化)

在一个模块中有多个办法,用到的办法会被打包进 bundle 中,没有用到的办法不会打包进去。

tree shaking就是只把用到的办法打包到 bundle,其余没有用到的会在uglify 阶段擦除。

webpack默认反对,在 .babelrc 里设置 modules: false 即可。production阶段默认开启。

必须是 ES6 语法,CJS的形式不反对。

ps: 如果把 ES6 转成 ES5,同时又要开启tree shaking,须要在.babelrc 里设置 module: false,不然babel 默认会把 ES6 转成 CJS 标准的写法,这样就不能进行tree shaking

DCE(deal code elimination)

DEC全称 deal code elimination,中文意思是死代码删除,次要是上面三种:

  1. 代码不会被执行
if (false) {console.log("代码不会被执行");
}
  1. 代码执行的后果不会被用到
function a() {return "this is a";}
let A = a();
  1. 代码只写不读
let a = 1;
a = 2;

tree shaking原理

对代码进行动态剖析,在编译阶段,代码有没有用到是要确定下来的,不能通过在代码运行时在剖析哪些代码有没有用到,tree shaking会把没用到的代码用正文标记进去,在 uglify 阶段将这些无用代码擦除。

利用 ES6 模块的特点:

  • 只能作为模块顶层的语句呈现
  • import的模块名只能是字符串常量
  • import bindingimmutable

删除无用的css

  • purgecss-webpack-plugin:遍历代码,辨认曾经用到的css class

    • mini-css-extract-plugin 配合应用
  • uncsshtml须要通过 jsdom 加载,所有的款式通过 postCSS 解析,通过 document.querySelector 来辨认在 html 文件外面不存在的选择器
const PurgecssPlugin = require("purgecss-webpack-plugin");

const PATHS = {src: path.join(__dirname, "src"),
};

module.exports = {
  plugins: [
    new PurgecssPlugin({paths: glob.sync(`${PATHS.src}/**/*`, {nodir: true}),
    }),
  ],
};

scope hoisting应用和原理剖析

构建够的代码会产生大量的闭包,如下图所示:

当引入内部模块时,会用一个函数包裹起来。当引入的模块越多时,就会产生大量的函数包裹代码,导致体积变大,在运行的时候,因为创立的函数作用域越多,内存开销也会变大。

  • webpack 转换后的模块会被包裹一层
  • import会被转换成__webpack_require

剖析

  1. 打包进去的是一个IIFE(匿名闭包)
  2. modules是一个数组,每一项是一个模块初始化函数
  3. __webpack_require用来加载模块,返回module.exports
  4. 通过 WEBPACK_REQUIRE_METHOD(0) 启动程序

scope hoisting原理

将所有模块的代码依照援用程序放在一个函数作用域里,而后适当的重名一些变量以避免变量名抵触。

模块调用有先后顺序,a模块调用 b 模块,因为有函数包裹,a模块和 b 模块的程序无所谓,如果打消包裹代码的话,须要依据模块的援用程序来排放模块,就要将 b 模块放到 a 模块之前,b模块能力被 a 模块调用。

通过 scope hoisting 能够缩小函数申明和内存开销。production阶段默认开启。

必须是 ES6 语法,CJS不反对。

ps: scope hoisting把多个作用域变成一个作用域。当模块被援用的次数大于 1 次时,是不产生成果的。如果一个模块援用次数大于 1 次,那么这个模块的代码会被内联屡次,从而减少打包后文件的体积。

应用ESLint

ESLint 可能对立团队的代码格调,可能帮忙发现带谬误。

两种应用办法:

  • CI/CD 集成
  • webpack 集成

webpackCI/CD 集成

须要装置husky

减少 scripts,通过lint-staged 查看批改文件。

"scripts": {"precommit": "lint-staged"},
"lint-staged": {
  "linters": {"*.{js,less}": ["eslint --fix", "git add"]
  }
}

webpackESLint 集成

应用 eslint-loader,构建是查看js 标准

装置插件babel-eslinteslinteslint-config-airbnbeslint-config-airbnb-baseeslint-loadereslint-plugin-importeslint-plugin-jsx-allyeslint-plugin-react

新建 .eslintrc.js 文件

module.exports = {
  parser: "babel-eslint",
  extends: "airbnb",
  env: {
    browser: true,
    node: true,
  },
  rules: {indent: ["error", 2],
  },
};

webpack.config.js文件配置eslint-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ["babel-loader", "eslint-loader"],
      },
    ],
  },
};

代码宰割和动静import

对于大的 web 利用来说,将所有的代码都放在一个文件中显然是不够无效的,特地是当你的某些代码块是在某些非凡状况下才会被用到。

webpack有一个性能是将你的代码宰割成chunks(语块),当代码运行到须要它们的时候在进行加载。

应用场景:

  • 抽离雷同代码到一个共享块
  • 脚本懒加载,使得初始下载的代码更小

懒加载 js 脚本的形式

  • CJSrequire.ensure
  • ES6import(目前还没有原生反对,须要 bable 转换)

装置 @bable/plugin-syntax-dynamic-import 插件

.babelrc文件:

{plugins: ["@bable/plugin-syntax-dynamic-import"];
}

例子:

class Index extends React.Component {constructor() {super(...arguments);
    this.state = {Text: null,};
  }
  loadComponent = () => {import("./text.js").then((Text) => {this.setState({ Text: Text.default});
    });
  };
  render() {const { Text} = this.state;
    return (
      <div className="search">
        react1
        {Text && <Text />}
        <div onClick={this.loadComponent}> 点我 </div>
        <img src={img} alt="" />
      </div>
    );
  }
}

这里要留神一点,当配置了 cacheGroups 时,minChunks设置了 1,下面设置的懒加载脚本就不失效了,因为import 在加载时是动态剖析的。

cacheGroups: {
  commons: {
    name: "commons",
    chunks: "all",
    priority: -20, // 值越大, 优先级越高. 模块先打包到优先级高的组里
    minChunks: 1,
  }
}

多过程 / 多实例:并行压缩

办法一:应用 webpack-parallel-uglify-plugin 插件

const WebpackParalleUglifyPlugin = require("webpack-parallel-uglify-plugin");

module.exports = {
  plugins: [
    new WebpackParalleUglifyPlugin({
      uglifyJs: {
        output: {
          beautify: false,
          comments: false,
        },
        compress: {
          warnings: false,
          drop_console: true,
          collapse_vars: true,
          reduce_vars: true,
        },
      },
    }),
  ],
};

办法二:应用 uglifyjs-webapck-plugin 开启 parallel 参数

const UglifyJsPlugin = require("uglifyjs-webpack-plugin")

modules.exports = {
  plugins: [
    UglifyJsPlugin: {
      warnings: false,
      parse: {},
      compress: {},
      mangle: true,
      output: null,
      toplevel: false,
      nameCache: null,
      ie8: false,
      keep_fnames: false,
    },
    parallel: true
  ]
}

办法三:terser-webpack-plugin开启 parallel 参数(webpack4举荐)

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({parallel: 4,}),
    ],
  },
};

晋升构建速度

思路:将 reactreact-domreduxreact-redux 根底包和业务根底包打包成一个文件

办法:应用 DLLPlugin 进行分包,DLLReferencePluginmanifest.json 援用

应用 DLLPlugin 进行分包

const path = require("path");
const webpack = require("webpack");

module.exports = {context: process.cwd(),
  resolve: {extensions: [".js", ".jsx", ".json", ".less", ".css"],
    modules: [__dirname, "nodu_modules"],
  },
  entry: {library: ["react", "react-dom", "redux", "react-redux"],
  },
  output: {filename: "[name].dll.js",
    path: path.resolve(__dirname, "./build/library"),
    library: "[name]",
  },
  plugins: [
    new webpack.DllPlugin({name: "[name]",
      path: "./build/library/[name].json",
    }),
  ],
};

webpack.config.js 引入

module.exports = {
  plugins: [
    new webapck.DllReferencePlugin({manifest: require("./build/library/manifest.json"),
    }),
  ],
};

在我的项目应用了 webpack4,对dll 的依赖没那么大了,应用 dll 相对来说晋升也不是特地显著,而 hard-source-webpack-plugin 能够极大的晋升二次构建。
不过从理论的前端工厂中来说,dll还是很有必要的。对于一个团队而言,根本是应用雷同的技术栈,要么 react,要么vue。这时候,通常的做法都是把公共框架打成一个common bundle 文件供所有我的项目应用。dll就能够很好的满足这种场景:将多个 npm 包打成一个公共包。因而团队外面的分包计划应用 dll 还是很有价值。

splitChunks也能够做 DllPlugin 的事件,然而举荐应用 splitChunks 去提取页面间的公共 js 文件,因为应用 splitChunks 每次去提取根底包还是须要消耗构建工夫的,如果是 DllPlugin 只须要预编译一次,前面的根底包工夫都是能够省略掉的。

晋升二次构建速度

办法一:应用 terser-webpack-plugin 开启缓存

module.exports = {
  optimization: {
    minimizer: [
      new TerserWebpackPlugin({
        parallel: true,
        cache: true,
      }),
    ],
  },
};

办法二:应用 cache-loader 或者hard-source-webpack-plugin

module.exports = {plugins: [new HardSourceWebpackPlugin()],
};

放大构建指标

比方 babel-loader 不解析node_modules

module.exports = {
  rules: {
    test: /\.js$/,
    loader: "happypack/loader",
    // exclude: "node_modules"
    /-- 或者 --/
    // include: path.resolve("src"),
  }
}

缩小文件搜寻范畴

  • 优化 resolve.modules 配置(缩小模块搜寻层级)
  • 优化 resolve.mainFields 配置

    • 先查找 package.jsonmain字段指定的文件 -> 查找根目录的index.js -> 查找lib/index.js
  • 优化 resolve.extensions 配置

    • 模块门路的查找,import xx from "index"会先找 .js 后缀的
  • 正当应用alias
module.exports = {
  resolve: {
    alias: {react: path.resolve(__dirname, "./node_modules/react/dist/react.min.js"),
    },
    modules: [path.resolve(__dirname, "node_modules")], // 查找依赖
    extensions: [".js"], // 查找模块门路
    mainFields: ["main"], // 查找入口文件
  },
};
正文完
 0