模块化开发时当前最重要的前端开发范式之一 模块化只是思想
模块化演变过程
Stage1 文件划分方式
- 污染全局作用域
- 命名冲突问题
- 无法管理模块依赖
- 早期模块化完全依靠约定
Stage2 命名空间方式
- 每个模块只暴露一个全局对象,所有模块都挂载到这个对象上
- 减少了命名冲突的可能
- 但是没有私有空间,模块成员可以在外部被访问或修改
- 模块之间的依赖关系没有得到解决
Stage3 IIFE 立即执行函数
- 使用立即执行函数包裹代码,要输出的遍历挂载到一个全局对象上
- 变量拥有了私有空间,只有通过闭包修改和访问变量
- 参数作为依赖声明去使用,使得每个模块的依赖关系变得明显
模块化规范
1. CommonJS规范
- 一个文件就是一个模块
- 每个模块都有单独的作用域
- 通过module.exports导出成员
- 通过require函数载入模块
- CommonJS是以同步模式加载模块
2. AMD(Asynchronous Module Definition)异步模块规范
-
模块加载器:Require.js
// 定义一个模块 define('module1', ['jquery', './module2'], function ($, module2) { return { start: function () { $('body').animate({ margin: '200px' }) module2() } } })
// 载入一个模块 require(['./module1'], function (module1) { module1.start() })
-
目前绝大多数第三方库都支持AMD规范
-
AMD使用起来相对复杂
-
模块JS文件请求频繁
3. 淘宝推出的Sea.js + CMD(Common Module Definition)通用模块规范
// CMD 规范 (类似 CommonJS 规范)
define(function (require, exports, module) {
// 通过 require 引入依赖
var $ = require('jquery')
// 通过 exports 或者 module.exports 对外暴露成员
module.exports = function () {
console.log('module 2~')
$('body').append('<p>module2</p>')
}
})
4. ES Module
// ./modulejs
const foo = 'es modules'
export { foo }
// ./app.js
import { foo } from './module.js'
console.log(foo) // => es modules
- 自动采用严格模式,忽略’use strict’
- 每个ESM模块都是单独的私有作用域
- ESM是通过CORS去请求外部JS模块的
- ESM的script标签会延迟脚本执行
ESModules
import用法
// 不能省略.js拓展名 CommonJS可以
// CommonJS载入目录就可以载入目录下面的index.js ESM不可以必须完整路径
import { name } from "./module.js";
console.log(name);
// 后期打包操作可以省略拓展名和默认文件是index.js这种操作
import { lowercase } from "./util/index.js";
console.log(lowercase);
// 必须以点开头 和CommonJS相同
// 字母开头 ESM人为加载第三方模块
// 斜线开头 根目录往后数
import { name } from "/04-import/module.js";
console.log(name);
// 完整的URL
import { name } from "http://localhost:52330/04-import/module.js";
// 可以直接引用CDN上的模块文件
console.log(name);
// ---------------------------------------------------------
import {} from "http://localhost:52330/04-import/module.js";
// 简写
import "http://localhost:52330/04-import/module.js";
// 不会重复加载只能加载一次
// ---------------------------------------------------------
// 导出的非常的多
import * as mod from "./module.js";
console.log(mod);
// 于是实现了导入成员
// mod.age = 19;
// 依旧是不可以更改 nice
// console.log(mod);
// ---------------------------------------------------------
var modulePath = "./module.js";
import { age } from modulePath
// Uncaught SyntaxError: Unexpected identifier
console.log(age);
if (true) {
import { age } from "./module.js";
}
Uncaught SyntaxError: Unexpected token '{'
// 如何动态导入? import()函数 返回一个promise
import("./module.js").then((module) => {
console.log(module.name);
});
import { age, default as chris } from "./module.js";
console.log(age, chris);
// 简写
import chris, { age } from "./module.js";
console.log(age, chris);
导出的同时引入(中间商)
// import { button } from "./button.js";
// import { avatar } from "./avatar.js";
// export { button, avatar };
// 注意 如果是default需要重命名 否则会当成index自己的默认导出
export { button } from "./button.js";
export { avatar } from "./avatar.js";
Polyfill
browser-es-module-loader
-
将浏览器中不识别的ESM去交给babel转换
-
需要import的文件通过ajax请求
-
- 请求回来的代码再次通过babel转换
-
支持的话polyfill会重复执行两次
-
script nomodule
-
开发可以 生产慎用 动态解析脚本 效率低
-
应该编译出来直接可以再浏览器中工作
<script src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
<script src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>polyfill</title>
</head>
<body>
<!-- IE不兼容 -->
<!-- 编译工具 编译成ES5就行了 -->
<!-- polyfill直接支持浏览器中的大部分ESM特性 -->
<script
nomodule
src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"
></script>
<script
nomodule
src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"
></script>
<!-- Promise未定义 -->
<!-- 引入promise ployfill -->
<script
nomodule
src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"
></script>
<script type="module">
import { name } from "./module.js";
console.log(name);
</script>
<script></script>
</body>
</html>
导入导出
导出的形式:
- 变量
- 函数
- 类
- default
- 集中导出才需要花括号
导入导出的注意事项
-
导入的是一个常量 => 无法修改变量
-
- 导出的是个对象就可以修改
-
花括号是规定,不是一个对象,所以不能直接解构
-
- 但是导出的是对象的情况也可以使用,还可以解构
-
导出的模块的变量更改,导入的模块也会更改
-
- 但是如果导出的是一个对象就更改不了了
ESM in Nodejs
nodejs 原生支持ESM,直接用。历史上有段时间不支持,需要加 –experimental-modules
于CJS交互
- ESM中可以载入CJS成员
- exports.是module.exports的别名
- esm载入cjs模块提取需要一个第三方的变量做个过渡+
- node中不允许commonjs载入es module
和CJS区别
- ESM中没有CJS中的全局模块成员了
// 导入导出的方式改变
require/module/exports => import/export
// 获取文件名的方式改变
__filename => import.meta.url + fileURLToPath = __filename
import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url);
- dirname => dirname(__filename )
import { fileURLToPath } from "url"; import { dirname } from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename);
- 把当前模块包装成一个函数的形参(实现私有模块作用域)
其他
拓展名设置cjs就不需要设置mjs,设置的是mjs,就不需要cjs了。
.babelrc => ''presets":["@babel/preset-env"]
通过babel来支持ESM!