forked from ascoders/weekly
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
213 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
本周精读的是 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),每周都有新的主题,每周五发布。** |