likes
comments
collection
share

前端的模块化发展与打包工具

作者站长头像
站长
· 阅读数 14

序言

前端模块化是前端工程化的基础之一,这种开发理念和技术实现,不仅让代码更易于维护,还能高效地管理依赖关系,极大提高了开发效率。然而,前端模块化的发展历程是怎样的呢?又有哪些关键的模块化工具和技术呢?

本文将带您深入探讨:

  1. 前端模块化的起源和演变;
  2. 不同时期的模块化方案及其特点,例如AMD、CMD、CommonJS和ESM等;
  3. 模块化打包方案演变,介绍、对比

一. 为什么要有模块化

  1. 可维护性

模块化分割了代码,让每个模块都独立承担一项功能。模块之间的依赖减少,有助于独立更新和改进,提高了代码的可维护性。

  1. 命名空间 「避免全局污染」

JavaScript的全局变量容易导致命名冲突。使用模块化封装变量,可以减少全局污染的风险,更好地管理命名空间。

  1. 复用代码

以往我们可能通过拷贝代码来实现复用,通过模块引用的方式,来避免重复的代码库。我们可以在更新了模块之后,让引用了该模块的所有项目都同步更新,还能指定版本号,避免 API 变更带来的麻烦。

二. 模块化发展阶段

模块化的发展经历了从全局函数到命名空间,再到匿名函数和不同标准的演变过程。

全局function => 命名空间 => 匿名函数 => commonjs => amd、cmd => es6模块

2.1 commonjs

commonjs规范采用同步加载,适用于服务端,但在浏览器端可能会阻塞页面渲染。

commonjs规范采用同步加载,适用于服务端,但在浏览器端可能会阻塞页面渲染。

优缺点

commonjs同步加载的特性使得它在服务端适用、浏览器不适用,原因:

  1. 服务端加载文件一般可以从本地读取。
  2. 浏览器端要走网络请求,会比较耗时。并且同步的特性使得它将阻塞页面渲染。

写法

通过require引入模块,module.exports导出模块

2.2 Amd (Asynchronous Module Definition)异步加载,尽早执行

异步模块定义,所谓异步是指模块和模块的依赖可以被异步加载,他们的加载不会影响它后面语句的运行。有效避免了采用同步加载方式中导致的页面假死现象。AMD代表:RequireJS。

Amd优缺点

AMD 运行时核心思想是「Early Executing」,也就是提前执行依赖 AMD 的这个特性有好有坏:

优点:

  1. 尽早执行依赖可以尽早发现错误。
  2. 尽早执行依赖通常可以带来更好的用户体验,也容易产生浪费。
  3. 在浏览器环境中异步加载模块,不阻塞后续流程
  4. 并行加载多个模块;

缺点

  1. 开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;
  2. 不符合通用的模块化思维方式,是一种妥协的实现。

Amd写法

通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。

2.3 Cmd(Common Module Definition) 异步加载,使用执行

CMD是SeaJS在推广过程中生产的对模块定义的规范,在Web浏览器端的模块加载器中,SeaJS与RequireJS并称,SeaJS作者为阿里的玉伯。 CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。

CMD的优缺点

优点:依赖就近,延迟执行 可以很容易在 Node.js 中运行; 缺点:依赖 SPM 打包,模块的加载逻辑偏重;

Amd和Cmd的区别

  1. AMD 推崇依赖前置、提前执行
  2. CMD 推崇依赖就近、延迟执行

2.4 es6 模块

ES6模块的设计思想,是尽量的静态化,编译时就能确定模块的依赖关系,以及输入和输出的变量。

所以说ES6是编译时加载,不同于CommonJS的运行时加载(实际加载的是一整个对象),ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式。

es6模块与Commonjs的差别

  1. CommonJS是动态导入, 模块是运行时加载,ES6 是静态导入,模块是编译时输出接口。
  2. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的动态映射。在commonJs中如果模块被加载过,就不会重新去加载模块,又因为输出了是值的拷贝,所以模块中值的变化不会影响引入的地方。
  3. 循环依赖的情况下,CommonJs因为获得值的副本,在循环依赖情况模块未执行完成的话,可能获取不到正确的值。而es6的特性更好的支持循环依赖的场景。

