diff --git a/dist/lively.modules-with-lively.vm.js b/dist/lively.modules-with-lively.vm.js index 848275f..770d0b5 100644 --- a/dist/lively.modules-with-lively.vm.js +++ b/dist/lively.modules-with-lively.vm.js @@ -17165,8 +17165,10 @@ module.exports = function(acorn) { }, { key: "visitClassExpression", value: function visitClassExpression(node, scope, path) { - scope.classExprs.push(node); - scope.classExprPaths.push(path); + if (node.id) { + scope.classExprs.push(node); + scope.classExprPaths.push(path); + } var visitor = this; // ignore id @@ -19361,7 +19363,7 @@ var nodes = Object.freeze({ var stmt = parsed.body[i]; if (topLevel.classDecls.indexOf(stmt) !== -1) { if (options.declarationWrapper) { - parsed.body.splice(i, 1, assignExpr(options.captureObj, stmt.id, funcCall(options.declarationWrapper, literal(stmt.id.name), literal("class"), stmt, options.captureObj), false)); + parsed.body.splice(i, 1, varDecl(stmt.id, assignExpr(options.captureObj, stmt.id, funcCall(options.declarationWrapper, literal(stmt.id.name), literal("class"), stmt, options.captureObj), false), "var")); } else { parsed.body.splice(i + 1, 0, assignExpr(options.captureObj, stmt.id, stmt.id, false)); } @@ -19512,7 +19514,7 @@ var nodes = Object.freeze({ body.push(stmt); } else { body = body.concat(stmt.specifiers.map(function (specifier) { - return topLevel.declaredNames.indexOf(specifier.local.name) > -1 ? null : varDeclOrAssignment(parsed, { + return lively_lang.arr.include(topLevel.declaredNames, specifier.local.name) ? null : varDeclOrAssignment(parsed, { type: "VariableDeclarator", id: specifier.local, init: member(options.captureObj, specifier.local) @@ -19776,6 +19778,8 @@ var capturing = Object.freeze({ rewriteToRegisterModuleToCaptureSetters: rewriteToRegisterModuleToCaptureSetters }); + var defaultDeclarationWrapperName = "lively.capturing-declaration-wrapper"; + function evalCodeTransform(code, options) { // variable declaration and references in the the source code get // transformed so that they are bound to `varRecorderName` aren't local @@ -19791,6 +19795,25 @@ var capturing = Object.freeze({ // 2. capture top level vars into topLevelVarRecorder "environment" if (options.topLevelVarRecorder) { + // 2.1 declare a function that should wrap all definitions, i.e. all var + // decls, functions, classes etc that get captured will be wrapped in this + // function. When using this with the option.keepPreviouslyDeclaredValues + // we will use a wrapping function that keeps the identity of prevously + // defined objects + + var declarationWrapperName = options.declarationWrapperName || defaultDeclarationWrapperName; + if (options.keepPreviouslyDeclaredValues) { + options.declarationWrapper = { + type: "MemberExpression", + object: { type: "Identifier", name: options.varRecorderName }, + property: { type: "Literal", value: declarationWrapperName }, + computed: true + }; + options.topLevelVarRecorder[declarationWrapperName] = declarationWrapperForKeepingValues; + } + + // 2.2 Here we call out to the actual code transformation that installs the + // capture and wrap logic var blacklist = (options.dontTransform || []).concat(["arguments"]), undeclaredToTransform = !!options.recordGlobals ? null /*all*/ : lively_lang.arr.withoutAll(Object.keys(options.topLevelVarRecorder), blacklist); @@ -19833,7 +19856,39 @@ var capturing = Object.freeze({ return result ? stringify(result) : code; } + function copyProperties(source, target) { + var exceptions = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2]; + + Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source)).forEach(function (name) { + return exceptions.indexOf(name) === -1 && Object.defineProperty(target, name, Object.getOwnPropertyDescriptor(source, name)); + }); + } + + function declarationWrapperForKeepingValues(name, kind, value, recorder) { + if (kind === "function") return value; + + if (kind === "class") { + var existingClass = recorder[name]; + if (typeof existingClass === "function") { + copyProperties(value, existingClass, ["name", "length", "prototype"]); + copyProperties(value.prototype, existingClass.prototype); + return existingClass; + } + return value; + } + + if (!value || (typeof value === "undefined" ? "undefined" : babelHelpers.typeof(value)) !== "object" || Array.isArray(value) || value.constructor === RegExp) return value; + + if (recorder.hasOwnProperty(name)) { + copyProperties(value, recorder[name]); + return recorder[name]; + } + + return value; + } + var evalSupport = Object.freeze({ + defaultDeclarationWrapperName: defaultDeclarationWrapperName, evalCodeTransform: evalCodeTransform, evalCodeTransformOfSystemRegisterSetters: evalCodeTransformOfSystemRegisterSetters }); @@ -21363,7 +21418,8 @@ var categorizer = Object.freeze({ topLevelVarRecorder: env.recorder, varRecorderName: env.recorderName, dontTransform: env.dontTransform, - recordGlobals: true + recordGlobals: true, + keepPreviouslyDeclaredValues: true }, isGlobal = env.recorderName === "System.global", header = debug ? "console.log(\"[lively.modules] executing module " + fullname + "\");\n" : "", @@ -21500,6 +21556,7 @@ var categorizer = Object.freeze({ debug && console.log("[lively.modules customTranslate] Installing System.register setter captures for %s", load.name); translated = prepareTranslatedCodeForSetterCapture(translated, load.name, env, debug); } + debug && console.log("[lively.modules customTranslate] done %s after %sms", load.name, Date.now() - start); return translated; }); @@ -21546,7 +21603,6 @@ var categorizer = Object.freeze({ } function instrumentSourceOfGlobalModuleLoad(System, load) { - return System.translate(load).then(function (translated) { // return {localDeps: depNames, declare: declare}; return { translated: translated }; @@ -23188,11 +23244,6 @@ var categorizer = Object.freeze({ 'use strict'; var babelHelpers = {}; - babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; - }; babelHelpers.asyncToGenerator = function (fn) { return function () { @@ -23436,38 +23487,6 @@ var categorizer = Object.freeze({ var defaultTopLevelVarRecorderName = '__lvVarRecorder'; var startEvalFunctionName = "lively.vm-on-eval-start"; var endEvalFunctionName = "lively.vm-on-eval-end"; - var declarationWrapperName = "lively.vm-declaration-wrapper"; - function copyProperties(source, target) { - var exceptions = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2]; - - Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source)).forEach(function (name) { - return exceptions.indexOf(name) === -1 && Object.defineProperty(target, name, Object.getOwnPropertyDescriptor(source, name)); - }); - } - - function declarationWrapperForKeepingValues(name, kind, value, recorder) { - if (kind === "function") return value; - - if (kind === "class") { - var existingClass = recorder[name]; - if (typeof existingClass === "function") { - copyProperties(value, existingClass, ["name", "length", "prototype"]); - copyProperties(value.prototype, existingClass.prototype); - return existingClass; - } - return value; - } - - if (!value || (typeof value === "undefined" ? "undefined" : babelHelpers.typeof(value)) !== "object" || Array.isArray(value) || value.constructor === RegExp) return value; - - if (recorder.hasOwnProperty(name)) { - copyProperties(value, recorder[name]); - return recorder[name]; - } - - return value; - } - function _normalizeEvalOptions(opts) { if (!opts) opts = {}; opts = Object.assign({ @@ -23506,15 +23525,6 @@ var categorizer = Object.freeze({ }; } - if (opts.keepPreviouslyDeclaredValues) { - opts.declarationWrapperFunction = declarationWrapperForKeepingValues; - opts.declarationWrapper = { - type: "MemberExpression", - object: { type: "Identifier", name: opts.varRecorderName }, - property: { type: "Literal", value: declarationWrapperName }, - computed: true - }; - } return opts; } @@ -23598,10 +23608,6 @@ var categorizer = Object.freeze({ }; } - if (options.declarationWrapperFunction) { - recorder[declarationWrapperName] = options.declarationWrapperFunction; - } - // 2. Transform the code to capture top-level variables, inject function calls, ... try { code = evalCodeTransform(code, options); @@ -23644,10 +23650,6 @@ var categorizer = Object.freeze({ delete recorder[endEvalFunctionName]; } - if (options.declarationWrapperFunction) { - delete recorder[declarationWrapperName]; - } - if (err) { result.isError = true;result.value = err; } else result.value = value; diff --git a/dist/lively.modules.js b/dist/lively.modules.js index e7ff54e..a0d88f5 100644 --- a/dist/lively.modules.js +++ b/dist/lively.modules.js @@ -17165,8 +17165,10 @@ module.exports = function(acorn) { }, { key: "visitClassExpression", value: function visitClassExpression(node, scope, path) { - scope.classExprs.push(node); - scope.classExprPaths.push(path); + if (node.id) { + scope.classExprs.push(node); + scope.classExprPaths.push(path); + } var visitor = this; // ignore id @@ -19361,7 +19363,7 @@ var nodes = Object.freeze({ var stmt = parsed.body[i]; if (topLevel.classDecls.indexOf(stmt) !== -1) { if (options.declarationWrapper) { - parsed.body.splice(i, 1, assignExpr(options.captureObj, stmt.id, funcCall(options.declarationWrapper, literal(stmt.id.name), literal("class"), stmt, options.captureObj), false)); + parsed.body.splice(i, 1, varDecl(stmt.id, assignExpr(options.captureObj, stmt.id, funcCall(options.declarationWrapper, literal(stmt.id.name), literal("class"), stmt, options.captureObj), false), "var")); } else { parsed.body.splice(i + 1, 0, assignExpr(options.captureObj, stmt.id, stmt.id, false)); } @@ -19512,7 +19514,7 @@ var nodes = Object.freeze({ body.push(stmt); } else { body = body.concat(stmt.specifiers.map(function (specifier) { - return topLevel.declaredNames.indexOf(specifier.local.name) > -1 ? null : varDeclOrAssignment(parsed, { + return lively_lang.arr.include(topLevel.declaredNames, specifier.local.name) ? null : varDeclOrAssignment(parsed, { type: "VariableDeclarator", id: specifier.local, init: member(options.captureObj, specifier.local) @@ -19776,6 +19778,8 @@ var capturing = Object.freeze({ rewriteToRegisterModuleToCaptureSetters: rewriteToRegisterModuleToCaptureSetters }); + var defaultDeclarationWrapperName = "lively.capturing-declaration-wrapper"; + function evalCodeTransform(code, options) { // variable declaration and references in the the source code get // transformed so that they are bound to `varRecorderName` aren't local @@ -19791,6 +19795,25 @@ var capturing = Object.freeze({ // 2. capture top level vars into topLevelVarRecorder "environment" if (options.topLevelVarRecorder) { + // 2.1 declare a function that should wrap all definitions, i.e. all var + // decls, functions, classes etc that get captured will be wrapped in this + // function. When using this with the option.keepPreviouslyDeclaredValues + // we will use a wrapping function that keeps the identity of prevously + // defined objects + + var declarationWrapperName = options.declarationWrapperName || defaultDeclarationWrapperName; + if (options.keepPreviouslyDeclaredValues) { + options.declarationWrapper = { + type: "MemberExpression", + object: { type: "Identifier", name: options.varRecorderName }, + property: { type: "Literal", value: declarationWrapperName }, + computed: true + }; + options.topLevelVarRecorder[declarationWrapperName] = declarationWrapperForKeepingValues; + } + + // 2.2 Here we call out to the actual code transformation that installs the + // capture and wrap logic var blacklist = (options.dontTransform || []).concat(["arguments"]), undeclaredToTransform = !!options.recordGlobals ? null /*all*/ : lively_lang.arr.withoutAll(Object.keys(options.topLevelVarRecorder), blacklist); @@ -19833,7 +19856,39 @@ var capturing = Object.freeze({ return result ? stringify(result) : code; } + function copyProperties(source, target) { + var exceptions = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2]; + + Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source)).forEach(function (name) { + return exceptions.indexOf(name) === -1 && Object.defineProperty(target, name, Object.getOwnPropertyDescriptor(source, name)); + }); + } + + function declarationWrapperForKeepingValues(name, kind, value, recorder) { + if (kind === "function") return value; + + if (kind === "class") { + var existingClass = recorder[name]; + if (typeof existingClass === "function") { + copyProperties(value, existingClass, ["name", "length", "prototype"]); + copyProperties(value.prototype, existingClass.prototype); + return existingClass; + } + return value; + } + + if (!value || (typeof value === "undefined" ? "undefined" : babelHelpers.typeof(value)) !== "object" || Array.isArray(value) || value.constructor === RegExp) return value; + + if (recorder.hasOwnProperty(name)) { + copyProperties(value, recorder[name]); + return recorder[name]; + } + + return value; + } + var evalSupport = Object.freeze({ + defaultDeclarationWrapperName: defaultDeclarationWrapperName, evalCodeTransform: evalCodeTransform, evalCodeTransformOfSystemRegisterSetters: evalCodeTransformOfSystemRegisterSetters }); @@ -21363,7 +21418,8 @@ var categorizer = Object.freeze({ topLevelVarRecorder: env.recorder, varRecorderName: env.recorderName, dontTransform: env.dontTransform, - recordGlobals: true + recordGlobals: true, + keepPreviouslyDeclaredValues: true }, isGlobal = env.recorderName === "System.global", header = debug ? "console.log(\"[lively.modules] executing module " + fullname + "\");\n" : "", @@ -21500,6 +21556,7 @@ var categorizer = Object.freeze({ debug && console.log("[lively.modules customTranslate] Installing System.register setter captures for %s", load.name); translated = prepareTranslatedCodeForSetterCapture(translated, load.name, env, debug); } + debug && console.log("[lively.modules customTranslate] done %s after %sms", load.name, Date.now() - start); return translated; }); @@ -21546,7 +21603,6 @@ var categorizer = Object.freeze({ } function instrumentSourceOfGlobalModuleLoad(System, load) { - return System.translate(load).then(function (translated) { // return {localDeps: depNames, declare: declare}; return { translated: translated }; diff --git a/dist/lively.modules_no-deps.js b/dist/lively.modules_no-deps.js index 4fd1097..d91580b 100644 --- a/dist/lively.modules_no-deps.js +++ b/dist/lively.modules_no-deps.js @@ -394,7 +394,8 @@ topLevelVarRecorder: env.recorder, varRecorderName: env.recorderName, dontTransform: env.dontTransform, - recordGlobals: true + recordGlobals: true, + keepPreviouslyDeclaredValues: true }, isGlobal = env.recorderName === "System.global", header = debug ? "console.log(\"[lively.modules] executing module " + fullname + "\");\n" : "", @@ -531,6 +532,7 @@ debug && console.log("[lively.modules customTranslate] Installing System.register setter captures for %s", load.name); translated = prepareTranslatedCodeForSetterCapture(translated, load.name, env, debug); } + debug && console.log("[lively.modules customTranslate] done %s after %sms", load.name, Date.now() - start); return translated; }); @@ -577,7 +579,6 @@ } function instrumentSourceOfGlobalModuleLoad(System, load) { - return System.translate(load).then(function (translated) { // return {localDeps: depNames, declare: declare}; return { translated: translated }; diff --git a/package.json b/package.json index 63e83e2..384d2dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lively.modules", - "version": "0.5.1", + "version": "0.5.2", "main": "dist/lively.modules.js", "repository": "https://github.com/LivelyKernel/lively.modules", "author": "Robert Krahn ", diff --git a/src/instrumentation.js b/src/instrumentation.js index ab92000..06de282 100644 --- a/src/instrumentation.js +++ b/src/instrumentation.js @@ -52,7 +52,8 @@ function prepareCodeForCustomCompile(source, fullname, env, debug) { topLevelVarRecorder: env.recorder, varRecorderName: env.recorderName, dontTransform: env.dontTransform, - recordGlobals: true + recordGlobals: true, + keepPreviouslyDeclaredValues: true }, isGlobal = env.recorderName === "System.global", header = (debug ? `console.log("[lively.modules] executing module ${fullname}");\n` : ""), @@ -192,6 +193,7 @@ function customTranslate(proceed, load) { debug && console.log("[lively.modules customTranslate] Installing System.register setter captures for %s", load.name); translated = prepareTranslatedCodeForSetterCapture(translated, load.name, env, debug); } + debug && console.log("[lively.modules customTranslate] done %s after %sms", load.name, Date.now()-start); return translated; }); @@ -275,7 +277,6 @@ function old_instrumentSourceOfEsmModuleLoad(System, load) { } function instrumentSourceOfGlobalModuleLoad(System, load) { - return System.translate(load).then(translated => { // return {localDeps: depNames, declare: declare}; return {translated: translated}; diff --git a/tests/changes-test.js b/tests/changes-test.js index ca6bd46..fd25af5 100644 --- a/tests/changes-test.js +++ b/tests/changes-test.js @@ -164,3 +164,46 @@ describe("code changes of global format module", () => { expect(moduleEnv(S, module1).recorder).property("z").equal(3); }); }); + +describe("persistent definitions", () => { + + var dir = System.normalizeSync("lively.modules/tests/"), + testProjectDir = dir + "test-project-2-dir/", + testProjectSpec = { + "file1.js": "'format esm'; class Foo { m() { return 23 }}\nvar x = {bar: 123, foo() { return this.bar + 42 }}\n", + "package.json": '{"name": "test-project-2", "main": "file1.js"}' + }, + module1 = testProjectDir + "file1.js"; + + var S; + beforeEach(async () => { + S = getSystem("test", {baseURL: testProjectDir}); + await createFiles(testProjectDir, testProjectSpec); + }); + + afterEach(async () => { + removeSystem("test"); + await removeDir(testProjectDir); + }); + + it("keeps identity of class", async () => { + await S.import(module1); + var class1 = moduleEnv(S, module1).recorder.Foo; + expect(new class1().m()).equals(23, "Foo class not working"); + await moduleSourceChangeAction(S, module1, s => "'format esm'; class Foo { m() { return 24 }}\n") + var class2 = moduleEnv(S, module1).recorder.Foo; + expect(new class2().m()).equals(24, "Foo class not changed"); + expect(class1).equals(class2, "Foo class identity changed"); + }); + + it("doesn't keep identity of anonymous class", async () => { + await S.import(module1); + var class1 = moduleEnv(S, module1).recorder.Foo; + expect(new class1().m()).equals(23, "Foo class not working"); + await moduleSourceChangeAction(S, module1, s => "let Foo = class { m() { return 24 }}\n") + var class2 = moduleEnv(S, module1).recorder.Foo; + expect(new class2().m()).equals(24, "Foo class not changed"); + expect(class1).not.equals(class2, "Foo class identity the same"); + }); + +}) \ No newline at end of file