手把手教你配置 Webpack,并优化

2,074 阅读7分钟

在平常的开发过程中,如果没有手动地配置或者优化打包后尺寸,那么用户打开网站时,首屏加载会很慢,几秒后才出现内容,大大增加了用户的等待时间。

为了解决这个问题,我们需要从打包这个环节进行优化。常见的优化打包工具webpack,我们从流行的ReactVue库着手,尝试着优化它们。


首先,使用create-react-app脚手架创建一个React应用。

如果没有,首先需要从全局中安装脚手架,命令如下:

npm install create-react-app -g

接着新建一个文件夹,并在终端编写命令为:

create-react-app webpack-optimiation-react

模板创建完毕之后,它目录结构如下:

.
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── README.md
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   ├── serviceWorker.js
│   └── setupTests.js
├── tree.txt
└── yarn.lock

模板文件建好后,我们在不同的库中添加相同的代码,然后使用router使他们能够正常运转。

// src/Home.js
import React from 'react';
export default () => <h1>Home</>;
// src/About.js
import React from 'react';
export default () => <h1>About</>;
// src/Concat.js
import React from 'react';
export default () => <h1>Concat</h1>;

然后使用npm包管理器,添加react-router-dom

npm install react-router-dom -D

以上的代码写好后,在src/index.js中添加:

import React, { lazy, Suspense } from 'react';
import { Switch, BrowserRouter as Router, Link, Route } from 'react-router-dom';

const Home = lazy(() => import('./Home'));
const Concat = lazy(() => import('./Concat'));
const About = lazy(() => import('./About'));

const NavBar = () => (
  <div>
    <Link to='/'>Home</Link>
    <Link to='/about'>About</Link>
    <Link to='/concat'>Concat</Link>
  </div>
);

function App() {
  return (
    <Router className='App'>
      <>
        <NavBar />
        <Suspense fallback={<div>loading...</div>}>
          <Switch>
            <Route path='/' exact component={Home} />
            <Route path='/about' component={About} />
            <Route path='/concat' component={Concat} />
          </Switch>
        </Suspense>
      </>
    </Router>
  );
}

export default App;

编写完成之后,使用npm run start,浏览器会自动打开,并显示:

react

OK,运行正常。

我们打包试试,在终端输入命令:

npm run build

这时就会出现一个build文件夹,这就是打包后的结果,可以给后端部署了。

build之后的文件大小居然有600k之多,为了查看具体那些包体积大,就需要配置webpack

analysis

但是create-react-app并不暴露webpack配置文件,需要输入命令才能看到webapck配置文件:

npm run eject

它的结构目录如下:

.
├── config
│   ├── env.js
│   ├── jest
│   │   ├── cssTransform.js
│   │   └── fileTransform.js
│   ├── modules.js
│   ├── paths.js
│   ├── pnpTs.js
│   ├── webpack.config.js
│   └── webpackDevServer.config.js
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── README.md
├── scripts
│   ├── build.js
│   ├── start.js
│   └── test.js
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   ├── serviceWorker.js
│   └── setupTests.js
├── tree.txt
└── yarn.lock

为了解决上面打包600kb的问题,首先需要分析,那些地方打包后的尺寸过大。我们需要webpack-bundle-analyzer这个插件才能可视化地看到那些包尺寸较大。

安装方法:

npm install webpack-bundle-analyzer -D

然后在config/webpack.config.js写入代码:

首先导入这个包,然后再添加插件:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

plugins:[
    ...
     isEnvProduction && new  BundleAnalyzerPlugin(),
     // isEnvProduction这个变量是指,在是否在生产环境
    ...
]

可视化分析出来的结果如下:

webpack_analysis

从上图我们可以发现,打包后最大的包是react-dom

那么我们就着手优化它吧,从两个方面考虑:

  1. 减少服务器端的压力方法

  2. 减少尺寸的方法

减少服务器压力的方法有:

  • 使用AggressiveSplittingPlugin插件

  • 使用prefetch&preload方法

  • 使用gzip方法

减少尺寸的方法有:

  • 使用ModuleConcatenationPlugin插件

  • 使用uglifyjs插件

  • 使用exterals选项


AggressiveSplittingPlugin

那么现在试着用AggressiveSplittingPlugin优化打包后的代码吧。

直接在config/webpack.config.jsplugins中添加代码:

plugins: [
  new webpack.optimize.AggressiveSplittingPlugin({
    minSize: 3000,
    maxSize: 5000,
    chunkOverhead: 0,
    entryChunkMultiplicator: 1
  })
];

build文件夹中出现了非常多的小文件。这个插件是的超过一定体积会分割文件。有利于减少服务器的请求压力。AggressiveSplittingPlugin可以将bundle拆分成更小的chunk,直到各个chunk的大小达到option设置的 maxSize。它通过目录结构将模块组织在一起。