三. 模块化打包工具介绍

3.1 模块化方案 & 打包工具演变背景

随着前端开发的复杂度逐渐提高,模块化成为了必然的趋势。早期的模块化方案如AMD、CMD,需要通过运行时库(如require.js和sea.js)来实现。而随着CommonJS和ESM的流行,模块化方案不再依赖运行时库,而是通过打包工具将它们转成浏览器支持的函数形式。

那么为什么会有这样的转变呢?我们可以从以下几个方面来探讨:

3.1.1 AMD、CMD阶段

在AMD、CMD这些方案下,因为浏览器不直接支持模块化,所以需要通过加载运行时库来实现模块化。

3.1.2 CommonJS和ESM阶段

随着JavaScript语言的发展和ES6的推广,模块化逐渐成为了语言标准的一部分,ESM即是其中的一种。

CommonJS

CommonJS是服务器端模块的规范,服务器端读取文件较快,所以它的模块加载是同步的。在浏览器端,这样的加载方式会造成阻塞。因此,需要打包工具将模块文件提前打包(预编译和合并、异步延迟加载支持、优化和压缩),再由浏览器加载。

ESM

ESM(ES Modules)是ECMAScript 6中的一项功能,支持通过importexport命令来导入和导出模块。现代浏览器大多支持ESM,但对于一些还不支持的环境,或者更复杂的模块依赖管理,也需要借助打包工具。

3.3 打包工具的作用

3.3.1 转译和兼容

打包工具如Webpack、Rollup等可以将CommonJS、ESM等格式的模块转译成浏览器可识别的代码,实现跨浏览器的兼容。

3.3.2 优化和管理

打包工具还可以优化代码、拆分代码、管理依赖等,使得前端开发更加高效和灵活。

以上的分析解释了为什么CommonJS和ESM阶段不再依赖运行时库,而是通过打包工具进行处理。这个阶段的模块化工具不仅提供了方便的模块管理能力,还推动了前端工程化的进展,成为现代前端开发不可或缺的部分。

3.4 具体的模块化打包工具

基于模块依赖分析的打包工具比如 webpack 是现在的主流,通过先进的机制提供了出色的性能优化。

3.4.1 Webpack

Webpack的核心理念是将前端项目的所有资源视为模块,并通过依赖关系进行打包。这样做使得开发者能够构建复杂的大型应用程序,同时保持结构的可维护性。

特点

  1. 模块化:任何资源都可以是模块,无论是JavaScript、CSS、图片等。
  2. 插件系统:通过插件可以自定义Webpack的行为,提供极大的灵活性。
  3. 代码优化:支持代码分割(按需加载)、懒加载、Tree Shaking(体积优化)等,帮助开发者优化性能。
  4. Webpack5还支持持久化缓存、模块联邦(有助于实现微前端)。
  5. 社区支持:丰富的文档和社区支持,使Webpack成为企业级应用的可靠选择。

3.4.2 Vite

Vite的诞生主要是为了解决Webpack在开发环境下的速度和效率问题。随着前端项目越来越复杂,Webpack的启动和重新构建时间开始变得难以承受。Vite通过利用现代浏览器的ES模块特性,实现了几乎即时的冷启动和高效的HMR。

特点

  1. 极快的冷启动:Vite在开发模式下不编译ES模块,使得启动速度极快。
  2. 即时HMR:只更新改变的文件,大大提高了更新速度。
  3. 按需编译:只在需要时处理文件,减轻了重建的负担。
  4. 简单的配置:相比Webpack的复杂配置,Vite提供了更简洁的配置选项。

Vite快的主要原因

