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

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

三種方法。 先上圖。

源代碼文件目錄(https://github.com/AK-47-D/nodejs_es6_tutorials):

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

目前這個模塊還處於實驗階段,還是不要放到生產環境,自己拿出來玩玩還是可以的。

原文:https://blog.csdn.net/zwkkkk1/article/details/81564971

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
  • 文件的importexport必須嚴格按照ECMAScript Modules語法
  • ECMAScript Modulesrequire()的cache機制不一樣

快速使用import/export

  • 新建mod-1.mjsmod-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

控制檯會顯示

使用簡述

執行了上述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

自定義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使用之前,儘量不要在生產環境中使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章