阅读 1613

重学webpack(性能优化的配置)

webpack性能优化的配置的总配置

  • 开发性能优化:
    • 使用HMR优化打包构建速度
    • 使用source-map优化代码调试
  • 生产环境性能优化:
    • 使用oneOfbabel缓存多进程打包externalsdll来优化打包构建速度
    • 使用文件资源缓存(hash-chunkhash-contenthash)tree shakingcode splitJS代码懒加载/预加载pwa来优化代码运行的性能

使用HMR优化打包构建速度

  • 在开发环境下(即mode: 'development'),之前我们通过配置开启了服务器devServer(即通过指令为:npx webpack-dev-server启动devServer)
  • 问题:我们会发现每次修改源目录文件的内容时整体页面都会被刷新,页面会被重新加载如果我们的页面含有之前填写过的表单信息,然后我们又去改动了源目录某个文件的内容,按下CTRL+S保存后我们会发现之前填写过的表单数据因为整体页面的reload被清空了,这种情况往往不是我们想要的效果
  • 问题:如果我们只是修改了样式文件(即css文件内容),我们会发现没有修改过的js等文件也会因为页面的刷新而被重新加载一次,所有的代码被重新执行一次即(如果有一万个模块,只要修改了其中一个模块,全部的模块都要被重新加载一次),这种情况往往不是我们想要的效果
  • 思路:当我们修改了其中一个模块时,我们的想法是只让这个模块被重新加载执行一遍,而其他的模块时不受影响的,其他的模块不需要被重新加载执行一遍。
  • 实现:使用HMR功能来完成这个需求。它的作用是当一个模块发生变化,只会重新打包(加载)这一个模块(而不是打包加载所有模块),极大提升构建速度。
  • HMR热模块替换/模块热替换(hot module replacement)。开启HRM的功能如下配置(在devServer配置的基础上添加hot配置即可开启):
  • HMR功能可以说是依托着服务器devServer的功能(通过websocket协议升级),同样执行指令npx webpack-dev-server。不会有任何输出,不会改变构建后的目录结构(即你需要自己执行webpack指令来生成构建后的目录)
devServer: {
  contentBase: resolve(__dirname, 'build'),
  compress: true,
  port: 3000,
  open: true,
  // 开启HMR功能
  // 当修改了webpack配置即(修改了webpack.config.js的配置)
  // 新配置要想生效,必须重新webpack服务
  // (重新执行npx webpack-dev-server指令)
  hot: true
}
复制代码
  • 执行指令npx webpack-dev-server后我们可以在控制台看到HMR功能已经开启:

  • 构造目录的结构为:

  • HMR功能开启后当我们修改源目录下的样式文件(这里是修改src/css/index.less),我们在控制台上可以发现此时只有该样式文件被重新加载刷新了,其他的文件模块不受影响。
  • 开启了HMR功能后我们没有在做任何的配置,但是我们此时修改了源目录下的样式文件,只有此样式文件被重新加载了,已经符合了我们的预期要求,难道这是HMR功能默认对样式文件进行了相应的处理吗才会有如此的效果吗? 答案是我们这里针对css文件处理使用的是style-loader,它内部实现了(HMR)我们的这个需求。
  • 使用的是style-loader(开发环境下使用(即mode: 'development')),它虽然不能让源目录下的CSS文件打包构建后单独生成CSS文件,它是以<style></style>的方式被自动插入HTML页面的。
module: {
  rules: [
    // loader的配置
    {
      // 处理less资源
      test: /\.less$/,
      use: ['style-loader', 'css-loader', 'less-loader']
    },
    {
      // 处理css资源
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    },
]  
}
复制代码
  • 执行的效果图为(这里我们只修改了样式CSS文件):

  • 样式文件的修改得到了解决,接着如果我们修改了没有添加任何HMR配置的JS文件,我们会发现这个JS文件走的功能是WDS,整个页面会被重新加载一次,HMR功能没有在JS文件生效,所以我们可以知道JS文件默认不能使用HMR功能,必须得在JS文件中额外添加支持HMR功能的代码
  • 下面是在入口文件中(src/js/index.js)添加能够支持HMR功能的代码:
//引入
import print from './print';
print();

if (module.hot) {
  // 一旦 module.hot 为true,说明开启了HMR功能。 
  // 额外添加下面的JS代码
  // 让HMR功能代码在此JS文件修改时生效
  module.hot.accept('./print.js', function() {
    // 方法会监听 print.js 文件的变化
    // 一旦发生变化,其他模块不会重新打包构建。
    // 会执行后面的回调函数
    print();
  });
}
复制代码
  • 下面是src/js/print.js文件的代码:
console.log('print.js被加载了~');

function print() {
  const content = 'hello print';
  console.log(content);
}

export default print;
复制代码
  • 样式文件和JS文件都得到了解决,接着我们去修改源目录下的src/index.html文件,我们会发现开启HMR功能后,修改了HTML文件是不会重新加载页面的,也就是说HTML文件默认不能使用HMR功能.同时会导致该HTML文件不能也热更新了(WDS失效即本地写的代码不会去刷新浏览器重新编译)
  • 解决这个问题的方式是修改入口(修改entry入口,将该html文件引入):
  • 注意点:修改入口后我们会发现每次修改src/index.html文件后,页面都会重新加载一次(走的是WDS功能),我们需要注意HTML文件不用做HMR功能,所以只要确保WDS对HTML文件不失效即可,因为HTML页面只有一个(只应该创建一个!!),所以不用考虑优化。
//修改了webpack.config.js的配置需要重启服务
//即重新执行npx webpack-dev-server

 entry: ['./src/js/index.js', './src/index.html']
