From 955af021de3ad55e5ec9f4ef311ce35769f615ec Mon Sep 17 00:00:00 2001 From: axetroy Date: Wed, 7 Mar 2018 19:57:35 +0800 Subject: [PATCH] feat: support es6 module --- README.md | 2 +- src/error.ts | 2 +- src/evaluate.ts | 71 ++++++++++++++++++++++++++++++ src/vm.ts | 30 +++++++++++-- test/ExportDeclaration.test.ts | 80 ++++++++++++++++++++++++++++++++++ test/ImportDeclaration.test.ts | 36 +++++++++++++++ 6 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 test/ExportDeclaration.test.ts create mode 100644 test/ImportDeclaration.test.ts diff --git a/README.md b/README.md index b984b9f4..3ece15cf 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ It base on [https://github.com/bramblex/jsjs](https://github.com/bramblex/jsjs) * [x] ECMA5 * [x] es2015 * [x] es2015-let-const - * [ ] es2015-modules-commonjs + * [x] es2015-modules-commonjs * [x] check-es2015-constants * [x] es2015-block-scoped-functions * [x] es2015-arrow-functions diff --git a/src/error.ts b/src/error.ts index d692b351..b1c21a1c 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,6 +1,6 @@ export class ErrNotDefined extends ReferenceError { constructor(varName: string) { - super(`${varName} is not defined`); + super(`Uncaught ReferenceError: ${varName} is not defined`); } } diff --git a/src/evaluate.ts b/src/evaluate.ts index 3603eb8d..21aa414f 100644 --- a/src/evaluate.ts +++ b/src/evaluate.ts @@ -787,6 +787,77 @@ const evaluate_map = { }, ObjectProperty(node: types.ObjectProperty, scope: Scope, arg) { // do nothing + }, + ImportDeclaration(node: types.ImportDeclaration, scope: Scope, arg) { + let defaultImport: string = ""; // default import object + const otherImport: string[] = []; // import property + const moduleNane: string = evaluate(node.source, scope, arg); + node.specifiers.forEach(n => { + if (types.isImportDefaultSpecifier(n)) { + defaultImport = evaluate_map.ImportDefaultSpecifier(n, scope, arg); + } else if (types.isImportSpecifier(n)) { + otherImport.push(evaluate_map.ImportSpecifier(n, scope, arg)); + } else { + throw n; + } + }); + + const _require = scope.$find("require"); + + if (_require) { + const requireFunc = _require.$get(); + + const targetModle: any = requireFunc(moduleNane) || {}; + + if (defaultImport) { + scope.$const( + defaultImport, + targetModle.default ? targetModle.default : targetModle + ); + } + + otherImport.forEach((varName: string) => { + scope.$const(varName, targetModle[varName]); + }); + } + }, + ImportDefaultSpecifier( + node: types.ImportDefaultSpecifier, + scope: Scope, + arg + ) { + return node.local.name; + }, + ImportSpecifier(node: types.ImportSpecifier, scope: Scope, arg) { + return node.local.name; + }, + ExportDefaultDeclaration( + node: types.ExportDefaultDeclaration, + scope: Scope, + arg + ) { + const moduleVar = scope.$find("module"); + if (moduleVar) { + const moduleObject = moduleVar.$get(); + moduleObject.exports = { + ...moduleObject.exports, + ...evaluate(node.declaration, scope, arg) + }; + } + }, + ExportNamedDeclaration( + node: types.ExportNamedDeclaration, + scope: Scope, + arg + ) { + node.specifiers.forEach(n => evaluate(n, scope, arg)); + }, + ExportSpecifier(node: types.ExportSpecifier, scope: Scope, arg) { + const moduleVar = scope.$find("module"); + if (moduleVar) { + const moduleObject = moduleVar.$get(); + moduleObject.exports[node.local.name] = evaluate(node.local, scope, arg); + } } }; diff --git a/src/vm.ts b/src/vm.ts index 09532033..da6cfc76 100644 --- a/src/vm.ts +++ b/src/vm.ts @@ -3,6 +3,10 @@ import Context, {Sandbox$} from "./context"; import {Scope} from "./scope"; import evaluate from "./evaluate"; +interface Options { + filename?: string; +} + class Vm { createContext(sandbox: Sandbox$ = {}): Context { return new Context(sandbox); @@ -10,19 +14,39 @@ class Vm { isContext(sandbox: any): boolean { return sandbox instanceof Context; } - runInContext(code: string, context: Context): any | null { + runInContext( + code: string, + context: Context, + ops: Options = { + filename: "index.js" + } + ): any | null { const scope = new Scope("block"); scope.isTopLevel = true; scope.$const("this", this); scope.$setContext(context); - // 定义 module + // define module const $exports = {}; const $module = {exports: $exports}; scope.$const("module", $module); - scope.$const("exports", $exports); + scope.$var("exports", $exports); + + // require can be cover + if (!scope.$find("require")) { + const requireFunc = + typeof require === "function" + ? require + : typeof context["require"] === "function" + ? context["require"] + : function _require(id: string) { + return {}; + }; + scope.$var("require", requireFunc); + } const ast = parse(code, { + sourceType: "module", plugins: [ // estree, // "jsx", diff --git a/test/ExportDeclaration.test.ts b/test/ExportDeclaration.test.ts new file mode 100644 index 00000000..6b09bc79 --- /dev/null +++ b/test/ExportDeclaration.test.ts @@ -0,0 +1,80 @@ +import test from "ava"; +import {isImportDefaultSpecifier} from "babel-types"; +import vm from "../src/vm"; + +test("ExportDeclaration-1", t => { + const sandbox: any = vm.createContext({}); + + const obj: any = vm.runInContext( + ` +const obj = { + a: 1, + b: 2 +}; + +export default obj; +export {obj} + `, + sandbox + ); + + t.deepEqual(obj.a, 1); + t.deepEqual(obj.b, 2); + t.deepEqual(obj.obj, { + a: 1, + b: 2 + }); +}); + +test("ExportDeclaration-2", t => { + const sandbox: any = vm.createContext({}); + + const obj: any = vm.runInContext( + ` +const obj = { + a: 1, + b: 2 +}; + +export {obj} +export default obj; + `, + sandbox + ); + + t.deepEqual(obj.a, 1); + t.deepEqual(obj.b, 2); + t.deepEqual(obj.obj, { + a: 1, + b: 2 + }); +}); + +test("ExportDeclaration-3", t => { + const sandbox: any = vm.createContext({}); + + const obj: any = vm.runInContext( + ` +const obj = { + a: 1, + b: 2 +}; + +const c = obj.a; +const d = obj.b; + +export {obj, c, d} +export default obj; + `, + sandbox + ); + + t.deepEqual(obj.a, 1); + t.deepEqual(obj.b, 2); + t.deepEqual(obj.c, 1); + t.deepEqual(obj.d, 2); + t.deepEqual(obj.obj, { + a: 1, + b: 2 + }); +}); diff --git a/test/ImportDeclaration.test.ts b/test/ImportDeclaration.test.ts new file mode 100644 index 00000000..a9aeda28 --- /dev/null +++ b/test/ImportDeclaration.test.ts @@ -0,0 +1,36 @@ +import test from "ava"; +import {isImportDefaultSpecifier} from "babel-types"; +import {parse} from "babylon"; +import vm from "../src/vm"; + +test("ImportDeclaration-1", t => { + const sandbox: any = vm.createContext({}); + + const obj: any = vm.runInContext( + ` +import {b, c, d, isImportDefaultSpecifier} from 'babel-types'; + +module.exports = {b, c, d, isImportDefaultSpecifier}; + `, + sandbox + ); + + t.deepEqual(obj.b, undefined); + t.deepEqual(obj.c, undefined); + t.deepEqual(obj.d, undefined); + t.deepEqual(obj.isImportDefaultSpecifier, isImportDefaultSpecifier); +}); + +test("ImportDeclaration-2", t => { + const sandbox: any = vm.createContext({}); + + const outputParser: any = vm.runInContext( + ` +import {parse} from "babylon"; + +module.exports = parse; + `, + sandbox + ); + t.true(outputParser === parse); +});