主要特点是提供极快的冷启动时间和即时的热模块更新(HMR),主要利用以下两个特性:

  1. ES Modules (ESM): 因为目前浏览器对ES已经支持的比较好,在开发模式下,Vite不编译ES模块。这意味着浏览器只会在需要时请求文件,从而实现了按需加载和编译。这大大减少了启动和重新加载的时间,因为不需要一次性处理所有文件。对比之下,传统的打包工具在开发环境下需要一次性编译整个应用。

  2. 模块热替换(HMR): Vite的HMR实现更具效率,因为它只更新改变的文件,而不是整个模块链。它能快速将更改推送到浏览器,而无需完全刷新页面。这让开发者能实时看到他们的改动效果,从而提升开发效率。

Vite HMR VS Weboack HMR

Webpack的HMR: 当一个文件被修改时,Webpack会重新构建整个模块,然后将新的模块发送到浏览器。在浏览器端,新的模块会替换旧的模块,而不需要刷新整个页面。这个过程通常需要一些时间,因为Webpack需要从入口开始解析依赖图,找到所有依赖该文件的模块,并重新构建。

Vite的HMR: 相比之下,Vite的HMR更为高效。当一个文件被修改时,Vite会立即知道哪些模块导入了这个文件,并且只更新这些模块,而不是重新构建整个模块链。这大大提高了更新的速度。此外,Vite还支持组件级的HMR,在Vue和React项目中,当单个组件被修改时,只有该组件会被更新,而不会影响其他组件。

这种差异主要是由于Webpack的设计初衷是作为一个通用的模块打包器,而Vite则是专为开发服务器和HMR设计的,其依赖预构建和ESM方式能让HMR更为迅速和精准。

每个阶段的前端打包工具都在不断地解决前端开发的问题,提升开发效率,也反映了前端开发技术的进步和演变。

3.4.3 其他打包工具对比

以下是关于Webpack、Parcel、Rollup和Vite的对比:

打包工具优点缺点适用情况
Webpack1. 模块化处理,支持各种资源 2. 功能强大,插件丰富 3. 社区成熟,支持良好1. 配置复杂 2. 学习成本高 3. 构建速度相对较慢适合大型、复杂的、需要模块化的前端项目
Parcel1. 零配置,易于上手 2. 自动处理资源和依赖 3. 构建速度快1. 相比 Webpack,定制性稍弱 2. 社区相对较小适合中小型项目,或者希望快速原型开发的场景
Rollup1. 简洁的API 2. 专注于ES6特性,适合库的打包 3. Tree-shaking能力强1. 功能不如Webpack丰富 2. 插件相对较少适用于打包Javascript库和其他可以利用ES6模块特性的项目
Vite1. 快速冷启动,按需编译 2. 模块热更新 3. 配置简单,内置对TS、JSX等的支持1. 社区相对较新,稳定性可能较低适用于中大型现代化前端项目,追求开发效率和体验的场景

总结

以下是本文中提取的一些关键和有用的知识点,这些点不仅能加深对前端工程化的理解,还涵盖了许多面试中可能会考察的重点基础知识:

  1. 模块化的演变

    • AMD、CMD阶段:通过运行时库实现模块化,如require.js和sea.js。
    • CommonJS阶段:服务器端模块规范,同步加载,适用于服务器端。
    • ESM阶段:ES6中的模块化标准,现代浏览器支持,通过importexport实现。
  2. 打包工具的作用

    • 转译和兼容:将不同格式的模块转译成浏览器可识别的代码。
    • 优化和管理:代码优化、拆分、依赖管理等,提高开发效率。
  3. 具体的模块化打包工具

    • Webpack:功能强大,支持模块化处理,插件丰富,适合大型项目。
    • Vite:快速冷启动,按需编译,简单配置,适合追求开发效率的项目。
    • Parcel、Rollup:Parcel易于上手,Rollup适合库的打包。
    • Vite与Webpack的HMR对比Webpack:重新构建整个模块,时间较长。Vite:只更新改变的文件,速度更快。
  4. 面试常考点

    • 模块化的理解和区别:AMD、CMD、CommonJS和ESM的区别和应用场景。
    • 打包工具的选择和使用:如何选择合适的打包工具,Webpack和Vite的特点和使用场景。
    • 打包工具代码优化技巧:如代码分割、懒加载、Tree Shaking等。