使用Webpack4优化Web性能

栏目: 编程语言 · 发布时间: 7年前

内容简介:可以在 webpack 中指定:或者 package.json 中配置:

production 模式下 webpack 会对代码进行优化,如减小代码体积,删除只在开发环境用到的代码。

可以在 webpack 中指定:

module.exports = {
  mode: 'production' // 或 development
};
复制代码

或者 package.json 中配置:

"scripts": {
    "dev": "webpack-dev-server --mode development --open --hot",
    "build": "webpack --mode production --progress"
}
复制代码

2、压缩代码

使用 bundle-level minifier 和 loader options 压缩代码。

  • Bundle-level minification

Bundle-level 的压缩会在代码编译后对整个包进行压缩。

在 webpack 4 中, production 模式下会自动执行 bundle-level 的压缩,底层使用了 the UglifyJS minifier 。(如果不想开启压缩,可以采用 development 模式或者设置 optimization.minimize 为 false)

  • Loader-specific options

通过 loader 层面的选项配置来对代码进行压缩,是为了压缩 bundle-level minifier 无法压缩的内容,比如,通过 css-loader 编译后的文件,会成为字符串,就无法被 minifier 压缩。因此,要进一步压缩文件内容,可进行如下配置:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { minimize: true } },
        ],
      },
    ],
  },
};
复制代码

3、使用 ES 模块

当使用 ES 模块时, webpack 能够进行 tree-shaking。

tree-shaking 是指 bundler 遍历整个依赖关系树,检查使用了哪些依赖关系,并删除未使用的依赖关系。因此,如果使用ES模块语法,webpack 可以消除未使用的代码。

★ 注意:在 webpack 中,如果没有 minifier,tree-shaking 就无法工作。webpack 只删除不使用的导出语句,而 minifier 则会删除未使用的代码。因此,如果在编译时不使用 minifier,代码量并不会减小。(除了使用 wbpack 内置的 minifier,其它的插件如 Babel Minify plugin 也能对代码进行压缩)。

✘ 警告:不要意外地将 ES 模块编译成 CommonJS 模块。如果你使用 Babel 的时候,采用了 babel-preset-env 或者 babel-preset-es2015 ,请检查这些预置的设置。默认情况下,它们会将 ES 的导入和导出转换为 CommonJS 的 requiremodule.exports ,可以通过传递 { modules: false } 选项来禁用它。

Introduction to ES Modules一口(很长的)气了解 babel ➹ Webpack docs about tree shaking

4、压缩图片资源

针对具体的依赖项进行优化( dependency-specific optimization

图像占了页面大小的一半以上。虽然它们不像JavaScript那样重要(例如,它们不会阻塞呈现),但它们仍然占用了很大一部分带宽。在 webpack 中可以使用 url-loadersvg-url-loaderimage-webpack-loader 来优化它们。

url-loader 可以将小型静态文件内联到应用程序中。如果不进行配置,它将把接受一个传递的文件,将其放在已编译的包旁边,并返回该文件的url。但是,如果指定 limit 选项,它将把小于这个限制的文件编码为Base64 数据的 url 并返回这个url,这会将图像内联到 JavaScript 代码中,从而可以减少一个HTTP请求。

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif)$/,
        loader: 'url-loader',
        options: {
          // Inline files smaller than 10 kB (10240 bytes)
          limit: 10 * 1024,
        },
      },
    ],
  }
};
复制代码
// index.js
import imageUrl from './image.png';
// → If image.png is smaller than 10 kB, `imageUrl` will include
// the encoded image: 'data:image/png;base64,iVBORw0KGg…'
// → If image.png is larger than 10 kB, the loader will create a new file,
// and `imageUrl` will include its url: `/2fcd56a1920be.png`
复制代码

★ 注意:需要在增大代码体积和减少 HTTP 请求数之前进行权衡。

svg-url-loader 的工作原理与 url-loader 类似 — 只是它使用的是URL编码而不是Base64编码来编码文件。这对SVG图像很有用 — 因为SVG文件只是纯文本,这种编码更高效。

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        loader: 'svg-url-loader',
        options: {
          // Inline files smaller than 10 kB (10240 bytes)
          limit: 10 * 1024,
          // Remove the quotes from the url
          // (they’re unnecessary in most cases)
          noquotes: true,
        },
      },
    ],
  },
};
复制代码

★ 注意: svg-url-loader 有一些选项可以改进Internet Explorer的支持,但会使其他浏览器的内联更加糟糕。如果需要支持此浏览器,请应用 iesafe: true 选项。

image-webpack-loader 可支持JPG、PNG、GIF和SVG图像的压缩。

这个加载器不嵌入图像到应用程序,所以它必须与 url-loadersvg-url-loader 成对工作。为了避免将其复制粘贴到两个规则中(一个用于JPG/PNG/GIF图像,另一个用于SVG图像),我们通过enforce: 'pre' 将这个加载器设为一个单独的规则:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'image-webpack-loader',
        // This will apply the loader before the other ones
        enforce: 'pre',
      },
    ],
  },
};
复制代码

