1 引言
基于 webpack 构建的大型项目开发速度已经非常慢了,前端开发者已经逐渐习惯忍受超过 100 秒的启动时间,超过 30 秒的 reload 时间。即便被寄予厚望的 webpack5 内置了缓存机制也不会得到质的提升。但放到十年前,等待时间是几百毫秒。
好在浏览器支持了 ESM import 模块化加载方案,终于原生支持了文件模块化,这使得本地构建不再需要处理模块化关系并聚合文件,这甚至可以将构建时间从 30 秒降低到 300 毫秒。
当然基于 ESM import 的构建框架不止 snowpack 一个,还有比如基于 vue 的 vite,因为浏览器支持模块化是一个标准,而不与任何框架绑定,未来任何构建工具都会基于此特性开发,这意味着在未来的五年,前端构建一定会回到十年前的速度,这个趋势是明显、确定的。
ESM import 带来的最直观的改变有下面三点:
node_modules
完全不需要参与到构建过程,仅这一点就足以让构建效率提升至少 10 倍。- 模块化交给浏览器管理,修改任何组件都只需做单文件编译,时间复杂度永远是 O(1),reload 时间与项目大小无关。
- 浏览器完全模块化加载文件,不存在资源重复加载问题,这种原生的 TreeShaking 还可以做到访问文件时再编译,做到单文件级别的按需构建。
所以可以说 ESM import 模式下的开发效率,能做到与十年前修改 HTML 单文件的零构建效率几乎相当。
2 简介 & 精读
snowpack 核心特征:
- 开发模式启动仅需 50ms 甚至更少。
- 热更新速度非常快。
- 构建时可以结合任何 bundler,比如 webpack。
- 内置支持 TS、JSX、CSS Modules 等。
- 支持自定义构建脚本以及三方插件。
安装
代码语言:javascript复制yarn add --dev snowpack
通过 snowpack.config.json
文件配置,并能自动读取 babel.config.json
生效 babel 插件。
开发调试
调试 snowpack dev
,编译 snowpack build
,会自动以 src/index
作为应用入口进行编译。
snowpack dev
命令几乎是零耗时的,因为文件仅会在被浏览器访问时进行按需编译,因此构建速度是理想的最快速。
当浏览器访问文件时,snowpack 会将文件做如下转换:
代码语言:javascript复制// Your Code:
import * as React from "react";
import * as ReactDOM from "react-dom";
// Build Output:
import * as React from "/web_modules/react.js";
import * as ReactDOM from "/web_modules/react-dom.js";
目的就是生成一个相对路径,并启动本地服务让浏览器可以访问到这些被 import 的文件。其中 web_modules
是 snowpack 对 node_modules
构建的结果。
在这之前也会对 Typescript 文件做 tsc 编译,或者 babel 编译。
编译
编译命令 snowpack build
默认方式与 snowpack dev
相同:
也可以指定以 webpack 作为构建器:
代码语言:javascript复制// snowpack.config.json
{
// Optimize your production builds with Webpack
"plugins": [
[
"@snowpack/plugin-webpack",
{
/* ... */
}
]
]
}
除了默认构建方式之外,还支持自定义文件处理,通过 snowpack.config.json
配置 scripts
指定:
{
"extends": "@snowpack/app-scripts-react",
"scripts": {
"build:scss": "sass $FILE"
},
"plugins": []
}
比如上述语法支持了对 scss
文件编译的拓展。
"build:*": "..."
对文件后缀进行编译,比如:"build:js,jsx": "babel --filename $FILE"
指定了对 js,jsx
后缀的文件进行 babel 构建。
"run:*": "..."
仅执行一次,可以用来做 lint,也可以用来配合批量文件处理命令,比如 tsc
: "run:tsc": "tsc"
"mount:*": "mount DIR [--to /PATH]"
将文件部署到某个 URL 地址,比如 "mount:public": "mount public --to /"
意味着将 public
文件夹下的文件部署到 /
这个 URL 地址。
还有 proxy
等 API 就不一一列举了,详细可以见 官方文档。
我们可以从构建命令体会到 snowpack 的理念,将源码以流式方式编译后,直接部署到本地 server 提供的 URL 地址,浏览器通过一个 main 入口以 ESM import 的方式加载这些文件。
所以所有加载与构建逻辑都是按需的,snowpack 要做的只是将本地文件逐个构建好并启动本地服务给浏览器调用。
前端开发离不开 node_modules
,snowpack 通过 snowpack install
的方式支持了这一点。
snowpack install
这个命令已经被 snowpack dev
内置了,所以 snowpack install
仅用来理解原理。
以下是 snowpack install
执行的结果:
✔ snowpack install complete. [0.88s]
⦿ web_modules/ size gzip brotli
├─ react-dom.js 128.93 KB 39.89 KB 34.93 KB
└─ react.js 0.54 KB 0.32 KB 0.28 KB
⦿ web_modules/common/ (Shared)
└─ index-8961bd84.js 10.83 KB 3.96 KB 3.51 KB
可以看到,snowpack
遍历项目源码对 node_modules
的访问,并对 node_modules
进行了 Web 版 install
,可以认为 npm install
是将 npm 包安装到了本地,而 snowpack install
是将 node_modules
安装到了 Web API,所以这个命令只需构建一次,node_modules
就变成了可以按需被浏览器加载的静态资源文件。
同时源码中对 npm 包的引用都会转换为对 web_modules
这个静态资源地址的引用:
import * as ReactDOM from "react-dom";
// 转换
import * as React from "/web_modules/react.js";
但同时可以看到 snowpack 对前端生态的高要求,如果某些包通过 webpack 别名设置了一些 magic 映射,就无法通过文件路径直接映射,所以 snowpack 生态成熟需要一段时间,但模块标准化一定是趋势,不规范的包在未来几年内会逐步被淘汰。
2020 年适合使用 snowpack 吗
答案是还不适合用在生产环境。
当然用在开发环境还是可以的,但需要承担三个风险:
- 开发与生产环境构建结果不一致的风险。
- 项目生态存在非 ESM import 模块化包而导致大量适配成本的风险。
- 项目存在大量 webpack 插件的 magic 魔法,导致标准化后丢失定制打包逻辑的风险。
但可以看到,这些风险的原因都是非标准化造成的。我们站在 2020 年看以前浏览器非标准化 API 适配与兼容工作,可能会觉得不可思议,为什么要与那些陈旧非标准化的语法做斗争;相应的,2030 年看 2020 年的今天可能也觉得不可思议,为什么很多项目存在大量 magic 自定义构建逻辑,明明标准化构建逻辑已经完全够用了 :P。
所以我们要看到未来的趋势,也要理解当下存在的问题,不要在生态尚未成熟的时候贸然使用,但也要跟进前端规范化的步伐,在合适的时机跟上节奏,毕竟 bundleless 模式带来的开发效率提升是非常明显的。
3 总结
前端发展到 2020 年这个时间点,代码规范已经基本稳定,工程化要做的事情已经从新增功能逐渐转移到研发提效上了,因此提升开发时热更新速度、构建速度是当下前端工程化的重中之重。
snowpack 代表的 bundleless 方案肯定是光明的未来,带来的构建提效非常明显,人力充足的前端团队与不需要考虑浏览器兼容性的敏捷小团队都已经开始实践 bundleless 方案了。
但对于业务需要兼容各浏览器的大团队来说,目前 bundleless 方案仅可用于开发环境,生产环境还是需要 webpack 打包,因此 webpack 生态还可以继续繁荣几年,直到大的前端团队也抛弃它为止。
如果看未来十年,可能前端工程化构建脚本都不需要了,浏览器可以直接运行源码。在这一点上,以 snowpack 为代表的 bundleless 模式着实跨越了一大步。