写在最后面

最近工作中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++++++nowebpack生成的代码(一个个的模块)
cheap-eval-source-map+++no通过 loader 转换后的代码(只能看到行)
cheap-module-eval-source-mapo++no源代码(只能看到行)
eval-source-map--+no源代码
cheap-source-map+oyes通过loader转换后的代码只能看到行)
cheap-module-source-mapo-yes源代码(只能看到行)
inline-cheap-source-map+-no通过loader转换后的代码(只能看到行)
inline-cheap-module-source-mapo-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"], // 查找入口文件  },};