早期的web应用非常简单,可以直接加载js的形式去实现。随着需求的越来越多,应用越做越大,需要模块化去管理项目中的js、css、图片等资源。这里有很多大家熟悉的模块化标准, CJS、AMD、CMD、UMD 等等。模块化提供了我们更好的方式来组织和维护函数以及变量。而在 npm 生态开发的背景下,CJS 模块是开发过程中接触最多也是无法避免的。但由于浏览器并不能直接执行基于 CJS 打包的模块,因此类似 webpack 等打包工具便应运而生。随着webpack 大有一统构建工具的趋势下,JavaScript 官方的标准化模块系统ESM完成了。本文主要介绍下模块化标准间差异、基本加载原理。
仔细想想,用 JavaScript 编码就是管理变量。这一切都是关于为变量赋值,或为变量添加数字,或将两个变量组合在一起并将它们放入另一个变量中。
如果代码中仅有少量的变量,那么组织起来其实是很简单的。一旦有很多的变量,我们会通过函数作用域去组织变量。因为函数作用域的缘故,一个函数无法访问另一个函数中定义的变量。
如果只维护少量变量非常简单。但是如果有很多的变量,我们就需要用一种方法来帮助做到这一点,叫做作用域。由于作用域在 JavaScript 中的工作方式,函数不能访问在其他函数中定义的变量。
这种方式是很有效的。在写一个函数的时候,只需要考虑当前函数,而不必担心其它函数可能会改变当前函数的变量。如果想要函数之间共享变量要怎么办呢?一种通用的做法是全局作用域。
在 jQuery 时代这种提升做法相当普遍。在我们加载任何 jQuery 插件之前,我们必须确保 jQuery 已经存在于全局作用域。
所有的 <script>
必须以正确的顺序排列,必须保证被依赖的变量先加载。如果排列错了,那么在运行过程中,应用将会抛出错误,并且停止继续运行。
代码之间的依赖是不透明的。这使得代码维护变得困难。代码变得充满不确定性。任何函数都可能依赖全局作用域中的任何变量。
其次,由于变量存在于全局作用域,所以任何代码都可以改变它。
模块化为你提供了一种更好的方式来组织变量和函数。可以把相关的变量和函数放在一起组成一个模块。这种实现形式可以把函数和变量放在模块作用域中。模块作用域还提供一种暴露变量给其他模块使用的方式。模块可以明确地指定哪些变量、类或函数对外暴露。
对外暴露的过程称为导出。一旦导出,其他模块就可以明确地声称它们依赖这些导出的变量、类或者函数。
因为这是一种明确的关系,所以你可以很简单地辨别哪些代码能移除,哪些不能移除。
拥有了在模块之间导出和导入变量的能力之后,你就可以把代码分割成更小的、可以独立运行地代码块了。基于这些代码块,你就可以像搭乐高积木一样,创建所有不同类型的应用。比较流程的规范有CommonJS,AMD,CMD,ES,UMD等
CJS
是 CommonJS
的缩写。只适用于node端:
const _ = require('lodash');
module.exports = function doSomething(n) {
}
AMD
代表异步模块定义。在浏览器端有效:
define(['dep1', 'dep2'], function (dep1, dep2) {
return function () {};
});
UMD
代表通用模块定义(Universal Module Definition
):
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.returnExports = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
}));
ESM是ES6提出的标准模块系统,ECMAScript modules。JS**自己的模块体系**
<script type="module">
import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.module.js';
class App extends Component {
state = {
count: 0
}
add = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return html`
<div class="app">
<div>count: ${this.state.count}</div>
<button onClick=${this.add}>Add Todo</button>
</div>
`;
}
}
render(html`<${App} page="All" />`, document.body);
</script>
思考:上述代码和在webpack中开发有啥区别?
回顾下Webpack执行流程
本地**模块化解析**(通过webpack或者babel,将import解析成cjs)
将各个库打包成一个js boundle
开启服务,托管资源
浏览器获取资源
执行代码
浏览器端ESM执行流程
3. 构建
a . 遍历依赖树,先解析文件,然后找出依赖,最后又定位并加载这些依赖,如此往复。(下载所有的js)
b . 模块映射
当加载器要从一个 URL 加载文件时,它会把 URL 记录到模块映射中,并把它标记为正在下载的文件。然后它会发出这个文件请求并继续开始获取下一个文件。
c . 解析模块
所有的模块都按照严格模式来解析的。不同文件类型按照不同的解析方式称。在浏览器中,通过 type="module"
属性告诉浏览器这个文件需要被解析为一个模块。不过在 Node 中,我们并不使用 HTML 标签,所以也没办法通过 type
属性来辨别。社区提出一种解决办法是使用 < .mjs
拓展名。
4 . 运行 采用深度优先的后序遍历方式,顺着关系图到达最底端没有任何依赖的模块,然后设置它们的导出。模块映射会以 URL 为索引来缓存模块,以确保每个模块只有一个模块记录。这保证了每个模块只会运行一次。
ES语法基本确定
http2普及
新浏览器普及
开发与发布代码一致
启动快
全新加载模式
目前只有5%的浏览器不兼容es相关规范。
部分浏览器的兼容性
历史包袱悠久
生态不完善
当我们在项目中使用需要考虑以下几个问题点
let a = 1;
new Promise()
() => {}
...
a . 加载包内部es目录 b . cjstoesm
a . https://unpkg.com/ b . https://www.skypack.dev/
type="module"
实现
如果浏览器不支持,他只识别type="text/javascript"
不识别 type="module"
,故不下载js;如果支持,则会下载js
如果浏览器不支持,则会忽略nomodule,下载js;如果支持,则不会下载js
<script type="module" src="app.js"></script>
<script nomodule src="app-bundle.js"></script>
<script src="system.js"></script>
<script type="systemjs-importmap">
{
"imports": {
"lodash": "https://unpkg.com/lodash@4.17.10/lodash.js"
}
}
</script>
<script type="systemjs-module" src="/js/main.js"></script>
4. jsx支持
<script type="module">
import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.module.js';
class App extends Component {
render() {
return html`
<div class="app">
</div>
`;
}
}
render(html`<${App} page="All" />`, document.body);
</script>
<APP {...Props}/>
=>
React.createElement(App, {...props})
托管node_modules
支持图片、css等资源
JSX 和 Typescript 编译
HMR
...
https://cn.vitejs.dev/guide/
目前snowpack的作者后续可能不再维护了,所以推荐大家使用vite
2018 年 5 月 Firefox 60 发布后,所有的主流浏览器就都默认支持 ESM 了。Node 也正在添加 ESM 支持,为此还成立了工作小组来专门研究 CJS 和 ESM 之间的兼容性问题。所以,在未来你可以直接在 <script>
标签中使用 type="module"
,并且在代码中使用 import
和 export
。同时,更多的模块功能也正在研究中。比如动态导入提案已经处于 Stage 3 状态;import.meta
也被提出以便 Node.js 对 ESM 的支持;模块定位提案 也致力于解决浏览器和 Node.js 之间的差异。
相信在不久的未来,跟模块一起玩耍将会变成一件更加愉快的事!
node v10以上版本全部支持ESM https://kentcdodds.com/blog/super-simple-start-to-es-modules-in-node-js
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8