不同打包工具对比

  • grunt:基于任务配置实现,配置规则复杂
  • gulp:基于node的stream流,通过配置pipe任务

    1. const gulp = require("gulp");
    2. const babel = require("gulp-babel");
    3. function defaultTask(callback){
    4. gulp.src("src/app.js")
    5. .pipe(
    6. babel({presets: ["@babel/preset-env"]})
    7. )
    8. .pipe(
    9. gulp.dest("dist")
    10. )
    11. callback();
    12. }
    13. export.default = defaultTask;
  • webpack:模块化打包,将各种类型的资源进行打包处理。

  • rollup:ES6模块化打包,一般多用于打包js类库。默认支持treeShaking

    1. import resolve from "rollup-plugin-node-resolve";
    2. import babel from "roll-plugin-babel";
    3. export default {
    4. input: "src/app.js",
    5. output: {
    6. file: "dist/bundle.js",
    7. format: "cjs", //commonjs
    8. exports: "default"
    9. },
    10. plugins:[
    11. resolve(),
    12. babel({
    13. presets: ["@babel/preset-env"],
    14. exclude: "node_modules/**"
    15. })
    16. ]
    17. }
  • parcel:零配置启动项目,可以以html文件作为入口。因为是0配置,所以不灵活。

    Loader和Plugin的不同

  • loader模块加载器:处理不同类型的工具,有了loader就可以处理css、img、tiff、map4等各种资源

  • plugin插件:在编译过程的不同时机执行不同任务,类似生命周期处理任务。Plugin是用于扩展webpack功能的手段。

    webpack的构建流程

  • 初始化参数:

  • 开始编译:
  • 编译模块:
  • 完成模块编译:
  • 输出结果:

module\chunk\bundle区别

  • module:js的模块化,所以webpack支持commonjs/es6模块规范,可以通过import导入的代码
  • chunk:chunk是webpack根据功能拆分处理的
    • 根据入口entry
    • 通过import()动态引入
    • 通过splitChunks拆分出来的代码
  • bundle:bundle是webpack打包后的文件,一般和chunk是一对一的,bundle是对chunk进行编译压缩打包处理后,写入到硬盘上的文件

webpack打包工具 - 图1

代码分割splitChunks

hash的种类

  • hash:整个项目的hash值,根据每次编译内容计算获得,每次编译后都会生成新的hash。
  • chunkhash:根据chunk生成hash值,来源于同一个chunk,则hash值是一样的。同一个chunk内任何一个文件发生改变,则chunkhash就会发生改变。
  • contenthash:根据内容值计算hash和改变hash,只有内容变化,才会更新contenthash

hash > chunkhash > contenthash ,影响程度越来越小
hash < chunkhash < contenthash, 稳定性越来越高

优化打包,提速打包时间

1分析打包时间

使用speedMeasureWebpackPlugin进行时间分析

  1. cosnt smw = require("speedMeasureWebpackPlugin");
  2. module.export = smw.wrap({
  3. // 默认的webpack配置
  4. })

2缩小范围

  • extensions:设置后可以省略导入文件扩展名,会依次自动匹配

    1. resolve: {
    2. extensions: [".js", ".jsx", ".json", ".css"]
    3. }
  • alias:配置别名加快webpack的查找模块速度

    1. resolve: {
    2. alias: {
    3. "jquery": path.resolve(__dirname, "node_modules/jquery/dist/index.js")
    4. }
    5. }
  • modules:对于声明的依赖名模块,webpack会类似node进行路径搜索。

    1. resolve: {
    2. modules: ["node_modules"]
    3. }
  • mainFields:默认是package.json文件按照文件中的main字段的文件名查找文件,也可自定义配置

    1. resolve:{
    2. // 配置的target为web或target是webworker,mainField默认值是
    3. mainFields: ["browser", "module", "main"],
    4. // target为其他时,mainFields默认值
    5. mainFields: ["module", "main"]
    6. }
  • mainFiles:当目录没有package.json时,默认使用目录下的index.js,这个也是可以配置

    1. resolve: {
    2. mainFiles: ["index"]//也可设置其他文件
    3. }
  • resolveLoader:用于配置解析loader时的resolve配置

    1. //默认配置
    2. module.exports = {
    3. resolveLoader: {
    4. modules: [ "node_modules" ],
    5. extensions: [".js", ".json"],
    6. mainFields: ["loader", "main"]
    7. }
    8. }

    3noParse

    配置不需要解析依赖的第三方大型类库,以提高整体构建速度

    1. module.exports = {
    2. module: {
    3. noParse: /jquery|lodash/
    4. }
    5. }

    4IgnorePlugin

    用于忽略某些特定的模块,让webpack不把这些模块打包进去 ```typescript import moment from “moment”; console.log(moment); //默认会有多语言包,很大

// 进行webpack配置,在 plugins中添加 new webpack.IgnorePlugin(/^.\/locale/, /moment$/)

  1. <a name="j2eRT"></a>
  2. ### 5日志优化
  3. 使用插件[friendly-errors-webpack-plugin](https://www.npmjs.com/package/friendly-errors-webpack-plugin), 可以设置输出日志的级别。<br />success成功时输出日志提示,warning警告日志提示,error错误日志提示。
  4. ```typescript
  5. module.exports = {
  6. stats: "errors-only",
  7. plugins: [
  8. new FriendlyErrorsWebpackPlugin()
  9. ]
  10. }

