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

写在最后面

最近工作中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"], // 查找入口文件
  },
};

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理