ES Module
本博主會持續(xù)更新各種前端的技術(shù),如果各位道友喜歡,可以關(guān)注、收藏、點贊下本博主的文章。
ES Module 基本特性
- ESM 自動采用嚴(yán)格模式,忽略 'use strict'
- 每個 ES Module 都是運行在單獨的私有作用域中
- ESM 是通過 CORS 的方式請求外部 JS 模塊的
- ESM 的 script 標(biāo)簽會延遲執(zhí)行腳本(瀏覽器頁面渲染后執(zhí)行)
export
在創(chuàng)建 JavaScript 模塊時,export 語句用于從模塊中導(dǎo)出實時綁定的函數(shù)、對象或原始值,以便其他程序可以通過 import 語句使用它們。被導(dǎo)出的綁定值依然可以在本地進(jìn)行修改。在使用 import 進(jìn)行導(dǎo)入時,這些綁定值只能被導(dǎo)入模塊所讀取,但在 export 導(dǎo)出模塊中對這些綁定值進(jìn)行修改,所修改的值也會實時地更新。
無論您是否聲明,導(dǎo)出的模塊都處于嚴(yán)格模式。 export 語句不能用在嵌入式腳本中。
// 導(dǎo)出單個特性
export let name1, name2, …, nameN; // also var, const
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName(){...}
export class ClassName {...}
// 導(dǎo)出列表
export { name1, name2, …, nameN };
// 重命名導(dǎo)出
export { variable1 as name1, variable2 as name2, …, nameN };
// 解構(gòu)導(dǎo)出并重命名
export const { name1, name2: bar } = o;
// 默認(rèn)導(dǎo)出
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
// 合并 modules
export * from …; // does not set the default export
export * as name1 from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;
import
靜態(tài)的 import 語句用于導(dǎo)入由另一個模塊導(dǎo)出的綁定。無論是否聲明了 strict mode ,導(dǎo)入的模塊都運行在嚴(yán)格模式下。在瀏覽器中,import 語句只能在聲明了 type="module" 的 script 的標(biāo)簽中使用。
此外,還有一個類似函數(shù)的動態(tài) import(),它不需要依賴 type="module" 的 script 標(biāo)簽。
在 script 標(biāo)簽中使用 nomodule 屬性,可以確保向后兼容。
在您希望按照一定的條件或者按需加載模塊的時候,動態(tài) import() 是非常有用的。而靜態(tài)型的 import 是初始化加載依賴項的最優(yōu)選擇,使用靜態(tài) import 更容易從代碼靜態(tài)分析工具和 tree shaking 中受益。
// 導(dǎo)入整個模塊的內(nèi)容
import * as myModule from '/modules/my-module.js';
// 導(dǎo)入單個接口
import {myExport} from '/modules/my-module.js';
// 導(dǎo)入多個接口
import {foo, bar} from '/modules/my-module.js';
// 導(dǎo)入帶有別名的接口
import {reallyReallyLongModuleExportName as shortName} from '/modules/my-module.js';
// 導(dǎo)入時重命名多個接口
import {
reallyReallyLongModuleMemberName as shortName,
anotherLongModuleName as short
} from '/modules/my-module.js';
// 僅為副作用而導(dǎo)入一個模塊
// 整個模塊僅為副作用(中性詞,無貶義含義)而導(dǎo)入,而不導(dǎo)入模塊中的任何內(nèi)容(接口)。 這將運行模塊中的全局代碼, 但實際上不導(dǎo)入任何值。
import '/modules/my-module.js';
// 導(dǎo)入默認(rèn)值
import myDefault from '/modules/my-module.js';
import myDefault, * as myModule from '/modules/my-module.js';
// myModule used as a namespace
import myDefault, {foo, bar} from '/modules/my-module.js';
// specific, named imports
// 動態(tài)import
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
let module = await import('/modules/my-module.js');
node 環(huán)境下
es module 使用
index.mjs
// 第一,將文件的擴展名由 .js 改為 .mjs;
// 第二,啟動時需要額外添加 `--experimental-modules` 參數(shù);
import { foo, bar } from './module.mjs';
console.log(foo, bar);
// 此時我們也可以通過 esm 加載內(nèi)置模塊了
import fs from 'fs';
fs.writeFileSync('./foo.txt', 'es module working');
// 也可以直接提取模塊內(nèi)的成員,內(nèi)置模塊兼容了 ESM 的提取成員方式
import { writeFileSync } from 'fs';
writeFileSync('./bar.txt', 'es module working');
// 對于第三方的 NPM 模塊也可以通過 esm 加載
import _ from 'lodash';
_.camelCase('ES Module');
// 不支持,因為第三方模塊都是導(dǎo)出默認(rèn)成員
// import { camelCase } from 'lodash'
// console.log(camelCase('ES Module'))
與 CommonJS 交互
- ES Module 中可以導(dǎo)入 CommonJS 模塊
- CommonJS 中不能導(dǎo)入 ES Module 模塊
- CommonJS 始終只會導(dǎo)出一個默認(rèn)成員
- 注意 import 不是解構(gòu)導(dǎo)出對象
commonjs.js
// CommonJS 模塊始終只會導(dǎo)出一個默認(rèn)成員
// module.exports = {
// foo: 'commonjs exports value'
// }
// exports.foo = 'commonjs exports value'
// 不能在 CommonJS 模塊中通過 require 載入 ES Module
// const mod = require('./es-module.mjs')
// console.log(mod)
es-module.mjs
// ES Module 中可以導(dǎo)入 CommonJS 模塊
// import mod from './commonjs.js'
// console.log(mod)
// 不能直接提取成員,注意 import 不是解構(gòu)導(dǎo)出對象
// import { foo } from './commonjs.js'
// console.log(foo)
// export const foo = 'es module export value'
與 CommonJS 的差異
esm.mjs
// ESM 中沒有模塊全局成員了
// // 加載模塊函數(shù)
// console.log(require)
// // 模塊對象
// console.log(module)
// // 導(dǎo)出對象別名
// console.log(exports)
// // 當(dāng)前文件的絕對路徑
// console.log(__filename)
// // 當(dāng)前文件所在目錄
// console.log(__dirname)
// -------------
// require, module, exports 自然是通過 import 和 export 代替
// __filename 和 __dirname 通過 import 對象的 meta 屬性獲取
// const currentUrl = import.meta.url
// console.log(currentUrl)
// 通過 url 模塊的 fileURLToPath 方法轉(zhuǎn)換為路徑
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(__filename);
console.log(__dirname);
Node v12 之后的版本,可以通過package.json中添加type字段為module,將默認(rèn)模塊系統(tǒng)修改為ES Module,此時就不需要修改文件擴展名為.mjs了
如果需要在type=module的情況下繼續(xù)使用CommonJS,需要將文件擴展名修改為.cjs
對于早期的 Node.js 版本,可以使用 Babel 實現(xiàn) ES Module 的兼容
// 配置:第一種方式
{
"plugins": [
"@babel/plugin-transform-modules-commonjs"
]
}
// 配置:第二種方式(合集)
{
"presets":["@babel/preset-env"]
}