├── .gitignore ├── .npmignore ├── README.md ├── bin └── node-esml.js ├── package.json ├── rollup.config.js ├── src └── node-es-module-loader.js └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModuleLoader/node-es-module-loader/0ee698cc390eb63c3338e0247c3116541010ac88/.npmignore -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NodeJS ES Module Loader 2 | === 3 | 4 | Loads ES modules and WebAssembly with CJS interop in Node, roughly according to https://github.com/nodejs/node-eps/blob/master/002-es6-modules.md. 5 | 6 | _WASM support is currently only provided for Node 8 nightly._ 7 | 8 | Follows the NodeJS resolution algorithm, loading modules first as CJS and then falling back to ES on import or export syntax failures. 9 | This effectively provides the "export {}" assumption to load an ES module. 10 | 11 | This does mean a double-parse, over the mjs approach currently being taken by Node. 12 | 13 | Built with the ES Module Loader polyfill 1.0 branch at https://github.com/ModuleLoader/es-module-loader. 14 | 15 | ### Installation 16 | 17 | ``` 18 | npm install -g node-es-module-loader 19 | ``` 20 | 21 | ### Usage 22 | 23 | Execute an ES module file: 24 | 25 | ``` 26 | node-esml module.js 27 | ``` 28 | 29 | For example, where `module.js` contains: 30 | 31 | ```javascript 32 | import fs from 'fs'; 33 | import {fn} from './local-es-module.js'; 34 | import {wasmFn} from './local-wasm-binary.wasm'; 35 | ``` 36 | 37 | Note that only the default import form for CommonJS modules is supported. 38 | 39 | Also supports dynamic loading via the dynamic import syntax: 40 | 41 | ```javascript 42 | export function lazyLoad(path) { 43 | return import(path); 44 | } 45 | ``` 46 | 47 | Source maps for errors are fully supported through the [source-map-support project](https://github.com/evanw/node-source-map-support). 48 | 49 | Source maps also work in Node 6 with the `node --inspect` flag via: 50 | 51 | ``` 52 | node --inspect node_modules/.bin/node-esml module.js 53 | ``` 54 | 55 | ### Programmatic Usage 56 | 57 | ```javascript 58 | var NodeESModuleLoader = require('node-es-module-loader'); 59 | 60 | var loader = new NodeESModuleLoader(/* optional basePath */); 61 | 62 | loader.import('x').then(function(m) { 63 | // ... 64 | }); 65 | ``` 66 | 67 | ## Caveats 68 | 69 | - Does not currently support the "module" package.json proposal described in the second paragraph at 70 | https://github.com/nodejs/node-eps/blob/master/002-es6-modules.md#51-determining-if-source-is-an-es-module 71 | - Does not allow any loading of ES modules from within CommonJS itself 72 | - Does not implement global require filtering described in 73 | https://github.com/nodejs/node-eps/blob/master/002-es6-modules.md#521-removal-of-non-local-dependencies 74 | - Does not provide CJS exports as named exports, skipping the algorithm defined in 75 | https://github.com/nodejs/node-eps/blob/master/002-es6-modules.md#311-dynamicmodulecreateo 76 | this may change, pending Node implementation intentions 77 | 78 | Alternative Babel options can be set with a local .babelrc file. 79 | 80 | LICENSE 81 | --- 82 | 83 | MIT 84 | -------------------------------------------------------------------------------- /bin/node-esml.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var LoaderNodeBabel = require('../dist/node-es-module-loader.js'); 3 | var path = require('path'); 4 | 5 | var filename = process.argv[2]; 6 | 7 | if (!filename) 8 | throw new Error('No filename argument provided'); 9 | 10 | global.loader = new LoaderNodeBabel(); 11 | 12 | loader.import(path.resolve(filename)) 13 | .catch(function(err) { 14 | setTimeout(function() { 15 | throw err; 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-es-module-loader", 3 | "version": "0.3.8", 4 | "description": "Loads ES modules with CJS interop in Node", 5 | "main": "dist/node-es-module-loader.js", 6 | "bin": { 7 | "node-esml": "bin/node-esml.js" 8 | }, 9 | "dependencies": { 10 | "babel-core": "^6.20.0", 11 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 12 | "babel-plugin-transform-es2015-modules-systemjs": "^6.19.0" 13 | }, 14 | "author": "Guy Bedford", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/ModuleLoader/node-es-module-loader" 18 | }, 19 | "license": "MIT", 20 | "devDependencies": { 21 | "es-module-loader": "^2.0.0", 22 | "rollup": "^0.34.7", 23 | "rollup-plugin-node-resolve": "^2.0.0", 24 | "source-map-support": "^0.4.4" 25 | }, 26 | "scripts": { 27 | "build": "rollup -c", 28 | "prepublish": "rollup -c", 29 | "test": "node test/test.js" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from 'rollup-plugin-node-resolve'; 2 | 3 | export default { 4 | entry: 'src/node-es-module-loader.js', 5 | format: 'umd', 6 | moduleName: 'NodeESModuleLoader', 7 | dest: 'dist/node-es-module-loader.js', 8 | 9 | plugins: [ 10 | nodeResolve({ 11 | module: false, 12 | jsnext: false, 13 | }) 14 | ], 15 | 16 | // skip rollup warnings (specifically the eval warning) 17 | onwarn: function() {} 18 | }; -------------------------------------------------------------------------------- /src/node-es-module-loader.js: -------------------------------------------------------------------------------- 1 | import RegisterLoader from 'es-module-loader/core/register-loader.js'; 2 | import { ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js'; 3 | 4 | import { isNode, baseURI, pathToFileUrl, fileUrlToPath } from 'es-module-loader/core/common.js'; 5 | import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js'; 6 | 7 | var babel = require('babel-core'); 8 | var modulesRegister = require('babel-plugin-transform-es2015-modules-systemjs'); 9 | var importSyntax = require('babel-plugin-syntax-dynamic-import'); 10 | var path = require('path'); 11 | var Module = require('module'); 12 | var fs = require('fs'); 13 | 14 | var sourceMapSources = global.nodeEsModuleLoaderSourceMapSources = global.nodeEsModuleLoaderSourceMapSources || {}; 15 | 16 | require('source-map-support').install({ 17 | retrieveSourceMap: function(source) { 18 | if (!sourceMapSources[source]) 19 | return null; 20 | 21 | return { 22 | url: source.replace('!transpiled', ''), 23 | map: sourceMapSources[source] 24 | }; 25 | } 26 | }); 27 | 28 | function NodeESModuleLoader(baseKey, rcPath) { 29 | if (!isNode) 30 | throw new Error('Node module loader can only be used in Node'); 31 | 32 | if (baseKey) 33 | this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI); 34 | else 35 | this.baseKey = baseURI; 36 | 37 | if (this.baseKey[this.baseKey.length - 1] !== '/') 38 | this.baseKey += '/'; 39 | 40 | if (rcPath) { 41 | if (typeof rcPath !== 'string') 42 | throw new TypeError('Second argument to Node loader must be a valid file path to the babelrc file.'); 43 | this.rcPath = rcPath; 44 | } 45 | 46 | RegisterLoader.call(this); 47 | 48 | var loader = this; 49 | 50 | // ensure System.register is available 51 | global.System = global.System || {}; 52 | global.System.register = function() { 53 | loader.register.apply(loader, arguments); 54 | }; 55 | } 56 | NodeESModuleLoader.prototype = Object.create(RegisterLoader.prototype); 57 | 58 | // normalize is never given a relative name like "./x", that part is already handled 59 | NodeESModuleLoader.prototype[RegisterLoader.resolve] = function(key, parent) { 60 | parent = parent || this.baseKey; 61 | key = RegisterLoader.prototype[RegisterLoader.resolve].call(this, key, parent) || key; 62 | 63 | return Promise.resolve() 64 | .then(function() { 65 | var parentPath = fileUrlToPath(parent); 66 | var requireContext = new Module(parentPath); 67 | requireContext.paths = Module._nodeModulePaths(parentPath); 68 | var resolved = Module._resolveFilename(key.substr(0, 5) === 'file:' ? fileUrlToPath(key) : key, requireContext, true); 69 | 70 | // core modules are returned as plain non-absolute paths 71 | return path.isAbsolute(resolved) ? pathToFileUrl(resolved) : resolved; 72 | }); 73 | }; 74 | 75 | // instantiate just needs to run System.register 76 | // so we fetch the source, convert into the Babel System module format, then evaluate it 77 | NodeESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, processAnonRegister) { 78 | var loader = this; 79 | 80 | // first, try to load the module as CommonJS 81 | var nodeModule = tryNodeLoad(key.substr(0, 5) === 'file:' ? fileUrlToPath(key) : key); 82 | 83 | if (nodeModule) 84 | return Promise.resolve(new ModuleNamespace({ 85 | default: nodeModule 86 | })); 87 | 88 | // otherwise load the buffe 89 | return new Promise(function(resolve, reject) { 90 | fs.readFile(fileUrlToPath(key), function(err, buffer) { 91 | if (err) 92 | return reject(err); 93 | 94 | // first check if it is web assembly, detected by leading bytes 95 | var bytes = new Uint8Array(buffer); 96 | if (bytes[0] === 0 && bytes[1] === 97 && bytes[2] === 115) { 97 | return WebAssembly.compile(bytes).then(function (m) { 98 | var deps = []; 99 | var setters = []; 100 | var importObj = {}; 101 | if (WebAssembly.Module.imports) 102 | WebAssembly.Module.imports(m).forEach(function (i) { 103 | var key = i.module; 104 | setters.push(function (m) { 105 | importObj[key] = m; 106 | }); 107 | if (deps.indexOf(key) === -1) 108 | deps.push(key); 109 | }); 110 | loader.register(deps, function (_export) { 111 | return { 112 | setters: setters, 113 | execute: function () { 114 | // for some reason exports are non-enumarable in the node build 115 | // which seems to contradict the WASM spec 116 | let exports = new WebAssembly.Instance(m, importObj).exports; 117 | let exportNames = Object.getOwnPropertyNames(exports); 118 | for (var i = 0; i < exportNames.length; i++) { 119 | var name = exportNames[i]; 120 | _export(name, exports[name]); 121 | } 122 | } 123 | }; 124 | }); 125 | processAnonRegister(); 126 | }) 127 | .then(resolve, reject); 128 | } 129 | 130 | // otherwise fall back to transform source with Babel 131 | var output = babel.transform(buffer.toString(), { 132 | compact: false, 133 | filename: key + '!transpiled', 134 | sourceFileName: key, 135 | moduleIds: false, 136 | sourceMaps: 'both', 137 | plugins: [importSyntax, modulesRegister], 138 | extends: loader.rcPath 139 | }); 140 | 141 | // evaluate without require, exports and module variables 142 | var path = fileUrlToPath(key) + '!transpiled'; 143 | output.map.sources = output.map.sources.map(fileUrlToPath); 144 | sourceMapSources[path] = output.map; 145 | (0,eval)(output.code + '\n//# sourceURL=' + path); 146 | processAnonRegister(); 147 | 148 | resolve(); 149 | }); 150 | }); 151 | }; 152 | 153 | function tryNodeLoad(path) { 154 | try { 155 | return require(path); 156 | } 157 | catch(e) { 158 | if (e instanceof SyntaxError && 159 | (e.message.indexOf('Unexpected token export') !== -1 || 160 | e.message.indexOf('Unexpected token import') !== -1 || 161 | e.message.indexOf('Unexpected reserved word') !== -1) || 162 | e.message.indexOf('Invalid or unexpected token') !== -1) 163 | return; 164 | throw e; 165 | } 166 | } 167 | 168 | export default NodeESModuleLoader; 169 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var NodeESModuleLoader = require('../'); 2 | 3 | var loader = new NodeESModuleLoader('.'); 4 | 5 | loader.import('./src/node-es-module-loader.js').then(function(m) { 6 | console.log('Failed'); 7 | }) 8 | .catch(function (err) { 9 | if (err.toString().indexOf('require is not defined') !== -1) 10 | console.log('Ok'); 11 | else 12 | console.log('Failed'); 13 | }); 14 | --------------------------------------------------------------------------------