Nodejs Commonjs和ECMAScript(ESM)模块系统导出与导入

Nodejs Commonjs和ECMAScript(ESM)模块系统导出与导入

传统的Nodejs采用Commonjs模块系统,从ES6之后ECMAScript(ESM)模块系统逐渐增多,目前两种模块管理系统仍在交叉使用,都需要有所了解。

1. Commonjs模块导出导入方法

常见的Commonjs的导出模式有命名导出、函数导出类导出和类的实例导出几种。Commonjs导出的关键词是exportsmodule.exports

  • 命名导出模式示例如下:
//namedexport.js
//命名的导出模式是commonjs的标准模式
exports.info=(msg)=>{
    console.log(`info:${msg}`);
}
exports.verbose=(msg)=>{
    console.log(`verbose:${msg}`);
}
  • 函数导出模式示例如下:
//funcexport.js
//函数导出模式是commonjs的扩展
module.exports=(msg)=>{
    console.log(`info:${msg}`);
}

module.exports.verbose=(msg)=>{
    console.log(`verbose:${msg}`);
}
  • 类导出模式示例如下:
//classexport.js
//类导出模式
class Logger{
    constructor(name) {
        this.name=name;
    }
    log(msg){
        console.log(`[${this.name}]-${msg}`);
    }
    info(msg){
        this.log(`info:${msg}`);
    }
    verbose(msg){
        this.log(`verbose:${msg}`);
    }
}

module.exports=Logger;
  • 类的实例导出模式:
//instanceexport.js
//类的实例导出
class Logger{
    constructor(name){
        this.count=0;
        this.name=name;
    }
    info(msg){
        this.count++;
        console.log(`[${this.name}] ,count=${this.count}:${msg}`);
    }
}

module.exports=new Logger('DEFAULT');
  • 通过Commonjs导出模块的导入示例
//命名导出的模块导入方法示例
const logger1=require('./namedexport');
logger1.info('This is a named export example');
logger1.verbose('With a verbose message');

//函数导出的模块导入方法示例
const logger2=require('./funcexport');
logger2('This is a function export example');
logger2.verbose('With a verbose message');

//类导出的模块导入方法示例
const Logger3=require('./classexport');
let logger3=new Logger3('DB');
logger3.info('This is a class export example');
logger3.verbose('With a verbose message');

//类的实例导出的模块导入方法示例
const logger4=require('./instanceexport');
logger4.info('This is a class instance export example');
const logger5=require('./instanceexport');
logger5.info('This is a class instance export example');

//可以通过以下方式向导入的模块添加customMessage方法
require('./namedexport').customMessage=(msg)=>{
    console.log(`This is a patched msg :${msg}`);
}
logger1.customMessage('logger 1 is patched');

2. ECMAScript模块导入与导出的方法

ESMAScript方式仅在较新的Nodejs版本中受支持,此外,Nodejs默认采用Commonjs语法导入导出模块,直接使用ECMAScript模块会报错,在文件对应的package.json中添加"type":"module",字段,才能正常使用ECMAScript模块。而一旦确定了type字段后,就不能再使用Commonjs语法,需要参考下一节的内容进行调整。

  • ECMAScript模块导出
//esmexport.js
//采用ESM导出示例
//导出函数
export function defaultlog(msg){
    console.log(msg);
}
//导出定义的常数
export const DEFAULT_VALUE='info';

export const LEVELS={
    error:0,
    debug:1,
    warn:2,
    data:3,
    infor:4,
    verbose:5
}

//导出类
export class DefaultClass{
    constructor(name){
        this.name=name;
    }
    log(msg){
        console.log(`[${this.name}]:${msg}`);
    }
}
  • 默认导出
//defaultexport.js
//默认类导出
export default class Logger{
    constructor(name){
        this.name=name;
    }
    log(msg){
        console.log(`[${this.name}]:${msg}`);
    }
}
  • 模块导入示例
 // 导入整个模块
import * as loggerModule from './esmexport.js';
console.log(loggerModule);
const defaultClass=new loggerModule.DefaultClass('ESMA');
defaultClass.log(`test info,DEFAULTVALUE is ${loggerModule.DEFAULT_VALUE}`);

//导入模块的某个对象
import { defaultlog } from './esmexport.js';
defaultlog('esma export example');

//导入默认的模块,自定义Logger2
import Logger2 from './defaultexport.js';
const logger2 = new Logger2('default');
logger2.log('sample message');

//导入其他目录下的Commonjs模块(在同一个目录下会失败)
import { info as newinfo,verbose as newverbose } from '../namedexport.js';
newinfo('newinfo message');
newverbose('new verbose message');

//命名导出的模块导入方法示例
//将.js后缀改为.cjs,可以让node将该文件当作Commonjs解析,可以通过import进行导入
//类似地,如果要让Nodejs将一个文件当作ECMAScript,需要将.js后缀修改为.mjs
import logger1 from './namedexport2.cjs';
logger1.info('This is a named export example');
logger1.verbose('With a verbose message');

3. 模块异步导入导出

Nodejs支持模块的异步导入与导出,本例通过一个根据命令行参数选择模块导入来进行说明。
创建el.jsen.js等文件。

//el.js希腊语
export const HELLO='Γειά σου Κόσμε';
//en.js英语
export const HELLO='Hello world';
//es.js西班牙语
export const HELLO='Hola mundo';
//it.js意大利语
export const HELLO='Ciao mondo';
//pl.js波兰语
export const HELLO='Witaj świecie';

异步导入模块

//根据命令行参数选择解析的语言,例如
// node ./main.js el
const SUPPORTED_LANGUAGES=['el','en','es','it','pl'];
const selectedLanguage=process.argv[2];

if(!SUPPORTED_LANGUAGES.includes(selectedLanguage)){
    console.error(`The selected language ${selectedLanguage} is not supported yet`);
    process.exit(0);
}

const translationModule=`./${selectedLanguage}.js`;
import(translationModule)
.then((str)=>{ //str是导入的模块
    console.log(`Your selected language is ${selectedLanguage} \n ${str.HELLO}`)
})

4. Commonjs和ECMAScript部分区别

ECMAScript是在严格模式下运行的,他也不支持Commonjs提供的一些引用,例如require,exports,__filename,__dirname。如果要使用这些内容,需要进行替换。

import {fileURLToPath} from 'url';
import {dirname} from 'path';
const __filename=fileURLToPath(import.meta.url);
const __dirname=dirname(__filename);

5. TypeScript的情况

TypeScript是微软主导的基于JavaScript的语言,可用于更严谨完整的编写JavaScript程序。在TypeScript中主要使用两种导入导出的方式,分别继承自ECMAScript和兼容Commonjs。使用export关键词导出的模块,使用import导入,而使用export=导出的模块则需要通过import Module=require('module')。在TypeScript官方文档(或中文文档)可以看到关于导入导出的详细示例。

6.示例代码说明

COMMON,ECMA、async和TS文件夹分别包含签署的Commonjs,ECMA,异步和Typescript模式下导入导出的代码示意。各目录下main文件为模块导入与测试,其他文件为不同的导出方法示意。
示意代码下载:
链接:https://pan.baidu.com/s/1C9YM5JEEITLnxOux3WILhw?pwd=7oyw
提取码:7oyw

作者
魏智勇(John)
加入讨论

此站点使用 Akismet 来减少垃圾评论。了解我们如何处理您的评论数据

魏智勇(John)

站长,80后,创业者,擅长工业自动化与信息化技术,熟悉各种PLC,组态软件,熟悉计算机技术,熟悉LabVIEW、C,C#,JavaScript程序设计技术。