1. 程式人生 > >如何讓 node 執行 es6 模組檔案,及其原理

如何讓 node 執行 es6 模組檔案,及其原理

如何讓 node 執行 es6 模組檔案,及其原理

最新版的 node 支援最新版 ECMAScript 幾乎所有特性,但有一個特性卻一直到現在都還沒有支援,那就是從 ES2015 開始定義的模組化機制。而現在我們很多專案都是用 es6 的模組化規範來寫程式碼的,包括 node 專案,所以,node 不能執行 es6 模組檔案就會很不便。

node 執行 es6 模組檔案的方式有兩種:

  1. 轉碼 es6 模組為 commonjs 模組
  2. hook noderequire 機制,直接讓 noderequire 載入 import/export

1. 轉碼 es6 模組為 commonjs
模組

因為 node 支援幾乎所有除 import/export 外的語法,所以我們只需要將 import/export 轉碼成 require/exports,而不需要轉碼其他語法。

比如下面的專案:


- package.json
- src/
  - index.js
  - print.js
  - ...

# package.json
{
  "main": "lib/index.js"               # 由工具轉碼 src 目錄下原始檔到 lib 目錄下
}


# src/index.js
import print from './print';

print('index');

export default print;


# src/print.js
export default str => {
  console.log('print: ' + str);
};

因為 src 目錄下的原始檔都是 es6 模組化規範的,node 並不能直接執行,所以需要轉碼成 commonjs 規範的程式碼。

這個過程有兩個方案:

  1. 如果不會單獨使用 src 目錄下的某個檔案,而僅僅是以 src/index.js 為入口檔案使用,可以把 src 目錄下的檔案打包成一個檔案到 lib/index.js:這種方式推薦使用工具 rollup
  2. 如果需要單獨使用 src 目錄下的檔案,那就需要把 src 目錄下的檔案一對一的轉碼到 lib 目錄下:這種方式推薦使用工具 gulp + babel

1.1 用 rollupsrc 目錄下的檔案打包成一個檔案到 lib/index.js

相關檔案:


# rollup.config.js
export default {
  input: 'src/index.js',
  output: {
    file: 'lib/index.js',
    format: 'cjs',
  },
};


# package.json
{
  "scripts": {
    "build": "rollup -c"
  },
  "devDependencies": {
    "rollup": "^0.66.4"
  }
}

執行命令:


npm run build

結果:


# lib/index.js
'use strict';

var print = str => {
  console.log('print: ' + str);
};

print('index');

module.exports = print;

1.2 用 gulp + babelsrc 目錄下的檔案一對一的轉碼到 lib 目錄下

相關檔案:


# build.js
const gulp = require('gulp');
const babel = require('gulp-babel');

gulp.task('babel', () =>
  gulp.src('src/**/*.js')
    .pipe(babel({
      plugins: ['@babel/plugin-transform-modules-commonjs']
    }))
    .pipe(gulp.dest('lib'))
);

gulp.series('babel')();


# package.json
{
  "scripts": {
    "build": "node build.js"
  },
  "devDependencies": {
    "@babel/core": "^7.1.2",
    "@babel/plugin-transform-modules-commonjs": "^7.2.0",
    "gulp": "^4.0.0",
    "gulp-babel": "^8.0.0"
  }
}

執行命令:


npm run build

結果:


# lib/index.js
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _print = _interopRequireDefault(require("./print"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

(0, _print.default)('index');
var _default = _print.default;
exports.default = _default;


# lib/print.js
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _default = str => {
  console.log('print: ' + str);
};

exports.default = _default;

2. hook noderequire 機制,直接載入 import/export

這種機制一般是通過對 noderequire 機制進行 hook,劫持 require 抓取的原始檔程式碼,把原始碼轉碼成 commonjs 規範之後,再傳送給 require 機制原本的程式碼流中。

pirates 之類的第三方 npm 包提供了這種新增 hook 的功能。

babel-register 便是使用這種方式達到 node 執行 es6 模組檔案的目的的。

2.1 使用 babel-register 直接執行 es6 模組檔案

示例目錄:


- package.json
- src/
  - entry.js                           # 這裡多了一個入口檔案,專門用於註冊 babel-register
  - index.js
  - print.js
  - ...

相關檔案:


# package.json
{
  "scripts": {
    "run": "node src/entry.js"
  },
  "devDependencies": {
    "@babel/core": "^7.1.2",
    "@babel/plugin-transform-modules-commonjs": "^7.2.0",
    "@babel/register": "^7.0.0"
  }
}


# src/entry.js                         # 入口檔案必須使用 commonjs 規範來寫,因為還沒有註冊 hook
require('@babel/register')({
  plugins: ['@babel/plugin-transform-modules-commonjs']
});
require('./index');


# src/index.js
import print from './print';

print('index');


# src/print.js
export default str => {
  console.log('print: ' + str);
};

執行:


npm run run

結果:


# 命令列列印

print: index

這種方式因為中間轉碼會有額外的效能損耗,所以不建議在生產環境下使用,只建議在開發模式下使用。

2.2 使用 babel-node 直接執行 es6 模組檔案

babel-nodebabel-register 進行了封裝,提供了在命令列直接執行 es6 模組檔案的便捷方式。

示例目錄:


- package.json
- src/
  - index.js
  - print.js
  - ...

相關檔案:


# package.json
{
  "scripts": {
    "run": "babel-node src/index.js --plugins @babel/plugin-transform-modules-commonjs"
  },
  "devDependencies": {
    "@babel/core": "^7.1.2",
    "@babel/node": "^7.2.0",
    "@babel/plugin-transform-modules-commonjs": "^7.2.0"
  }
}


# src/index.js
import print from './print';

print('index');


# src/print.js
export default str => {
  console.log('print: ' + str);
};

執行:


npm run run

結果:


# 命令列列印

print: index

這種方式也不建議在生產環境下使用,只建議在開發模式下使用。

3. 連結

到寫這篇文章為止,已釋出了 ECMAScript 2018

後續

更多部落格,檢視 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證