复制代码
  • 执行的效果图为(这里我们只修改了HTML文件):

  • 使用HMR优化打包构建速度的总结:

  • 使用HMR优化打包构建速度的配置如下(省略在入口文件配置额外JS代码):
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // 解决:修改entry入口,将html文件引入
  entry: ['./src/js/index.js', './src/index.html'],
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
      {
        // 处理less资源
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        // 处理css资源
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          // 关闭es6模块化
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
        // 处理html中img资源
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        // 处理其他资源
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    // 开启HMR功能
    // 当修改了webpack配置,新配置要想生效,必须重新webpack服务
    hot: true
  }
};
复制代码

使用source-map优化代码调试

  • source-map是一种提供源代码到构建后代码映射技术(如果构建后代码出错了,通过映射可以追踪源代码错误),使用方式只要在webpack.config.js下配置devtool即可,参考如下:
devtool: 'source-map'
复制代码
  • 源目录结构为:

  • 配置好devtool: 'source-map'后执行webpack指令,可以观察到打包构建后的目录build/js/下多了一个built.js.map文件,如下:

  • 我们之前配置的是devtool: 'source-map',当然还有许多其他选项的配置,配置表如下:
    • 内联是构建后的目录中没有单独生成一个外部的文件,直接写在build/built.js中,例如devtool: 'inline-source-map'
    • 内联和外部的区别:1. 外部生成了文件内联没有 2. 内联构建速度更快
  • 我们这里是统一在开发环境下(即mode: 'development'),执行指令npx webpack-dev-server来查看devtool各个配置选项的功能:

source-map(外部) 能够提供错误代码准确信息源代码的错误位置
inline-source-map(内联) 只生成一个内联的source-map 并且以base64的方式写在构建后输出的js代码中 而且能够提供错误代码准确信息源代码的错误位置
hidden-source-map(外部) 能够提供错误代码错误原因,但是没有提供源目录代码的错误位置信息(他的错误位置提示的是构建后输出的.js中的错误位置,我们想要的错误位置是构建前的源代码错误位置),不能追踪源代码错误,只能提示到构建后代码的错误位置
eval-source-map(内联) 每一个文件都生成对应的source-map,都在eval中 能够提供错误代码准确信息和源代码的错误位置
nosources-source-map(外部) 能够提供错误代码准确信息, 但是没有任何源代码信息
cheap-source-map(外部) 能够提供错误代码准确信息和源代码的错误位置 只能精确的行而不是列
cheap-module-source-map(外部) 能够提供错误代码准确信息和源代码的错误位置 它会将loader的source map也加入
  • 在开发环境下(即mode: 'development')我们更需要考虑速度快,调试更友好,参考下表:
速度快(eval>inline>cheap>...) eval-cheap-souce-map和eval-source-map
调试更友好 souce-map和cheap-module-souce-map和cheap-souce-map
总结可以使用 eval-source-map和eval-cheap-module-souce-map
  • 在生产环境下(即mode: 'production')我们更需要考虑源代码要不要隐藏、调试要不要更友好、的问题,参考下表:
  • 注意点:内联会让代码体积变大,所以在生产环境不用内联
source-map 能够提供错误代码准确信息和源代码的错误位置
cheap-module-souce-map 能够提供错误代码准确信息和源代码的错误位置只能精确的行而不是列
nosources-source-map 全部隐藏
hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
总结可以使用 以上四种(一般情况用source-map即可)
  • 使用source-map优化代码调试的总结:

使用oneOf优化打包构建速度

  • 生产环境(即mode: 'production')下去体验用oneOf来优化打包构建速度。
  • 场景重现:我们先来看一下我们之前写过的Loader配置:
module: {
  rules: [
    // loader的配置
    {
      // 处理less资源
      test: /\.less$/,
      use: ['style-loader', 'css-loader', 'less-loader']
    },
    {
      // 处理css资源
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    },
    {
      // 处理图片资源
      test: /\.(jpg|png|gif)$/,
      loader: 'url-loader',
      options: {
        limit: 8 * 1024,
        name: '[hash:10].[ext]',
        // 关闭es6模块化
        esModule: false,
        outputPath: 'imgs'
      }
    },
    {
      // 处理html中img资源
      test: /\.html$/,
      loader: 'html-loader'
    },
    {
      // 处理其他资源
      exclude: /\.(html|js|css|less|jpg|png|gif)/,
      loader: 'file-loader',
      options: {
        name: '[hash:10].[ext]',
        outputPath: 'media'
      }
    }
  ]
},
复制代码
  • 场景重现:在看完我们之前写过的Loader配置之后,我们需要注意如果我们我们不使用oneOf做优化的话,一个文件是需要被所有Loader的test给过滤检查一遍的(即在检查过滤中有些Loader的test处理不了这个文件,而有些Loader的test正好命中可以处理这个文件,但是该文件要把每一个Loader都走一遍)
  • 场景解决:我们是否可以将所有的Loader给提取出来,通过添加某种规则让这些提取出来的Loder匹配命中处理这个文件之后,(即不要让文件都通过每个Loder过滤检查一遍),只要该文件被某()个Loader处理过之后,后续的Loader就不会在对此文件进行检查了。
  • 解决方式:使用oneOf可以解决上述的问题,让打包构建速度更快。
  • 使用oneOf的注意点不能有两个配置处理同一种类型文件。如果存在两个配置处理同一种类型文件,只有一个配置会生效。我们可以先回顾之前写过的代码(我们使用了 'eslint-loader'和'babel-loader'都要来处理JS文件):