6利用缓存

webpack开启缓存的办法:

  • babel-loader开启缓存
  • 使用cache-loader
  • 使用hard-source-webpack-plugin

    babel-loader

    Babel在转化js文件过程中消耗性能高,可以将babel-loader执行结果换成起来,重新打包时使用缓存/ ```typescript module.exports = { module: { rules:[{
    1. test: /\.js$/,
    2. exclude: /node_modules/,
    3. use: [
    4. {loader: "babel-loader",
    5. options: {
    6. cacheDirectory: true
    7. }
    8. }
    9. ]
    }] } }
  1. <a name="qqflZ"></a>
  2. #### cache-loader
  3. - 在一些性能开销大的loader之前添加cache-loader,将结果缓存到硬盘
  4. - 存取也需要性能开销,所以只在性能开销大的loader中设置cache-loader
  5. ```typescript
  6. module.exports = {
  7. module: {
  8. rules:[{
  9. test: /\.js$/,
  10. exclude: /node_modules/,
  11. use: [
  12. "cache-loader",
  13. {
  14. loader: "babel-loader",
  15. options: {
  16. cacheDirectory: true
  17. }
  18. }
  19. ]
  20. }]
  21. }
  22. }

hard-source-webpack-plugin

  • 为模块提供中间缓存,缓存默认路径为node_module/.cache/hard-source
  • 配置hard-source-webpack-plugin后首次不会有时间变化,但是第二次后会有显著提升。
  • webpack5默认内置了hard-source-webpack-plugin
    1. const HardSourceWebpackPlugin = require("hard-source-webpack-plugin");
    2. module.exports = {
    3. plugins: [
    4. new HardSourceWebpackPlugin()
    5. ]
    6. }

    oneOf:匹配rules时只匹配一个即退出

    默认情况下,每个文件对于rules中的所有规则都会遍历一遍,使用oneOf可以解决该问题。
    1. module.exports = {
    2. module: {
    3. rules: [
    4. {
    5. text: /\.js$/,
    6. exclude: /node_modules/,
    7. enforce: 'pre',
    8. loader: "eslint-loader",
    9. options: {
    10. fix: true
    11. }
    12. },
    13. {
    14. oneOf:[
    15. // 这里配置的loader之后匹配一个就退出
    16. ]
    17. }
    18. ]
    19. }
    20. }

    7多进程处理

    thread-loader多进程打包处理

    把thread-loader放到其他loader之前,那么被它装饰的其他loader会有一个单独的worker进程运行。 ```typescript module.exports = { module: { rules: [
    1. {
    2. text: /\.js$/,
    3. exclude: /node_modules/,
    4. include: path.resolve(__dirname, "/src"),
    5. use: [
    6. {
    7. loader: "thread-loader", // 给babel-loader开启多进程
    8. options: { workers : 3 }
    9. },
    10. {
    11. loader: "babel-loader",
    12. options: {
    13. presets: ["@babel/preset-env", "@babel/preset-react"]
    14. }
    15. }
    16. ]
    17. }
    ] }

}

  1. <a name="koOUa"></a>
  2. #### ParallelUglifyPlugin
  3. 处理多个js都需要压缩,可以开启一个子进程。
  4. ```typescript
  5. const path = require('path');
  6. const DefinePlugin = require('webpack/lib/DefinePlugin');
  7. const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
  8. module.exports = {
  9. plugins: [
  10. // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
  11. new ParallelUglifyPlugin({
  12. // 传递给 UglifyJS 的参数
  13. uglifyJS: {
  14. output: {
  15. // 最紧凑的输出
  16. beautify: false,
  17. // 删除所有的注释
  18. comments: false,
  19. },
  20. compress: {
  21. // 在UglifyJs删除没有用到的代码时不输出警告
  22. warnings: false,
  23. // 删除所有的 `console` 语句,可以兼容ie浏览器
  24. drop_console: true,
  25. // 内嵌定义了但是只用到一次的变量
  26. collapse_vars: true,
  27. // 提取出出现多次但是没有定义成变量去引用的静态值
  28. reduce_vars: true,
  29. }
  30. },
  31. }),
  32. ],
  33. };

