深入浅出Vite入门&&为什么ESM是前端模块化的未来(1)


深入浅出 Vite

当下,在项目开发的过程中,前端工程师们越来越离不开构建工具了,可以说构建工具已经成为了前端工程项目的标配。

不过,如今的前端构建工具可谓乱花渐欲迷人眼,有远古时代的browserify、grunt,有传统的Webpack、Rollup、Parcel,也有现代的Esbuild、Vite 等等,不仅种类繁多,更新也很快。

到底哪个构建工具更好用、值得学。事实上,无论工具层面如何更新,它们解决的核心问题,即前端工程的痛点是不变的。因此,想要知道哪个工具更好用,就要看它解决前端工程痛点的效果。

那么,前端工程都有哪些痛点呢?

首先是前端的模块化需求。我们知道,业界的模块标准非常多,包括 ESM、CommonJS、AMD 和 CMD 等等。前端工程一方面需要落实这些模块规范,保证模块正常加载。另一方面需要兼容不同的模块规范,以适应不同的执行环境。

其次是兼容浏览器,编译高级语法。由于浏览器的实现规范所限,只要高级语言/语法(TypeScript、 JSX 等)想要在浏览器中正常运行,就必须被转化为浏览器可以理解的形式。这都需要工具链层面的支持,而且这个需求会一直存在。

再者是线上代码的质量问题。和开发阶段的考虑侧重点不同,生产环境中,我们不仅要考虑代码的安全性、兼容性问题,保证线上代码的正常运行,也需要考虑代码运行时的性能问题。由于浏览器的版本众多,代码兼容性和安全策略各不相同,线上代码的质量问题也将是前端工程中长期存在的一个痛点。

同时,开发效率也不容忽视。 我们知道,项目的冷启动/二次启动时间、热更新时间都可能严重影响开发效率,尤其是当项目越来越庞大的时候。因此,提高项目的启动速度和热更新速度也是前端工程的重要需求。

那么,前端构建工具是如何解决以上问题的呢?

  1. 模块化方面,提供模块加载方案,并兼容不同的模块规范。

  2. 语法转译方面,配合Sass、TSC、Babel 等前端工具链,完成高级语法的转译功能,同时对于静态资源也能进行处理,使之能作为一个模块正常加载。

  3. 产物质量方面,在生产环境中,配合 Terser等压缩工具进行代码压缩和混淆,通过 Tree Shaking 删除未使用的代码,提供对于低版本浏览器的语法降级处理等等。

  4. 开发效率方面,构建工具本身通过各种方式来进行性能优化,包括使用原生语言 Go/Rust、no-bundle等等思路,提高项目的启动性能和热更新的速度。

为什么 Vite 是当前最高效的构建工具?

  1. 首先是开发效率。传统构建工具普遍的缺点就是太慢了,与之相比,Vite 能将项目的启动性能提升一个量级,并且达到毫秒级的瞬间热更新效果

就拿 Webpack 来说,我在工作中发现,一般的项目使用 Webpack 之后,启动花个几分钟都是很常见的事情,热更新也经常需要等待十秒以上。这主要是因为:

  • 项目冷启动时必须递归打包整个项目的依赖树
  • JavaScript 语言本身的性能限制,导致构建性能遇到瓶颈,直接影响开发效率
    这样一来,代码改动后不能立马看到效果,自然开发体验也越来越差。而其中,最占用时间的就是代码打包和文件编译。

而 Vite 很好地解决了这些问题。一方面,Vite 在开发阶段基于浏览器原生 ESM 的支持实现了no-bundle服务,另一方面借助 Esbuild 超快的编译速度来做第三方库构建和 TS/JSX 语法编译,从而能够有效提高开发效率。

除了开发效率,在其他三个维度上, Vite 也表现不俗。

  1. 模块化方面,Vite 基于浏览器原生 ESM 的支持实现模块加载,并且无论是开发环境还是生产环境,都可以将其他格式的产物(如 CommonJS)转换为 ESM

  2. 语法转译方面,Vite 内置了对 TypeScript、JSX、Sass 等高级语法的支持,也能够加载各种各样的静态资源,如图片、Worker 等等。

  3. 产物质量方面,Vite 基于成熟的打包工具 Rollup 实现生产环境打包,同时可以配合Terser、Babel等工具链,可以极大程度保证构建产物的质量。

因此,如果你想要学习一个前端构建工具,Vite 将会是你当下一个最好的选择。它不仅解决了传统构建工具的开发效率问题,而且具备一个优秀构建工具的各项要素,还经历了社区大规模的验证与落地。

模块标准:为什么ESM是前端模块化的未来?

CommonJS 规范 -> AMD规范 -> ESM规范

ES6 Module 也被称作 ES Module(或 ESM), 是由 ECMAScript 官方提出的模块化规范,作为一个官方提出的规范,ES Module 已经得到了现代浏览器的内置支持。在现代浏览器中,如果在 HTML 中加入含有type=”module”属性的 script 标签,那么浏览器会按照 ES Module 规范来进行依赖加载和模块解析,这也是 Vite 在开发阶段实现 no-bundle 的原因,由于模块加载的任务交给了浏览器,即使不打包也可以顺利运行模块代码

下面是一个使用 ES Module 的简单例子:

1
2
3
4
5
6
7
8
9
10
// main.js
import { methodA } from "./module-a.js";
methodA();

//module-a.js
const methodA = () => {
console.log("a");
};

export { methodA };
1
<script type="module" src="./main.js"></script>

如果在 Node.js 环境中,你可以在package.json中声明type: “module”属性:

1
2
3
4
// package.json
{
"type": "module"
}

然后 Node.js 便会默认以 ES Module 规范去解析模块:

1
2
node main.js
// 打印 a

顺便说一句,在 Node.js 中,即使是在 CommonJS 模块里面,也可以通过 import 方法顺利加载 ES 模块,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
async function func() {
// 加载一个 ES 模块
// 文件名后缀需要是 mjs
const { a } = await import("./module-a.mjs");
console.log(a);
}

func();

module.exports = {
func,
};

总结

由于前端构建工具的改革与底层模块化规范的发展息息相关,从一开始我就带你从头梳理了前端模块化的演进史,从无模块化标准的时代开始谈起,跟你介绍了文件划分的模块化方案,并分析了这个方案潜在的几个问题。随后又介绍了命名空间IIFE两种方案,但这两种方式并没有解决模块自动加载的问题。由此展开对前端模块化规范的介绍,我主要给你分析了三个主流的模块化标准: CommonJS、AMD 以及 ES Module,针对每个规范从模块化代码标准、模块自动加载方案这两个维度给你进行了详细的拆解,最后得出 ES Module 即将成为主流前端模块化方案的结论。

参考

深入浅出 Vite
博客