module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      // 确保优先执行,不在考虑书写的先后顺序
      enforce: 'pre',
      loader: 'eslint-loader',
      options: {
        fix: true
      }
    },
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      options: {
        presets: [
          [
            '@babel/preset-env',
            {
              useBuiltIns: 'usage',
              corejs: {version: 3},
              targets: {
                chrome: '60',
                firefox: '50'
              }
            }
          ]
        ]
      }
    }
  ]
},
复制代码
  • 两个配置处理同一种类型文件的解决方法将其中一个配置提取出去,不放在oneOf的规则中,可以参考下面的代码:(在生产环境中)
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
// 设置nodejs环境变量让css兼容规则以开发环境的配置来做
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中配置browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];



module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        // 还需要在package.json中配置eslintConfig
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        // 这里是先执行 'eslint-loader'在执行'babel-loader'
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        // 以下loader匹配命中一个文件后后续的loader不会在对这个文件进行检查
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
            test: /\.css$/,
            use: [...commonCssLoader]
          },
          {
            test: /\.less$/,
            use: [...commonCssLoader, 'less-loader']
          },
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: {version: 3},
                    targets: {
                      chrome: '60',
                      firefox: '50'
                    }
                  }
                ]
              ]
            }
          },
          {
            test: /\.(jpg|png|gif)/,
            loader: 'url-loader',
            options: {
              limit: 8 * 1024,
              name: '[hash:10].[ext]',
              outputPath: 'imgs',
              esModule: false
            }
          },
          {
            test: /\.html$/,
            loader: 'html-loader'
          },
          {
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: 'file-loader',
            options: {
              outputPath: 'media'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};
复制代码
  • 使用oneOf优化打包构建速度的总结:

使用缓存优化打包构建速度

  • 注意点:下面以两个方面为例来使用缓存优化打包构建速度。一个方面是从Babel入手,对Babel进行缓存。另一个方面是从整体加载的文件资源进行缓存
  • 现在我们先来关注第一个方面:对Babel进行缓存。我们可以回顾一下之前写过的用到Babel的代码(js兼容性处理之babel):
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          // 预设:指示babel做怎么样的兼容性处理
          // 告诉'babel-loader'要进行怎么样的语法转换
          presets: [
            [
              '@babel/preset-env',
              {
                // 按需加载
                useBuiltIns: 'usage',
                // 指定core-js版本
                corejs: {
                  version: 3
                },
                // 指定兼容性做到哪个版本浏览器
                targets: {
                  chrome: '60',
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17'
                }
              }
            ]
          ]
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'production'
};
复制代码
  • 使用Babel存在的问题:Babel会对我们书写的所有JS代码进行编译处理成浏览器能够识别的语法,它的工作原理是对所有的JS模块都进行编译处理,假设我有一万个JS模块,当我只改动其中一个JS模块的代码时,Babel会对这一万个JS模块进行重新的编译处理,这不是我们希望看到的结果。我们希望的是只有改动的这个JS模块的代码被Babel重新的编译处理,而其他未改动的JS模块不要被Babel重新编译处理。
  • 上述问题的解决方法:开启Babel缓存。使得速度更快。只需要开启'babel-loader''cacheDirectory'即可。可参考如下代码:(在生产环境下即mode: 'production')
{
  test: /\.js$/,
  exclude: /node_modules/,
  loader: 'babel-loader',
  options: {
    presets: [
      [
        '@babel/preset-env',
        {
          useBuiltIns: 'usage',
          corejs: { version: 3 },
          targets: {
            chrome: '60',
            firefox: '50'
          }
        }
      ]
    ],
    // 开启babel缓存
    // 第二次构建时,会读取之前的缓存
    cacheDirectory: true
  }
},
复制代码
  • 现在来关注第二个方面:文件资源缓存。想要观察到文件资源缓存的效果,我们这里使用的方式是用node.js配合express新建一个服务器。下面是我们新建的服务器代码(server.js文件)和构建目录:

/*
  服务器代码
  启动服务器指令:

    第一种方式:
    npm i nodemon -g
    nodemon server.js

    第二种方式:
    node server.js
    
  访问服务器地址:
    http://localhost:3000
*/
const express = require('express');

const app = express();
// express.static向外暴露静态资源
// maxAge 资源缓存的最大时间,单位ms
app.use(express.static('build', { maxAge: 1000 * 3600 }));

app.listen(3000);
复制代码
  • 我们通过上面的代码中的app.use(express.static('build', { maxAge: 1000 * 3600 }));可以观察出我们设置了资源缓存,然后我们可以访问服务器地址http://localhost:3000,通过控制台查看文件是否缓存设置成功:

  • 设置了缓存的好处是在用户在缓存还没过期的时间内访问该页面时,页面被加载呈现在用户眼前的时间是十分快速的。
  • 设置了缓存带来的问题是:当我们需要改变源目录结构下的代码(例如改变了src/js/index.js页面的代码),然后重新执行webpack指令后,在刷新http://localhost:3000服务器页面时,我们会发现因为缓存的原因,导致服务器页面没有得到实时更新(即使是我们已经重新运行webpack指令重新打包构建了)
  • 设置了缓存带来的问题的原因是:在我们设置了文件资源缓存时,在资源缓存的时间内是不会去访问服务器的,而是直接读取本地的缓存
  • 危险警告:在文件资源被强缓存期间,因为是直接读取本地的缓存,如果此时本地的缓存的某个文件出现BUG,则导致没有办法通过服务器上的文件更新来解决。难道只能等到强缓存时间过期才能解决这个问题吗?
  • 解决方案:给文件资源的名称做点手脚!!(下图是还没有对文件资源的名称做手脚之前的文件资源名称)

  • 解决方案的思路:我们通过给文件资源的名称添加一种特殊的记号,当我们本地缓存的文件资源名称服务器上的对应的该文件的资源名称是一样的时候,再次请求这个文件时让它直接读取本地资源缓存
  • 解决方案的思路:如果我们修改了服务器上的文件资源内容,则生成与本地文件资源名称不一样的文件资源名称,这时候再次请求这个文件时,因为本地资源名称和对应的服务器上的这个资源名称是不一样的,这时候是去服务器上请求这个文件资源
  • 解决方案的思路:用什么来做这个特殊的记号呢?答案是哈希值
  • 解决方案的第一步:给打包构建后的文件名称中添加设置哈希值,代码如下:

