UNPKG

@adpt/core

Version:
335 lines 12.9 kB
"use strict"; /* * Copyright 2018-2019 Unbounded Systems, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const utils_1 = require("@adpt/utils"); const path = tslib_1.__importStar(require("path")); const vm = tslib_1.__importStar(require("vm")); // tslint:disable-next-line:variable-name no-var-requires const Module = require("module"); const ld = tslib_1.__importStar(require("lodash")); const error_1 = require("../error"); const utils_2 = require("../utils"); const compile_1 = require("./compile"); const builtInModules = new Set(Module.builtinModules); const debugVm = false; const packageDirs = utils_1.findPackageDirs(__dirname); // Remove each line that has a filename that's in our dist/src/ts directory // or our src/ts directory (depending on how the module is installed). // There are often 2-3 of these compilation-related frames between each // stack frame that the user cares about, which makes the backtraces super // confusing for them. const tsStackExclude = RegExp("\n.*?\\(" + packageDirs.root + "(?:/dist)?/src/ts/.*?$", "mg"); // Script.runInContext is the call that starts the user's project script. // Delete that line and all following lines. const ctxStackEnd = /\n[^\n]*Script\.runInContext(?:.|\n)*/; function getProjectStack(projectError) { if (projectError.stack) { let ctxStack = projectError.stack.replace(ctxStackEnd, ""); ctxStack = ctxStack.replace(tsStackExclude, ""); return ctxStack; } return "[No stack]"; } function isRelative(loc) { return loc.startsWith("./") || loc.startsWith("../"); } class VmModule { constructor(id, vmContext, host, parent) { this.id = id; this.vmContext = vmContext; this.host = host; this.parent = parent; this._compile = this.runJs; if (parent) { this.extensions = parent.extensions; this.requireCache = parent.requireCache; this.hostModCache = parent.hostModCache; this.vmModules = parent.vmModules; } else { this.extensions = Object.create(null); this.requireCache = Object.create(null); this.hostModCache = Object.create(null); this.vmModules = Object.create(null); this.extensions[".js"] = this.runJsModule.bind(this); this.extensions[".json"] = this.runJsonModule.bind(this); } this.ctxModule = new Module(id, (parent && parent.ctxModule) || null); this.ctxModule.filename = id; } /** * Init that happens only once per VmContext, after the context's VM has * been created. This should be called on the "main" module for the context * ONLY. * @param ctx The vm context where the DOM code will run. */ initMain(ctx) { if (this.parent) throw new error_1.InternalError(`initMain should only be called on top-level VmModule`); this.vmContext = ctx; // NOTE(mark): There's some strange behavior with allowing this // module to be re-loaded in the context when used alongside // source-map-support in unit tests. Even though callsites overrides // Error.prepareStackTrace with its own function, that function // never gets called. If you figure out why, remove this. this.loadHostMod("callsites"); } requireResolve(request, options) { if (options) { throw new Error("require.resolve options not supported yet."); } const resolved = this.host.resolveModuleName(request, this.id, true); if (resolved) return resolved.resolvedFileName; if (isRelative(request)) request = path.join(path.dirname(this.id), request); if (!path.isAbsolute(request)) { // mimic Node's error for this case. const err = new Error(`Cannot find module '${request}'`); err.code = "MODULE_NOT_FOUND"; throw err; } return require.resolve(request, options); } require(modName) { let hostMod = this.requireHostMod(modName); if (hostMod !== undefined) return hostMod; if (builtInModules.has(modName)) { hostMod = this.requireBuiltin(modName); if (hostMod === undefined) throw new error_1.InternalError(`Cannot find module '${modName}'`); return hostMod; } let resolved; try { resolved = this.requireResolve(modName); } catch (e) { if (!ld.isError(e)) throw e; if (!e.message.startsWith("Cannot find")) throw e; } if (resolved) { const resolvedPath = resolved; const cached = this.requireCache[resolvedPath]; if (cached) return cached.exports; const newMod = new VmModule(resolvedPath, this.vmContext, this.host, this); this.vmModules[resolvedPath] = newMod; this.requireCache[resolvedPath] = newMod.ctxModule; const ext = path.extname(resolvedPath) || ".js"; // Run the module this.extensions[ext](newMod, resolvedPath); newMod.ctxModule.loaded = true; return newMod.ctxModule.exports; } throw new Error(`Unable to find module ${modName} ` + `imported from ${this.id}`); } registerExt(ext, func) { this.extensions[ext] = func; } loadHostMod(modName) { const cached = this.hostModCache[modName]; if (cached !== undefined) return cached; const mod = require(modName); this.hostModCache[modName] = mod; return mod; } requireHostMod(modName) { return this.hostModCache[modName]; } requireBuiltin(modName) { try { return this.loadHostMod(modName); } catch (e) { if (e.code === "MODULE_NOT_FOUND") return undefined; throw e; } } runJsonModule(mod, filename) { const contents = this.host.readFile(filename); if (contents == null) { throw new Error(`Unable to find file contents for ${filename}`); } try { mod.ctxModule.exports = JSON.parse(contents); } catch (err) { err.message = filename + ": " + err.message; throw err; } } runJsModule(mod, filename) { const contents = this.host.readFile(filename); if (contents == null) { throw new Error(`Unable to find file contents for ${filename}`); } return mod.runJs(contents, filename); } runJs(content, filename) { if (!this.vmContext) throw new Error(`vmContext is not set`); const wrapper = Module.wrap(content); const compiled = vm.runInContext(wrapper, this.vmContext, { filename }); const require = (() => { const ret = this.require.bind(this); ret.resolve = this.requireResolve.bind(this); ret.cache = this.requireCache; return ret; })(); const dirname = path.dirname(filename); try { return compiled.call(this.ctxModule.exports, this.ctxModule.exports, require, this.ctxModule, filename, dirname); } catch (err) { if ((err instanceof error_1.ProjectRunError) || (err instanceof compile_1.CompileError)) { throw err; } if (!error_1.isError(err)) err = new error_1.ThrewNonError(err); throw new error_1.ProjectRunError(err, getProjectStack(err), err.stack); } } } tslib_1.__decorate([ utils_2.tracef(debugVm), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String, Object]), tslib_1.__metadata("design:returntype", void 0) ], VmModule.prototype, "requireResolve", null); tslib_1.__decorate([ utils_2.tracef(debugVm), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String]), tslib_1.__metadata("design:returntype", void 0) ], VmModule.prototype, "require", null); tslib_1.__decorate([ utils_2.tracef(debugVm), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String, Function]), tslib_1.__metadata("design:returntype", void 0) ], VmModule.prototype, "registerExt", null); tslib_1.__decorate([ utils_2.tracef(debugVm), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String]), tslib_1.__metadata("design:returntype", void 0) ], VmModule.prototype, "requireHostMod", null); tslib_1.__decorate([ utils_2.tracef(debugVm), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String]), tslib_1.__metadata("design:returntype", void 0) ], VmModule.prototype, "requireBuiltin", null); tslib_1.__decorate([ utils_2.tracef(debugVm), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [VmModule, String]), tslib_1.__metadata("design:returntype", void 0) ], VmModule.prototype, "runJsonModule", null); tslib_1.__decorate([ utils_2.tracef(debugVm), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [VmModule, String]), tslib_1.__metadata("design:returntype", void 0) ], VmModule.prototype, "runJsModule", null); tslib_1.__decorate([ utils_2.tracef(debugVm), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String, String]), tslib_1.__metadata("design:returntype", void 0) ], VmModule.prototype, "runJs", null); exports.VmModule = VmModule; // Javascript defines a set of properties that should be available on the // global object. V8 takes care of those. Only add the ones that Node defines. const hostGlobals = () => ({ version: parseInt(process.versions.node.split(".")[0], 10), process, console, setTimeout, setInterval, setImmediate, clearTimeout, clearInterval, clearImmediate, Buffer, }); /* * Prepares a context object to be the global object within a new * V8 context. */ class VmContext { constructor(vmGlobal, dirname, filename, host) { this.vmGlobal = vmGlobal; this.filename = filename; this.host = host; vmGlobal.__filename = filename; vmGlobal.__dirname = dirname; const module = new VmModule(filename, undefined, host, undefined); this.mainModule = module; vmGlobal.exports = module.ctxModule.exports; vmGlobal.module = module.ctxModule; vmGlobal.require = module.require.bind(module); vmGlobal.global = vmGlobal; const hGlobals = hostGlobals(); for (const prop of Object.keys(hGlobals)) { vmGlobal[prop] = hGlobals[prop]; } vm.createContext(vmGlobal); module.initMain(vmGlobal); } run(jsText) { let val; try { // Execute the program val = vm.runInContext(jsText, this.vmGlobal, { filename: this.filename }); } catch (err) { // Translate internal error that has all the diags in it // to an external API text-only version. if (err instanceof compile_1.CompileError) { err = new error_1.ProjectCompileError(err.message); } if (!error_1.isError(err)) err = new error_1.ThrewNonError(err); if (!(err instanceof error_1.ProjectRunError)) { err = new error_1.ProjectRunError(err, getProjectStack(err), err.stack); } throw err; } if (debugVm) { utils_2.trace(debugVm, `RESULT: ${JSON.stringify(val, null, 2)}`); } return val; } } tslib_1.__decorate([ utils_2.tracef(debugVm), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String]), tslib_1.__metadata("design:returntype", Object) ], VmContext.prototype, "run", null); exports.VmContext = VmContext; //# sourceMappingURL=context.js.map