Webpack 配置优化
By Yadomin
起因
为什么想要写这篇呢?
之前写的工作室招新平台,在构建的时候,总是会弹出来 Bundle 大小过大的警告,于是看了一眼 Bundle 里面到底有些啥
非常的离谱,尤其是 @ant-design/icons
, 我只是引用了几个图标,却把整个库都引了进来, 我也不懂为什么 Webpack 的 tree shaking
没有起作用。这个项目我用的是 umi
, 在看了文档进行一番操作之后并没有成功,所以我想脱离 umi
来看看,到底是 umi
的问题,还是 @ant-design/icons
的问题,还是我太菜 的问题。
新建项目
当你需要新建一个 React 项目的时候,第一反应是什么? create-react-app
, create-next-app
还是 yarn create @umijs/umi-app
? 我们似乎已经习惯用这些一键脚手架来配置项目的babel
和webpack
? 但是这些工具是怎么工作的, babel
和 webpack
又是怎么配置的,今天让我们从0开始探索一遍。
1 | mkdir myapp && cd myapp |
添加依赖
1 | Babel |
简单介绍一下这里的依赖都是干啥的
- babel: JS 语法编译器,这里是将 ts 和 jsx 编译到浏览器能用commonjs (其实不用babel, 只用typescript的编译器也可以,但是限制比较多,而且 babel 的插件多)
- @babel/core
- @babel/preset-env: 根据你选择的
target
和其他配置,自动加载 babel 的插件 - @babel/preset-react: React 的相关配置
- @babel/typescript: Typescript 的相关设置
- @babel/plugin-proposal-class-properties : 为 JS 的类成员添加属性
- @babel/plugin-proposal-object-rest-spread : 添加 JS 的剩余参数
- Webpack: JS 打包器,主要是将各个模块打包到一个文件里,精简体积,减少文件数量,适合前端使用
- webpack
- webpack-cli
- webpack-dev-server : webpack 的开发服务器,可以实现在上面实现 HMR 等
- fork-ts-checker-webpack-plugin : webpack 的 ts 类型检查插件
- html-webpack-plugin : 创建 HTML 文件
- babel-loader : Webpack 的 Babel Loader, 将 Babel 编译完的文件交给webpack打包
.babelrc
1 | { |
webpack.config.js
1 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); |
tsconfig.json
1 | tsc --init |
src/index.html
HTML模板
1 |
|
src/index.tsx
1 | import React from "react"; |
编译和查看
1 | webpack |
配置优化
Dev Server
Dev Server
来自我们前面安装的 webpack-dev-server
,正如你所见,前面用的先编译,再通过 serve
提供 http
访问的方式过于繁琐,而 webpack-dev-server
不仅提供了一键编译运行,还提供了热更新等功能。
打开 webpack.config.js
,添加
1 | { |
然后 webpack serve
,打开 devServer
同时,还可以向 package.json
里添加 script
1 | { |
yarn serve
& yarn build
Source Map
Source Map 能够让我们在浏览器更方便的调试,在 webpack.config.js
中添加
1 | { |
然后再 NODE_ENV=development yarn serve
, 打开页面,按F12, 选择 Source 中的 bundle.js, chrome会提示 Source Map Detect, 然后按Ctrl + P 即可选择自己需要的文件查看。
Bundle Analyzer
当我们使用 webpack serve
的时候, Webpack 会亲切的提示我们
1 | WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). |
但是,我们在上面不过是写了 <h1>Hello, World</h1>
, 为什么 Bundle 的大小会大了这么多呢,这个时候我们就需要使用 webpack-bundle-analyzer
来分析 Bundle 里究竟打包了些什么玩意。
1 | yarn add webpack-bundle-analyzer |
然后在 webpack.config.js
里加入
1 | ... |
接着 ANALYZER=1 webpack
,然后就弹出来 Bundle 大小分析页面
可以看到绝大部分都来自 react
的包,那么我们如何精简这个 Bundle 呢? 那就要用到
Externals
从 React 的教程上我们可以看到, React 可以直接从 <script></script>
里面引入,那么我们如果直接从 <script></script>
去引入 React, 是不是就可以精简 Bundle 的大小了呢?
这个时候我们就可以用到,它可以让某些包在打包的时候被排除出去,比如我们这里提到的 React
在 webpack.config.js
里面添加
1 | { |
这样就够了吗? 不, 如果你直接编译查看,浏览器会爆
1 | Uncaught TypeError: Cannot read property 'render' of undefined |
为什么会这样呢,如你所见,上面externals
是指,打包时不引入 react
和 react-dom
, 而是从 window.React
和 window.ReactDOM
中引入,从编译出来的代码上来看,就是
1 | module.exports = window.Reat; |
但是我们并没有引入 <script></script>
来定义这两个全局变量, 那如何引入呢?
最简单的方式当然修改src/index.html
,在其上添加,但是我觉得这样并不优雅,我想要在 webpack.config.js
里解决这些问题,那应该怎么做呢,不知道你是否还记得,我们前面提到的一个插件
html-webpack-plugin
这个插件就是用来根据模板修改html页面的,在使用它之前,我们先把 index.ejs copy 下来,并放到 src/index.ejs
中,ejs 是一个嵌入式 JavaScript 模板引擎,某种意义上来说和 jsx
有点像,这里我们主要用它来做模板。
修改 webpack.config.js
1 | { |
经过上面这些配置,现在我们再看看 bundle.js
我们成功的将 bundle.js
缩小到不到 1kb!
CSS
上面我们演示的是一个非常简单的程序,页面的代码只有一个 <h1>Hello, World</h1>
, 在实际应用中我们肯定还会用到css
,我们如何打包css
呢?
1 | yarn add -D style-loader css-loader |
- style-loader: 读取
css
并且插入到<head></head>
中 - css-loader: 对css中的
@import()
和url()
解析
需要注意的是,如果你需要在tsx
中import xxx.css
,你需要创建src/react-app-env.d.ts
,并写入
1 | declare module '*.css'; |
然后我们修改 webpack.config.js
1 | { |
index.css
1 | .container { |
index.tsx
1 | import React from "react"; |
编译后可以看到css生效了。
上面的配置是将css写进·bundle.js
,但是习惯上我们会将css
和js
分开来,如何将css
单独打包出来呢
1 | yarn add -D mini-css-extract-plugin |
然后修改webpack.config.js
1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); |
编译运行,可以看到css被打包进了了dist/main.css
Public Folder
我们的网页可能需要一些静态文件,比如favicon
,如何让webpack
在打包的时候复制静态文件到/dist
呢?
1 | yarn add -D copy-webpack-plugin |
然后修改webpack.config.js
1 | module.exports = { |
Hash
网页比起App有个好处,它没有版本更新的概念,可以让用户使用到最新的版本。
但是有个问题,大多数网站都会使用CDN,而CDN有缓存,我们的bundle.js
很可能会被CDN缓存,让用户无法获取最新的版本,手动清除缓存或者不缓存bundle.js
都不是最优解决方案,那怎样解决最好呢?
webpack.config.js
1 | module.exports = { |
关于这里的明明规则,请看 Template strings
Tree shaking
本来是想提一下的,但是发现 Webpack 5
的 Three shaking
似乎比 4 要好的多,就先🕊了吧。
Chunks Split
前面我们所配置的webpack
,都是将代码打包到一个文件里,但是实际网页中,单文件形式的包往往过大,非常影响体验,所以我们要对生成的文件进行分割,将不在首页加载的包分离出来,提高首屏加载时间。
1 | optimization: { |
Dynamic Import
1 | import React from 'react'; |
1 | import React, {Suspense} from 'react'; |
Server side render
SSR
我感觉可以单独拎出来再聊一次, 就下次再说吧。