//这个是给打包构建后的JS文件名称设置哈希值
//设置哈希值并只取十位
output: {
    filename: 'js/built.[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  
  
  
  
plugins: [
  //这个是给打包构建后的CSS文件名称设置哈希值
  new MiniCssExtractPlugin({
    filename: 'css/built.[contenthash:10].css'
  }),
],
复制代码
  • 我们通过打开http://localhost:3000服务器页面中的控制台查看network中请求的文件资源,可以发现到此时请求的的文件资源的名称都带上了哈希值(特殊记号)


  • 使用哈希值的原理:每一次的执行webpack打包命令后自动构建的文件名称都是不一样的(每一次打包都会生成一个新的哈希值),因此服务器在请求该文件时发现文件名称发生改变时,会自动的去重新请求这个新的文件资源。
  • 使用哈希值的注意点
hash:每次wepack构建时会生成一个唯一的hash值。 问题: 因为js和css同时使用一个hash值。如果重新打包,会导致所有缓存失效。但是可能我却只改动了一个文件(例如只改动了js文件执行了webpack指令后服务器上缓存的所有文件会失效)
chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样 问题: js和css的hash值还是一样的 因为css总是(这里也是)在入口文件js中被引入的,所以这种情况下它们依旧是同属于一个chunk。(我们这里只规定了一个入口文件即src/js/index.js)
contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样 推荐使用contenthash
  • 该webpack.config.js的总配置如下:(mode: 'production')
  • 忽略了在package.json中写的代码:
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');



// 定义nodejs环境变量:决定使用browserslist的哪个环境
// 设置nodejs环境变量让css兼容规则以开发环境的配置来做
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        //下面用到了oneOf:因为oneOf已经有一个处理js文件的Loader
        // 所以这个Loader不能再配置在oneOf规则下
        // oneOf的规则是不能有两个配置处理同一种类型文件
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
            test: /\.css$/,
            use: [...commonCssLoader]
          },
          {
            test: /\.less$/,
            use: [...commonCssLoader, 'less-loader']
          },
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: { version: 3 },
                    targets: {
                      chrome: '60',
                      firefox: '50'
                    }
                  }
                ]
              ],
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true
            }
          },
          {
            test: /\.(jpg|png|gif)/,
            loader: 'url-loader',
            options: {
              limit: 8 * 1024,
              name: '[hash:10].[ext]',
              outputPath: 'imgs',
              esModule: false
            }
          },
          {
            test: /\.html$/,
            loader: 'html-loader'
          },
          {
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: 'file-loader',
            options: {
              outputPath: 'media'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.[contenthash:10].css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production',
  devtool: 'source-map'
};

复制代码
  • 使用缓存优化打包构建速度的总结:

使用tree shaking来优化代码运行的性能

  • 场景假设:假设有一棵树,它有着活力(参与工作运行)的树叶,也有着枯萎(不参与工作运行)的树叶,好看的树叶需要保留下来(有用的代码),枯萎的树叶(无用的、没参加过的代码)需要外力(tree shaking)的帮忙让它们飘落下来。
  • tree shaking的作用:去掉无用的、没有使用过的代码,让代码体积变得更小。

  • 下面是src/js/test.js文件的代码:
export function mul(x, y) {
  return x * y;
}

export function count(x, y) {
  return x - y;
}
复制代码
  • 下面是src/js/index.js文件的代码:
import { mul } from './test';


//副作用可能(是可能不是绝对)将我们引用的CSS代码给shaking了
import '../css/index.css';  

function sum(...args) {
  return args.reduce((p, c) => p + c, 0);
}

// eslint-disable-next-line
console.log(mul(2, 3)); //只是使用了test.js中导出的mul方法
// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
复制代码
  • 使用tree shaking的前提条件必须使用ES6模块化语法必须开启production环境。满足这两种条件后就可以自动的对代码进行tree shaking(执行webpack指令后)
  • 在来看上面的那张图的场景下,我们执行webpack指令后,通过查看构建后的代码build/js/built.[contenthash:10].js,我们可以发现没有被使用过的代码(这里指test.js中的另一个没有被使用的方法)并没有被打包进这个js文件中
  • 使用tree shaking需要注意的问题:在不同的版本中tree shaking有些许的差异,可能会无意之间将我们的css等引入未使用的文件当作未经使用的代码给去除掉了
  • 注意点:如果在package.json中配置"sideEffects": false 则表示所有代码都没有副作用,都可以进行tree shaking。这样做可能会把css或者@babel/polyfill这类引入但是未使用的文件给干(shaking)掉。
  • 注意点:如果在package.json中配置"sideEffects": ["*.css", "*.less",'*.sass']则表示这些文件资源不要进行tree shaking。
//如果不配置"sideEffects"
//则把tree shaking的选择全交给webpack自动来做
"sideEffects": [
  "*.css"
]


------


"sideEffects": false
复制代码
  • 使用tree shaking来优化代码运行的性能的总结:

使用code split来优化代码运行的性能

  • 基本思想:将打包输出的一个chunk(文件)分割为多个文件,加载时可以并行加载,从而提高加载速度。与此同时还可以实现按需加载的功能(即当我需要这个文件时我才去加载这个文件)。
  • 按需加载的应用场景:在一个庞大的项目中,可以按照路由去拆分成各个不同的文件(页面),从而实现按需加载。所以可以使用webpack的code split技术来进行代码分割,从而实现将每一个路由文件拆分为单独的JS文件页面。
  • code split的第一种做法:(代码分割主要研究的是JS代码):根据入口文件进行代码分割
  • code split的单入口文件场景的构造目录和代码如下:

//这里是webpack.config.js的配置代码

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // 单入口
  entry: './src/js/index.js',
  output: {
    // [name]:取文件名 
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};
复制代码
  • code split的单入口文件场景时只有输出一个JS文件:(其中src/js/index.jssrc/js/test.js被整合到一个JS文件中)


  • 如果我们的需求是在执行webpack指令后在源目录结构下的src/js/index.jssrc/js/test.js的两个文件分别输出两个各自的文件,而不是被整合到一个JS文件中,则需要使用code split的多入口文件场景
  • code split的多入口文件场景:修改webpack.config.js中entry的配置,代码如下:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    //多入口:有一个入口,最终输出就有一个bundle
    //多入口时index.js不需要再引入test.js文件了
    //(即index.js中不需要在import {mul} from './test')
    index: './src/js/index.js',

    test: './src/js/test.js'
  },
  output: {
    // [name]:取文件名 
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};
复制代码
  • code split的多入口文件场景执行webpack指令后,我们可以观察到输出后的目录结构:


  • code split的第一种做法中多入口文件场景的局限性:我们如果需要一直添加入口文件的话,就需要一直去更改entry的配置,有点麻烦。
  • code split的第一种做法的总结:

  • code split的第二种做法:可以在细分为四种情况:分别是在单入口时的webpackage.config.js中配置或者不配置optimization选项(这包含了两种情况);在多入口时的webpackage.config.js中配置或者不配置optimization选项(这也包含了两种情况)代码如下:
