Vite配置引出IIFE,CJS,AMD,UMD,ESM知识点

4,100 阅读6分钟

vite(rollup)的打包配置里有一个format的选项,可以指定最终打包产物的格式,默认情况下是ES module,但是不同的场景可能会有所区别,本文就想通过这个来讲讲前端模块化。

先贴上本文核心的一张图:

Vite构建个纯打包项目

这里主要是通过vite来演示打包产物,所以就事先建了个vite的项目:

github.com/Tinsson/vit…

第一步:

利用commander这个包知道当前要打包的输出格式,vite.config.ts如下:

第二步:

利用npm-run-all这个包来执行所有带build前缀的命令,每个命令进行指定格式

执行完下面的命令:

pnpm build

最终输出的目录如下:

每个格式对应自己的文件,这样就很方便后面的案例学习了。

最后贴上main.ts的代码,当然这块jym可以自行发挥:

IIFE

自执行函数

英文全称:immediately invoked function expressions

符合下面格式的代码其实都算是IIFE

打包输出

适用场景

浏览器中带有<script>标签包裹的代码块,简单来说就是普通的业务应用都可,没啥兼容问题

优点

  • 函数作用域避免了全局变量的污染

iife也算是比较早期的模块化方案了,只需对外暴露一个全局变量,最知名的就是jQuery

CJS

真正的模块化解决方案,最早从Node应用开始

全称:CommonJS

关键词:module,exports,global,require

特点

  • 模块加载require就是代码执行
  • 所有代码运行在模块作用域中,不污染全局
  • 模块可以多次加载,但是只在第一次加载运行,后面是缓存

适用场景

Nodejs,浏览器端需要用其他打包工具支持

如果在你的package.json下定义一个main的字段,值是文件的路径,那么require就会按这个优先加载,不然就是根目录下的index.js或index.node

这里再来讲个比较容易犯错的点:

export.xxx不能和module.exports一起使用(推荐只用module.exports,不用export.xxx

上述代码的showTips是无法引入的,因为在每个模块里,exports就是module.exports的引用,类似头部有这样的代码:

AMD

异步模块定义

全拼:Asynchronous module definition

amd可以理解是CommonJs在浏览器端的解决方案,cjs在服务端是同步加载依赖代码的,因为服务端都是本地磁盘读取文件,没有网络开销,速度很快,浏览器端要是也这么干,很有可能因为依赖在远程,因网络时间开销而导致出现“假死”,所以浏览器端采用异步加载模块的方式。

关键词:require,define

require的第一个参数是需要异步加载的依赖列表,第二个是模块异步加载后的执行代码,define第一个参数是模块的依赖模块,第二个是模块代码

打包输出

优点

  • 体积更小,代码按需加载
  • 不堵塞js线程运行

UMD

联合模块定义

全拼:Universal Module Definition

准确来说并不是一个独立的模块标准,而是集合了cjs,amd,iife等一体的打包模式,会自动判断当前可用环境。

打包输出

优点

  • 一套代码,多端使用,模块化兼容性好

一般会在webpack中成为备用模块

ESM

ES6标准中的模块规则,一统浏览器和服务端标准的解决方案

全拼:ES6 Module

先来讲个题外话:

在上周11-12号举行的ViteConf里,尤雨溪作为1号嘉宾介绍了vite的起源(下文第一张图),内容包含了前端模块化,vite的冷启动,以及vite生态解决了什么问题。其中我印象最深的还是开发环境no-bundler的创意起源(下文第二张图),使用浏览器原生ES Module能力,这个也可以说是vite比较让人惊艳的点。

关键字:import,export

适用场景

结合其他打包工具(webpack)使用或浏览器<script type="module">标签包裹

特点

  • 浏览器端会异步加载,延续AMD的优势,但利用了浏览器原生的解析能力,代码体积更小
  • 模块内自动采用严格模式
  • 模块中的this指向并不是window或global,而是undefined

上文有提到cjs一般会优先放package.json的main字段里,而这里的esm入口文件会优先放在module字段里

那么这里也简单讲下和cjs的三个差异:

  • cjs 模块输出的是一个值的拷贝(浅拷贝),esm输出的是值的引用。
  • cjs 模块是运行时加载,esm是编译时输出接口。
  • cjs同步加载模块,esm异步加载,有一个独立的模块依赖的解析阶段。

在cjs中,如果多个地方都用了同一个模块,对这个模块进行修改,其实不会有影响

比如这里调用了incCounter方法就不会影响到counter的实际值

但是在esm中,如果你用incCounter修改了counter值,其他业务模块调用counter的时候就会有变化

原因是esm中模块加载的时候还没完全去取值,实际业务代码执行到了才会去拿真正的值,import的时候只是建了个具体地址,留着后面调用时候方便。

打包产物

这里再来讲讲这个link[rel="modulepreload"]是啥?

这个其实和link[rel="preload"]有点类似,一个是css,字体资源等的预加载,一个是js模块的预加载,但是modulepreload的好处就是会在获取完资源后立即解析并编译这个模块,方便后续运行时使用,这种方式算是最大程度利用网络带宽来优化体验问题,一般来说模块多的话搭配http2.0的多路复用更香。

nodejs从v16开始为esm提供默认支持,13.2.0以上的早起版本需要用--experimental-modules来激活

结束

本文梳理了下vite(rollup)打包产物的各种格式以及适用场景,同时,上面的vite仓库大家也可以自行写代码打包进行研究,这样收益更深,最后希望帮助大家在日常开发中避开一些坑,节省开发时间。

创造不易,希望jym多多 点赞 + 关注 + 收藏 三连,持续更新中!!!

PS: 文中有任何错误,欢迎掘友指正

往期精彩📌

参考:

developer.mozilla.org/en-US/docs/…

developer.chrome.com/blog/module…

es6.ruanyifeng.com/#docs/modul…