本周精读的是 webpack4.0 一些变化，以及 typescript 该怎么做才能最大化利用 webpack4.0 的所有特性。

## 1 引言

前段时间尝试了 parcel 作为构建工具，就像农村人享受了都市的生活，就再也回不去了一样，发现无配置真是前端构建工具的大趋势，用起来非常方便快捷，再也不想碰 webpack 的配置了。

可是实践一段实践后，发现 parcel 还是不够成熟，主要体现在暂时不支持一些 rollup 优秀特性：Tree shaking、Scope Hoist，大型项目打包速度反而比 webpack3.0 慢。由于笔者完全零配置，当发现构建速度急速下降时，自然把矛头指向了 parcel :p.

就在前几周，webpack4.0 发布了，也拥抱了零配置，我想，是时候再回到 webpack 了。可是，文档好少，怎么迁移呢？

就在这几天，webpack 文档发布了 4.0 版本，虽然遗留了大量旧文档，不过也足够参考了。

## 2 精读

笔者尝试了 [webpack node api](https://webpack.js.org/api/node/)，尝试了很久，发现被坑了。文档里只字未提 `mode` 模式，4.0 环境下 `compiler` 总是提示没有 `mode` 的 warning。

读了一些文档，发现 webpack4.0 大力度宣传的是 cli 方式启动，里面提到了最重要的 `webpack --mode` 模式，可见 webpack4.0 更推崇的是让开发者使用高度封装的 cli，而不是使用 node 方式开发（那 node 文档也应该更新呀）。笔者又看了一圈，发现 `webpack-dev-server` 的 webpack 版本升到了 4.0，`ts-loader` 也升级到了 4.0，可能生态已经全部准备好了。

### 使用 webpack cli、webpack-dev-server cli

安装 `webpack^4.1.1` `webpack-cli^2.0.10` `webpack-dev-server^3.1.0`，以及创建一个公共配置文件 `webpack.config.ts`:

```typescript
export default {
  entry,

  output,

  module: {
    rules
  },

  resolve,

  resolveLoader,

  devServer: {
    https: true,
    open: true,
    overlay: {
      warnings: true,
      errors: true
    },
    port
  }
}
```

记得用 `tsc` 转换为 `webpack.config.js` 作为 cli 入口。

开发模式下使用 `webpack-dev-server`:

```bash
webpack-dev-server --mode development --progress --hot --hotOnly --config ./webpack.config.js
```

生产环境 build 使用 `webpack`:

```bash
webpack --mode production --progress --config ./webpack.config.js
```

开发/生产模式，都以 `webpack.config.ts` 作为配置，其中 `devServer` 项仅在开发模式下，对 `webpack-dev-server` 生效。

一旦开启了 `--mode production`，会自动开启代码压缩、scope hoist 等插件，以及自动传递环境变量给 lib 包，所以已经不需要 `plugins` 这个配置项了。同理，开启了 `--mode development` 会自动开启 sourceMap 等开发插件，我们只要关心更简单的配置，这就是 4.0 零配置的重要改变。

> `mode=production`, `mode=development` 具体内置了哪些配置，可以参考这篇文章：[webpack 4 终于知道「约定优于配置」了](https://zhuanlan.zhihu.com/p/32886546)。恰恰有意思的是，webpack4 这么做，就是不想我们浪费时间了解这些机制，社区应该会慢慢习惯零配置的开发方式。

当然，虽然说零配置，但配置文件基本三板斧还是非常有必要配置：`entry` `output` `module`。

我们可能还要给配置文件传一些参数，比如定制多种开发模式的入口，通过 `--env` 传递：

```bash
webpack-dev-server --mode development --env.entry ./src/main.tsx
```

`webpack.config.ts` 接收：

```typescript
const entry = yargs.argv.env.entry
```

### 使用 typescript + webpack

简单来说，只需要 `ts-loader` 就够了。在 `webpack.config.ts` 中增加新的 `rules`:

```typescript
{
  module: {
    rules: [{
      test: /\.(tsx|ts)?$/,
      use: ["ts-loader"]
    }]
  }
}
```

注意 `tsconfig.json` 中模块解析策略使用： `"module": "esnext"`。

原因是 webpack 需要 es6 import 语句，才能进行 tree shaking 或者动态 import 优化，我们不再让 `ts-loader` 包办模块设置，换句话说，我们采用白名单方式看待 `typescript` 以及 `babel`，只让他做我们需要的工作，剩下的丢给 webpack 处理，可以获得最大程度性能优化。

如果仅使用 webpack + typescript，建议将 ts 编译输出模式调整为 `es3`，因为 webpack 自带的压缩工具对 es6 语法还存在报错，而且也不会做兼容处理。

### 使用 typescript + babel + webpcak

注意处理顺序，ts -> babel -> webpack。

因为多出了 babel，我们将 ts 编译兼容模式关闭：`"target": "esnext"`，模块也不要解析：`"module": "esnext"`，`ts-loader` 仅仅将 typescript 代码转换成 js，其他一切优化都不要做，将 esnext 原生代码直接传给 babel 处理。

babel 这一层的职责是对代码进行兼容处理，不要压缩，也不要把 import 转成 require。笔者发现 babel 直接解析 import 代码会无法处理，因此需要 `stage-2` preset:

```typescript
{
  presets: [
    ["env", {
      modules: false,
    }],
    ["stage-2"]
  ],
  plugins: [
    ["transform-runtime"]
  ],
  comments: true
}
```

从上面配置可以看到，babel 这层对 esnext 的代码进行了浏览器兼容处理（env 插件），直接透传 `import`（stage-2 插件让 babel 识别 esModule），以及支持 async await（transform-runtime） 插件。

> 本来想用 env 替代 transform-runtime 的功能，笔者暂时没有查询到可行方式，欢迎读者补充。

另外要允许 babel 保留注释（`comments: true`），因为 webpack import 支持自定义 chunkName 是通过注释的方式：

```typescript
import(/* webpackChunkName: "src" */ "./src")
```

配合 `react-loadable` 使用更佳：

```typescript
Loadable({
  loader: () => import(/* webpackChunkName: "src" */ "./src"),
  loading: (): any => null
})
```

因为 `react-loadable` 让页面按 chunk 方式打包，而 webpack 又会自动 picke shared chunks，配合给每个 page chunks 通过 `webpackChunkName` 定义名称，webpack 可以给每个共享 chunks 更加可读的名字，比如：`vendor~src,about,login`，你就知道这个是 `src` `about` `login` 三个页面间公共模块。

可能已经有人看出瑕疵了，给每个文件增加 `webpackChunkName` 注释既麻烦又不优雅，而且只要有一个开发者没有加这个注释，上面说的可读 chunks 可能就缺少了某个模块名。

这就要笔者之前一篇精读来看了：[精读《Rekit Studio》](https://zhuanlan.zhihu.com/p/33853805)，**项目可以通过约定的方式定义页面，入口文件通过 cli 自动生成**，不就既减少业务代量，又统一加上了 `webpackChunkName` 嘛？

这里小小安利下集成了这个思路的项目脚手架 [pri](https://github.com/ascoders/pri)，使用了 ts + babel + webpack4.0，上述的小优化也是内置的功能之一。

### webpack4 带来的是适配成本的大幅优化

社区似乎有部分声音在抱怨，webpack 又发新版本，我们又要适配一轮。其实 webpack 这么做恰恰没有带来适配成本，出问题的在于我们对 webpack 的使用方式与理念。

如果我们开始就将 webpack 当作一体化打包方案，开发调试使用 `webpack-dev-server cli`，开发环境编译使用 `webpack cli`，那么 webpack4 其实只是补充了开发环境这个最重要的配置变量而已。类比 `parcel` 的两个命令：

```bash
parcel index.html
parcel build index.html
```

对应：

```bash
webpack-dev-server --mode development
webpack --mode production
```

所以 webpack4 几乎是有史以来最方便使用与迁移的版本，前提是使用思维得正确，舍得将编译环节全权交给两个官方的 Cli。

## 3 总结

只要合理的使用 typescript、babel，让各自只发挥最小功能，将原生的模块化代码抛给 webpack，再配合 `--mode production` 配置，webpack 会自动开启一切可能的插件优化你的项目，而我们再不需要阅读形形色色的 webpack 插件了，更令人激动的是，随着 webpack 版本升级，优化会不断升级，而我们只要留着 `--mode` 参数，不需要改一行配置。

总结起来，就是不用关心优化相关的配置，我们只需要配置业务相关的 `entry` `output` `module`，这就是 webpack4.0.

我以前为了实现第一次编译完后立即打开浏览器的功能，写了一共 200 行的 `customCompiler` 以及 `format-webpack-message`，而且利用 koa 开了一个 server，利用 await 和 flags 等待第一次编译完的时机，并利用 `opn` 库打开网页。

其实用 cli 只需要 `webpack-dev-server --open`。

随着新的一波零配置浪潮，真的不应该在编译配置上花那么多时间了。

## 4 番外 - prefetch

读者自习阅读就会发现，这不是一篇单纯 webpack4 升级指南，仔细阅读可以发现文中蕴藏的一些工程优化思路。文章末尾再给一波福利，分析一下 prefetch 优化是什么，以及怎么做。

现代浏览器支持了以下两种语法：

```html
<link rel="preload" />
<link rel="prefetch" />
```

兼容性自己查 [Caniuse](https://caniuse.com/)，笔者重点在功能上。`preload` 收集当前用到的资源，`prefetch` 收集未来用到的资源。

页面本质上也是未来一种资源，如果认为用户会点击另一个页面（如果对产品没自信，或者 pv 过低可以忽略这个功能），就可以用 `prefetch` 让浏览器在空闲时间下载下一个页面的 chunk 文件。

前端包体积优化效率一般和用户体验是违背的，既然下一个页面在另一个 chunk 中，用户点击后必然会产生 loading。可是如果结合了 `prefetch`，鱼和熊掌就兼得了（正常用户不可能页面还没加载完就立刻点按钮跳页，所以唯一的缺点几乎不会对正常用户产生影响）。

api 有了，那么最大的问题就是，当前页面怎么知道要加载哪些 chunks？一般两种做法：

**全量模式** 使用比如 [preload-webpack-plugin](https://github.com/GoogleChromeLabs/preload-webpack-plugin) 插件，将所有生成的 chunk 都作为 `prefetch` 资源，在所有页面中。几乎所有规模的项目都不会产生过多的 chunks，所以这个方案理论上不够优雅，但能解决实际问题。

**按需模式**，是理论和实践双重优雅的方案，是否要这么做取决于您是否有代码洁癖。方法是提供一个定制的 `Link` 标签，根据 URL 地址按需生成 `prefetch` 标签。这种方案最大缺陷是，如果用户不按照约定使用内置的 `Link`，`prefetch` 规则将会无效。

## 5 更多讨论

> 讨论地址是：[精读《webpack4.0 升级指南》 · Issue #66 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/66)

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，每周五发布。**