/*
1. 单入口时可以将依赖于node_modules中的第三方库的代码单独打包一个chunk最终输出(单独生成一个文件)
2. 多入口时(上面的事情也会做)还可以自动分析多入口的chunk中有没有公共依赖的文件。
   如果有会打包成单独一个chunk(输出时单独生成一个文件)
*/
optimization: {
  splitChunks: {
    chunks: 'all'
  }
},
复制代码
  • code split的第二种做法的第一种情况:在单入口的前提下,在入口文件中引入Jquey(这是node_modules下的第三方库代码),然后就去执行webpack命令打包,此时我们没有webpackage.config.js中配置optimization。我们可以观察到生成的JS文件只有一个而且打包的体积有87.6KiB,这体积中有绝大部分是Jquery的代码webpack.config.jsindex.js(单入口)的的代码如下:
//webpack.config.js的代码
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // 单入口
  entry: './src/js/index.js',
  output: {
    // [name]:取文件名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

--------------------------

//index.js的代码
import $ from 'jquery';
function sum(...args) {
  return args.reduce((p, c) => p + c, 0);
}

// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
// eslint-disable-next-line
console.log($);
复制代码

  • code split的第二种做法的第二种情况:在单入口的前提下,在入口文件中引入Jquey(这是node_modules下的第三方库代码),然后就去执行webpack命令打包,此时我们在webpackage.config.js配置optimization。我们可以观察到生成的JS文件有两个,一个打包的体积有1.61KiB(之前自己写的JS的代码),另一个打包的体积有97.9KiB(这里是Jquery的代码)。webpack.config.jsindex.js(单入口)的的代码如下:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // 单入口
  entry: './src/js/index.js',
  output: {
    // [name]:取文件名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  /*
1. 单入口时可以将依赖于node_modules中的第三方库的代码单独打包一个chunk最终输出(单独生成一个文件)
2. 多入口时(上面的事情也会做)还可以自动分析多入口的chunk中有没有公共依赖的文件。
   如果有会打包成单独一个chunk(输出时单独生成一个文件)
*/
  */
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  mode: 'production'
};

--------------------------

//index.js的代码
import $ from 'jquery';
function sum(...args) {
  return args.reduce((p, c) => p + c, 0);
}

// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
// eslint-disable-next-line
console.log($);
复制代码

  • code split的第二种做法的第二种情况的总结单入口时添加optimization:{splitChunks: {chunks: 'all'}}可以将依赖于node_modules下的第三方库代码单独分割为一个chunk(即单独输出为一个文件)

  • code split的第二种做法的第三种情况多入口时添加没有添加配置optimization:{splitChunks: {chunks: 'all'}},两个入口文件都引入了node_modules下的第三方库Jquery,然后我们在执行webpack指令后会发现,两个入口文件的体积都在87.6KiB左右,这时因为两个入口文件在打包后都自己重复引入了JQuery的代码,执行图如下:

  • code split的第二种做法的的第四种情况多入口时添加配置optimization:{splitChunks: {chunks: 'all'}}可以分析这些多入口文件中有没有共同依赖(即有没有共同引入node_modules下的第三方库的代码),如果有会提取出来为一个Chunk(即单独输出为一个文件),效果是共同依赖的第三方库的代码被单独输出为一个文件,然后由入口文件去引入这个文件即可,避免了重复引入。

  • code split的第二种做法的第四种情况的代码演示:下面的源目录结构中src/js/index.jssrc/js/test.js两个文件都去引入了node_modules下的第三方库Jquery。代码如下:
//src/js/index.js的代码:


import $ from 'jquery';
function sum(...args) {
  return args.reduce((p, c) => p + c, 0);
}

// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
// eslint-disable-next-line
console.log($);


-----------------------


//src/js/test.js的代码:

import $ from 'jquery';

// eslint-disable-next-line
console.log($);

export function mul(x, y) {
  return x * y;
}

export function count(x, y) {
  return x - y;
}
复制代码
  • code split的第二种做法的第四种情况的总结:(多入口且都引入了node_modules下的第三方库Jquery)下去执行webpack指令后,效果图如下:

  • code split的第二种做法的总结:
  • 第二种做法的单入口只能做第一件事,即可以将node_modules中代码单独打包一个chunk最终输出,将别人(第三方库)的代码分离另外生成一个文件
  • 第二种做法的多入口(也会做第一件事)还能做第二件事,即自动分析多入口chunk中,有没有公共的(第三方库...)文件。如果有则将此公共依赖打包成单独一个chunk

  • code split的第三种做法的注意点:在许多的应用场景中,我们接触到的更多的是单入口的场景。此时我们使用code split的第二种做法中的配置,是做不了分析公共依赖并将其单独提取为一个chunk输出的这件事情的。(但是不打紧)
  • code split的第三种做法的思路:我们希望在单入口的场景中在保持entry: './src/js/index.js',的写法上,可以做到将源目录src/js下的test.jsindex.js两个文件都单独打包为一个chunk(即单独输出为各自的JS文件):

  • code split的第三种做法:我们采取的不是entry: {index: './src/js/index.js',test: './src/js/test.js'},这种写法,而是通过在入口文件中(这里是在src/js/index.js文件中)书写JS代码,让让某个文件被单独打包成一个chunk(即单独输出为一个文件)。
  • code split的第三种做法:通过在入口文件中通过使用import动态导入语法,能将某个文件单独打包。
  • code split的第三种做法:下面是实现的代码(执行webpack指令查看结果):
//这里是src/js/index.js的代码(入口文件)


function sum(...args) {
  return args.reduce((p, c) => p + c, 0);
}

/*
  通过js代码,让某个文件被单独打包成一个chunk
  import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName: 'test' */'./test')
  .then(({ mul, count }) => {
    // 文件加载成功~
    // eslint-disable-next-line
    console.log(mul(2, 5));
  })
  .catch(() => {
    // eslint-disable-next-line
    console.log('文件加载失败~');
  });

// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));


----------

//这里是src/js/test.js的代码

export function mul(x, y) {
  return x * y;
}

export function count(x, y) {
  return x - y;
}

复制代码

  • code split的第三种做法的总结:

  • 使用code split来优化代码运行的性能的总结:

使用懒加载/预加载来优化代码运行的性能

  • 这里的懒加载和预加载针对的是JS文件的懒加载和预加载,不要和图片的懒加载混淆了。
  • 如果在index.js中直接通过import { mul } from './test';的方式去引入src/js/test.js文件,我们在执行webpack命令后,打开HTML(即index.html)页面,我们会发现两个文件是一起被加载的。并不会发生懒加载(延迟加载)即(当指定的条件被触发才加载文件)。
  • 如何使用懒加载呢?请看下面的代码:
//下面是src/index.html的页面代码:
<body>
  <h1>hello lazy loading</h1>
  //我们想要做到当点击按钮时才去加载src/js/test.js文件
  <button id="btn">按钮</button>
</body>


------------------

//下面是src/js/test.js的页面代码:


console.log('test.js文件被加载了~');

export function mul(x, y) {
  return x * y;
}

export function count(x, y) {
  return x - y;
}



-------------------------------

//下面是src/js/index.js(主(单)入口)的代码


console.log('index.js文件被加载了~');

// 下面一行的代码引入是不能够进行懒加载的所以注释掉
// import { mul } from './test';



document.getElementById('btn').onclick = function() {
  // 懒加载:当文件需要使用时才去加载该文件
  // 而且import语法会进行code split
  // 现在的import语法是放在了异步回调的函数下
  import(/* webpackChunkName: 'test'*/'./test')
  .then(({ mul }) => {
    console.log(mul(4, 5));
  });
};

复制代码

  • 如何使用预加载呢?请看下面的代码:
//下面是src/js/index.js(主(单)入口)的代码

console.log('index.js文件被加载了~');


// 下面一行的代码引入是不能够进行懒加载的所以注释掉
// import { mul } from './test';

document.getElementById('btn').onclick = function() {
  // 预加载 prefetch:会在使用之前,提前加载js文件 
  // 正常加载可以认为是并行加载(同一时间加载多个文件)  
  // 预加载 prefetch:虽然是提前加载
  // 但是它其实会等其他资源加载完毕,浏览器空闲了再偷偷提前加载资源
  // 预加载不会阻塞其他资源的下载
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test')
    .then(({ mul }) => {
    console.log(mul(4, 5));
  });
};
复制代码

  • 使用懒加载/预加载来优化代码运行的性能的总结:

使用pwa来优化代码运行的性能

  • PWA是渐进式网络开发应用程序,它可以让我们的网页支持离线缓存技术,也就是说让用户在离线状态下访问网页的体验更为友好,而不是用户在离线状态下访问网页时什么也看不了(白屏)。
  • 实际场景一:我们访问掘金的官网然后把network调为offline,离线之后访问掘金官网,我们会发现页面内容什么也无法看到了。(掘金没有用到PWA技术)

  • 实际场景二:我们访问淘宝官网然后把network调为offline,离线之后访问淘宝官网,我们会发现页面中大部分的内容是正常的,并且我们可以查看network中成功的请求文件资源中大部分有出现size(ServiceWorker)


  • 实现第一步:在webpack中想要实现PWA技术需要使用插件:workbox-webpack-plugin,即(cnpm i workbox-webpack-plugin -D),并且在webpack.config.js中进行配置,代码如下:
 plugins: [
    new WorkboxWebpackPlugin.GenerateSW({
      /*
        1. 帮助serviceworker快速启动
        2. 删除旧的 serviceworker

        //生成一个 serviceworker 配置文件
        //后续要通过这个配置文件去注册serviceworker
      */
      clientsClaim: true,
      skipWaiting: true
    })
  ],
