webpack代码优化

840 阅读5分钟

代码分离

代码分离是webpack最重要的特性,可将代码分离到不同的bundle中,方便资源的按需加载或并行加载,从而影响加载时间。 常用代码分割方法:

  • 入口起点:使用entry配置手动分离代码
  • 防止重复:使用Entry dependencied或者 SplitChunksPlugin去重和分离chunk
  • 动态导入:通过模块的内联函数调用来分离代码。

入口起点

  • index.js
import _ from 'lodash'
import { add } from './a.js'
function component() {
    const element = document.createElement('div');
  
    // lodash(目前通过一个 script 引入)对于执行这一行是必需的
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  
    return element;
  }
  console.log(add(2,5))
  document.body.appendChild(component());
  • another-module.js
import _ from 'lodash';

console.log(_.join(['Another', 'module', 'loaded!'], ' '));
  • webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  mode: 'development',
  entry: {
    index:'./src/index.js', // 入口1
    Another:'./src/another-module.js', // 入口2
  },
  devtool: 'inline-source-map', // 此处可准确定位出出错模块的信息(sourcemap)
  plugins:[
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
          title:'管理输出'
      })
  ],
  output: {
    filename: '[name].main.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

以上方式存在一些隐患:

  • 如果入口chunk之间包含一些重复的模块,那些重复的模块会被引入到各个不同的bundle中。
  • 同时通过这样的方式不能动态的将核心应用程序从代码中分割 以上第一点隐患很容易看出,我们在index.js文件中引入了lodash,但是在another-module.js文件从还需要重复引入才可使用。

防止重复

通过在在文件中配置dependOn选项,这样可以在多个chunk之间共享模块。提取两个文件之间共同的依赖项,在打包时生成单独的模块文件,从而减少模块代码的大小。 如果我们要在一个 HTML 页面上使用多个入口时,还需设置 optimization.runtimeChunk: 'single'

  • webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  mode: 'development',
  entry: {
    index: {
      import: './src/index.js',
      dependOn: 'shared',
    },
    another: {
      import: './src/another-module.js',
      dependOn: 'shared',
    },
    shared: 'lodash',
  },
  devtool: 'inline-source-map', // 此处可准确定位出出错模块的信息(sourcemap)
  plugins:[
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
          title:'管理输出'
      })
  ],
  output: {
    filename: '[name].main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  optimization: {
    runtimeChunk: 'single',
  }
};

动态代码导入

当涉及到动态代码拆分时,webpack使用了两个类似的方式。

  • 1.使用import语法进行动态导入,在打包后的代码,会将动态导入的包自动打包成一个vendors。此时不用去配置。
  import('lodash').then(_ => {
    // Do something with lodash (a.k.a '_')...
  });
  • 2.使用webpack特定的require.ensure
 require.ensure(['b'], function(require) {
    var c = require('c');

    // Do something special...
  });

预获取/预加载模块(prefetch/preload)

结合import实现

function button(){
  const ele = document.createElement('button')
  ele.innerHTML = 'button'
  ele.onclick = function(){
    import(/* webpackPrefetch: true */ './a').then(res=>{
      console.log(res)
    })
  }
  return ele
}

指令配置会生成<link rel="prefetch" as="script" href="src_a_js.main.js"></head>并追加到页面头部,表示浏览器闲置时间预取src_a_js.main.js文件。 也可通过浏览器的performance查看资源获取的方式 An image 与prefetch指令相比,preload有部分不同:

  • preload chunk会在父chunk加载时,以并行的方式加载,prefetch chunk会在父chunk加载结束后开始加载;
  • preload chunk具有中等优先级,并立即下载,prefetch chunk会在浏览器闲置时下载。
  • preload chunk 会在父组件中立即请求,用于当下时刻。prefetch chunk 会用于未来某个时刻。
  • 浏览器对prefetch的支持度约为70%,对于preload的约50%

缓存

从浏览器获取服务器上的资源通常是比较耗时的。浏览器通过命中缓存来降低网络流量,使网站加载速度更快,然而如果在部署时不更改资源的文件名,浏览器可能会被认为他没有更新,就会使用其缓存版本。

filename

在output.filename配置contenthash将根据资源内容创建出唯一hash,当资源内容发生变化时间,[contenthash]也会变化,在打包之后的文件中,文件名会包含一个唯一hash值。

提取引导模板

在代码分离中,splitchunksplugin可以用于将模块分离到单独的bundle中。可使用 optimization.runtimeChunk 选项将 runtime 代码拆分为一个单独的 chunk。将其设置为 single 来为所有 chunk 创建一个 runtime bundle

asset vendors-node_modules_lodash_lodash_js.0120508cd546f3f22c19.js 1.37 MiB [emitted] [immutable] (id hint: vendors)
asset runtime.c84f401aa9ba9c916849.js 34.1 KiB [emitted] [immutable] (name: runtime)
asset index.e429b554c829d01de78b.js 3.47 KiB [emitted] [immutable] (name: index)
asset src_a_js.eded08a4d8cac1861aca.js 939 bytes [emitted] [immutable]
asset index.html 303 bytes [emitted]
Entrypoint index 37.6 KiB = runtime.c84f401aa9ba9c916849.js 34.1 KiB index.e429b554c829d01de78b.js 3.47 KiB
runtime modules 9.86 KiB 13 modules

通常将第三方库例如lodash、Vue、React提取到单独的vendor chunk文件中,因为其很少进行改动,因此可利用科幻段的缓存机制,通过命中缓存来消除请求,并减少向服务器获取资源,同时保证代码的准确性。 这可以通过使用 SplitChunksPlugin 插件的 cacheGroups 选项来实现。我们在 optimization.splitChunks 添加 cacheGroups 参数并构建:

  • 添加前:
asset vendors-node_modules_lodash_lodash_js.0120508cd546f3f22c19.js 1.37 MiB [emitted] [immutable] (id hint: vendors)
asset runtime.c84f401aa9ba9c916849.js 34.1 KiB [emitted] [immutable] (name: runtime)
asset index.c8aef78ae031df99c50e.js 5.85 KiB [emitted] [immutable] (name: index)
asset src_a_js.eded08a4d8cac1861aca.js 939 bytes [emitted] [immutable]
asset index.html 303 bytes [emitted]
Entrypoint index 40 KiB = runtime.c84f401aa9ba9c916849.js 34.1 KiB index.c8aef78ae031df99c50e.js 5.85 KiB
  • 添加后:
asset vendorstest.a477574b3560f21a228f.js 1.37 MiB [emitted] [immutable] (name: vendorstest) (id hint: vendor)
asset runtime.2bba52c28f516bccc037.js 33.9 KiB [emitted] [immutable] (name: runtime)
asset index.2dfd9b3c84a3ec76ba9e.js 3.86 KiB [emitted] [immutable] (name: index)
asset src_a_js.eded08a4d8cac1861aca.js 939 bytes [emitted] [immutable]
asset index.html 362 bytes [emitted]
Entrypoint index 1.41 MiB = runtime.2bba52c28f516bccc037.js 33.9 KiB vendorstest.a477574b3560f21a228f.js 1.37 MiB index.2dfd9b3c84a3ec76ba9e.js 3.86 KiB

对比前后index文件的大小,在优化代码之前时5.85kb,优化后是3.56kb;

懒加载

懒加载或则按需加载,能够很好的优化网页。这种方式实际是在一些逻辑断点处分开,然后在一些代码块中完成某些操作。这样加快了应用的加载速度,减轻了提及,因为部分代码块可能永远不会被加载。

总结

webpack优化方式