5、优化第三方依赖

JavaScript 的大小平均有一半以上来自依赖项,而其中的一部分可能是不必要的。我们可以对这些依赖的库进行优化:arrow_right: webpack-libs-optimizations

比如:moment.js 删除未使用的地区、react-router 移除未使用的模块,生产环境去除 react propTypes 声明等。

6、对于ES6模块开启模块连接

也叫做作用域提升(Scope Hoisting)

早期的时候,为了隔离 CommonJS/AMD 模块,webpack 在打包的时候,会把每个模块都打包到一个函数中,这样就会增大每个模块的大小和性能开销。webpack 2 的时候支持了 ES 模块,然后 webpack 3 的时候使模块连接成为了可能。

【原理】:它会分析模块间的依赖关系,尽可能将被打散的模块合并到一个函数中,但不能造成代码冗余,所以只有被引用一次的模块才能被合并。由于需要分析模块间的依赖关系,所以源码必须是采用了ES6模块化的,否则Webpack会降级处理不采用Scope Hoisting。

开启模块连接之后,打出的包将会具有更少的模块,以及更少的模块开销。如果在生产模式下使用 webpack 4,则模块连接已经启用。

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    concatenateModules: true,
  },
};
复制代码

★ 注意:为什么默认情况下不启用此行为?连接模块很酷,但是它增加了构建时间,并中断了热模块替换。这就是为什么应该只在生产中启用它。

三十分钟掌握Webpack性能优化

7、如果觉得有意义的话,使用 externals

具体请参考: webpack-configuration-externals

二、使用长期缓存

1、文件名输出

缓存包( bundle ),并通过更改包名称(bundle name)来区分版本,将文件名替换成 [name].[chunkname].js

[hash] 替换:可以用于在文件名中包含一个构建相关(build-specific)的 hash; [chunkhash] 替换:在文件名中包含一个 chunk 相关(chunk-specific)的哈希,比 [hash] 替换更好; [contenthash] 替换:会根据资源的内容添加一个唯一的 hash,当资源内容不变时, [contenthash] 就不会变。

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

  module.exports = {
-   entry: './index.js',
+   entry: {
+     main: './index.js',
+   },
    output: {
-     filename: 'bundle.js',
+     filename: '[name].[contenthash].js',  // / → bundle.8e0d62a03.js
      path: path.resolve(__dirname, 'dist')
    }
    plugins: [
      new HtmlWebpackPlugin({
-       title: 'Output Management'
+       title: 'Caching'
      })
    ],
  };
复制代码

Hash vs chunkhash vs ContentHash

2、提取第三方库和样板代码

bundle 拆分成程序代码( app )、第三方库代码( vendor )和运行时代码( runtime )。

  • 开启智能 code splitting

在 webpack 4 中添加以下的代码,当第三方库代码大于 30 kb 时(未压缩和未gzip前),webpack 能够自动提取 vendor 代码,并且如果你在路由层面使用了代码分割的话,它也能够提取公共代码。

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
    }
  },
};
复制代码

这样,每次打包都会生成两个文件: main.[chunkhash].jsvendors~main.[chunkhash].js (for webpack 4). 在 webpack 4 中, 当第三方库依赖很小的时候,vendor 包可能不会被生成,但也没关系。

  • webpack 运行时代码

Webpack 在入口 chunk 中,包含了其运行时的引导代码: runtime ,以及伴随的 manifest 数据, runtime 是用来管理模块交互的一小片段代码。当你将代码分割成多个文件时,这段代码包含了 chunk id 和模块文件之间的映射,包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。

Webpack 会将这个运行时包含到最后生成的 chunk 中,即 vendor 。每次有任何块发生变化时,这段代码也会发生变化,导致 vendor bundle 发生变化。

【解决方法】:设置 runtimeChunktrue 来为所有 chunks 创建一个单一的运行时包:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    runtimeChunk: true,
  },
};
复制代码

webpack 运行时代码很小,内联它,可以减少 HTTP 请求。

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      // Inline all files which names start with “runtime~” and end with “.js”.
      // That’s the default naming of runtime chunks
      inlineSource: 'runtime~.+\\.js',
    }),
    // This plugin enables the “inlineSource” option
    new InlineSourcePlugin(),
  ],
};
复制代码

webpack-concepts-manifest

3、代码懒加载

单页应用中,使用 import 对不关键的代码进行懒加载。

// videoPlayer.js
export function renderVideoPlayer() { … }

// comments.js
export function renderComments() { … }

// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();

// …Custom event listener
onShowCommentsClick(() => {
  import('./comments').then((comments) => {
    comments.renderComments();
  });
});
复制代码

import() 表示你想要动态加载特定模块,当 webpack 看到 import('./module.js') 时,它会自动把该模块从 chunk 中移除,只有在执行的时候才会被下载。

这会使 main 模块更小,能够减少初始加载时间,并且也能很好的提高缓存,如果你在 main chunk 中改了代码,懒加载的模块不会被影响。

按路由/页面分割代码(Code Splitting),以避免加载不必要的内容。

单页应用中,除了通过 import() 进行懒加载,还可以通过框架层面的手段来进行。 React 应用懒加载——> Code Splitting(react-router) 或者 React.lazy(react doc)

WebpackGuides-CachingWebpackConcepts-The Manifest

4、模块标识符

使模块标识符更稳定

在 webpack 构建时,每个 module.id 会基于默认的解析顺序(resolve order)进行增量,也就是说,当解析顺序发生变化,ID 也会随之改变。如:当新增一个模块的时候,它可能会出现在模块列表的中间,那么它之后的模块 ID 就会发生变化。

如果在业务代码里新引入一个模块,则:

  • main bundle 会随着自身的新增内容的修改,而发生变化 ——> 符合预期
  • vendor bundle 会随着自身的 module.id 的修改,而发生变化 ——> 【不符合预期】
  • runtime bundle 会因为当前包含一个新模块的引用,而发生变化 ——> 符合预期
+ const webpack = require('webpack');

  module.exports = {
    plugins: [
+      new webpack.HashedModuleIdsPlugin()
    ],
  };
复制代码

为了解决这个问题,模块 ID 通过 HashedModuleIdsPlugin 来进行计算,它会把基于数字增量的 ID 替换成模块自身的 hash。这样的话,一个模块的 ID 只会在重命名或者移除的时候才会改变,新模块不会影响到它的 ID 变化。

[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
    + 1 hidden module
复制代码

三、监控和分析应用程序

在开发阶段使用 webpack-dashboardbundlesize 来调整应用程序的大小

  • webpack-dashboard

webpack-dashboard 通过展示依赖项大小、进度和其他细节来增强 webpack 输出,有助于跟踪大型依赖项。

npm install webpack-dashboard --save-dev
复制代码
// webpack.config.js
const DashboardPlugin = require('webpack-dashboard/plugin');

module.exports = {
  plugins: [
    new DashboardPlugin(),
  ],
};
复制代码
  • bundlesize

bundlesize 用于验证 webpack 的资源不超过指定的大小,当应用程序变得太大时能够及时得知。

(1)运行打包命令 (2)开启 bundlesize

npm install bundlesize --save-dev
复制代码

(3)在 package.json 中指定文件大小限制

// package.json
{
  "bundlesize": [
    {
      "path": "./dist/*.png",
      "maxSize": "16 kB",
    },
    {
      "path": "./dist/main.*.js",
      "maxSize": "20 kB",
    },
    {
      "path": "./dist/vendor.*.js",
      "maxSize": "35 kB",
    }
  ]
}
复制代码

(4)执行 bundlesize

npx bundlesize
复制代码

或者用 npm 执行:

// package.json
{
  "scripts": {
    "check-size": "bundlesize"
  }
}
复制代码

通过 webpack-bundle-analyzer 分析包的大小

webpack-bundle-analyzer 能够扫描 bundle 并对其内部内容进行可视化呈现,从而可以发现大型的或者不必要的依赖项。

npm install webpack-bundle-analyzer --save-dev
复制代码
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin(),
  ],
};
复制代码

运行生产构建,该插件会在浏览器中打开可视化页面。

默认情况下,统计页面显示的是已解析文件的大小(当文件出现在包中时)。您可能想比较 gzip 之后的大小,因为它更接近实际用户体验,可以使用左边的边栏来切换大小。

对于报告,我们需要关注的点有:

  • 大型依赖项:为什么这么大?是否有更小的替代方案(例如,用 Preact 代替 React)?您是否使用了该库包含的所有代码(例如,Moment.js 包含了许多  经常不使用且可能被删除的地区设置 )?

  • 重复的依赖关系:您是否看到同一个库在多个文件中重复出现?(在 webpack 4 中使用 optimization.splitChunks.chunks 将重复的依赖关系移动到一个公共文件)。或者某个包具有相同库的多个版本?

  • 相似的依赖关系:是否有类似的库可以做大致相同的工作?(例如, momentdate-fns ,或 lodashlodash-es ),试着只用一个工具。

四、总结

(1)削减不必要的字节。压缩所有内容,删除未使用的代码,明智地添加依赖项; (2)按路由拆分代码 。只加载现在真正需要的东西,稍后再加载其他东西; (3)缓存代码 。应用程序的某些部分(如第三方库)更新的频率低于其他部分,将这些部分分离到文件中,以便只在必要时重新下载; (4)追踪代码大小 。使用像 webpack-dashboard 和 webpack-bundle-analyzer 这样的 工具 来了解你的应用程序有多大。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

CSS3 Solutions

CSS3 Solutions

Marco Casario / Apress / 2012-8-13 / GBP 35.50

CSS3 brings a mass of changes, additions, and improvements to CSS across a range of new modules. Web designers and developers now have a whole host of new techniques up their sleeves, from working wit......一起来看看 《CSS3 Solutions》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具