复制代码
  • 实现第二步:紧接着(一般)要去入口文件(这里是src/js/index.js)中去注册serviceworker,并且还要处理兼容性问题。代码如下:
// 注册serviceWorker
// 处理兼容性问题
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      //需要传递一个参数由serviceWorker帮我们生成这个文件
      //执行webpack指令后就能看到该文件('/service-worker.js')
      .register('/service-worker.js')
      .then(() => {
        console.log('sw注册成功了~');
      })
      .catch(() => {
        console.log('sw注册失败了~');
      });
  });
}
复制代码
  • 实现第三步:问题一:之前通过eslint对js进行语法检查,但是eslint不认识window、navigator全局变量,我们在这里使用了window.addEventListener会引发问题,解决这个问题只需要在package.json中增加"eslintConfig"的配置,代码如下:
"eslintConfig": {
  "extends": "airbnb-base",
  //支持浏览器端全局变量
  "env": {
    "browser": true
  }
},
复制代码
  • 在配置完第三步后,我们执行webpack指令来查看一下构建的代码目录结构:

  • 实现第四步:serviceWorker代码必须运行在服务器上,所以我们要开启一个服务器来启动构建后的build/index.html页面。这是我们使用的是第三方库来帮我们启动服务器页面。通过npm i serve -g和 serve -s build启动服务器,将构建后的build目录下所有资源作为静态资源暴露出去。(也可以使用koa等方式)


  • 在配置完第四步后,我们将启动的服务器页面的网络调为offline,我们会发现此时的页面还是可以访问的。

  • 使用pwa来优化代码运行的性能的总结:

使用多进程打包来优化打包构建速度

  • 场景回顾:JS是单线程语言,如果需要处理的事情十分的多,相应需要处理的时间是比较长的,于是可以使用多线程打包来优化打包构建速度。
  • 实现第一步:如果想要在webpack中使用多线程打包,可以借助工具'thread-loader',即(cnpm i thread-loader -D)。使用方式是在需要启动多线程打包的地方
  • 实现第二步分析谁需要使用多线程打包。我们回顾之前在webpack.config.js写的代码可以判断出'babel-loader'可以使用多线程打包,代码如下:
module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: [
        /* 
          开启多进程打包。 
          注意点:进程启动大概为600ms,进程通信也有开销。
          只有工作消耗时间比较长,才需要多进程打包
        */
        {
          loader: 'thread-loader',
          options: {
            workers: 2 // 进程2个
          }
        },
        {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  useBuiltIns: 'usage',
                  corejs: { version: 3 },
                  targets: {
                    chrome: '60',
                    firefox: '50'
                  }
                }
              ]
            ],
            // 开启babel缓存
            // 第二次构建时,会读取之前的缓存
            cacheDirectory: true
          }
        }
      ]
    },
  ]
},
复制代码
  • 使用多进程打包来优化打包构建速度的总结:

使用externals来优化打包构建速度

  • 场景回顾:我们在入口文件中引入了node_modules下的第三方库Jquery,但是我们希望最终输出的文件中是以CDN的方式来引入这个Jquery,而不是通过单独生成一个第三方库的文件或其他的方式来引入这个Jquery.
  • 实现第一步:externals(外控者)可以帮助我们实现这个需求。我们可以在webpack.config.js中配置externals,代码如下:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'production',
  externals: {
    // 忽略的库名:'cnpm包名'
    // 拒绝jQuery被打包进来
    jquery: 'jQuery'
  }
};
复制代码

  • 第一步注意点:当我们忽略掉第三方库文件,让这个第三方库不被打包,执行webpack指令后,如果我们自动构建后的项目确实还是需要用到此第三方库的时候,我们应该要在之前就去手动的通过CDN的方式引入该文件(这里是在src/index.html去引入Jquery)。代码如下:
// 下面是src/index.html的代码

<body>
  <h1 id="title">hello html</h1>
  <!--自己去找CDN地址自己手动引入--!>
  <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
</body>


-----------------------------------


//下面是src/js/index.js的代码


import $ from 'jquery';

console.log($);


复制代码
  • 使用externals来优化打包构建速度的总结:
    • 注意使用externals是彻底不打包指定文件的意思。

使用dll来优化打包构建速度

  • 场景回顾:在之前的code split的案例中我们会发现所有引入node_modules下的第三方库的代码都只会被打包输出成一个文件。如果我们引入的node_modules下的第三方库的代码十分之多,那么这一个文件的体积是十分庞大的。
  • 问题解决dll(动态连接库)也能够指示webpack哪些库是不参与进打包后的文件的,但是dll还能够单独的对库进行单独的打包,单独的输出,然后在(使用插件)来完成自动引入单独输出的第三方库文件,更加有利于性能优化。(即使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包输出成文件再利用插件自动去引入这些文件)

  • 实现第一步:想要在webpack中使用dll技术,我们得新建一个JS文件,在这里我们新建一个名为webpack.dll.js(不一定要叫这个名称)的文件。

  • 实现第二步:在这里我们在入口文件src/index.js中引入node_modules下的第三方库jQuery为例即(import $ from 'jquery';),我们想要使用dll技术,在执行webpack指令后,构建后的JS文件(这里只有输出一个JS文件因为是单入口)不要去引入Jquery代码,而是自动引入单独输出的第三方库文件
  • 实现第三步:要想完成第二步的需求,我们需要去配置之前创建的webpack.dll.js文件,代码如下:
  • 实现第三步注意点:当你运行webpack指令时,默认是查找 webpack.config.js配置文件,但是我们现在的需求是在配置完webpack.dll.js后需要运行该文件,因为只有先运行该文件后,才能让我们配置的先单独输出的第三方库文件输出出去(即这里是先单独输出jquery.js文件)。
  • 实现第三步注意点:我们只需要在配置好webpack.dll.js 文件后执行webpack --config webpack.dll.js即可解决第三步的问题。

//这里是webpack.dll.js的代码:



const { resolve } = require('path');


//使用webpack自带的插件:new webpack.DllPlugin
const webpack = require('webpack');


module.exports = {
  entry: {
    // ['jquery'] --> 要打包的库是jquery
    jquery: ['jquery'],
  },
  output: {
    // 最终打包生成的文件的[name] ---这里是---> jquery
    filename: '[name].js',  //指定输出的文件名
    path: resolve(__dirname, 'dll'), //指定输出的目录
    library: '[name]_[hash]' // 打包的库(里面)向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个 manifest.json --> 提供和jquery映射(因为这里只打包了jquery)
    new webpack.DllPlugin({
      name: '[name]_[hash]', // 映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
    })
  ],
  mode: 'production'
};
复制代码
  • 实现第三步注意点:因为我们在webpack.dll.js文件中只有针对jquery做了相应的配置,所以在执行webpack --config webpack.dll.js指令后,只会自动帮我们构建了dll目录,该目录下有jquery.js(即jquery的代码被整合到这个文件),还有manifest.json(即提供和库的映射关系这里只有jQuery),我们点开manifest.json文件,可以观察到如下:

  • 实现第四步:在完成第三步之后,我们可以回到webpack.config.js中去配置相关的配置。在这里我们需要用到'webpack''add-asset-html-webpack-plugin',即(cnpm i webpack add-asset-html-webpack-plugin - D),通过new webpack.DllReferencePlugin的相关配置告诉webpack哪些库不参与打包,同时使用时的名称也得变。代码如下:
  plugins: [
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, 'dll/manifest.json')
    })
  ],
复制代码
  • 实现第四步注意点:new webpack.DllReferencePlugin配置之后只是告诉webpack不打包在dll/manifest.json里的映射库(这里只有jquery),所以它真的没有打包即(没有引入这个jquery)。
  • 实现第五步:我们还要在通过插件'add-asset-html-webpack-plugin' 将我们之前输出出去的jquery(即dll/jquery.js)在引入进来,所以才可以在构建后的目录文件中(即build/index.html)继续正常使用jquery。代码如下:
plugins: [
    //复制模板HTML自动引入打包输出的所有资源(JS/CSS等)
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    // 将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
      filepath: resolve(__dirname, 'dll/jquery.js')
    })
  ],
复制代码
  • 实现第五步注意点:我们先创建并配置了webpack.dll.js文件,这是希望将来在构建的时候不用重复打包某个库(这里的场景是不用重复打包jquery)的代码,因为我们在执行webpack --config webpack.dll.js指令之后生成了dll/jquery.jsdll/manifest.json
  • 实现第五步注意点:接下来只要webpack.dll.js的配置无需再继续添加的话,只要继续关注webpack.config.js的配置就可以了,通过在webpack.config.js使用'webpack''add-asset-html-webpack-plugin'就可以实现每一次执行webpack指令后不需要在重复打包第三方库。
  • 下面是完整的webpack.config.js的代码:
const { resolve } = require('path');



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


// cnpm i add-asset-html-webpack-plugin - D
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    //复制模板HTML自动引入打包输出的所有资源(JS/CSS等)
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
    // 只是告诉它不打包 所以它真的没有打包 没有引入这个jquery
    // 我完成了不打包的需求 
    // 我还需要它在不打包的同时完成自动引入这个jquery的操作
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    // 将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
      filepath: resolve(__dirname, 'dll/jquery.js')
    })
  ],
  mode: 'production'
};
复制代码
  • 使用dll来优化打包构建速度的总结:

综合案例:性能优化的基本总配置

  • 需求一:使用HMR优化打包构建速度

  • 需求二:使用source-map优化代码调试

  • 需求三:使用oneOf优化打包构建速度:

  • 需求四:使用缓存优化打包构建速度

  • 需求五:使用tree shaking来优化代码运行的性能

  • 需求六:使用code split来优化代码运行的性能




  • 需求七:使用懒加载/预加载来优化代码运行的性能

  • 需求八:使用多进程打包来优化打包构建速度

  • 需求九:使用externals来优化打包构建速度:

  • 需求十:使用dll来优化打包构建速度:

  • 该项目使用的package.json的代码如下:
{
  "name": "webpack_code",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/polyfill": "^7.8.3",
    "@babel/preset-env": "^7.8.4",
    "add-asset-html-webpack-plugin": "^3.1.3",
    "babel": "^6.23.0",
    "babel-loader": "^8.0.6",
    "core-js": "^3.6.4",
    "css-loader": "^3.4.2",
    "eslint": "^6.8.0",
    "eslint-config-airbnb-base": "^14.0.0",
    "eslint-loader": "^3.0.3",
    "eslint-plugin-import": "^2.20.1",
    "file-loader": "^5.0.2",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.11.1",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.9.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "postcss-preset-env": "^6.7.0",
    "style-loader": "^1.1.3",
    "terser-webpack-plugin": "^2.3.5",
    "thread-loader": "^2.1.3",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.6",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3",
    "workbox-webpack-plugin": "^5.0.0"
  },
  "dependencies": {
    "jquery": "^3.4.1"
  },
  "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  },
  "sideEffects": [
    "*.css"
  ]
}

复制代码
文章分类
前端
文章标签