From 3df5de2449f14dad41d0fdf0ee281d176b0b141c Mon Sep 17 00:00:00 2001 From: guybedford Date: Wed, 1 Apr 2015 01:38:23 +0200 Subject: [PATCH] minimal loader build, first attempt --- Gruntfile.js | 73 ++--- package.json | 2 - src/declarative.js | 284 +++++++++++++++++ src/dynamic-only.js | 30 ++ src/loader.js | 306 +----------------- src/module-tag.js | 39 +++ src/polyfill-wrapper-end.js | 2 + src/polyfill-wrapper-start.js | 35 +-- src/register.js | 566 ++++++++++++++++++++++++++++++++++ src/system.js | 259 +++++++--------- src/transpiler.js | 14 +- 11 files changed, 1090 insertions(+), 520 deletions(-) create mode 100644 src/declarative.js create mode 100644 src/dynamic-only.js create mode 100644 src/module-tag.js create mode 100644 src/register.js diff --git a/Gruntfile.js b/Gruntfile.js index 3259e20..0d0b950 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -20,48 +20,43 @@ module.exports = function (grunt) { concat: { dist: { files: { + 'dist/<%= pkg.name %>-prod.src.js': [ + 'node_modules/when/es6-shim/Promise.js', + 'src/polyfill-wrapper-start.js', + 'src/loader.js', + 'src/dynamic-only.js', + 'src/system.js', + 'src/polyfill-wrapper-end.js' + ], + 'dist/<%= pkg.name %>-prod-sans-promises.src.js': [ + 'src/polyfill-wrapper-start.js', + 'src/loader.js', + 'src/dynamic-only.js', + 'src/system.js', + 'src/polyfill-wrapper-end.js' + ], 'dist/<%= pkg.name %>.src.js': [ 'node_modules/when/es6-shim/Promise.js', 'src/polyfill-wrapper-start.js', - 'dist/<%= pkg.name %>.js', + 'src/loader.js', + 'src/declarative.js', + 'src/transpiler.js', + 'src/system.js', + 'src/module-tag.js', 'src/polyfill-wrapper-end.js' ], 'dist/<%= pkg.name %>-sans-promises.src.js': [ 'src/polyfill-wrapper-start.js', - 'dist/<%= pkg.name %>.js', + 'src/loader.js', + 'src/declarative.js', + 'src/transpiler.js', + 'src/system.js', + 'src/module-tag.js', 'src/polyfill-wrapper-end.js' ] } } }, - esnext: { - dist: { - src: [ - 'src/loader.js', - 'src/transpiler.js', - 'src/system.js' - ], - dest: 'dist/<%= pkg.name %>.js' - } - }, - 'string-replace': { - dist: { - files: { - 'dist/<%= pkg.name %>.js': 'dist/<%= pkg.name %>.js' - }, - options: { - replacements:[{ - pattern: 'var $__Object$getPrototypeOf = Object.getPrototypeOf;\n' + - 'var $__Object$defineProperty = Object.defineProperty;\n' + - 'var $__Object$create = Object.create;', - replacement: '' - }, { - pattern: '$__Object$getPrototypeOf(SystemLoader.prototype).constructor', - replacement: '$__super' - }] - } - } - }, uglify: { options: { banner: '<%= meta.banner %>\n', @@ -80,18 +75,26 @@ module.exports = function (grunt) { distSansPromises: { src: 'dist/<%= pkg.name %>-sans-promises.src.js', dest: 'dist/<%= pkg.name %>-sans-promises.js' + }, + prodDist: { + options: { + banner: '<%= meta.banner %>\n' + }, + src: 'dist/<%= pkg.name %>-prod.src.js', + dest: 'dist/<%= pkg.name %>-prod.js' + }, + prodDistSansPromises: { + src: 'dist/<%= pkg.name %>-prod-sans-promises.src.js', + dest: 'dist/<%= pkg.name %>-prod-sans-promises.js' } } }); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-esnext'); grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-string-replace'); grunt.registerTask('lint', ['jshint']); - grunt.registerTask('compile', ['esnext', 'string-replace', 'concat']); - grunt.registerTask('default', [/*'jshint', */'esnext', 'string-replace', - 'concat', 'uglify']); + grunt.registerTask('compile', ['concat']); + grunt.registerTask('default', [/*'jshint', */'concat', 'uglify']); }; diff --git a/package.json b/package.json index 044e09c..5c047e2 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,6 @@ "grunt-contrib-concat": "^0.5.0", "grunt-contrib-jshint": "~0.6.0", "grunt-contrib-uglify": "~0.6.0", - "grunt-esnext": "0.0.3", - "grunt-string-replace": "^0.2.7", "karma": "^0.12.28", "karma-benchmark": "^0.4.0", "karma-benchmark-reporter": "^0.1.1", diff --git a/src/declarative.js b/src/declarative.js new file mode 100644 index 0000000..c4d3025 --- /dev/null +++ b/src/declarative.js @@ -0,0 +1,284 @@ +/* + * ES6 Module Declarative Linking Code - Dev Build Only + */ + + // 15.2.5.3 Module Linking Groups + + // 15.2.5.3.2 BuildLinkageGroups alternative implementation + // Adjustments (also see https://bugs.ecmascript.org/show_bug.cgi?id=2755) + // 1. groups is an already-interleaved array of group kinds + // 2. load.groupIndex is set when this function runs + // 3. load.groupIndex is the interleaved index ie 0 declarative, 1 dynamic, 2 declarative, ... (or starting with dynamic) + function buildLinkageGroups(load, loads, groups) { + groups[load.groupIndex] = groups[load.groupIndex] || []; + + // if the load already has a group index and its in its group, its already been done + // this logic naturally handles cycles + if (indexOf.call(groups[load.groupIndex], load) != -1) + return; + + // now add it to the group to indicate its been seen + groups[load.groupIndex].push(load); + + for (var i = 0, l = loads.length; i < l; i++) { + var loadDep = loads[i]; + + // dependencies not found are already linked + for (var j = 0; j < load.dependencies.length; j++) { + if (loadDep.name == load.dependencies[j].value) { + // by definition all loads in linkset are loaded, not linked + console.assert(loadDep.status == 'loaded', 'Load in linkSet not loaded!'); + + // if it is a group transition, the index of the dependency has gone up + // otherwise it is the same as the parent + var loadDepGroupIndex = load.groupIndex + (loadDep.isDeclarative != load.isDeclarative); + + // the group index of an entry is always the maximum + if (loadDep.groupIndex === undefined || loadDep.groupIndex < loadDepGroupIndex) { + + // if already in a group, remove from the old group + if (loadDep.groupIndex !== undefined) { + groups[loadDep.groupIndex].splice(indexOf.call(groups[loadDep.groupIndex], loadDep), 1); + + // if the old group is empty, then we have a mixed depndency cycle + if (groups[loadDep.groupIndex].length == 0) + throw new TypeError("Mixed dependency cycle detected"); + } + + loadDep.groupIndex = loadDepGroupIndex; + } + + buildLinkageGroups(loadDep, loads, groups); + } + } + } + } + + // 15.2.5.4 + // declarative linking implementation + function link(linkSet, linkError) { + + var loader = linkSet.loader; + + if (!linkSet.loads.length) + return; + + // console.log('linking {' + logloads(linkSet.loads) + '}'); + // snapshot(loader); + + // 15.2.5.3.1 LinkageGroups alternative implementation + + // build all the groups + // because the first load represents the top of the tree + // for a given linkset, we can work down from there + var groups = []; + var startingLoad = linkSet.loads[0]; + startingLoad.groupIndex = 0; + buildLinkageGroups(startingLoad, linkSet.loads, groups); + + // determine the kind of the bottom group + var curGroupDeclarative = startingLoad.isDeclarative == groups.length % 2; + + // run through the groups from bottom to top + for (var i = groups.length - 1; i >= 0; i--) { + var group = groups[i]; + for (var j = 0; j < group.length; j++) { + var load = group[j]; + + // 15.2.5.5 LinkDeclarativeModules adjusted + if (curGroupDeclarative) { + linkDeclarativeModule(load, linkSet.loads, loader); + } + // 15.2.5.6 LinkDynamicModules adjusted + else { + var module = doDynamicExecute(linkSet, load, linkError); + if (!module) + return; + load.module = { + name: load.name, + module: module + }; + load.status = 'linked'; + } + finishLoad(loader, load); + } + + // alternative current kind for next loop + curGroupDeclarative = !curGroupDeclarative; + } + } + + + // custom module records for binding graph + // store linking module records in a separate table + function getOrCreateModuleRecord(name, loader) { + var moduleRecords = loader.moduleRecords; + return moduleRecords[name] || (moduleRecords[name] = { + name: name, + dependencies: [], + module: new Module(), // start from an empty module and extend + importers: [] + }); + } + + // custom declarative linking function + function linkDeclarativeModule(load, loads, loader) { + if (load.module) + return; + + var module = load.module = getOrCreateModuleRecord(load.name, loader); + var moduleObj = load.module.module; + + var registryEntry = load.declare.call(__global, function(name, value) { + // NB This should be an Object.defineProperty, but that is very slow. + // By disaling this module write-protection we gain performance. + // It could be useful to allow an option to enable or disable this. + module.locked = true; + moduleObj[name] = value; + + for (var i = 0, l = module.importers.length; i < l; i++) { + var importerModule = module.importers[i]; + if (!importerModule.locked) { + var importerIndex = indexOf.call(importerModule.dependencies, module); + importerModule.setters[importerIndex](moduleObj); + } + } + + module.locked = false; + return value; + }); + + // setup our setters and execution function + module.setters = registryEntry.setters; + module.execute = registryEntry.execute; + + // now link all the module dependencies + // amending the depMap as we go + for (var i = 0, l = load.dependencies.length; i < l; i++) { + var depName = load.dependencies[i].value; + var depModule = loader.modules[depName]; + + // if dependency not already in the module registry + // then try and link it now + if (!depModule) { + // get the dependency load record + for (var j = 0; j < loads.length; j++) { + if (loads[j].name != depName) + continue; + + // only link if already not already started linking (stops at circular / dynamic) + if (!loads[j].module) { + linkDeclarativeModule(loads[j], loads, loader); + depModule = loads[j].module; + } + // if circular, create the module record + else { + depModule = getOrCreateModuleRecord(depName, loader); + } + } + } + + // only declarative modules have dynamic bindings + if (depModule.importers) { + module.dependencies.push(depModule); + depModule.importers.push(module); + } + else { + // track dynamic records as null module records as already linked + module.dependencies.push(null); + } + + // run the setter for this dependency + if (module.setters[i]) + module.setters[i](depModule.module); + } + + load.status = 'linked'; + } + + /* + * Module Object non-exotic for ES5: + * + * module.module bound module object + * module.execute execution function for module + * module.dependencies list of module objects for dependencies + * See getOrCreateModuleRecord for all properties + * + */ + function doExecute(module) { + try { + module.execute.call(__global); + } + catch(e) { + return e; + } + } + + // 15.2.5.5.1 LinkImports not implemented + // 15.2.5.7 ResolveExportEntries not implemented + // 15.2.5.8 ResolveExports not implemented + // 15.2.5.9 ResolveExport not implemented + // 15.2.5.10 ResolveImportEntries not implemented + + // 15.2.6.1 + function evaluateLoadedModule(loader, load) { + console.assert(load.status == 'linked', 'is linked ' + load.name); + + doEnsureEvaluated(load.module, [], loader); + return load.module.module; + } + + // propogate execution errors + // see https://bugs.ecmascript.org/show_bug.cgi?id=2993 + function doEnsureEvaluated(module, seen, loader) { + var err = ensureEvaluated(module, seen, loader); + if (err) + throw err; + } + // 15.2.6.2 EnsureEvaluated adjusted + function ensureEvaluated(module, seen, loader) { + if (module.evaluated || !module.dependencies) + return; + + seen.push(module); + + var deps = module.dependencies; + var err; + + for (var i = 0, l = deps.length; i < l; i++) { + var dep = deps[i]; + // dynamic dependencies are empty in module.dependencies + // as they are already linked + if (!dep) + continue; + if (indexOf.call(seen, dep) == -1) { + err = ensureEvaluated(dep, seen, loader); + // stop on error, see https://bugs.ecmascript.org/show_bug.cgi?id=2996 + if (err) { + err = addToError(err, 'Error evaluating ' + dep.name + '\n'); + return err; + } + } + } + + if (module.failed) + return new Error('Module failed execution.'); + + if (module.evaluated) + return; + + module.evaluated = true; + err = doExecute(module); + if (err) { + module.failed = true; + } + else if (Object.preventExtensions) { + // spec variation + // we don't create a new module here because it was created and ammended + // we just disable further extensions instead + Object.preventExtensions(module.module); + } + + module.execute = undefined; + return err; + } \ No newline at end of file diff --git a/src/dynamic-only.js b/src/dynamic-only.js new file mode 100644 index 0000000..2945df8 --- /dev/null +++ b/src/dynamic-only.js @@ -0,0 +1,30 @@ +/* + * Dynamic-only Linking Code + */ + + // 15.2.5.4 + // dynamic-only linking implementation + function link(linkSet, linkError) { + + var loader = linkSet.loader; + + if (!linkSet.loads.length) + return; + + for (var i = 0; i < linkSet.loads.length; i++) { + var load = linkSet.loads[i]; + var module = doDynamicExecute(linkSet, load, linkError); + if (!module) + return; + load.module = { + name: load.name, + module: module + }; + load.status = 'linked'; + finishLoad(loader, load); + } + } + + function evaluateLoadedModule(loader, load) { + return load.module.module; + } \ No newline at end of file diff --git a/src/loader.js b/src/loader.js index 11ee8dc..922c3ad 100644 --- a/src/loader.js +++ b/src/loader.js @@ -100,9 +100,6 @@ function logloads(loads) { } } } */ - - -(function() { var Promise = __global.Promise || require('when/es6-shim/Promise'); if (__global.console) console.assert = console.assert || function() {}; @@ -116,7 +113,22 @@ function logloads(loads) { } return -1; }; - var defineProperty = $__Object$defineProperty; + + var defineProperty; + (function () { + try { + if (!!Object.defineProperty({}, 'a', {})) + defineProperty = Object.defineProperty; + } + catch (e) { + defineProperty = function(obj, prop, opt) { + try { + obj[prop] = opt.value || opt.get.call(obj); + } + catch(e) {} + } + } + })(); // 15.2.3 - Runtime Semantics: Loader State @@ -246,7 +258,7 @@ function logloads(loads) { // instead of load.kind, use load.isDeclarative load.isDeclarative = true; - return loader.loaderObj.transpile(load) + return transpile.call(loader.loaderObj, load) .then(function(transpiled) { // Hijack System.register to set declare function var curSystem = __global.System; @@ -575,58 +587,6 @@ function logloads(loads) { load.linkSets.splice(0, load.linkSets.length); } - // 15.2.5.3 Module Linking Groups - - // 15.2.5.3.2 BuildLinkageGroups alternative implementation - // Adjustments (also see https://bugs.ecmascript.org/show_bug.cgi?id=2755) - // 1. groups is an already-interleaved array of group kinds - // 2. load.groupIndex is set when this function runs - // 3. load.groupIndex is the interleaved index ie 0 declarative, 1 dynamic, 2 declarative, ... (or starting with dynamic) - function buildLinkageGroups(load, loads, groups) { - groups[load.groupIndex] = groups[load.groupIndex] || []; - - // if the load already has a group index and its in its group, its already been done - // this logic naturally handles cycles - if (indexOf.call(groups[load.groupIndex], load) != -1) - return; - - // now add it to the group to indicate its been seen - groups[load.groupIndex].push(load); - - for (var i = 0, l = loads.length; i < l; i++) { - var loadDep = loads[i]; - - // dependencies not found are already linked - for (var j = 0; j < load.dependencies.length; j++) { - if (loadDep.name == load.dependencies[j].value) { - // by definition all loads in linkset are loaded, not linked - console.assert(loadDep.status == 'loaded', 'Load in linkSet not loaded!'); - - // if it is a group transition, the index of the dependency has gone up - // otherwise it is the same as the parent - var loadDepGroupIndex = load.groupIndex + (loadDep.isDeclarative != load.isDeclarative); - - // the group index of an entry is always the maximum - if (loadDep.groupIndex === undefined || loadDep.groupIndex < loadDepGroupIndex) { - - // if already in a group, remove from the old group - if (loadDep.groupIndex !== undefined) { - groups[loadDep.groupIndex].splice(indexOf.call(groups[loadDep.groupIndex], loadDep), 1); - - // if the old group is empty, then we have a mixed depndency cycle - if (groups[loadDep.groupIndex].length == 0) - throw new TypeError("Mixed dependency cycle detected"); - } - - loadDep.groupIndex = loadDepGroupIndex; - } - - buildLinkageGroups(loadDep, loads, groups); - } - } - } - } - function doDynamicExecute(linkSet, load, linkError) { try { var module = load.execute(); @@ -641,236 +601,6 @@ function logloads(loads) { return module; } - // 15.2.5.4 - function link(linkSet, linkError) { - - var loader = linkSet.loader; - - if (!linkSet.loads.length) - return; - - // console.log('linking {' + logloads(linkSet.loads) + '}'); - // snapshot(loader); - - // 15.2.5.3.1 LinkageGroups alternative implementation - - // build all the groups - // because the first load represents the top of the tree - // for a given linkset, we can work down from there - var groups = []; - var startingLoad = linkSet.loads[0]; - startingLoad.groupIndex = 0; - buildLinkageGroups(startingLoad, linkSet.loads, groups); - - // determine the kind of the bottom group - var curGroupDeclarative = startingLoad.isDeclarative == groups.length % 2; - - // run through the groups from bottom to top - for (var i = groups.length - 1; i >= 0; i--) { - var group = groups[i]; - for (var j = 0; j < group.length; j++) { - var load = group[j]; - - // 15.2.5.5 LinkDeclarativeModules adjusted - if (curGroupDeclarative) { - linkDeclarativeModule(load, linkSet.loads, loader); - } - // 15.2.5.6 LinkDynamicModules adjusted - else { - var module = doDynamicExecute(linkSet, load, linkError); - if (!module) - return; - load.module = { - name: load.name, - module: module - }; - load.status = 'linked'; - } - finishLoad(loader, load); - } - - // alternative current kind for next loop - curGroupDeclarative = !curGroupDeclarative; - } - } - - - // custom module records for binding graph - // store linking module records in a separate table - function getOrCreateModuleRecord(name, loader) { - var moduleRecords = loader.moduleRecords; - return moduleRecords[name] || (moduleRecords[name] = { - name: name, - dependencies: [], - module: new Module(), // start from an empty module and extend - importers: [] - }); - } - - // custom declarative linking function - function linkDeclarativeModule(load, loads, loader) { - if (load.module) - return; - - var module = load.module = getOrCreateModuleRecord(load.name, loader); - var moduleObj = load.module.module; - - var registryEntry = load.declare.call(__global, function(name, value) { - // NB This should be an Object.defineProperty, but that is very slow. - // By disaling this module write-protection we gain performance. - // It could be useful to allow an option to enable or disable this. - module.locked = true; - moduleObj[name] = value; - - for (var i = 0, l = module.importers.length; i < l; i++) { - var importerModule = module.importers[i]; - if (!importerModule.locked) { - var importerIndex = indexOf.call(importerModule.dependencies, module); - importerModule.setters[importerIndex](moduleObj); - } - } - - module.locked = false; - return value; - }); - - // setup our setters and execution function - module.setters = registryEntry.setters; - module.execute = registryEntry.execute; - - // now link all the module dependencies - // amending the depMap as we go - for (var i = 0, l = load.dependencies.length; i < l; i++) { - var depName = load.dependencies[i].value; - var depModule = loader.modules[depName]; - - // if dependency not already in the module registry - // then try and link it now - if (!depModule) { - // get the dependency load record - for (var j = 0; j < loads.length; j++) { - if (loads[j].name != depName) - continue; - - // only link if already not already started linking (stops at circular / dynamic) - if (!loads[j].module) { - linkDeclarativeModule(loads[j], loads, loader); - depModule = loads[j].module; - } - // if circular, create the module record - else { - depModule = getOrCreateModuleRecord(depName, loader); - } - } - } - - // only declarative modules have dynamic bindings - if (depModule.importers) { - module.dependencies.push(depModule); - depModule.importers.push(module); - } - else { - // track dynamic records as null module records as already linked - module.dependencies.push(null); - } - - // run the setter for this dependency - if (module.setters[i]) - module.setters[i](depModule.module); - } - - load.status = 'linked'; - } - - - - // 15.2.5.5.1 LinkImports not implemented - // 15.2.5.7 ResolveExportEntries not implemented - // 15.2.5.8 ResolveExports not implemented - // 15.2.5.9 ResolveExport not implemented - // 15.2.5.10 ResolveImportEntries not implemented - - // 15.2.6.1 - function evaluateLoadedModule(loader, load) { - console.assert(load.status == 'linked', 'is linked ' + load.name); - - doEnsureEvaluated(load.module, [], loader); - return load.module.module; - } - - /* - * Module Object non-exotic for ES5: - * - * module.module bound module object - * module.execute execution function for module - * module.dependencies list of module objects for dependencies - * See getOrCreateModuleRecord for all properties - * - */ - function doExecute(module) { - try { - module.execute.call(__global); - } - catch(e) { - return e; - } - } - - // propogate execution errors - // see https://bugs.ecmascript.org/show_bug.cgi?id=2993 - function doEnsureEvaluated(module, seen, loader) { - var err = ensureEvaluated(module, seen, loader); - if (err) - throw err; - } - // 15.2.6.2 EnsureEvaluated adjusted - function ensureEvaluated(module, seen, loader) { - if (module.evaluated || !module.dependencies) - return; - - seen.push(module); - - var deps = module.dependencies; - var err; - - for (var i = 0, l = deps.length; i < l; i++) { - var dep = deps[i]; - // dynamic dependencies are empty in module.dependencies - // as they are already linked - if (!dep) - continue; - if (indexOf.call(seen, dep) == -1) { - err = ensureEvaluated(dep, seen, loader); - // stop on error, see https://bugs.ecmascript.org/show_bug.cgi?id=2996 - if (err) { - err = addToError(err, 'Error evaluating ' + dep.name + '\n'); - return err; - } - } - } - - if (module.failed) - return new Error('Module failed execution.'); - - if (module.evaluated) - return; - - module.evaluated = true; - err = doExecute(module); - if (err) { - module.failed = true; - } - else if (Object.preventExtensions) { - // spec variation - // we don't create a new module here because it was created and ammended - // we just disable further extensions instead - Object.preventExtensions(module.module); - } - - module.execute = undefined; - return err; - } - function addToError(err, msg) { if (err instanceof Error) err.message = msg + err.message; @@ -1087,5 +817,3 @@ function logloads(loads) { __global.Reflect.global = __global.Reflect.global || __global; __global.LoaderPolyfill = Loader; -})(); - diff --git a/src/module-tag.js b/src/module-tag.js new file mode 100644 index 0000000..a5a4013 --- /dev/null +++ b/src/module-tag.js @@ -0,0 +1,39 @@ + //