Skip to content

Instantly share code, notes, and snippets.

@rksm
Created May 1, 2016 03:13
Show Gist options
  • Save rksm/69df8e50b174aa6d7f93131293c9b4af to your computer and use it in GitHub Desktop.
Save rksm/69df8e50b174aa6d7f93131293c9b4af to your computer and use it in GitHub Desktop.
lively.vm es6 + cjs
/*global require, before, after, beforeEach, afterEach, describe, it*/
import { expect } from "mocha-es6";
import { fork } from "child_process"
if (System.get("@system-env").node) {
describe("bootstrap", function() {
this.timeout(5000);
it("vm in node", () =>
new Promise(function(resolve, reject) {
var proc = fork("bootstrap-nodejs.js", {cwd: "..", silent: true}),
out = "";
proc.on("error", reject);
proc.on("exit", function() { resolve(out); });
proc.stdout.on("data", d => out += String(d));
})
.then(out => expect(out).to.match(/\nDONE\n/m)))
});
}
/*global before, after, beforeEach, afterEach, describe, it*/
import { expect } from "mocha-es6";
import lang from "lively.lang";
import { es6, cjs } from "lively.vm";
import { Module } from "module";
// yet-another-cjs-module depends on another-cjs-module
// another-cjs-module depends on some-cjs-module
var module1, module2, module3, module1Full, module2Full, module3Full, parentModule;
var __filename = cjs.resolve("./cjs-test.js");
function require(id) {
return Module._load(id, {id: __filename})
}
if (System.get("@system-env").node) {
describe("common-js modules", () => {
before(() => {
cjs.wrapModuleLoad();
module1 = "./test-resources/cjs/module1";
module2 = "./test-resources/cjs/module2";
module3 = "./test-resources/cjs/module3";
module1Full = cjs.resolve(module1, __filename);
module2Full = cjs.resolve(module2, __filename);
module3Full = cjs.resolve(module3, __filename);
parentModule = __filename;
});
after(() => cjs.unwrapModuleLoad());
beforeEach(() => require(module1));
afterEach(() => cjs.forgetModule(module1));
describe("module state", () => {
it("captures internal module state", () => {
expect(cjs.envFor(module1))
.deep.property('recorder.internalState').equals(23);
expect(cjs.envFor(module1))
.deep.property('recorder.exports.state').equals(42);
});
});
describe("eval", () => {
it("evaluates inside of module", () =>
cjs.runEval("internalState", {targetModule: module1, parentModule: parentModule})
.then(evalResult => expect(evalResult).property("value").equals(23)));
});
describe("reloading", () => {
beforeEach(() => require(module3));
afterEach(() => cjs.forgetModule(module3));
it("computes required modules of some module", () => {
expect(cjs.findRequirementsOf(module3)).to.deep.equal(
[cjs.resolve(module2Full), cjs.resolve(module1Full), "fs"]);
});
it("computes dependent modules of some module", () => {
expect(cjs.findDependentsOf(module1)).to.deep.equal(
[parentModule, cjs.resolve(module2Full), cjs.resolve(module3Full)]);
});
it("can reload module dependencies", () => {
expect(require(module3).myVal).to.equal(44);
// we change module1 and check that the value of module3 that indirectly
// depends on module1 has changed as well
return cjs.sourceOf(module1, parentModule)
.then(s => s.replace(/(externalState = )([0-9]+)/, "$123"))
.then(s => cjs.runEval(s, {targetModule: module1, parentModule: parentModule}))
.then(() => cjs.forgetModuleDeps(module1, parentModule))
.then(() => expect(require(module3).myVal).to.equal(25))
});
});
});
}
/*global process, require, global, __dirname*/
var isNode = System.get("@system-env").node
var GLOBAL = typeof window !== "undefined" ? window : (typeof Global !== "undefined" ? Global : global);
var debug = false;
// var debug = true;
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// utils
import * as path from "path";
import * as lang from "lively.lang";
var join = lang.string.joinPath,
isAbsolute = p => !!p.match(/^(\/|[^\/]+:\/\/)/),
relative = path.relative || ((base, path) => path),
dirname = path => path.replace(/\/[^\/]+$/, "");
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// node interface
import { Module } from "module";
var nodeModules = isNode ? {
get cache() { return Module._cache; },
get require() { return (name, parent) => System._nodeRequire(name, parent); },
get resolve() { return (name, parent) => Module._resolveFilename(name, parent); },
} : {
get cache() {
console.warn("[lively.vm cjs] module cache accessor used on non-node system");
return {};
},
get require() {
return (name, parent) => {
console.warn("[lively.vm cjs] require used on non-node system");
return undefined;
};
},
get resolve() {
return (name, parent) => {
console.warn("[lively.vm cjs] resolveFilename used on non-node system");
return name;
};
}
}
var __dirname = System.normalizeSync("lively.vm/lib/").replace(/^[^:]+:\/\//, '');
var __filename = isNode ?
join(__dirname, "commonjs-interface.js") :
System.normalizeSync("lively.vm/lib/commonjs-interface.js").replace(/^[^:]+:\/\//, '');
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// other deps
import * as fs from "fs";
import * as evaluator from "./evaluator";
function callsite(){
var orig = Error.prepareStackTrace;
Error.prepareStackTrace = function(_, stack){ return stack; };
var err = new Error;
Error.captureStackTrace(err, arguments.callee);
var stack = err.stack;
Error.prepareStackTrace = orig;
return stack;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// helper
function resolve(file, parent) {
if (typeof parent === "string") parent = {id: parent};
// normal resolve
try {
return nodeModules.resolve(file, parent);
} catch (e) {}
// manual lookup using path
if (!isAbsolute(file) && parent) {
if (!file.match(/\.js$/)) file += ".js";
try {
return nodeModules.resolve(join(parent.id, "..", file));
} catch (e) {}
}
// manual lookup using callstack
var frames = callsite(), frame;
for (var i = 2; i < frames.length; i++) {
frame = frames[i];
var frameFile = frame.getFileName();
if (!frameFile) continue;
var dir = dirname(frameFile),
full = join(dir, file);
try { return nodeModules.resolve(full, parent); } catch (e) {}
}
// last resort: current working directory
try {
return nodeModules.resolve(join(process.cwd(), file), parent);
} catch (e) {}
return file;
}
function sourceOf(moduleName, parent) {
var read = lang.promise.convertCallbackFun(fs.readFile);
return read(resolve(moduleName, parent)).then(String);
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// module wrapping + loading
// maps filenames to envs = {isLoaded: BOOL, loadError: ERROR, recorder: OBJECT}
var loadedModules = {},
requireMap = {},
originalCompile = null, originalLoad = null,
// exceptions = [
// // (id) => console.log(!!id.match(/lib\/commonjs-interface\.js$/)) || id.match(/lib\/commonjs-interface\.js$/),
// (id) => id.indexOf("commonjs-interface.js") > -1,
// (id) => ["lively.lang/node_modules", "lively.vm/node_modules", "code-pump/node_modules"]
// .some(ignore => id.indexOf(ignore) > -1)
// ],
// exceptions = [module.filename],
exceptions = [],
scratchModule = join(__dirname, "commonjs-scratch.js"); // fallback eval target
function getExceptions() { return exceptions; }
function setExceptions(value) { return exceptions = value; }
function instrumentedFiles() { return Object.keys(loadedModules); }
function isLoaded(fileName) { return nodeModules.cache[fileName] && fileName in loadedModules; }
function ensureRecorder(fullName) { return ensureEnv(fullName).recorder; }
function ensureEnv(fullName) {
return loadedModules[fullName]
|| (loadedModules[fullName] = {
isInstrumented: false,
loadError: undefined,
// recorderName: "eval_rec_" + path.basename(fullName).replace(/[^a-z]/gi, "_"),
// recorderName: "eval_rec_" + fullName.replace(/[^a-z]/gi, "_"),
recorderName: "__lv_rec__",
recorder: Object.create(GLOBAL)
});
}
function prepareCodeForCustomCompile(source, filename, env) {
source = String(source);
var magicVars = ["exports", "require", "module", "__filename", "__dirname"],
tfmOptions = {
topLevelVarRecorder: env.recorder,
varRecorderName: env.recorderName,
dontTransform: [env.recorderName, "global"].concat(magicVars),
recordGlobals: true
},
header = `var __cjs = System.get('file://${__filename}'),\n ${env.recorderName} = __cjs.envFor('${filename}').recorder;\n`
+ magicVars.map(varName => `${env.recorderName}.${varName} = ${varName};`).join("\n");
try {
return (header + "\n"
+ ";(function() {\n"
+ evaluator.evalCodeTransform(source, tfmOptions)
+ "\n})();");
} catch (e) { return e; }
}
var loadDepth = 0;
function customCompile(content, filename) {
// wraps Module.prototype._compile to capture top-level module definitions
if (exceptions.some(exc => exc(filename)) || isLoaded(filename))
return originalCompile.call(this, content, filename);
debug && console.log("[lively.vm customCompile] %s", filename);
// console.log(lang.string.indent("[lively.vm commonjs] loads %s", " ", loadDepth), filename);
// if cache was cleared also reset our recorded state
if (!nodeModules.cache[filename] && loadedModules[filename])
delete loadedModules[filename];
var env = ensureEnv(filename),
_ = env.isInstrumented = true,
tfmedContent = prepareCodeForCustomCompile(content, filename, env);
if (tfmedContent instanceof Error) {
console.warn("Cannot compile module %s:", filename, tfmedContent);
env.loadError = tfmedContent;
var result = originalCompile.call(this, content, filename);
return result;
}
GLOBAL[env.recorderName] = env.recorder;
loadDepth++;
try {
var result = originalCompile.call(this, tfmedContent, filename);
env.loadError = undefined;
return result;
} catch (e) {
console.log("-=-=-=-=-=-=-=-");
console.error("[lively.vm commonjs] evaluator error loading module: ", e.stack || e);
console.log("-=-=-=-=-=-=-=-");
console.log(tfmedContent);
console.log("-=-=-=-=-=-=-=-");
env.loadError = e; throw e;
} finally {
loadDepth--;
// console.log(lang.string.indent("[lively.vm commonjs] done loading %s", " ", loadDepth), filename);
// delete global[env.recorderName];
}
}
function customLoad(request, parent, isMain) {
// console.log("LOADING ", request, parent ? parent.id : "NO PARENT")
var id = resolve(request, parent),
parentId = resolve(parent.id);
if (exceptions.some(exc => exc(id)) || exceptions.some(exc => exc(parentId)))
return originalLoad.call(this, request, parent, isMain);
if (debug) {
var parentRel = relative(process.cwd(), parentId);
console.log(lang.string.indent("[lively.vm cjs dependency] %s -> %s", " ", loadDepth), parentRel, request);
// console.log(id);
}
if (!requireMap[parent.id]) requireMap[parent.id] = [id];
else requireMap[parent.id].push(id);
return originalLoad.call(this, request, parent, isMain);
}
function wrapModuleLoad() {
if (!originalCompile)
originalCompile = Module.prototype._compile;
Module.prototype._compile = customCompile;
if (!originalLoad)
originalLoad = Module._load;
Module._load = customLoad;
}
function unwrapModuleLoad() {
if (originalCompile)
Module.prototype._compile = originalCompile;
if (originalLoad)
Module._load = originalLoad;
}
function envFor(moduleName) {
// var fullName = require.resolve(moduleName);
var fullName = resolve(moduleName);
return ensureEnv(fullName);
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// instrumented code evaluation
function runEval(code, options) {
options = lang.obj.merge({targetModule: null, parentModule: null}, options);
return Promise.resolve().then(() => {
if (!options.targetModule) {
options.targetModule = scratchModule;
} else {
options.targetModule = resolve(options.targetModule, options.parentModule);
}
var fullName = resolve(options.targetModule);
if (!nodeModules.cache[fullName]) {
try {
nodeModules.require(fullName);
} catch (e) {
throw new Error(`Cannot load module ${options.targetModule} (tried as ${fullName})\noriginal load error: ${e.stack}`)
}
}
var m = nodeModules.cache[fullName],
env = envFor(fullName),
rec = env.recorder,
recName = env.recorderName;
rec.__filename = m.filename;
var d = rec.__dirname = dirname(m.filename);
// rec.require = function(fname) {
// if (!path.isAbsolute(fname))
// fname = path.join(d, fname);
// return Module._load(fname, m);
// };
rec.exports = m.exports;
rec.module = m;
GLOBAL[recName] = rec;
options = lang.obj.merge(options, {
recordGlobals: true,
dontTransform: [recName, "global"],
varRecorderName: recName,
topLevelVarRecorder: rec,
sourceURL: options.targetModule,
context: rec.exports || {}
});
return evaluator.runEval(code, options);
});
}
function importCjsModule(name) {
return new Promise(ok => ok(nodeModules.require(resolve(name))));
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// module runtime status
function status(thenDo) {
var files = Object.keys(loadedModules),
envs = files.reduce((envs, fn) => {
envs[fn] = {
loadError: loadedModules[fn].loadError,
isLoaded: isLoaded(fn),
recorderName: loadedModules[fn].recorderName,
isInstrumented: loadedModules[fn].isInstrumented,
recordedVariables: Object.keys(loadedModules[fn].recorder)
}
return envs;
}, {});
if (typeof thenDo === "function") thenDo(null, envs);
return Promise.resolve(envs);
}
function statusForPrinted(moduleName, options, thenDo) {
options = lang.obj.merge({depth: 3}, options);
var env = envFor(moduleName);
var state = {
loadError: env.loadError,
recorderName: env.recorderName,
recordedVariables: env.recorder
}
thenDo(null, lang.obj.inspect(state, {maxDepth: options.depth}));
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// module reloading
function _invalidateCacheForModules(fullModuleIds, moduleMap) {
fullModuleIds.forEach(id => {
delete moduleMap[id];
delete loadedModules[id];
});
}
function forgetModule(moduleName, parent) {
var id = resolve(moduleName, parent),
deps = findDependentsOf(id);
_invalidateCacheForModules([id].concat(deps), Module._cache);
return id;
}
function forgetModuleDeps(moduleName, parent) {
var id = resolve(moduleName, parent),
deps = findDependentsOf(id);
_invalidateCacheForModules(deps, Module._cache);
return id;
}
function reloadModule(moduleName, parent) {
var id = forgetModule(moduleName, parent);
return nodeModules.require(id);
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// module dependencies
function findDependentsOf(id) {
// which modules (module ids) are (in)directly required by module with id
// Let's say you have
// module1: exports.x = 23;
// module2: exports.y = require("./module1").x + 1;
// module3: exports.z = require("./module2").y + 1;
// `findDependentsOf` gives you an answer what modules are "stale" when you
// change module1
return lang.graph.hull(lang.graph.invert(requireMap), resolve(id));
}
function findRequirementsOf(id) {
// which modules (module ids) are (in)directly required by module with id
// Let's say you have
// module1: exports.x = 23;
// module2: exports.y = require("./module1").x + 1;
// module3: exports.z = require("./module2").y + 1;
// `findRequirementsOf("./module3")` will report ./module2 and ./module1
return lang.graph.hull(requireMap, resolve(id));
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
export {
// internals
requireMap as _requireMap,
loadedModules as _loadedModules,
instrumentedFiles,
prepareCodeForCustomCompile as _prepareCodeForCustomCompile,
getExceptions as _getExceptions,
setExceptions as _setExceptions,
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// accessing
sourceOf,
envFor,
status,
statusForPrinted,
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// eval + changes
runEval,
// sourceChange,
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// loading + dependencies
// reloadModule,
resolve,
importCjsModule as import,
reloadModule,
forgetModule,
forgetModuleDeps,
findRequirementsOf,
findDependentsOf,
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// instrumentation
wrapModuleLoad,
unwrapModuleLoad
}
/*global process, before, beforeEach, afterEach, describe, it, expect*/
import { expect } from "mocha-es6";
import * as vm from "lively.vm";
import lang from "lively.lang";
var Global = typeof global !== "undefined" ? global : window;
var es6 = vm.es6;
var cjs = vm.cjs;
var cjsModule1 = "test-resources/es6-with-cjs/module1.js";
var cjsModule2 = "test-resources/cjs/module2.js";
var es6Module1 = "test-resources/es6/module1.js";
var es6Module2 = "test-resources/es6-with-cjs/module2.js";
var cjsModule1Id = cjs.resolve(cjsModule1, __filename);;
describe("es6 + node modules", () => {
before(function() {
es6._init({
baseURL: env.isCommonJS ? 'tests/' : document.URL.replace(/\/[^\/]*$/, ""),
transpiler: 'babel', babelOptions: {},
map: env.isCommonJS ? {} : {babel: '../node_modules/babel-core/browser.js'}
});
es6.wrapModuleLoad();
cjs.wrapModuleLoad();
});
beforeEach(() => {
cjs.forgetModule(cjsModule1);
es6.forgetModule(es6Module1);
});
describe("cjs loads es6", () => {
it("succeeds", () =>
lang.promise.delay(120, // wait for es6 module to be loaded
cjs.import(cjsModule1Id).then(m => expect(m.x).to.equal(1)))
.then(() => expect(cjs.envFor(cjsModule1Id).recorder.loaded).to.equal(true))
.then(() => expect(es6.envFor(es6.resolve(es6Module1)).recorder.x).to.equal(3)));
});
describe("es6 loads cjs", () => {
it("succeeds", () =>
es6.import(es6Module2)
.then(m => expect(m.y).to.equal(43))
.then(() => expect(es6.envFor(es6.resolve(es6Module2)).recorder.val).to.equal(43))
.then(() => expect(cjs.envFor(cjs.resolve(cjsModule2, __filename)).recorder.someVal).to.equal(43)));
});
});
import path from "path";
import { arr, obj, string, graph, properties, classHelper } from "lively.lang";
import * as ast from "lively.ast";
import * as evaluator from "./evaluator";
import * as cjs from "./commonjs-interface.js";
import { Module } from "module";
var __require = (name, parent) => Module._load(name, parent);
var GLOBAL = typeof window !== "undefined" ? window : (typeof Global !== "undefined" ? Global : global);
function currentSystem() { return GLOBAL.System; }
var SystemLoader = currentSystem().constructor;
SystemLoader.prototype.__defineGetter__("__lively_vm__", function() {
return {
envFor: envFor,
evaluationDone: function(moduleId) {
var env = envFor(moduleId);
addGetterSettersForNewVars(moduleId, env);
runScheduledExportChanges(moduleId);
},
dumpConfig: function() {
var System = currentSystem(),
json = {
baseURL: System.baseURL,
transpiler: System.transpiler,
map: System.map,
meta: System.meta,
packages: System.packages,
paths: System.paths,
packageConfigPaths: System.packageConfigPaths
}
return JSON.stringify(json, null, 2);
},
loadedModules: this.__lively_vm__loadedModules || (this.__lively_vm__loadedModules = {})
}
});
var isNode = currentSystem().get("@system-env").node;
var debug = false;
// var debug = true;
function relative(a, b) {
return !path || !path.relative ? b : relative(a,b);
}
function relativeName(name) {
var base = currentSystem().baseURL.replace(/^[\w]+:\/\//, ""),
abs = name.replace(/^[\w]+:\/\//, "");
return relative(base, abs);
}
function join(pathA, pathB) {
return pathA.replace(/\/$/, "") + "/" + pathB.replace(/^\//, "");
}
var node_modulesDir = resolve("lively.vm/node_modules/");
var exceptions = [
// id => id.indexOf(resolve("node_modules/")) > -1,
id => canonicalURL(id).indexOf(node_modulesDir) > -1,
id => string.include(id, "babel-core/browser.js") || string.include(id, "system.src.js"),
// id => lang.string.include(id, "lively.ast.es6.bundle.js"),
id => id.slice(-3) !== ".js"
],
pendingConfigs = [], configInitialized = false,
esmFormatCommentRegExp = /['"]format (esm|es6)['"];/,
cjsFormatCommentRegExp = /['"]format cjs['"];/,
// Stolen from SystemJS
esmRegEx = /(^\s*|[}\);\n]\s*)(import\s+(['"]|(\*\s+as\s+)?[^"'\(\)\n;]+\s+from\s+['"]|\{)|export\s+\*\s+from\s+["']|export\s+(\{|default|function|class|var|const|let|async\s+function))/;
function getExceptions() { return exceptions; }
function setExceptions(v) { return exceptions = v; }
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// configuration
function init(cfg) {
var SystemLoader = currentSystem().constructor;
debug && console.log("[lively.vm es6] defining new System");
GLOBAL.System = new SystemLoader();
currentSystem().trace = true;
// _currentSystem.__defineGetter__("__lively_vm__", () => module.exports);
cfg = obj.merge({transpiler: 'babel', babelOptions: {}}, cfg);
if (currentSystem().get("@system-env").node) {
var nodejsCoreModules = ["addons", "assert", "buffer", "child_process",
"cluster", "console", "crypto", "dgram", "dns", "domain", "events", "fs",
"http", "https", "module", "net", "os", "path", "punycode", "querystring",
"readline", "repl", "stream", "stringdecoder", "timers", "tls",
"tty", "url", "util", "v8", "vm", "zlib"],
map = nodejsCoreModules.reduce((map, ea) => { map[ea] = "@node/" + ea; return map; }, {});
cfg.map = obj.merge(map, cfg.map);
// for sth l ike map: {"lively.lang": "node_modules:lively.lang"}
cfg.paths = obj.merge({"node_modules:*": "./node_modules/*"}, cfg.paths);
cfg.packageConfigPaths = cfg.packageConfigPaths || ['./node_modules/*/package.json'];
if (!cfg.hasOwnProperty("defaultJSExtensions")) cfg.defaultJSExtensions = true;
}
config(cfg);
}
function config(cfg) {
// First config call needs to have baseURL. To still allow setting other
// config parameters we cache non-baseURL calls that come before and run them
// as soon as we get the baseURL
if (!configInitialized && !cfg.baseURL) {
debug && console.log("[lively.vm es6 config call queued]");
pendingConfigs.push(cfg);
return;
}
debug && console.log("[lively.vm es6 System] config");
currentSystem().config(cfg);
if (!configInitialized) {
configInitialized = true;
pendingConfigs.forEach(ea => currentSystem().config(ea));
}
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// loading
function importES6Module(path, options) {
if (typeof options !== "undefined") config(options);
return currentSystem().import(path);
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// module environment + runtime state
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function instrumentedFiles() { return Object.keys(currentSystem().__lively_vm__.loadedModules); }
function isLoaded(fullname) { return fullname in currentSystem().__lively_vm__.loadedModules; }
function canonicalURL(url) {
// removes double slashes, doesn't resolve relative parts yet
var m = url.match(/([^:]+:\/\/)(.*)/);
if (m) {
var protocol = m[1];
url = m[2];
}
url = url.replace(/([^:])\/[\/]+/g, "$1/");
return (protocol || "") + url;
}
function resolve(name, parentName, parentAddress) {
// if (name.match(/^([\w+_]+:)?\/\//)) return name;
return canonicalURL(currentSystem().normalizeSync(name, parentName, parentAddress));
}
function addGetterSettersForNewVars(moduleId, env) {
// after eval we modify the env so that all captures vars are wrapped in
// getter/setter to be notified of changes
// FIXME: better to not capture via assignments but use func calls...!
var prefix = "__lively.vm__";
Object.keys(env).forEach(key => {
if (key.indexOf(prefix) === 0 || env.__lookupGetter__(key)) return;
env[prefix + key] = env[key];
env.__defineGetter__(key, () => env[prefix + key]);
env.__defineSetter__(key, (v) => {
scheduleModuleExportsChange(moduleId, key, v, false/*add export*/);
return env[prefix + key] = v;
});
});
}
function envFor(fullname) {
if (currentSystem().__lively_vm__.loadedModules[fullname]) return currentSystem().__lively_vm__.loadedModules[fullname];
var env = currentSystem().__lively_vm__.loadedModules[fullname] = {
loadError: undefined,
recorderName: "__rec__",
// recorderName: "__lvVarRecorder",
dontTransform: ["__lively_vm__", "__rec__", "__lvVarRecorder", "global", "System", "_moduleExport", "_moduleImport"].concat(ast.query.knownGlobals),
recorder: Object.create(GLOBAL, {
_moduleExport: {
get() { return (name, val) => scheduleModuleExportsChange(fullname, name, val, true/*add export*/); }
},
_moduleImport: {
get: function() {
return (moduleName, name) => {
var fullModuleName = resolve(moduleName, fullname),
imported = currentSystem()._loader.modules[fullModuleName];
if (!imported) throw new Error(`import of ${name} failed: ${moduleName} (tried as ${fullModuleName}) is not loaded!`);
if (name == undefined)
return imported.module;
if (!imported.module.hasOwnProperty(name))
console.warn(`import from ${moduleName}: Has no export ${name}!`);
return imported.module[name];
// var fullModuleName = resolve(moduleName, fullname),
// rec = moduleRecordFor(fullModuleName);
// if (!rec) throw new Error(`import of ${name} failed: ${moduleName} (tried as ${fullModuleName}) is not loaded!`);
// return rec.exports[name];
}
}
}
})
}
return env;
}
function moduleRecordFor(fullname) {
var record = currentSystem()._loader.moduleRecords[fullname];
if (!record) return null;
if (!record.hasOwnProperty("__lively_vm__")) record.__lively_vm__ = {
evalOnlyExport: {}
};
return record;
}
function updateModuleRecordOf(fullname, doFunc) {
var record = moduleRecordFor(fullname);
if (!record) throw new Error(`es6 environment global of ${fullname}: module not loaded, cannot get export object!`);
record.locked = true;
try {
doFunc(record);
} finally { record.locked = false; }
}
function sourceOf(moduleName, parent) {
var name = resolve(moduleName),
load = (currentSystem().loads && currentSystem().loads[name]) || {
status: 'loading', address: name, name: name,
linkSets: [], dependencies: [], metadata: {}};
return currentSystem().fetch(load);
}
function importsAndExportsOf(moduleName) {
return currentSystem().normalize(moduleName)
.then(id =>
Promise.resolve(sourceOf(id))
.then(source => {
var parsed = ast.parse(source),
scope = ast.query.scopes(parsed);
// compute imports
var imports = scope.importDecls.reduce((imports, node) => {
var nodes = ast.query.nodesAtIndex(parsed, node.start);
var importStmt = arr.without(nodes, scope.node)[0];
if (!importStmt) return imports;
var from = importStmt.source ? importStmt.source.value : "unknown module";
if (!importStmt.specifiers.length) // no imported vars
return imports.concat([{
localModule: id,
local: null,
imported: null,
fromModule: from,
importStatement: importStmt
}]);
return imports.concat(importStmt.specifiers.map(importSpec => {
var imported;
if (importSpec.type === "ImportNamespaceSpecifier") imported = "*";
else if (importSpec.type === "ImportDefaultSpecifier") imported = "default";
else if (importStmt.source) imported = importStmt.source.name;
else imported = null;
return {
localModule: id,
local: importSpec.local ? importSpec.local.name : null,
imported: imported,
fromModule: from,
importStatement: importStmt
}
}))
}, []);
var exports = scope.exportDecls.reduce((exports, node) => {
var nodes = ast.query.nodesAtIndex(parsed, node.start);
var exportsStmt = arr.without(nodes, scope.node)[0];
if (!exportsStmt) return exports;
if (exportsStmt.type === "ExportAllDeclaration") {
var from = exportsStmt.source ? exportsStmt.source.value : null;
return exports.concat([{
localModule: id,
local: null,
exported: "*",
fromModule: from,
exportStatement: exportsStmt
}])
}
return exports.concat(exportsStmt.specifiers.map(exportSpec => {
return {
localModule: id,
local: exportSpec.local ? exportSpec.local.name : null,
exported: exportSpec.exported ? exportSpec.exported.name : null,
fromModule: id,
exportStatement: exportsStmt
}
}))
}, []);
return {
imports: arr.uniqBy(imports, (a, b) => a.local == b.local && a.imported == b.imported && a.fromModule == b.fromModule),
exports: arr.uniqBy(exports, (a, b) => a.local == b.local && a.exported == b.exported && a.fromModule == b.fromModule)
}
}))
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// update exports
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
var pendingExportChanges = {};
function scheduleModuleExportsChange(moduleId, name, value, addNewExport) {
var rec = moduleRecordFor(moduleId);
if (rec && (name in rec.exports || addNewExport)) {
var pending = pendingExportChanges[moduleId] || (pendingExportChanges[moduleId] = {});
pending[name] = value;
}
}
function runScheduledExportChanges(moduleId) {
var keysAndValues = pendingExportChanges[moduleId];
if (!keysAndValues) return;
clearPendingModuleExportChanges(moduleId);
updateModuleExports(moduleId, keysAndValues);
}
function clearPendingModuleExportChanges(moduleId) {
delete pendingExportChanges[moduleId];
}
function updateModuleExports(moduleId, keysAndValues) {
updateModuleRecordOf(moduleId, (record) => {
var newExports = [], existingExports = [];
Object.keys(keysAndValues).forEach(name => {
var value = keysAndValues[name];
debug && console.log("[lively.vm es6 updateModuleExports] %s export %s = %s", relativeName(moduleId), name, String(value).slice(0,30).replace(/\n/g, "") + "...");
var isNewExport = !(name in record.exports);
if (isNewExport) record.__lively_vm__.evalOnlyExport[name] = true;
// var isEvalOnlyExport = record.__lively_vm__.evalOnlyExport[name];
record.exports[name] = value;
if (isNewExport) newExports.push(name);
else existingExports.push(name);
});
// if it's a new export we don't need to update dependencies, just the
// module itself since no depends know about the export...
// HMM... what about *-imports?
newExports.forEach(name => {
var oldM = currentSystem()._loader.modules[moduleId].module,
m = currentSystem()._loader.modules[moduleId].module = new oldM.constructor(),
pNames = Object.getOwnPropertyNames(record.exports);
for (var i = 0; i < pNames.length; i++) (function(key) {
Object.defineProperty(m, key, {
configurable: false, enumerable: true,
get() { return record.exports[key]; }
});
})(pNames[i]);
// Object.defineProperty(System._loader.modules[fullname].module, name, {
// configurable: false, enumerable: true,
// get() { return record.exports[name]; }
// });
});
// For exising exports we find the execution func of each dependent module and run that
// FIXME this means we run the entire modules again, side effects and all!!!
if (existingExports.length) {
debug && console.log("[lively.vm es6 updateModuleExports] updating %s dependents of %s", record.importers.length, relativeName(moduleId));
for (var i = 0, l = record.importers.length; i < l; i++) {
var importerModule = record.importers[i];
if (!importerModule.locked) {
var importerIndex = importerModule.dependencies.indexOf(record);
importerModule.setters[importerIndex](record.exports);
importerModule.execute();
}
}
}
});
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// code instrumentation
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function prepareCodeForCustomCompile(source, fullname, env) {
source = String(source);
var tfmOptions = {
topLevelVarRecorder: env.recorder,
varRecorderName: env.recorderName,
dontTransform: env.dontTransform,
recordGlobals: true
},
header = (debug ? `console.log("[lively.vm es6] executing module ${relativeName(fullname)}");\n` : "")
+ `var __lively_vm__ = System.__lively_vm__, ${env.recorderName} = __lively_vm__.envFor("${fullname}").recorder;\n`,
footer = `\n__lively_vm__.evaluationDone("${fullname}");`;
try {
return header + evaluator.evalCodeTransform(source, tfmOptions) + footer;
} catch (e) {
console.error("Error in prepareCodeForCustomCompile", e.stack);
return source;
}
}
function getCachedNodejsModule(load) {
// On nodejs we might run alongside normal node modules. To not load those
// twice we have this little hack...
try {
var Module = __require("module").Module,
id = Module._resolveFilename(load.name.replace(/^file:\/\//, "")),
nodeModule = Module._cache[id];
return nodeModule;
} catch (e) {
debug && console.log("[lively.vm es6 getCachedNodejsModule] %s unknown to nodejs", relativeName(load.name));
}
return null;
}
function addNodejsWrapperSource(load) {
// On nodejs we might run alongside normal node modules. To not load those
// twice we have this little hack...
var m = getCachedNodejsModule(load);
if (m) {
load.source = `export default System._nodeRequire('${m.id}');\n`;
load.source += properties.allOwnPropertiesOrFunctions(m.exports).map(k =>
classHelper.isValidIdentifier(k) ?
`export var ${k} = System._nodeRequire('${m.id}')['${k}'];` :
`/*ignoring export "${k}" b/c it is not a valid identifier*/`).join("\n")
debug && console.log("[lively.vm es6 customTranslate] loading %s from nodejs module cache", relativeName(load.name));
return true;
}
debug && console.log("[lively.vm es6 customTranslate] %s not yet in nodejs module cache", relativeName(load.name));
return false;
}
function customTranslate(proceed, load) {
// load like
// {
// address: "file:///Users/robert/Lively/lively-dev/lively.vm/tests/test-resources/some-es6-module.js",
// name: "file:///Users/robert/Lively/lively-dev/lively.vm/tests/test-resources/some-es6-module.js",
// metadata: { deps: [/*...*/], entry: {/*...*/}, format: "esm", sourceMap: ... },
// source: "..."
// }
if (exceptions.some(exc => exc(load.name))) {
debug && console.log("[lively.vm es6 customTranslate ignoring] %s", relativeName(load.name));
return proceed(load);
}
if (currentSystem().get("@system-env").node && addNodejsWrapperSource(load)) {
debug && console.log("[lively.vm es6] loaded %s from nodejs cache", relativeName(load.name))
return proceed(load);
}
var start = Date.now();
var isEsm = load.metadata.format == 'esm' || load.metadata.format == 'es6'
|| (!load.metadata.format && esmFormatCommentRegExp.test(load.source.slice(0,5000)))
|| (!load.metadata.format && !cjsFormatCommentRegExp.test(load.source.slice(0,5000)) && esmRegEx.test(load.source)),
isCjs = load.metadata.format == 'cjs',
isGlobal = load.metadata.format == 'global';
// console.log(load.name + " isEsm? " + isEsm)
if (isEsm) {
load.metadata.format = "esm";
load.source = prepareCodeForCustomCompile(load.source, load.name, envFor(load.name));
load.metadata["lively.vm instrumented"] = true;
debug && console.log("[lively.vm es6] loaded %s as es6 module", relativeName(load.name))
// debug && console.log(load.source)
} else if (isCjs && isNode) {
load.metadata.format = "cjs";
var id = cjs.resolve(load.address.replace(/^file:\/\//, ""));
load.source = cjs._prepareCodeForCustomCompile(load.source, id, cjs.envFor(id));
load.metadata["lively.vm instrumented"] = true;
debug && console.log("[lively.vm es6] loaded %s as instrumented cjs module", relativeName(load.name))
// console.log("[lively.vm es6] no rewrite for cjs module", load.name)
} else if (isGlobal) {
load.source = prepareCodeForCustomCompile(load.source, load.name, envFor(load.name));
load.metadata["lively.vm instrumented"] = true;
} else {
debug && console.log("[lively.vm es6] customTranslate ignoring %s b/c don't know how to handle global format", relativeName(load.name));
}
debug && console.log("[lively.vm es6 customTranslate] done %s after %sms", relativeName(load.name), Date.now()-start);
return proceed(load);
}
function wrapModuleLoad() {
if (!currentSystem().origTranslate) {
currentSystem().origTranslate = currentSystem().translate
currentSystem().translate = function(load) {
return customTranslate(currentSystem().origTranslate.bind(currentSystem()), load);
}
}
}
function unwrapModuleLoad() {
if (currentSystem().origTranslate) {
currentSystem().translate = currentSystem().origTranslate;
delete currentSystem().origTranslate;
}
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// evaluation
function ensureImportsAreLoaded(code, parentModule) {
var body = ast.parse(code).body,
imports = body.filter(node => node.type === "ImportDeclaration");
return Promise.all(imports.map(node => {
var fullName = resolve(node.source.value, parentModule);
return moduleRecordFor(fullName) ? undefined : currentSystem().import(fullName);
})).catch(err => {
console.error("Error ensuring imports: " + err.message);
throw err;
});
}
function runEval(code, options) {
options = obj.merge({
targetModule: null, parentModule: null,
parentAddress: null
}, options);
return Promise.resolve().then(() => {
// if (!options.targetModule) return reject(new Error("options.targetModule not defined"));
if (!options.targetModule) {
options.targetModule = "*scratch*"
// resolve(options.targetModule);
} else {
options.targetModule = resolve(options.targetModule, options.parentModule || currentSystem().baseURL, options.parentAddress);
}
var fullname = options.targetModule;
// throw new Error(`Cannot load module ${options.targetModule} (tried as ${fullName})\noriginal load error: ${e.stack}`)
return importES6Module(fullname)
.then(() => ensureImportsAreLoaded(code, options.targetModule))
.then(() => {
var env = envFor(fullname),
rec = env.recorder,
recName = env.recorderName,
header = `var ${recName} = System.__lively_vm__.envFor("${fullname}").recorder,\n`
+ ` _moduleExport = ${recName}._moduleExport,\n`
+ ` _moduleImport = ${recName}._moduleImport;\n`;
options = obj.merge(
{waitForPromise: true},
options, {
recordGlobals: true,
dontTransform: env.dontTransform,
varRecorderName: recName,
topLevelVarRecorder: rec,
sourceURL: options.sourceURL || options.targetModule,
context: rec,
es6ExportFuncId: "_moduleExport",
es6ImportFuncId: "_moduleImport",
header: header
});
clearPendingModuleExportChanges(fullname);
return evaluator.runEval(code, options).then(result => {
currentSystem().__lively_vm__.evaluationDone(fullname); return result; })
})
// .catch(err => console.error(err) || err)
});
}
function sourceChange(moduleName, newSource, options) {
var fullname = resolve(moduleName),
load = {
status: 'loading',
source: newSource,
name: fullname,
linkSets: [],
dependencies: [],
metadata: {format: "esm"}
};
return (currentSystem().get(fullname) ? Promise.resolve() : importES6Module(fullname))
.then((_) => _systemTranslateParsed(load))
.then(updateData => {
var record = moduleRecordFor(fullname),
_exports = (name, val) => scheduleModuleExportsChange(fullname, name, val),
declared = updateData.declare(_exports);
currentSystem().__lively_vm__.evaluationDone(fullname);
// ensure dependencies are loaded
debug && console.log("[lively.vm es6] sourceChange of %s with deps", fullname, updateData.localDeps);
return Promise.all(
// gather the data we need for the update, this includes looking up the
// imported modules and getting the module record and module object as
// a fallback (module records only exist for esm modules)
updateData.localDeps.map(depName =>
currentSystem().normalize(depName, fullname)
.then(depFullname => {
var depModule = currentSystem().get(depFullname),
record = moduleRecordFor(depFullname);
return depModule && record ?
{name: depName, fullname: depFullname, module: depModule, record: record} :
importES6Module(depFullname).then((module) => ({
name: depName,
fullname: depFullname,
module: currentSystem().get(depFullname) || module,
record: moduleRecordFor(depFullname)
}));
})))
.then(deps => {
// 1. update dependencies
record.dependencies = deps.map(ea => ea.record);
// hmm... for house keeping... not really needed right now, though
var load = currentSystem().loads && currentSystem().loads[fullname];
if (load) {
load.deps = deps.map(ea => ea.name);
load.depMap = deps.reduce((map, dep) => { map[dep.name] = dep.fullname; return map; }, {});
if (load.metadata && load.metadata.entry) {
load.metadata.entry.deps = load.deps;
load.metadata.entry.normalizedDeps = deps.map(ea => ea.fullname);
load.metadata.entry.declare = updateData.declare;
}
}
// 2. run setters to populate imports
deps.forEach((d,i) => declared.setters[i](d.module));
// 3. execute module body
return declared.execute();
});
});
}
function _systemTranslateParsed(load) {
// brittle!
// The result of System.translate is source code for a call to
// System.register that can't be run standalone. We parse the necessary
// details from it that we will use to re-define the module
// (dependencies, setters, execute)
return currentSystem().translate(load).then(translated => {
// translated looks like
// (function(__moduleName){System.register(["./some-es6-module.js", ...], function (_export) {
// "use strict";
// var x, z, y;
// return {
// setters: [function (_someEs6ModuleJs) { ... }],
// execute: function () {...}
// };
// });
var parsed = ast.parse(translated),
call = parsed.body[0].expression,
moduleName = call.arguments[0].value,
registerCall = call.callee.body.body[0].expression,
depNames = arr.pluck(registerCall["arguments"][0].elements, "value"),
declareFuncNode = call.callee.body.body[0].expression["arguments"][1],
declareFuncSource = translated.slice(declareFuncNode.start, declareFuncNode.end),
declare = eval(`var __moduleName = "${moduleName}";(${declareFuncSource});\n//@ sourceURL=${moduleName}\n`);
if (typeof $morph !== "undefined" && $morph("log")) $morph("log").textString = declare;
return {localDeps: depNames, declare: declare};
});
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// module dependencies
function modulesMatching(stringOrRegExp) {
var re = stringOrRegExp instanceof RegExp ? stringOrRegExp : new RegExp(stringOrRegExp);
return Object.keys(currentSystem()._loader.modules).filter(ea => stringOrRegExp.test(ea));
}
function forgetEnvOf(fullname) {
delete currentSystem().__lively_vm__.loadedModules[fullname]
}
function forgetModuleDeps(moduleName, opts) {
opts = obj.merge({forgetDeps: true, forgetEnv: true}, opts)
var id = resolve(moduleName),
deps = findDependentsOf(id);
deps.forEach(ea => {
currentSystem().delete(ea);
if (currentSystem().loads) delete currentSystem().loads[ea];
opts.forgetEnv && forgetEnvOf(ea);
});
return id;
}
function forgetModule(moduleName, opts) {
opts = obj.merge({forgetDeps: true, forgetEnv: true}, opts);
var id = opts.forgetDeps ? forgetModuleDeps(moduleName, opts) : resolve(moduleName);
currentSystem().delete(moduleName);
currentSystem().delete(id);
if (currentSystem().loads) {
delete currentSystem().loads[moduleName];
delete currentSystem().loads[id];
}
if (opts.forgetEnv) {
forgetEnvOf(id);
forgetEnvOf(moduleName);
}
}
function reloadModule(moduleName, opts) {
opts = obj.merge({reloadDeps: true, resetEnv: true}, opts);
var id = resolve(moduleName),
toBeReloaded = [id];
if (opts.reloadDeps) toBeReloaded = findDependentsOf(id).concat(toBeReloaded);
forgetModule(id, {forgetDeps: opts.reloadDeps, forgetEnv: opts.resetEnv});
return Promise.all(toBeReloaded.map(ea => ea !== id && importES6Module(ea)))
.then(() => importES6Module(id));
}
// function computeRequireMap() {
// return Object.keys(_currentSystem.loads).reduce((requireMap, k) => {
// requireMap[k] = lang.obj.values(_currentSystem.loads[k].depMap);
// return requireMap;
// }, {});
// }
function computeRequireMap() {
if (currentSystem().loads) {
var store = currentSystem().loads,
modNames = arr.uniq(Object.keys(currentSystem().__lively_vm__.loadedModules).concat(Object.keys(store)));
return modNames.reduce((requireMap, k) => {
var depMap = store[k] ? store[k].depMap : {};
requireMap[k] = Object.keys(depMap).map(localName => {
var resolvedName = depMap[localName];
if (resolvedName === "@empty") return `${resolvedName}/${localName}`;
return resolvedName;
})
return requireMap;
}, {});
}
return Object.keys(currentSystem()._loader.moduleRecords).reduce((requireMap, k) => {
requireMap[k] = currentSystem()._loader.moduleRecords[k].dependencies.filter(Boolean).map(ea => ea.name);
return requireMap;
}, {});
}
function findDependentsOf(id) {
// which modules (module ids) are (in)directly import module with id
// Let's say you have
// module1: export var x = 23;
// module2: import {x} from "module1.js"; export var y = x + 1;
// module3: import {y} from "module2.js"; export var z = y + 1;
// `findDependentsOf` gives you an answer what modules are "stale" when you
// change module1 = module2 + module3
return graph.hull(graph.invert(computeRequireMap()), resolve(id));
}
function findRequirementsOf(id) {
// which modules (module ids) are (in)directly required by module with id
// Let's say you have
// module1: export var x = 23;
// module2: import {x} from "module1.js"; export var y = x + 1;
// module3: import {y} from "module2.js"; export var z = y + 1;
// `findRequirementsOf("./module3")` will report ./module2 and ./module1
return graph.hull(computeRequireMap(), resolve(id));
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// packaging
function importPackage(packageLocation, options) {
options = obj.deepMerge({modules: [], config: {}}, options);
// add the package to the System's config
currentSystem().config({
packages: {
[packageLocation]: options.config
},
// hmm, indicate package.json, what if non existing?
packageConfigPaths: [join(packageLocation, "package.json")]
});
// either load default entrypoint or when options.modules is defined chain-
// load those modules
var mods = options.modules.map(ea =>
ea.indexOf(packageLocation) === 0 ?
ea : join(packageLocation, ea));
return mods.length ?
new Promise((resolve, reject) => {
var loadedModules = [];
mods.reduce( // load chain
(nextLoad, modName) =>
() => importES6Module(modName)
.then(mod => loadedModules.push(mod))
.then(nextLoad),
() => resolve(loadedModules))().catch(reject)
}) : importES6Module(packageLocation);
}
/*
importPackage("http://localhost:9001/acorn", {modules: ["dist/acorn.js", "dist/walk.js", "dist/acorn_loose.js"]})
.then(show.curry("%o"))
.catch(show.curry("%s"))
forgetModule("http://localhost:9001/acorn/src/index.js")
Object.keys(computeRequireMap()).grep("9001/acorn").forEach(ea => forgetModule(ea));
var c = {map: {"./src.js": "http://localhost:9001/acorn/src/index.js"}}
var opts = {modules: ["src/index.js", "src/walk/index.js", "src/loose/index.js"], config: c}
importPackage("http://localhost:9001/acorn", opts)
.then(show.curry("%s"))
.catch(show.curry("%s"))
importPackage("http://localhost:9001/lively.ast-es6", {
modules: ["index.js"],
config: {}
}).then(show.curry("%s")).catch(show.curry("%s"))
*/
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
/*
function matchNames(name) { return name.startsWith("http://localhost:9001/acorn") && !name.include("dist/"); }
function transformNames(name) { return name.replace(/http:\/\/localhost:9001\//, ""); }
// URL.root.withFilename("traced.json").asWebResource().put(JSON.stringify(trace, null, 2));
var outfile = 'test-acorn-bundle.js'
var c = {
baseURL: ".",
transpiler: "babel",
defaultJSExtensions: true,
map: {
babel: join(lively.shell.WORKSPACE_LK, "node_modules/babel-core/browser.js")
}
}
exportTrace("acorn", outfile, matchNames, transformNames, c, {transpiler: "babel", defaultJSExtensions: true}).then(() => show("OK")).catch(show.curry("%s"))
*/
function exportTrace(bundleName, outfile, matchNames, transformNames, builderConfig, bundleConfig) {
var trace = Object.keys(currentSystem().loads)
.filter(matchNames)
.reduce((trace, name) => {
var load = currentSystem().loads[name];
load.metadata && load.metadata && load.metadata.entry && (delete load.metadata.entry);
// delete load.source
load.source = getOrigSource(name)
if (load.name) load.name = transformNames(load.name);
if (load.address) load.address = transformNames(load.address);
load.depMap = Object.keys(load.depMap).reduce((depMap, name) => {
depMap[name] = transformNames(load.depMap[name]);
return depMap;
}, {});
// load.metadata && load.metadata && load.metadata.entry && (load.metadata.entry.module = null);
trace[name] = load;
return trace;
}, {});
return _runBuilderInNodejs(bundleName, outfile, trace, builderConfig, bundleConfig);
function getOrigSource(address) {
return new URL(address).asWebResource().get().content
}
}
function _runBuilderInNodejs(bundleName, outfile, trace, builderConfig, bundleConfig) {
var names = Object.keys(trace).map(name => trace[name].name);
if (!bundleConfig.bundles) bundleConfig.bundles = {};
bundleConfig.bundles[bundleName] = names;
var program = `var trace = ${JSON.stringify(trace, null, 2)};\n`;
program += `var outputFile = '${outfile}';\n`
program += `var builderConfig = ${JSON.stringify(builderConfig, null, 2)};\n`;
program += `var bundleConfig = ${JSON.stringify(bundleConfig, null, 2)};\n`;
program += `var livelyVMDir = '${join(lively.shell.WORKSPACE_LK, "node_modules/lively.vm/")}';\n`;
program += `var path = require("path");
var Builder = require(livelyVMDir + 'node_modules/systemjs-builder');
var builder = new Builder();
builder.config(builderConfig);
// delete require.cache[require.resolve("/Users/robert/Lively/LivelyKernel2/traced.json")]
// var x = require("/Users/robert/Lively/LivelyKernel2/traced.json")
// Object.keys(x).forEach(name => {
// x[name].source = String(fs.readFileSync(name.replace("http://localhost:9001/", "/Users/robert/Lively/LivelyKernel2/")))
// });
builder.bundle(trace, outputFile)
.then(() => {
require("fs").appendFileSync(outputFile, '\\nSystem.config(' + JSON.stringify(bundleConfig, null, 2) + ');\\n');
console.log("%s bundled", outputFile);
})
.catch(err => { console.error(err); process.exit(1); });
`
var programFile = join(lively.shell.WORKSPACE_LK, ".lively.modules-bundle-program.js");
return writeProgram()
.then(() => runProgram())
// .then(() => deleteProgram(), (err) => { deleteProgram(); throw err; });
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function runProgram() {
return new Promise((resolve, reject) => {
lively.shell.run("node " + programFile, {cwd: lively.shell.WORKSPACE_LK},
(err, cmd) => cmd.getCode() > 0 ? reject(new Error(cmd.resultString(true))) : resolve())
})
}
function writeProgram() {
return new Promise((resolve, reject) =>
lively.shell.writeFile(programFile, program,
(cmd) => cmd.getCode() > 0 ? reject(cmd.resultString(true)) : resolve()))
}
function deleteProgram() {
return new Promise((resolve, reject) => lively.shell.rm(programFile, (err) => err ? reject(err) : resolve()));
}
}
function groupIntoPackages(moduleNames, packageNames) {
return arr.groupBy(moduleNames, groupFor);
function groupFor(moduleName) {
var fullname = resolve(moduleName),
matching = packageNames.filter(p => fullname.indexOf(p) === 0);
return matching.length ?
matching.reduce((specific, ea) => ea.length > specific.length ? ea : specific) :
"no group";
}
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// update after source changes...
if (currentSystem().origTranslate) { unwrapModuleLoad(); wrapModuleLoad(); }
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
export {
// internals
currentSystem,
init as _init,
config,
moduleRecordFor as _moduleRecordFor,
updateModuleRecordOf as _updateModuleRecordOf,
updateModuleExports as _updateModuleExports,
computeRequireMap as _computeRequireMap,
getExceptions,
setExceptions,
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// accessing
sourceOf,
envFor,
// status,
// statusForPrinted,
importsAndExportsOf,
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// eval + changes
runEval,
sourceChange,
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// loading + dependencies
resolve,
modulesMatching,
importES6Module as import,
// reloadModule: reloadModule,
reloadModule,
forgetModule,
forgetModuleDeps,
findRequirementsOf,
findDependentsOf,
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// packaging
groupIntoPackages,
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// instrumentation
wrapModuleLoad,
unwrapModuleLoad
}
/*global process, before, beforeEach, afterEach, describe, it, expect*/
import { expect } from "mocha-es6";
import { es6 } from "lively.vm";
import lang from "lively.lang";
var isNode = System.get("@system-env").node;
var base = es6.resolve("lively.vm/tests/");
var module1 = base + "test-resources/es6/module1.js";
var module2 = base + "test-resources/es6/module2.js";
var module3 = base + "test-resources/es6/module3.js";
describe("es6 modules", () => {
before(function() {
// no need to init when we are in Lively
if (typeof $world === "undefined") {
es6._init(isNode ? {baseURL: './'} : {
transpiler: 'babel', babelOptions: {},
baseURL: document.URL.replace(/\/[^\/]*$/, ""),
map: {babel: '../node_modules/babel-core/browser.js'}
});
es6.wrapModuleLoad();
}
});
afterEach(() => {
es6.forgetModule(module1);
es6.forgetModule(module2);
es6.forgetModule(module3);
});
it("can be loaded", () =>
es6.import(module1).then(m => expect(m.x).equals(3)));
it("captures internal module state", () =>
Promise.all([es6.import(module1), es6.resolve(module1)])
.then((exportsAndName) =>
expect(es6.envFor(exportsAndName[1]))
.deep.property('recorder.internalState').equals(1)));
describe("eval", () => {
it("inside of module", () =>
es6.runEval("2 + internalState", {targetModule: module1})
.then(result => expect(result.value).equals(3)));
it("of export statement", () =>
// Load module1 and module2 which depends on module1
lang.promise.chain([
() => Promise.all([es6.import(module1), es6.import(module2)]),
(modules, state) => {
state.m1 = modules[0]; state.m2 = modules[1];
expect(state.m1.x).to.equal(3);
expect(state.m2.y).to.equal(5);
},
// Modify module1
() => es6.runEval("export var x = 9;", {asString: true, targetModule: module1}),
(result, state) => {
expect(result.value).to.not.match(/error/i);
expect(state.m1.x).to.equal(9, "module1 not updated");
expect(state.m2.y).to.equal(11, "module2 not updated after its dependency changed");
return Promise.all([
es6.import(module1).then(m => expect(m.x).to.equal(9)),
es6.import(module2).then(m => expect(m.y).to.equal(11)),
]);
}]));
it("of export statement with new export", () =>
lang.promise.chain([
() => Promise.all([es6.import(module1), es6.import(module2)]),
(modules, state) => { state.m1 = modules[0]; state.m2 = modules[1]; },
() => es6.runEval("export var foo = 3;", {asString: true, targetModule: module1}),
(result, state) => {
expect(result.value).to.not.match(/error/i);
// Hmmm.... frozen modules require us to re-import... damn!
// expect(state.m1.foo).to.equal(3, "foo not defined in module1 after eval");
return es6.import(module1)
.then((m1) => expect(m1.foo).to.equal(3, "foo not defined in module1 after eval"))
},
() => es6.runEval("export var foo = 5;", {asString: true, targetModule: module1}),
(result, state) => {
expect(result.value).to.not.match(/error/i);
// expect(state.m1.foo).to.equal(5, "foo updated in module1 after re-eval");
return es6.import(module1)
.then((m1) => expect(m1.foo).to.equal(5, "foo updated in module1 after re-eval"))
}]));
it("of import statement", () =>
// test if import is transformed to lookup + if the imported module gets before eval
lang.promise.chain([
() => es6.runEval("import {y} from './module2.js'; y", {targetModule: module1}),
(result, state) => {
expect(result.value).to.not.match(/error/i);
expect(result.value).to.equal(5, "imported value");
}]))
it("of var being exported", () =>
// Load module1 and module2 which depends on module1
lang.promise.chain([
() => Promise.all([es6.import(module1), es6.import(module2)]),
(modules, state) => {
state.m1 = modules[0]; state.m2 = modules[1];
expect(state.m1.x).to.equal(3);
expect(state.m2.y).to.equal(5);
},
// Modify module1
() => es6.runEval("var x = 9;", {asString: true, targetModule: module1}),
(result, state) => {
expect(result.value).to.not.match(/error/i);
expect(state.m1.x).to.equal(9, "module1 not updated");
expect(state.m2.y).to.equal(11, "module2 not updated after its dependency changed");
return Promise.all([
es6.import(module1).then(m => expect(m.x).to.equal(9)),
es6.import(module2).then(m => expect(m.y).to.equal(11)),
]);
}]));
it("of new var that is exported and then changes", () =>
es6.import(module1)
// define a new var that is exported
.then(_ => es6.runEval("var foo = 1; export { foo }", {asString: true, targetModule: module1}))
.then(() => expect(es6._moduleRecordFor(module1).exports).to.have.property("foo", 1, "of record"))
.then(() => es6.import(module1).then(m1 => expect(m1).to.have.property("foo", 1, "of module")))
// now change that var and see if the export is updated
.then(() => es6.runEval("var foo = 2;", {asString: true, targetModule: module1}))
.then(() => expect(es6._moduleRecordFor(module1).exports).to.have.property("foo", 2, "of record after change"))
.then(() => es6.import(module1).then(m1 => expect(m1).to.have.property("foo", 2, "of module after change"))));
});
describe("dependencies", () => {
it("computes required modules of some module", () =>
es6.import(module3).then(() => {
expect(es6.findRequirementsOf(module3)).to.deep.equal(
[es6.resolve(module2), es6.resolve(module1)]);
}));
it("computes dependent modules of some module", () =>
es6.import(module3).then(() => {
expect(es6.findDependentsOf(module1)).to.deep.equal(
[es6.resolve(module2), es6.resolve(module3)]);
}));
});
describe("code changes", () => {
function changeModule1Source() {
// "internalState = 1" => "internalState = 2"
return es6.sourceOf(module1).then(s =>
es6.sourceChange(module1,
s.replace(/(internalState = )([0-9]+)/, "$12"),
{evaluate: true}));
}
it("modifies module and its exports", () =>
es6.import(module1).then(m => {
expect(es6.envFor(es6.resolve(module1)).recorder.internalState).to.equal(1, "internal state before change");
expect(m.x).to.equal(3, "export state before change");
return changeModule1Source().then(() => {
expect(es6.envFor(es6.resolve(module1)).recorder.internalState).to.equal(2, "internal state after change");
expect(m.x).to.equal(5, "export state after change");
});
}));
it("modifies imports", () =>
es6.import(module2)
.then(() =>
expect(es6._moduleRecordFor(es6.resolve(module2)).dependencies.map(ea => ea.name))
.to.deep.equal([es6.resolve(module1)], "deps before"))
.then(m => es6.sourceChange(module2,
"import { z as x } from './module3.js'; export var y = x + 2;",
{evaluate: true}))
.then(() =>
expect(es6._moduleRecordFor(es6.resolve(module2)).dependencies.map(ea => ea.name))
.to.deep.equal([es6.resolve(module3)], "deps after")));
it("affects dependent modules", () =>
es6.import(module2).then(m => {
expect(m.y).to.equal(5, "before change");
return changeModule1Source().then(() =>
expect(m.y).to.equal(7, "state after change"));
}));
it("affects eval state", () =>
es6.import(module1)
.then(m => changeModule1Source())
.then(() => es6.runEval("[internalState, x]", {targetModule: module1}))
.then(result => expect(result.value).to.deep.equal([2, 5])));
it("reload module dependencies", () =>
es6.import(module3)
.then(m => expect(m.z).to.equal(15))
// we change module1 and check that the value of module3 that indirectly
// depends on module1 has changed as well
.then(() => es6.sourceOf(module1))
// es6.sourceOf("tests/" + module1)
.then(s => s.replace(/(internalState = )([0-9]+)/, "$12"))
.then(s => es6.runEval(s, {asString: true, targetModule: module1}))
.then(result => expect(result.value).to.not.match(/error/i))
.then(() => es6.forgetModuleDeps(module1))
.then(() => es6.import(module3))
.then(m => expect(m.z).to.equal(21)));
});
describe("unload module", () => {
it("forgets module and recordings", () =>
es6.import(module3)
.then(() => es6.forgetModule(module2))
.then(_ => {
expect(es6._moduleRecordFor(es6.resolve(module2))).to.equal(null);
expect(es6._moduleRecordFor(es6.resolve(module3))).to.equal(null);
expect(es6.envFor(es6.resolve(module2)).recorder).to.not.have.property("x");
expect(es6.envFor(es6.resolve(module3)).recorder).to.not.have.property("z");
}));
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment