1. 如何去做js模块化开发 => 模块化标准 + 加载器
1.1、我们说讨论的仅限于javascript代码的模块化,如果要涉及到所有文件的模块化请使用webpack。
1.2、那么js的模块化可以用一句话概括:模块化标准 + 加载器;本文主要介绍模块化标准。
2. 几种模块化标准对比:
CommonJS
1、以同步的模式加载模块:通常在Nodejs环境使用,不适合浏览器
1. 因为服务器读本地磁盘文件会比较快,所以nodejs的执行机制是在启动的时候加载所有模块,不需要在执行过程中才加载模块;
2. 如果在浏览器端代码执行时去同步require很多模块,也会影响页面执行效率。
2、一个文件就是一个模块,且每个模块都有单独的作用域;因为模块输出的是一个值的浅拷贝。
3、require(模块)加载的是一个对象,该对象是运行时生成;
4、导入导出
// 导出
module.exports = {
name: 'wangyi',
age: 18
}
// 导入
const { name, age } = require('./module.js')
ES Modules
1、以异步的模式加载模块:支持Nodejs环境使用,也适合浏览器(ES6以上才支持的规范,存在兼容性问题,最好配合 webpack 进行加载);
2、ES Modules的导入导出是固定用法,输出的是值的只读引用(原始值变了,取值跟着变);
3、ES Modules的导入导出不是对象而是对外接口,该接口在代码编译时就完成,执行效率更高
4、导入导出
// 注意:此处是固定用法,export 后面不是对象;export default 后面才是对象
// 导出1
export { name, age }
// 导入1
import { name, age } from './module.js'
// 导出2
export default { name, age }
// 导入2
import module from './module.js'
const { name, age } = module
AMD + require.js
1、以异步的方式加载模块,可以指定回调函数;
2、AMD规范配合require.js库作为加载器使用;
3、目前绝大多数第三方库都支持AMD
4、使用起来比较复杂
5、模块js文件请求频繁,因为每个模块都会创建一个script标签去请求文件
6、导入导出
define('module1', ['jquery', './module2'], function($, module2){
return {
// 可以在里面使用依赖的 $、module2 模块
}
})
CMD + sea.js
CMD规范配合sea.js库作为加载器使用,实现了模块化开发(淘宝);后来sea.js被require.js兼容了,便不再使用。
3. 模块化标准使用最佳实践
3.1、Nodejs环境 => CommonJS
3.1.1、因为服务器读本地磁盘文件会比较快,所以 nodejs 的执行机制是在启动的时候加载所有模块,不需要在执行过程中才加载模块;
3.1.2、如果在浏览器端代码执行时去同步 require 很多模块,也会影响页面执行效率。
3.1.3、module 对象是在 Nodejs环境定义的,配合 require 函数使用;如果说谁是 CommonJS 的加载器,那就是 Nodejs环境。
3.2、浏览器端 => ES Modules + Webpack
3.2.1、因为浏览器端会有很多的异步加载且当前的ES6开发比较简单,所以浏览器端适合使用ES Modules。
3.2.2、ES Modules 通常经过 webpack + babel 进行转换;将其转换成立即执行函数的方式,以此来模仿块级作用域;(webpack 也支持在源码中使用 CommonJS 和 ESM 互相导入导出,但一般不用)
3.2.3、因为 webpack 是在 nodejs 环境运行,所以其配置文件通常使用 CommonJS 规范。
3.2.4、因为 ES Modules 通常需要配合打包工具进行使用,所以 webpack 可以算得上它的加载器。
4. ES Module 基本使用知识点
4.1、ES Modules虽然是ES6才出现的规范,但是未来浏览器原生支持
4.2、ES Modules支持在script标签上直接定义使用:
4.2.1. ESM 自动采用严格模式,忽略 'use strict'
4.2.2. 每个 ES Module 都是运行在单独的私有作用域中
4.2.3. ESM 是通过 CORS 的方式请求外部 JS 模块的
4.2.4. ESM 的 script 标签会延迟执行脚本,和defer一样的效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ES Module - 模块的特性</title>
</head>
<body>
<!-- 通过给 script 添加 type = module 的属性,就可以以 ES Module 的标准执行其中的 JS 代码了 -->
<script type="module">
console.log('this is es module')
</script>
<!-- 1. ESM 自动采用严格模式,忽略 'use strict' -->
<script type="module">
console.log(this)
</script>
<!-- 2. 每个 ES Module 都是运行在单独的私有作用域中 -->
<script type="module">
var foo = 100
console.log(foo)
</script>
<script type="module">
console.log(foo)
</script>
<!-- 3. ESM 是通过 CORS 的方式请求外部 JS 模块的 -->
<!-- <script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script> -->
<!-- 4. ESM 的 script 标签会延迟执行脚本,和defer一样的效果 -->
<script type="module" src="demo.js"></script>
<p>需要显示的内容</p>
</body>
</html>
4.3、ES Modules 的 export和 import:
导出:
- export { } 后面不是一个对象,而是固定用法 { XXX },import { sss } from module也是固定用法,不是解构语法;export default { } 后面才可以跟一个对象、字符串等都行。
- export 导出的是一个引用关系,而且是只读的!不是深拷贝的对象。
var name = 'foo module'
function hello () {
console.log('hello')
}
class Person {}
export {
name as foo,
hello,
Person
}
导入(导入路径必须完整):
- 文件名称必须完整,不能省略.js、/index.js;
- 相对路径也必须完整,不能省略./;
- 可以绝对路径或者完整的url
import不支持动态导入,需要使用import().then()
import { foo, hello, Person } from './module.js'
console.log(name, hello, Person)
// 只加载模块不提取模块变量,可以简写:import './module.js'
import {} from './module.js'
import './module.js'
// 导入模块内的全部变量
import * as mod from './module.js'
console.log(mod)
// 动态导入
import('./module.js').then(function (module) {
console.log(module)
})
4.4、ES Modules 的 export default
- export default 后面可以跟对象、字符串等类型
- export 过后可以继续添加 export default
- import 的第一个位置默认对应export default 导出的值
// module.js
var name = 'jack'
var age = 18
export { name, age }
console.log('module action')
export default 'default export'
// import.js
// import { name, age, default as title } from './module.js'
// abc 为 export default 导出值的重命名,abc 后面的 { } 不是对象解构,而是固定用法
// import abc from './module.js'
import abc, { name, age } from './module.js'
console.log(name, age, abc)
4.5、ES Modules 的浏览器兼容
IE基本不兼容ES Modules
插件 browser-es-module-loader 用于兼容ES Modules(开发阶段可用,不建议生产环境使用)
1、在html中直接使用,参考:npm官方地址
// 该方法需要动态的去解析脚本执行ESM,性能差!只能在开发阶段使用。
// script加上nomodule 属性,避免在支持ESM的浏览器上执行两次
// babel-browser-build.js为babel的运行环境(浏览器端)
<script nomodule src="dist/babel-browser-build.js"></script>
// ES Modules把代码读出来交给babel转换
<script nomodule src="dist/browser-es-module-loader.js"></script>
<!-- script type=module loading -->
<script nomodule type="module" src="path/to/module.js"></script>
...
2、npm中使用,估计还是作为依赖资源动态解析ESM(不建议使用)
npm install browser-es-module-loader --save-dev
4.6、ES Modules 的NodeJS支持情况(8.5+版本)
NodeJS 8.5以上的版本支持ES Modules,但是还是实验版本;
// 第一,将文件的扩展名由 .js 改为 .mjs;(nodejs 12.10版本以上不需要修改文件名了)
// 第二,启动时需要额外添加 `--experimental-modules` 参数;
import { foo, bar } from './module.mjs'
console.log(foo, bar)
// 此时我们也可以通过 esm 加载内置模块了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')
// 也可以直接提取模块内的成员,内置模块兼容了 ESM 的提取成员方式
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')
// 对于第三方的 NPM 模块也可以通过 esm 加载
import _ from 'lodash'
_.camelCase('ES Module')
// 不支持,因为第三方模块都是导出默认成员
// import { camelCase } from 'lodash'
// console.log(camelCase('ES Module'))
4.7、ES Modules 和CommonJS相互使用(在NodeJS环境中)
- ESM 中可用导入CommonJS
- CommonJS中不能导入ESM
- CommonJS始终只会导出一个默认成员
- import不是解构导出对象,只能:import mod from './commonjs.js'
4.8、ES Modules 和CommonJS的差异(在NodeJS环境中)
// nodejs、CommonJS,文件名为:mjs
// 加载模块函数
console.log(require)
// 模块对象
console.log(module)
// 导出对象别名
console.log(exports)
// 当前文件的绝对路径
console.log(__filename)
// 当前文件所在目录
console.log(__dirname)
// nodejs、ES Modules,文件名为:mjs
// require, module, exports 自然是通过 import 和 export 代替
// __filename 和 __dirname 通过 import 对象的 meta 属性获取
// const currentUrl = import.meta.url
// console.log(currentUrl)
// 通过 url 模块的 fileURLToPath 方法转换为路径
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)
5. Webpack 基于所有资源去做模块化
完整的讲解参考后续文章:待续;
5.1、支持新特性语言版本的编译
5.2、针对javascript模块化打包
5.3、针对所有资源,例如样式、图片、字体等进行模块化
对于1、2两点,grunt、gulp等脚手架可以很好的解决,但是无法解决第3点。
6、Rollup:专门针对ES Modules进行打包的轻量化工具
- webpack 大而全 => 适合做大型应用程序
- rollup 小而美 => 适合做类库
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。