如何在 NodeJS 中写 ES6 import?

2020 年更新

什么时候可以不要下面那么曲折的方法,直接在 Node 里使用 ES Modules ?

答案在 Node changelogs_V14

In Node.js 13 we removed the need to include the --experimental-modules flag, but when running EcmaScript Modules in Node.js, this would still result in a warning ExperimentalWarning: The ESM module loader is experimental.

自 Node 13 开始可以直接使用 ES Modules 但是会报警,Node 14 后可以使用并且不报警了。

问题

本人 node 环境是 9.0.0,直接起项目时发现下文(更新:Node 12 中也是如此):

$ node server.js
/Users/everlose/workspace/fedidea/graphql-server/server.js:1
(function (exports, require, module, __filename, __dirname) { import Koa from 'koa'
                                                              ^^^^^^

SyntaxError: Unexpected token import
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:152:10)
    at Module._compile (module.js:605:28)
    at Object.Module._extensions..js (module.js:652:10)
    at Module.load (module.js:560:32)
    at tryModuleLoad (module.js:503:12)
    at Function.Module._load (module.js:495:3)
    at Function.Module.runMain (module.js:682:10)
    at startup (bootstrap_node.js:191:16)
    at bootstrap_node.js:613:3

在本机可以检测 node 所支持的 es6 语法,如下所示,由此可知我本地其他 es6 语法大体上都支持,除了 import 和 export。而 es6 注定绕不过这个。

$ node -v
v9.0.0

$ npm install -g es-checker

$ es-checker

ECMAScript 6 Feature Detection (v1.4.1)

Variables
  √ let and const
  √ TDZ error for too-early access of let or const declarations
  √ Redefinition of const declarations not allowed
  √ destructuring assignments/declarations for arrays and objects
  √ ... operator

Data Types
  √ For...of loop
  √ Map, Set, WeakMap, WeakSet
  √ Symbol
  √ Symbols cannot be implicitly coerced

Number
  √ Octal (e.g. 0o1 ) and binary (e.g. 0b10 ) literal forms
  √ Old octal literal invalid now (e.g. 01 )
  √ Static functions added to Math (e.g. Math.hypot(), Math.acosh(), Math.imul() )
  √ Static functions added to Number (Number.isNaN(), Number.isInteger() )

String
  √ Methods added to String.prototype (String.prototype.includes(), String.prototype.repeat() )
  √ Unicode code-point escape form in string literals (e.g. \u{20BB7} )
  √ Unicode code-point escape form in identifier names (e.g. var \u{20BB7} = 42; )
  √ Unicode code-point escape form in regular expressions (e.g. var regexp = /\u{20BB7}/u; )
  √ y flag for sticky regular expressions (e.g. /b/y )
  √ Template String Literals

Function
  √ arrow function
  √ default function parameter values
  √ destructuring for function parameters
  √ Inferences for function name property for anonymous functions
  × Tail-call optimization for function calls and recursion

Array
  × Methods added to Array.prototype ([].fill(), [].find(), [].findIndex(), [].entries(), [].keys(), [].values() )
  √ Static functions added to Array (Array.from(), Array.of() )
  √ TypedArrays like Uint8Array, ArrayBuffer, Int8Array(), Int32Array(), Float64Array()
  √ Some Array methods (e.g. Int8Array.prototype.slice(), Int8Array.prototype.join(), Int8Array.prototype.forEach() ) added to the TypedArray prototypes
  √ Some Array statics (e.g. Uint32Array.from(), Uint32Array.of() ) added to the TypedArray constructors

Object
  √ __proto__ in object literal definition sets [[Prototype]] link
  √ Static functions added to Object (Object.getOwnPropertySymbols(), Object.assign() )
  √ Object Literal Computed Property
  √ Object Literal Property Shorthands
  √ Proxies
  √ Reflect

Generator and Promise
  √ Generator function
  √ Promises

Class
  √ Class
  √ super allowed in object methods
  √ class ABC extends Array { .. }

Module
  × Module export command
  × Module import command


=========================================
Passes 38 feature Detections
Your runtime supports 90% of ECMAScript 6
=========================================

步骤

  1. 安装三个依赖
npm install --save-dev babel-cli babel-preset-stage-2 babel-preset-es2015  babel-plugin-transform-runtime

其中 babel-cli 自带 babel-node 命令

  1. 修改 package.json 的 script
"scripts": {
    "start": "babel-node server.js"
},
  1. 再加入.babelrc
{
    "presets": [
        "es2015",
        "stage-2"
    ],
    "plugins": ["transform-runtime"]
}

  1. 最后运行 npm run start 启动程序

如果要在 vscode 中调试 es6 代码

在 package.json 里加入一下代码,能将除了 node_modules 以外的代码都打包入 dist 文件夹

"scripts": {
    // 省略其他...
    "build": "babel ./ --ignore node_modules  -d dist --source-maps",
},

接着在 .vscode 文件夹下加入 task.json

{
    "version": "0.1.0",
    "command": "npm",
    "isShellCommand": true,
    "showOutput": "always",
    "suppressTaskName": true,
    "tasks": [
        {
            "taskName": "build",
            "args": [ "run", "build" ],
            "isBuildCommand": true
        }
    ]
}

再于 .vscode 加入 launch.json

{
    // 使用 IntelliSense 了解相关属性。
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.1.0",
    "configurations": [
        {
            "name": "Launch",
            "type": "node",
            "request": "launch",
            "program": "${workspaceRoot}/server.js",
            "stopOnEntry": false,
            "args": [],
            "cwd": "${workspaceRoot}",
            "preLaunchTask": "build",
            "runtimeExecutable": null,
            "runtimeArgs": [ "--nolazy" ],
            "env": {
                "NODE_ENV": "development"
            },
            "console": "integratedTerminal",
            "sourceMaps": true,
            "outFiles": ["${workspaceRoot}/dist/*.js"]
        }
    ]
}

最后按F5启动,每次启动都会由 preLaunchTask 来预先 运行 build task 来把 es6 代码编译了,再启动