0
点赞
收藏
分享

微信扫一扫

Node.js 中使用 ES6 中的 import / export 的方法大全

GG_lyf 2022-10-18 阅读 121


Node.js 中使用 ES6 中的 import / export 的方法大全

三种方法。
先上图。

源代码文件目录(​​https://github.com/AK-47-D/nodejs_es6_tutorials​​):




Node.js 中使用 ES6 中的  import / export 的方法大全_自定义


image.png


方法1 放弃使用 ES6, 使用 Node中的 module 模块语法

util_for_node.js

function log(o) {
console.log(o);
}

module.exports = log;

es6_const_let_node_demo.js

// 在 Node 中使用模块的正确姿势:
const log = require("./lib/util_for_node");
// ES5
var a = 1;
a = a + 1;
log(a); // 2

// ES6
const b = 1;
// b = b + 1; // error : TypeError: Assignment to constant variable.
log(b);

// ES6
let c = 1;
c = c + 1;
log(c);

运行测试:

$ node es6_const_let_node_demo.js 
2
1
2

方法2 使用万能变换器:babel

util_for_babel.js

function log(o) {
console.log(o);
}

export {log}

es6_const_let_babel_demo.js

import {log} from "./lib/util_for_babel";

/**

node: module.exports和require
es6:export和import

nodejs仍未支持import/export语法,需要安装必要的npm包–babel,使用babel将js文件编译成node.js支持的commonjs格式的代码。
因为一些历史原因,虽然 Node.js 已经实现了 99% 的 ES6 新特性,不过截止 2018.8.10,How To Enable ES6 Imports in Node.JS 仍然是老大难问题

借助 Babel
1.下载必须的包

npm install babel-register babel-preset-env --D

命令行执行:

babel-node es6_const_let_babel_demo.js

*
* @type {number}
*/


// ES5
var a = 1;
a = a + 1;
log(a); // 2

// ES6
const b = 1;
// b = b + 1; // error : TypeError: Assignment to constant variable.
log(b);

// ES6
let c = 1;
c = c + 1;
log(c);

上面的代码,直接 node 命令行运行是要报错的:

$ node es6_const_let_babel_demo.js
/Users/jack/WebstormProject/node-tutorials/hello-node/es6_const_let_babel_demo.js:1
(function (exports, require, module, __filename, __dirname) { import {log} from "./lib/util_for_babel";
^

SyntaxError: Unexpected token {
at new Script (vm.js:79:7)
at createScript (vm.js:251:10)
at Object.runInThisContext (vm.js:303:10)
at Module._compile (internal/modules/cjs/loader.js:656:28)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
at Module.load (internal/modules/cjs/loader.js:598:32)
at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
at Function.Module._load (internal/modules/cjs/loader.js:529:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
at startup (internal/bootstrap/node.js:285:19)

是的,这个时候,我们需要再加上一层 Babel 的映射逻辑。下面就是 Babel 出场了。

1.安装依赖

npm install babel-register babel-preset-env --D

package.json

{
"name": "hell-node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel-preset-env": "^1.7.0",
"babel-register": "^6.26.0"
}
}

2.写处理启动脚本

es6_const_let_babel_demo_start.js

require('babel-register') ({
presets: [ 'env' ]
})

module.exports = require('./es6_const_let_babel_demo.js')

OK,多费了这么多事,终于可以跑了:

$ node es6_const_let_babel_demo_start.js
2
1
2

跑的时候,你会明显发现,真 TMD 慢了许多!!! 慢就慢在 Babel 这层处理映射逻辑上。

方法3 使用 Node 中的实验特性:node --experimental-modules

你看,为了特意区分这是module JavaScript,文件后缀名必须改成 .mjs

util_for_node_exp.mjs

/**
* 注意到这里的源码文件的后缀 .mjs
* @param o
*/

function log(o) {
console.log(o);
}

export {log};

es6_const_let_node_exp_demo.mjs

import {log} from "./lib/util_for_node_exp";

// ES5
var a = 1;
a = a + 1;
log(a); // 2

// ES6
const b = 1;
// b = b + 1; // error : TypeError: Assignment to constant variable.
log(b);

// ES6
let c = 1;
c = c + 1;
log(c);

/**
* 源码后缀 .mjs
*/

命令行执行:

 $ node  --experimental-modules es6_const_let_node_exp_demo.mjs

输出:

 (node:1402) ExperimentalWarning: The ESM module loader is experimental.
2
1
2

OK,上面就是 Node.js 中使用 ES6 中的 import / export 方法。

Node.js 中使用 import / export

因为一些历史原因,虽然 Node.js 已经实现了 99% 的 ES6 新特性,不过截止 2018.8.10,How To Enable ES6 Imports in Node.JS 仍然是老大难问题

下面我来介绍两种方法可以让我们在 Node.js 中使用 import/export 。

借助 Babel
1.下载必须的包

npm install babel-register babel-preset-env --D

  1. 修改你的 server.js
    下面是一个 server.js 的例子:

const Koa = require('koa')
const app = new Koa()

app.listen(3000, console.log("application is start at port 3000"))

用 import 替换 require

import Koa from 'koa'
const app = new Koa()

app.listen(3000, console.log("application is start at port 3000"))

如果你现在用 node server.js 跑这个文件,你会收到像这样的错误提示:

/Users/zyf/myStudy/demo/chatroom/server/app.js:1
(function (exports, require, module, __filename, __dirname) { import Koa from 'koa'
^^^

SyntaxError: Unexpected identifier

下面让我们用 babel 来解决这个问题

3.新增一个 start.js 文件
这个文件将成为我们的入口文件,里面是一些 babel 的代码

require('babel-register') ({
presets: [ 'env' ]
})

module.exports = require('./server.js')

注意,接下来不是 node server.js,而是用 node start.js 来启动这个文件

来自 Node.js 官方的力量
Node 9提供了一个尚处于 Experimental 阶段的模块,让我们可以抛弃 babel 等一类工具的束缚,直接在 Node 环境下使用 import/export。

官方手册:ECMAScript Modules
一个不错的教程:Node 9下import/export的丝般顺滑使用
有兴趣的可以去了解一下,嫌字多的可以继续往下看看我总结的使用方法

用前须知
Node 版本需在 9.0 及以上
不加 loader 时候,使用 import/export 的文件后缀名必须为 .mjs
举个栗子
还是用上面的例子,请将代码回退到 Babel 中第一步的样子

1.改写 server.js

import Koa from 'koa'
const app = new Koa()

app.listen(3000, console.log("application is start at port 3000"))

和前面一样,不过将文件名改一下,从 server.js 改为 server.mjs

2.启动文件
执行下面代码,来启动文件

node --experimental-modules ./server.mjs

注意这是引用 koa 第三方模块不用做其他变化,如果要 import 自己的文件,那么那个待引入的文件也要改后缀。

比如

import example from './example'

那么原来应该是 example.js 要改为 example.mjs

目前这个模块还处于实验阶段,还是不要放到生产环境,自己拿出来玩玩还是可以的。

Node 9下import/export的丝般顺滑使用

前言

Node 9最激动人心的是提供了在flag模式下使用​​ECMAScript Modules​​​,虽然现在还是​​Stability: 1 - Experimental​​​阶段,但是可以让Noder抛掉babel等工具的束缚,直接在Node环境下愉快地去玩耍​​import/export​

如果觉得文字太多,看不下去,可以直接去玩玩demo,地址是​​https://github.com/chenshenhai/node-modules-demo​​

Node 9下import/export使用简单须知

  • Node 环境必须在 9.0以上
  • 不加loader时候,使用​​import/export​​​的文件后缀名必须为​​*.mjs​​​(下面会讲利用Loader Hooks兼容​​*.js​​后缀文件)
  • 启动必须加上flag​​--experimental-modules​
  • 文件的​​import​​​和​​export​​​必须严格按照​​ECMAScript Modules​​语法
  • ​ECMAScript Modules​​​和​​require()​​的cache机制不一样

快速使用import/export

  • 新建​​mod-1.mjs​​​,​​mod-2.mjs​​文件

/* ./mod-1.mjs */ 
export default {
num: 0,
increase() {
this.num++;
},
decrease() {
this.num--;
}
}

/*  ./mod-2.mjs */ 
import Mod1 from './mod-1';

export default {
increase() {
Mod1.increase();
},
decrease() {
Mod1.decrease();
}
}

  • 建立启动文件​​index.mjs​

import Mod1 from './mod-1';
import Mod2 from './mod-2';

console.log(`Mod1.num = ${Mod1.num}`)
Mod1.increase();
console.log(`Mod1.num = ${Mod1.num}`)
Mod2.increase();
console.log(`Mod1.num = ${Mod1.num}`)

  • 执行代码

node --experimental-modules ./index.mjs

控制台会显示



Node.js 中使用 ES6 中的  import / export 的方法大全_node.js_02


使用简述

执行了上述demo后,快速体验了Node的原生​​import/export​​​能力,那我们来讲讲目前的支持状况,Node 9.x官方文档 ​​https://nodejs.org/dist/latest-v9.x/docs/api/esm.html​​

与require()区别

能力

描述

require()

import

NODE_PATH

从NODE_PATH加载依赖模块

Y

N

cache

缓存机制

可以通过require的API操作缓存

自己独立的缓存机制,目前不可访问

path

引用路径

文件路径

URL格式文件路径,例如​​import A from './a?v=2017'​

extensions

扩展名机制

require.extensions

Loader Hooks

natives

原生模块引用

直接支持

直接支持

npm

npm模块引用

直接支持

需要Loader Hooks

file

文件(引用)

​*.js​​​,​​*.json​​等直接支持

默认只能是​​*.mjs​​​,通过​​Loader Hooks​​​可以自定义配置规则支持​​*.js​​​,​​*.json​​等Node原有支持文件

Loader Hooks模式使用

由于历史原因,在ES6的Modules还没确定之前,JavaScript的模块化处理方案都是八仙过海,各显神通,例如前端的AMD、CMD模块方案,Node的CommonJS方案也在这个“乱世”诞生。
当到了ES6规范确定后,Node的CommonJS方案已经是JavaScript中比较成熟的模块化方案,但ES6怎么说都是正统的规范,“法理”上是需要兼容的,所以​​​*.mjs​​​这个针对​​ECMAScript Modules​​规范的Node文件方案在一片讨论声中应运而生。

当然如果​​import/export​​​只能对​​*.mjs​​​文件起作用,意味着Node原生模块和npm所有第三方模块都不能。所以这时候Node 9就提供了 ​​Loader Hooks​​​,开发者可自定义配置​​Resolve Hook​​​规则去利用​​import/export​​​加载使用Node原生模块,​​*.js​​文件,npm模块,C/C++的Node编译模块等Node生态圈的模块。

Loader Hooks 使用步骤

  • 自定义loader规则
  • 启动的flag要加载loader规则文件
  • 例如:​​node --experimental-modules --loader ./custom-loader.mjs ./index.js​

如果觉得以下文字太长,可以先去玩玩对应的demo3 ​​https://github.com/chenshenhai/node-modules-demo/tree/master/demo3​​

自定义规则快速上手

  • 文件目录

├── demo3
│ ├── es
│ │ ├── custom-loader.mjs
│ │ ├── index.js
│ │ ├── mod-1.js
│ │ └── mod-2.js
│ └── package.json

  • 加载自定义loader,执行​​import/export​​​的​​*.js​​文件

node --experimental-modules  --loader ./es/custom-loader.mjs ./es/index.js

自定义loader规则解析

以下是Node 9.2官方文档提供的一个自定义loader文件

import url from 'url';
import path from 'path';
import process from 'process';

// 获取所有Node原生模块名称
const builtins = new Set(
Object.keys(process.binding('natives')).filter((str) =>
/^(?!(?:internal|node|v8)\/)/.test(str))
);

// 配置import/export兼容的文件后缀名
const JS_EXTENSIONS = new Set(['.js', '.mjs']);

// flag执行的resolve规则
export function resolve(specifier, parentModuleURL /*, defaultResolve */) {

// 判断是否为Node原生模块
if (builtins.has(specifier)) {
return {
url: specifier,
format: 'builtin'
};
}

// 判断是否为*.js, *.mjs文件
// 如果不是则,抛出错误
if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) {
// For node_modules support:
// return defaultResolve(specifier, parentModuleURL);
throw new Error(
`imports must begin with '/', './', or '../'; '${specifier}' does not`);
}
const resolved = new url.URL(specifier, parentModuleURL);
const ext = path.extname(resolved.pathname);
if (!JS_EXTENSIONS.has(ext)) {
throw new Error(
`Cannot load file with non-JavaScript file extension ${ext}.`);
}

// 如果是*.js, *.mjs文件,封装成ES6 Modules格式
return {
url: resolved.href,
format: 'esm'
};
}

规则总结

在自定义loader中,export的resolve规则最核心的代码是

return {
url: '',
format: ''
}

  • url 是模块名称或者文件URL格式路径
  • format 是模块格式有​​esm​​​,​​cjs​​​,​​json​​​,​​builtin​​​,​​addon​​这四种模块/文件格式.

Koa2 直接使用import/export

看看demo4,​​https://github.com/chenshenhai/node-modules-demo/tree/master/demo4​​

  • 文件目录

├── demo4
│ ├── README.md
│ ├── custom-loader.mjs
│ ├── index.js
│ ├── lib
│ │ ├── data.json
│ │ ├── path.js
│ │ └── render.js
│ ├── package-lock.json
│ ├── package.json
│ └── view
│ ├── index.html
│ └── todo.html

代码片段太多,不一一贴出来,只显示主文件

import Koa from 'koa';
import { render } from './lib/render.js';
import data from './lib/data.json';

let app = new Koa();
app.use((ctx, next) => {
let view = ctx.url.substr(1);
let content;
if ( view === 'data' ) {
content = data;
} else {
content = render(view);
}
ctx.body = content;
})
app.listen(3000, ()=>{
console.log('the modules test server is starting');
});

  • 执行代码

node --experimental-modules  --loader ./custom-loader.mjs ./index.js

  • 访问
  • 访问​​http://127.0.0.1:3000/index​​
  • 访问​​http://127.0.0.1:3000/data​​
  • 访问​​http://127.0.0.1:3000/todo​​

自定义loader规则优化

从上面官方提供的自定义loader例子看出,只是对​​*.js​​​文件做​​import/export​​​做loader兼容,然而我们在实际开发中需要对npm模块,​​*.json​​​文件也使用​​import/export​

loader规则优化解析

import url from 'url';
import path from 'path';
import process from 'process';
import fs from 'fs';

// 从package.json中
// 的dependencies、devDependencies获取项目所需npm模块信息
const ROOT_PATH = process.cwd();
const PKG_JSON_PATH = path.join( ROOT_PATH, 'package.json' );
const PKG_JSON_STR = fs.readFileSync(PKG_JSON_PATH, 'binary');
const PKG_JSON = JSON.parse(PKG_JSON_STR);
// 项目所需npm模块信息
const allDependencies = {
...PKG_JSON.dependencies || {},
...PKG_JSON.devDependencies || {}
}

//Node原生模信息
const builtins = new Set(
Object.keys(process.binding('natives')).filter((str) =>
/^(?!(?:internal|node|v8)\/)/.test(str))
);

// 文件引用兼容后缀名
const JS_EXTENSIONS = new Set(['.js', '.mjs']);
const JSON_EXTENSIONS = new Set(['.json']);

export function resolve(specifier, parentModuleURL, defaultResolve) {
// 判断是否为Node原生模块
if (builtins.has(specifier)) {
return {
url: specifier,
format: 'builtin'
};
}

// 判断是否为npm模块
if ( allDependencies && typeof allDependencies[specifier] === 'string' ) {
return defaultResolve(specifier, parentModuleURL);
}

// 如果是文件引用,判断是否路径格式正确
if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) {
throw new Error(
`imports must begin with '/', './', or '../'; '${specifier}' does not`);
}

// 判断是否为*.js、*.mjs、*.json文件
const resolved = new url.URL(specifier, parentModuleURL);
const ext = path.extname(resolved.pathname);
if (!JS_EXTENSIONS.has(ext) && !JSON_EXTENSIONS.has(ext)) {
throw new Error(
`Cannot load file with non-JavaScript file extension ${ext}.`);
}

// 如果是*.js、*.mjs文件
if (JS_EXTENSIONS.has(ext)) {
return {
url: resolved.href,
format: 'esm'
};
}

// 如果是*.json文件
if (JSON_EXTENSIONS.has(ext)) {
return {
url: resolved.href,
format: 'json'
};
}

}

后记

目前Node对​​import/export​​​的支持现在还是​​Stability: 1 - Experimental​​阶段,后续的发展还有很多不确定因素,自己练手玩玩还可以,但是在还没去flag使用之前,尽量不要在生产环境中使用。

Kotlin 开发者社区

国内第一Kotlin 开发者社区公众号,主要分享、交流 Kotlin 编程语言、Spring Boot、Android、React.js/Node.js、函数式编程、编程思想等相关主题。



Node.js 中使用 ES6 中的  import / export 的方法大全_自定义_03


开发者社区 QRCode.jpg

举报

相关推荐

0 条评论