它记录了在webpack Records里的分离点,并尝试按照它开始的方式还原分离。这确保了在更改应用程序后,旧的分离点(和 chunk)是可再使用的,因为它们可能早已在客户端的缓存中。因此强烈推荐使用Records


preload&prefetch

第二项是预加载的能力,和预请求能力。我们先来看看使用preload会怎么样?

我的html-webpack-plugin版本是4.0.0-beta.11,为了正确安装这个 preload 插件,必须要将它的版本设置为3.0.0-beta.3,不然它会报以下的错误:

Plugin could not be registered at 'html-webpack-plugin-before-html-processing'. Hook was not found.

使用npm管理器安装这个版本:

npm install preload-webpack-plugin@3.0.0-beta.3

接着在webpack.config.js中添加:

const PreloadWebpackPlugin = require('preload-webpack-plugin');
// ...
plugins: [
  new HtmlWebpackPlugin(
    Object.assign(
      {},
      {
        inject: true,
        template: paths.appHtml
      },
      isEnvProduction
        ? {
            minify: {
              removeComments: true,
              collapseWhitespace: true,
              removeRedundantAttributes: true,
              useShortDoctype: true,
              removeEmptyAttributes: true,
              removeStyleLinkTypeAttributes: true,
              keepClosingSlash: true,
              minifyJS: true,
              minifyCSS: true,
              minifyURLs: true
            }
          }
        : undefined
    )
  ),
  // ...
  new PreloadWebpackPlugin()
];
// ...

打开调试台,如果的你节点出现了这样的样子,那么说明你成功了。

preload

后面有一个ref=preload就是预加载。

preload有能做什么?

  • 可以预先加载文件或者资源。
  • 不会阻塞页面加载。

举个例子,如果一个网页中使用了许多的字体文件,那么用户在浏览的时候就会等待字体加载,出现白屏的现象。

如果使用了preload的话,那么浏览器不会每次请求一个页面,到指定页面中重新加载,而是预先加载字体文件,到指定页面中不用再次加载。


现在,我们尝试使用prefetch,是什么结果。

config/webpack.config.js修改:

// ...
plugins: [
  // ...
  new PreloadWebpackPlugin({
    rel: 'prefetch'
  })
  // ...
];
// ...

这时,preload就变成了prefetch,如下图:

prefetch

如果你看到了ref=prefetch的话,那么代表预处理成功。

它们的共同点是?

  1. 异步加载资源,不会阻塞网页渲染
  2. 下载并不执行文件
  3. 能够提前请求文件
  4. 没有同域名限制

prefetch 和 preload 的区别是什么?

  1. preload会首先优先加载,并且会占用HTTP并发数,也就是刚进入页面就会请求。而prefetch会浏览器出空闲期时,再请求文件。
  2. preload可以跨域请求,prefetch不会。

Gzip

gzip能够为我们减少存储空间,以及减少传输的时间。

我们需要安装一下插件:

npm install -D compression-webpack-plugin

config/webpack.config.js中添加:

//...
plugins: [
  //...
  new CompressionWebpackPlugin({
    filename: '[path].gz[query]',
    algorithm: 'gzip',
    test: /\.(js|css)/,
    threshold: 1024,
    minRatio: 0.8
  })
  //...
];
//...

build完成之后,打开浏览器。如果你看到这样的header的话,那么代表开启gzip成功。

gzip


现在,我们开始减少尺寸的优化。

使用ModuleConcatenationPlugin可以提高代码的执行速度和预编译:

config/webpack.config.js中添加:

// ...
plugins: [
  // ...
  new webpack.optimize.ModuleConcatenationPlugin()
];
// ...

build后,尺寸比以前集减少接近10kb,而且网页的打开速度也提高不少。

相比之前打开网页的速度,提高了50ms之多。再加上懒加载,并不需要提前加载所有页面,所以首屏渲染速度提高了许多。


Uglify

接着,我们上一个大杀器,UglifyJS插件,它最大程度压缩和丑化代码。

安装它的命令行:

npm install -D uglifyjs-webpack-plugin

导入到config/webpack.config.js中,并使用:

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

 // ...
optimization: {
  minimizer:{
    new UglifyJsPlugin(),
  }
}
// ...

可以看见,压缩后的代码有多180kb

uglify

Externals

externals这个webpack选项是在打包过程中剔除掉。这样就有效地减少了依赖,使用第三方CDN减少HTTP压力和请求的压力。

使用externals只需要在webpack.config.js中添加一下的代码:

// ...
externals:{
      'react': 'React',
      'react-dom': 'ReactDOM',
      'react-router-dom': 'ReactRouterDOM'
}
// ...

接着在index.html中添加CDN

<script src="https://cdn.bootcss.com/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-router-dom/5.1.2/react-router-dom.min.js"></script>
<script src="https://cdn.bootcss.com/react-dom/16.10.2/umd/react-dom.production.min.js"></script>