8DLL动态链接库

动态链接库 项目中使用了react和react-dom库,打包之后文件会非常大。DllPlugin可以先把react和react-dom单独抽离出来。

  1. 新建一个专门打包链接库的配置文件,webpack.config.dll.js

    1. let webpack = require('webpack');
    2. let path = require('path');
    3. module.exports = {
    4. mode: 'development',
    5. entry: {
    6. react: ['react', 'react-dom'],
    7. },
    8. output: {
    9. filename: '_dll_[name].js',
    10. path: path.resolve(__dirname, 'dist'),
    11. library: '_dll_[name]',
    12. // libraryTarget: 'var'
    13. },
    14. plugins: [
    15. new webpack.DllPlugin({
    16. name: '_dll_[name]',
    17. path: path.resolve(__dirname, 'dist', 'manifest.json'),
    18. }),
    19. ],
    20. };
  2. 执行npx webpack —config webpack.config.dll.js。打包出来_dll_react.js和manifest.json

  3. 在html模版中引入_dll_react.js静态文件,可以使用插件add-asset-html-webpack-plugin给html模板添加静态文件
  4. 给标准webpack配置文件添加插件,使用并引入manifest.json文件
    1. plugins: [
    2. new webpack.DllReferencePlugin({
    3. manifest: path.resolve(__dirname, 'dist', 'manifest.json'),
    4. })
    5. ]

    loader的分类

  • preLoader:前置
  • normalLoader:普通默认loader
  • inlineLoader:内联loader
  • postLoader:后置loader

preLoader-》normalLoader-〉inlineLoader-》postLoader

打包后生成的文件

webpack打包后生成bundle.js,删除注释后。文件可直接引入html中使用

  1. (function(modules) {
  2. function __webpack_require__(moduleId) {
  3. let module = {
  4. i: moduleId,
  5. exports: {},
  6. };
  7. modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  8. return module.exports;
  9. }
  10. return __webpack_require__('./src/index.js');
  11. })(
  12. {
  13. './src/title.js': function(module, exports, __webpack_require__) {
  14. module.exports = 'title';
  15. },
  16. './src/index.js': function(module, exports, __webpack_require__) {
  17. let title = __webpack_require__('./src/title.js');
  18. console.log(title);
  19. },
  20. }
  21. );

treeShaking实现原理

基于esModule规范,将未使用到的模块不加载。使用插件进行处理,将默认的一个ImportDeclaration转化为多个importDefaultSpecifier。

  1. const babel = require('@babel/core');
  2. const types = require('babel-types');
  3. const visitor = {
  4. ImportDeclaration: {
  5. enter(path, state = { opts }) {
  6. const specifiers = path.node.specifiers;
  7. const source = path.node.source;
  8. if (state.opts.library == source.value && !types.isImportDefaultSpecifier(specifiers[0])){
  9. const declarations = specifiers.map((specifier, index) => {
  10. return types.ImportDeclaration(
  11. [types.importDefaultSpecifier(specifier.local)],
  12. types.stringLiteral(`${source.value}/${specifier.local.name}`)
  13. );
  14. });
  15. path.replaceWithMultiple(declarations);
  16. }
  17. }
  18. }
  19. }
  20. module.export = function (babel){
  21. return { visitor }
  22. }

通过对specifiers的处理,将ImportDeclaration转为多个importDefaultSpecifier。

hmr热更新实现原理

hash
websocket,客户端和服务端通信
jsonp请求更新后的代码