3

CommonJS模块 和 ECMAScript模块

 1 year ago
source link: https://www.fly63.com/article/detial/12096
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
CommonJS模块(下面简称 cjs) 是为Node.js打包JavaScript代码的原始方法。使用 require 和 exports(module.exports的简写) 语句定义模块。 参见 nodejs cjs
ECMAScript模块(下面简称 esm)是ecma262标准下封装的JavaScript代码重用的官方标准格式。使用 import 和export 语句定义模块。参见node esm

-cjs只有在node.js环境使用。
-esm在node.js和浏览器环境都可以使用

1、commonjs

在node.js中,每个文件都被视为一个单独的模块。模块的局部变量是私有的,只有exports出去的变量,才能被外界访问。

默认情况下,node.js会将以下情形视为 cjs模块:

  • 扩展名为.cjs的文件;
  • 扩展名为.js的文件,且离自己最近的package.json文件包含一个顶级字段“type”,其值为“commonjs”;
  • 扩展名为.js的文件,且离自己最近的package.json文件不包含一个顶级字段“type”(建议明确指定 type值,而不是不定义);
  • 扩展名不为.mjs, .cjs, .json, .node, .js的文件,且离自己最近的package.json文件包含一个顶级字段“type”,其值为“module”,但是这些文件通过require引入。

调用require()时,始终使用cjs模块加载器,
require采用同步方式加载,可以在代码的任意位置使用。
由于require()的同步特性,无法使用它加载ECMAScript模块文件。尝试这样做将抛出ERR_REQUIRE_ESM错误。请改用import()。

要获取调用require()时将加载的确切文件名,请使用require.resolve()函数

1)、对于一个文件,导出的是对象的引用。如果内部的属性变化了,外部也会变化。
2)、对于再次引入同一个文件,如果文件名一模一样,则从缓存里面取
3)、还有种方式,要使模块多次执行代码,请导出函数并调用该函数。

导出一个原始值

//child.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};

//parent.js
let child = require('./child.js')
console.log(child.counter);  // 3
child.incCounter();
console.log(child.counter); // 3没变

导出一个对象

//child.js
var obj = {
    counter:3
};
function incCounter() {
    obj.counter++;
}
module.exports = {
  obj: obj,
  incCounter: incCounter,
};
//parent.js
let child = require('./child.js')
console.log(child.obj.counter);  // 3
test.incCounter();
console.log(child.obj.counter); // 4 变了

导出一个对象,多个文件,会从缓存里获取

//child.js
var obj = {
    counter:3
};
function incCounter() {
    obj.counter++;
}
module.exports = {
  obj: obj,
  incCounter: incCounter,
};
//parent.js
let child = require('./child.js')
let child1 = require('./child.js')
console.log(child.obj.counter);  // 3
test.incCounter();
console.log(child.obj.counter); // 4 变了
console.log(child1.obj.counter); // 4 变了

如果require加载的是个文件夹,那么node.js默认会尝试加载 index.js 或者 index.node

import() 既支持cjs,又支持 esm

import('./lib.js').then((res)=>{
    //res.default  
}) 

2、es module

调用 import()时,始终使用esm模块加载器,

默认情况下,node.js会将以下情形视为 esm 模块:
扩展名为 .mjs 的文件;
扩展名为.js的文件,且离自己最近的package.json文件包含一个顶级字段“type”,其值为“module”;

1)、对于一个文件,导出的是对象的引用。如果内部的属性变化了,外部也会变化。
2)、对于再次引入同一个文件,如果文件名一模一样,则从缓存里面取。如果文件名加了query,则重新加载文件,不会缓存里取
3)、还有种方式,要使模块多次执行代码,请导出函数并调用该函数。

单次导入,导入基本类型

//child.mjs
var counter = 3;
function incCounter() {
  counter++;
}
export default {
  counter: counter,
  incCounter: incCounter,
};

//index.mjs
import child from "./lib.mjs";
console.log(child.counter); // 3
child.incCounter();
console.log(child.counter); // 3没变

多次导入,导入引用类型

//child.mjs
var obj = {
  counter: 3,
};
function incCounter() {
  obj.counter++;
}
export default {
  obj: obj,
  incCounter: incCounter,
};

//index.mjs
import child from "./lib.mjs";
import child1 from "./lib.mjs";  //从缓存里面取
console.log(child.obj.counter); // 3
child.incCounter();
console.log(child.obj.counter); // 4变了

console.log(child1.obj.counter); // 4变了,拿的缓存里的

多次导入,避免缓存

//child.mjs
var obj = {
  counter: 3,
};
function incCounter() {
  obj.counter++;
}
export default {
  obj: obj,
  incCounter: incCounter,
};

//index.mjs
import child from "./lib.mjs?time=1";
import child1 from "./lib.mjs?time=2";  //增加query避免缓存
console.log(child.obj.counter); // 3
child.incCounter();
console.log(child.obj.counter); // 4变了
console.log(child1.obj.counter); // 3没变,重新加载的文件

import.meta.url

当前文件文件模块的 url地址。

import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url));

import.meta.resolve 实验功能

await import.meta.resolve('./dep', import.meta.url);
const dependencyAsset = await import.meta.resolve('component-lib/asset.css');

在esm中使用 require

import { createRequire } from 'node:module';  
const require = createRequire(import.meta.url);

// sibling-module.js is a CommonJS module.
const siblingModule = require('./sibling-module');

注意: 从node.js v16以后,使用node内置核心模块都采用node:xx, 比如 node:fs,它和fs的区别是,不能被require缓存,而fs能被缓存,可以查看 v16 changeLog-issue

const path = require('node:path');  //不被缓存
const path = require('path');    //可以被缓存

3、esm模块与cjs模块之间的差异

  • esm使用import/export, 而cjs使用require/exports
  • cjs可以使用 __filename 或者 __dirname, 而esm不行,esm只能使用 import.meta.url
  • esm不支持本机模块。 ith module.createRequire() or process.dlopen.
  • cjs使用 require.resolve, 而esm使用new URL(), import.meta.resolve
  • cjs可以通过环境变量指定的路径,去查找本机上对应位置的模块,而esm不行,
  • cjs 是在运行时确定,而esm则在静态编译时确定。
  • cjs可以同步执行,esm不行

共同点:模块导出的都是引用。

以上文章来自官方文档

链接: https://www.fly63.com/article/detial/12096


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK