From 97d58d31c064df4bd3823957df01b338ae690f54 Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Fri, 1 Mar 2019 14:26:36 -0700 Subject: [PATCH 1/5] Adding support for output libraryTarget 'system'. Resolves #8833. Tests, prettier Self review Add comments to System tests. Adding typescript definition Guy Bedford's feedback Self review Improving code coverage --- declarations/WebpackOptions.d.ts | 3 +- lib/ExternalModule.js | 1 + lib/LibraryTemplatePlugin.js | 7 ++ lib/SystemMainTemplatePlugin.js | 113 ++++++++++++++++++ schemas/WebpackOptions.json | 3 +- .../externals/externals-system/index.js | 16 +++ .../externals-system/webpack.config.js | 31 +++++ .../configCases/target/system-export/index.js | 22 ++++ .../target/system-export/webpack.config.js | 28 +++++ test/configCases/target/system-named/index.js | 15 +++ .../target/system-named/webpack.config.js | 25 ++++ .../target/system-unnamed/index.js | 15 +++ .../target/system-unnamed/webpack.config.js | 25 ++++ 13 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 lib/SystemMainTemplatePlugin.js create mode 100644 test/configCases/externals/externals-system/index.js create mode 100644 test/configCases/externals/externals-system/webpack.config.js create mode 100644 test/configCases/target/system-export/index.js create mode 100644 test/configCases/target/system-export/webpack.config.js create mode 100644 test/configCases/target/system-named/index.js create mode 100644 test/configCases/target/system-named/webpack.config.js create mode 100644 test/configCases/target/system-unnamed/index.js create mode 100644 test/configCases/target/system-unnamed/webpack.config.js diff --git a/declarations/WebpackOptions.d.ts b/declarations/WebpackOptions.d.ts index f112da0e527..6be525d1f5b 100644 --- a/declarations/WebpackOptions.d.ts +++ b/declarations/WebpackOptions.d.ts @@ -1122,7 +1122,8 @@ export interface OutputOptions { | "amd-require" | "umd" | "umd2" - | "jsonp"; + | "jsonp" + | "system"; /** * The output directory as **absolute path** (required). */ diff --git a/lib/ExternalModule.js b/lib/ExternalModule.js index d7e4e3f5574..e2da635a9da 100644 --- a/lib/ExternalModule.js +++ b/lib/ExternalModule.js @@ -137,6 +137,7 @@ class ExternalModule extends Module { case "amd-require": case "umd": case "umd2": + case "system": return this.getSourceForAmdOrUmdExternal( this.id, this.optional, diff --git a/lib/LibraryTemplatePlugin.js b/lib/LibraryTemplatePlugin.js index eb6e7ed26a8..87b558c81f6 100644 --- a/lib/LibraryTemplatePlugin.js +++ b/lib/LibraryTemplatePlugin.js @@ -169,6 +169,13 @@ class LibraryTemplatePlugin { new JsonpExportMainTemplatePlugin(this.name).apply(compilation); break; } + case "system": { + const SystemMainTemplatePlugin = require("./SystemMainTemplatePlugin"); + new SystemMainTemplatePlugin({ + name: this.name + }).apply(compilation); + break; + } default: throw new Error(`${this.target} is not a valid Library target`); } diff --git a/lib/SystemMainTemplatePlugin.js b/lib/SystemMainTemplatePlugin.js new file mode 100644 index 00000000000..0b1df5e9a6a --- /dev/null +++ b/lib/SystemMainTemplatePlugin.js @@ -0,0 +1,113 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Joel Denning @joeldenning + */ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const Template = require("./Template"); + +/** @typedef {import("./Compilation")} Compilation */ + +/** + * @typedef {Object} SystemMainTemplatePluginOptions + * @param {string=} name the library name + */ + +class SystemMainTemplatePlugin { + /** + * @param {SystemMainTemplatePluginOptions} options the plugin options + */ + constructor(options) { + this.name = options.name; + } + + /** + * @param {Compilation} compilation the compilation instance + * @returns {void} + */ + apply(compilation) { + const { mainTemplate, chunkTemplate } = compilation; + + const onRenderWithEntry = (source, chunk, hash) => { + const externals = chunk.getModules().filter(m => m.external); + + // The name this bundle should be registered as with System + const name = this.name ? `"${this.name}", ` : ``; + + // The array of dependencies that are external to webpack and will be provided by System + const systemDependencies = JSON.stringify( + externals.map(m => + typeof m.request === "object" ? m.request.amd : m.request + ) + ); + + // The name of the variable provided by System for exporting + const dynamicExport = `__WEBPACK_DYNAMIC_EXPORT__`; + + // An array of the internal variable names for the webpack externals + const externalWebpackNames = externals.map( + m => `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(`${m.id}`)}__` + ); + + // Declaring variables for the internal variable names for the webpack externals + const externalVarDeclarations = + externalWebpackNames.length > 0 + ? `\n var ${externalWebpackNames.join(", ")};` + : ``; + + // The system.register format requires an array of setter functions for externals. + const setters = + externalWebpackNames.length === 0 + ? "" + : `\n setters: [` + + externalWebpackNames + .map( + external => + `\n function(module) {` + + `\n ${external} = module;` + + `\n }` + ) + .join(",") + + `\n ],`; + + return new ConcatSource( + `System.register(${name}${systemDependencies}, function(${dynamicExport}) {` + + externalVarDeclarations + + `\n return {` + + setters + + `\n execute: function() {` + + `\n ${dynamicExport}(`, + source, + `\n )\n }\n };\n});` + ); + }; + + for (const template of [mainTemplate, chunkTemplate]) { + template.hooks.renderWithEntry.tap( + "SystemMainTemplatePlugin", + onRenderWithEntry + ); + } + + mainTemplate.hooks.globalHashPaths.tap( + "SystemMainTemplatePlugin", + paths => { + if (this.name) { + paths.push(this.name); + } + return paths; + } + ); + + mainTemplate.hooks.hash.tap("SystemMainTemplatePlugin", hash => { + hash.update("exports system"); + if (this.name) { + hash.update(this.name); + } + }); + } +} + +module.exports = SystemMainTemplatePlugin; diff --git a/schemas/WebpackOptions.json b/schemas/WebpackOptions.json index c7e54b83e56..85dcce9b4ef 100644 --- a/schemas/WebpackOptions.json +++ b/schemas/WebpackOptions.json @@ -986,7 +986,8 @@ "amd-require", "umd", "umd2", - "jsonp" + "jsonp", + "system" ] }, "path": { diff --git a/test/configCases/externals/externals-system/index.js b/test/configCases/externals/externals-system/index.js new file mode 100644 index 00000000000..90ff3b3d36a --- /dev/null +++ b/test/configCases/externals/externals-system/index.js @@ -0,0 +1,16 @@ +/* This test verifies that webpack externals are properly indicated as dependencies to System. + * Also that when System provides the external variables to webpack that the variables get plumbed + * through correctly and are usable by the webpack bundle. +*/ +afterEach(function(done) { + delete global.System; + done() +}) + +it("should get an external from System", function() { + const external1 = require("external1"); + expect(external1).toBe('the external1 value'); + + const external2 = require("external2"); + expect(external2).toBe('the external2 value'); +}); diff --git a/test/configCases/externals/externals-system/webpack.config.js b/test/configCases/externals/externals-system/webpack.config.js new file mode 100644 index 00000000000..ace2ff33837 --- /dev/null +++ b/test/configCases/externals/externals-system/webpack.config.js @@ -0,0 +1,31 @@ +const webpack = require("../../../../"); +module.exports = { + output: { + libraryTarget: "system" + }, + externals: { + external1: "external1", + external2: "external2" + }, + plugins: [ + new webpack.BannerPlugin({ + raw: true, + banner: ` + System = { + register: function(deps, fn) { + function dynamicExport() {} + var mod = fn(dynamicExport); + deps.forEach((dep, i) => { + mod.setters[i](System.registry[dep]); + }) + mod.execute(); + }, + registry: { + external1: 'the external1 value', + external2: 'the external2 value', + }, + } + ` + }) + ] +}; diff --git a/test/configCases/target/system-export/index.js b/test/configCases/target/system-export/index.js new file mode 100644 index 00000000000..44798c17df8 --- /dev/null +++ b/test/configCases/target/system-export/index.js @@ -0,0 +1,22 @@ +// This test verifies that values exported by a webpack bundle are consumable by systemjs. + +export const namedThing = { + hello: 'there' +} + +export default 'the default export' + +it("should successfully export values to System", function(done) { + var fs = require("fs"); + var source = fs.readFileSync(__filename, "utf-8"); + + // set timeout allows the webpack bundle to finish exporting, which exports to System at the very + // end of its execution. + setTimeout(() => { + expect(global.SystemExports['default']).toBe('the default export') + expect(global.SystemExports.namedThing).toBe(namedThing) + delete global.System; + delete global.SystemExports + done() + }) +}); \ No newline at end of file diff --git a/test/configCases/target/system-export/webpack.config.js b/test/configCases/target/system-export/webpack.config.js new file mode 100644 index 00000000000..2c09f946d53 --- /dev/null +++ b/test/configCases/target/system-export/webpack.config.js @@ -0,0 +1,28 @@ +const webpack = require("../../../../"); +module.exports = { + output: { + libraryTarget: "system" + }, + node: { + __dirname: false, + __filename: false + }, + plugins: [ + new webpack.BannerPlugin({ + raw: true, + banner: ` + global.SystemExports = { + 'export': function(exports) { + Object.assign(global.SystemExports, exports); + } + }; + global.System = { + register: function(deps, fn) { + var mod = fn(global.SystemExports['export']); + mod.execute(); + } + }; + ` + }) + ] +}; diff --git a/test/configCases/target/system-named/index.js b/test/configCases/target/system-named/index.js new file mode 100644 index 00000000000..3274941f941 --- /dev/null +++ b/test/configCases/target/system-named/index.js @@ -0,0 +1,15 @@ +/* This test verifies that when output.library is specified that the compiled bundle provides +* the library name to System during the System.register +*/ + +afterEach(function(done) { + delete global.System; + done() +}) + +it("should call System.register without a name", function() { + var fs = require("fs"); + var source = fs.readFileSync(__filename, "utf-8"); + + expect(source).toMatch(/.*System\.register\("named-system-module", \[[^\]]*\]/); +}); \ No newline at end of file diff --git a/test/configCases/target/system-named/webpack.config.js b/test/configCases/target/system-named/webpack.config.js new file mode 100644 index 00000000000..838038e8c9c --- /dev/null +++ b/test/configCases/target/system-named/webpack.config.js @@ -0,0 +1,25 @@ +const webpack = require("../../../../"); +module.exports = { + output: { + library: "named-system-module", + libraryTarget: "system" + }, + node: { + __dirname: false, + __filename: false + }, + plugins: [ + new webpack.BannerPlugin({ + raw: true, + banner: ` + System = { + register: function(name, deps, fn) { + function dynamicExport() {} + var mod = fn(dynamicExport); + mod.execute(); + } + } + ` + }) + ] +}; diff --git a/test/configCases/target/system-unnamed/index.js b/test/configCases/target/system-unnamed/index.js new file mode 100644 index 00000000000..0991f8cda3c --- /dev/null +++ b/test/configCases/target/system-unnamed/index.js @@ -0,0 +1,15 @@ +/* This test verifies that when there is no output.library specified that the call to +* System.register does not include a name argument. +*/ + +afterEach(function(done) { + delete global.System; + done() +}) + +it("should call System.register without a name", function() { + var fs = require("fs"); + var source = fs.readFileSync(__filename, "utf-8"); + + expect(source).toMatch(/.*System\.register\(\[[^\]]*\], function\(__WEBPACK_DYNAMIC_EXPORT__\) {\s+(var .*;)?\s*return \{\s+setters: [^]+,[^]+execute: function\(\) {/); +}); \ No newline at end of file diff --git a/test/configCases/target/system-unnamed/webpack.config.js b/test/configCases/target/system-unnamed/webpack.config.js new file mode 100644 index 00000000000..41c2d0fcf1a --- /dev/null +++ b/test/configCases/target/system-unnamed/webpack.config.js @@ -0,0 +1,25 @@ +const webpack = require("../../../../"); +module.exports = { + output: { + libraryTarget: "system" + }, + node: { + __dirname: false, + __filename: false + }, + plugins: [ + new webpack.BannerPlugin({ + raw: true, + banner: ` + System = { + register: function(deps, fn) { + function dynamicExport() {} + + var mod = fn(dynamicExport) + mod.execute() + } + } + ` + }) + ] +}; From ec94b823747c5468a58bb1adb85672214c982324 Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Mon, 18 Mar 2019 15:20:28 -0600 Subject: [PATCH 2/5] Feedback from sokra. Using Template.indent and Template.asString. --- lib/SystemMainTemplatePlugin.js | 57 ++++++++++++------- .../externals/externals-system/index.js | 8 +-- .../externals-system/webpack.config.js | 30 +++++----- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/lib/SystemMainTemplatePlugin.js b/lib/SystemMainTemplatePlugin.js index 0b1df5e9a6a..483cd509e33 100644 --- a/lib/SystemMainTemplatePlugin.js +++ b/lib/SystemMainTemplatePlugin.js @@ -34,7 +34,7 @@ class SystemMainTemplatePlugin { const externals = chunk.getModules().filter(m => m.external); // The name this bundle should be registered as with System - const name = this.name ? `"${this.name}", ` : ``; + const name = this.name ? `"${this.name}", ` : ""; // The array of dependencies that are external to webpack and will be provided by System const systemDependencies = JSON.stringify( @@ -44,7 +44,7 @@ class SystemMainTemplatePlugin { ); // The name of the variable provided by System for exporting - const dynamicExport = `__WEBPACK_DYNAMIC_EXPORT__`; + const dynamicExport = "__WEBPACK_DYNAMIC_EXPORT__"; // An array of the internal variable names for the webpack externals const externalWebpackNames = externals.map( @@ -54,33 +54,48 @@ class SystemMainTemplatePlugin { // Declaring variables for the internal variable names for the webpack externals const externalVarDeclarations = externalWebpackNames.length > 0 - ? `\n var ${externalWebpackNames.join(", ")};` - : ``; + ? `var ${externalWebpackNames.join(", ")};` + : ""; // The system.register format requires an array of setter functions for externals. const setters = externalWebpackNames.length === 0 ? "" - : `\n setters: [` + - externalWebpackNames - .map( - external => - `\n function(module) {` + - `\n ${external} = module;` + - `\n }` - ) - .join(",") + - `\n ],`; + : Template.asString([ + "setters: [", + Template.indent( + externalWebpackNames.map((external, index) => + Template.asString([ + "function(module) {", + Template.indent(`${external} = module;`), + `}${index < externalWebpackNames.length - 1 ? "," : ""}` + ]) + ) + ), + "]," + ]); return new ConcatSource( - `System.register(${name}${systemDependencies}, function(${dynamicExport}) {` + - externalVarDeclarations + - `\n return {` + - setters + - `\n execute: function() {` + - `\n ${dynamicExport}(`, + Template.asString([ + `System.register(${name}${systemDependencies}, function(${dynamicExport}) {`, + Template.indent([ + externalVarDeclarations, + "return {", + Template.indent([ + setters, + "execute: function() {", + Template.indent(`${dynamicExport}(`) + ]) + ]) + ]), source, - `\n )\n }\n };\n});` + Template.asString([ + Template.indent([ + Template.indent([Template.indent([");"]), "}"]), + "};" + ]), + "})" + ]) ); }; diff --git a/test/configCases/externals/externals-system/index.js b/test/configCases/externals/externals-system/index.js index 90ff3b3d36a..2e8fb5a7f1f 100644 --- a/test/configCases/externals/externals-system/index.js +++ b/test/configCases/externals/externals-system/index.js @@ -3,14 +3,14 @@ * through correctly and are usable by the webpack bundle. */ afterEach(function(done) { - delete global.System; - done() + delete global.System; + done() }) it("should get an external from System", function() { const external1 = require("external1"); expect(external1).toBe('the external1 value'); - const external2 = require("external2"); - expect(external2).toBe('the external2 value'); + const external2 = require("external2"); + expect(external2).toBe('the external2 value'); }); diff --git a/test/configCases/externals/externals-system/webpack.config.js b/test/configCases/externals/externals-system/webpack.config.js index ace2ff33837..be11a7f1152 100644 --- a/test/configCases/externals/externals-system/webpack.config.js +++ b/test/configCases/externals/externals-system/webpack.config.js @@ -11,21 +11,21 @@ module.exports = { new webpack.BannerPlugin({ raw: true, banner: ` - System = { - register: function(deps, fn) { - function dynamicExport() {} - var mod = fn(dynamicExport); - deps.forEach((dep, i) => { - mod.setters[i](System.registry[dep]); - }) - mod.execute(); - }, - registry: { - external1: 'the external1 value', - external2: 'the external2 value', - }, - } - ` + System = { + register: function(deps, fn) { + function dynamicExport() {} + var mod = fn(dynamicExport); + deps.forEach((dep, i) => { + mod.setters[i](System.registry[dep]); + }) + mod.execute(); + }, + registry: { + external1: 'the external1 value', + external2: 'the external2 value', + }, + } + ` }) ] }; From b59e0d1fc2bbf54a2f39becc7fa9a957f9e1fa8a Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Fri, 5 Apr 2019 13:56:34 -0600 Subject: [PATCH 3/5] Self review -- fixing test name. --- test/configCases/target/system-named/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/configCases/target/system-named/index.js b/test/configCases/target/system-named/index.js index 3274941f941..0dafc34d9ce 100644 --- a/test/configCases/target/system-named/index.js +++ b/test/configCases/target/system-named/index.js @@ -7,7 +7,7 @@ afterEach(function(done) { done() }) -it("should call System.register without a name", function() { +it("should call System.register with a name", function() { var fs = require("fs"); var source = fs.readFileSync(__filename, "utf-8"); From 39a680d339b658a56e825c5f8b2a7757efbb6b11 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 10 Apr 2019 13:29:35 +0200 Subject: [PATCH 4/5] formating of output code --- lib/SystemMainTemplatePlugin.js | 35 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/SystemMainTemplatePlugin.js b/lib/SystemMainTemplatePlugin.js index 483cd509e33..0958fd89847 100644 --- a/lib/SystemMainTemplatePlugin.js +++ b/lib/SystemMainTemplatePlugin.js @@ -34,7 +34,7 @@ class SystemMainTemplatePlugin { const externals = chunk.getModules().filter(m => m.external); // The name this bundle should be registered as with System - const name = this.name ? `"${this.name}", ` : ""; + const name = this.name ? `${JSON.stringify(this.name)}, ` : ""; // The array of dependencies that are external to webpack and will be provided by System const systemDependencies = JSON.stringify( @@ -64,13 +64,15 @@ class SystemMainTemplatePlugin { : Template.asString([ "setters: [", Template.indent( - externalWebpackNames.map((external, index) => - Template.asString([ - "function(module) {", - Template.indent(`${external} = module;`), - `}${index < externalWebpackNames.length - 1 ? "," : ""}` - ]) - ) + externalWebpackNames + .map(external => + Template.asString([ + "function(module) {", + Template.indent(`${external} = module;`), + "}" + ]) + ) + .join(",\n") ), "]," ]); @@ -87,15 +89,16 @@ class SystemMainTemplatePlugin { Template.indent(`${dynamicExport}(`) ]) ]) - ]), + ]) + "\n", source, - Template.asString([ - Template.indent([ - Template.indent([Template.indent([");"]), "}"]), - "};" - ]), - "})" - ]) + "\n" + + Template.asString([ + Template.indent([ + Template.indent([Template.indent([");"]), "}"]), + "};" + ]), + "})" + ]) ); }; From f7d0c2545f72dbd66314579a33154935efdfde7b Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 10 Apr 2019 13:48:47 +0200 Subject: [PATCH 5/5] Improve way how System is tested --- test/ConfigTestCases.test.js | 74 ++++++++--------- .../externals/externals-system/index.js | 11 +-- .../externals/externals-system/test.config.js | 16 ++++ .../externals-system/webpack.config.js | 24 +----- .../configCases/target/system-export/index.js | 25 ++---- .../target/system-export/test.config.js | 13 +++ .../target/system-export/webpack.config.js | 21 +---- test/configCases/target/system-named/index.js | 16 +--- .../target/system-named/test.config.js | 13 +++ .../target/system-named/webpack.config.js | 17 +--- .../target/system-unnamed/index.js | 16 +--- .../target/system-unnamed/test.config.js | 13 +++ .../target/system-unnamed/webpack.config.js | 18 +---- test/helpers/fakeSystem.js | 80 +++++++++++++++++++ 14 files changed, 194 insertions(+), 163 deletions(-) create mode 100644 test/configCases/externals/externals-system/test.config.js create mode 100644 test/configCases/target/system-export/test.config.js create mode 100644 test/configCases/target/system-named/test.config.js create mode 100644 test/configCases/target/system-unnamed/test.config.js create mode 100644 test/helpers/fakeSystem.js diff --git a/test/ConfigTestCases.test.js b/test/ConfigTestCases.test.js index 9ebd6f94a49..2206cef1763 100644 --- a/test/ConfigTestCases.test.js +++ b/test/ConfigTestCases.test.js @@ -174,7 +174,6 @@ describe("ConfigTestCases", () => { function _require(currentDirectory, module) { if (Array.isArray(module) || /^\.\.?\//.test(module)) { - let fn; let content; let p; if (Array.isArray(module)) { @@ -189,45 +188,47 @@ describe("ConfigTestCases", () => { p = path.join(currentDirectory, module); content = fs.readFileSync(p, "utf-8"); } - if ( - options.target === "web" || - options.target === "webworker" - ) { - fn = vm.runInNewContext( - "(function(require, module, exports, __dirname, __filename, it, beforeEach, afterEach, expect, jest, window) {" + - 'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' + - content + - "\n})", - globalContext, - p - ); - } else { - fn = vm.runInThisContext( - "(function(require, module, exports, __dirname, __filename, it, beforeEach, afterEach, expect, jest) {" + - "global.expect = expect; " + - 'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' + - content + - "\n})", - p - ); - } const m = { exports: {} }; - fn.call( - m.exports, - _require.bind(null, path.dirname(p)), - m, - m.exports, - path.dirname(p), - p, - _it, - _beforeEach, - _afterEach, + let runInNewContext = false; + const moduleScope = { + require: _require.bind(null, path.dirname(p)), + module: m, + exports: m.exports, + __dirname: path.dirname(p), + __filename: p, + it: _it, + beforeEach: _beforeEach, + afterEach: _afterEach, expect, jest, - globalContext - ); + _globalAssign: { expect }, + nsObj: m => { + Object.defineProperty(m, Symbol.toStringTag, { + value: "Module" + }); + return m; + } + }; + if ( + options.target === "web" || + options.target === "webworker" + ) { + moduleScope.window = globalContext; + runInNewContext = true; + } + if (testConfig.moduleScope) { + testConfig.moduleScope(moduleScope); + } + const args = Object.keys(moduleScope).join(", "); + if (!runInNewContext) + content = `Object.assign(global, _globalAssign); ${content}`; + const code = `(function({${args}}) {${content}\n})`; + const fn = runInNewContext + ? vm.runInNewContext(code, globalContext, p) + : vm.runInThisContext(code, p); + fn.call(m.exports, moduleScope); return m.exports; } else if ( testConfig.modules && @@ -239,6 +240,7 @@ describe("ConfigTestCases", () => { let filesCount = 0; if (testConfig.noTests) return process.nextTick(done); + if (testConfig.beforeExecute) testConfig.beforeExecute(); for (let i = 0; i < optionsArr.length; i++) { const bundlePath = testConfig.findBundle(i, optionsArr[i]); if (bundlePath) { @@ -256,9 +258,9 @@ describe("ConfigTestCases", () => { "Should have found at least one bundle file per webpack config" ) ); + if (testConfig.afterExecute) testConfig.afterExecute(); if (getNumberOfTests() < filesCount) return done(new Error("No tests exported by test case")); - if (testConfig.afterExecute) testConfig.afterExecute(); done(); }); }) diff --git a/test/configCases/externals/externals-system/index.js b/test/configCases/externals/externals-system/index.js index 2e8fb5a7f1f..8f634069218 100644 --- a/test/configCases/externals/externals-system/index.js +++ b/test/configCases/externals/externals-system/index.js @@ -1,16 +1,11 @@ /* This test verifies that webpack externals are properly indicated as dependencies to System. * Also that when System provides the external variables to webpack that the variables get plumbed * through correctly and are usable by the webpack bundle. -*/ -afterEach(function(done) { - delete global.System; - done() -}) - + */ it("should get an external from System", function() { const external1 = require("external1"); - expect(external1).toBe('the external1 value'); + expect(external1).toBe("the external1 value"); const external2 = require("external2"); - expect(external2).toBe('the external2 value'); + expect(external2).toBe("the external2 value"); }); diff --git a/test/configCases/externals/externals-system/test.config.js b/test/configCases/externals/externals-system/test.config.js new file mode 100644 index 00000000000..3f8225baf26 --- /dev/null +++ b/test/configCases/externals/externals-system/test.config.js @@ -0,0 +1,16 @@ +const System = require("../../../helpers/fakeSystem"); + +module.exports = { + beforeExecute: () => { + System.init({ + external1: "the external1 value", + external2: "the external2 value" + }); + }, + moduleScope(scope) { + scope.System = System; + }, + afterExecute: () => { + System.execute("(anonym)"); + } +}; diff --git a/test/configCases/externals/externals-system/webpack.config.js b/test/configCases/externals/externals-system/webpack.config.js index be11a7f1152..9a4ae5336e5 100644 --- a/test/configCases/externals/externals-system/webpack.config.js +++ b/test/configCases/externals/externals-system/webpack.config.js @@ -1,4 +1,3 @@ -const webpack = require("../../../../"); module.exports = { output: { libraryTarget: "system" @@ -6,26 +5,5 @@ module.exports = { externals: { external1: "external1", external2: "external2" - }, - plugins: [ - new webpack.BannerPlugin({ - raw: true, - banner: ` - System = { - register: function(deps, fn) { - function dynamicExport() {} - var mod = fn(dynamicExport); - deps.forEach((dep, i) => { - mod.setters[i](System.registry[dep]); - }) - mod.execute(); - }, - registry: { - external1: 'the external1 value', - external2: 'the external2 value', - }, - } - ` - }) - ] + } }; diff --git a/test/configCases/target/system-export/index.js b/test/configCases/target/system-export/index.js index 44798c17df8..bfe3bb7e747 100644 --- a/test/configCases/target/system-export/index.js +++ b/test/configCases/target/system-export/index.js @@ -1,22 +1,13 @@ // This test verifies that values exported by a webpack bundle are consumable by systemjs. export const namedThing = { - hello: 'there' -} + hello: "there" +}; -export default 'the default export' +export default "the default export"; -it("should successfully export values to System", function(done) { - var fs = require("fs"); - var source = fs.readFileSync(__filename, "utf-8"); - - // set timeout allows the webpack bundle to finish exporting, which exports to System at the very - // end of its execution. - setTimeout(() => { - expect(global.SystemExports['default']).toBe('the default export') - expect(global.SystemExports.namedThing).toBe(namedThing) - delete global.System; - delete global.SystemExports - done() - }) -}); \ No newline at end of file +it("should successfully export values to System", function() { + const exports = eval("System").registry["(anonym)"].exports; + expect(exports["default"]).toBe("the default export"); + expect(exports.namedThing).toBe(namedThing); +}); diff --git a/test/configCases/target/system-export/test.config.js b/test/configCases/target/system-export/test.config.js new file mode 100644 index 00000000000..97ebf538dbe --- /dev/null +++ b/test/configCases/target/system-export/test.config.js @@ -0,0 +1,13 @@ +const System = require("../../../helpers/fakeSystem"); + +module.exports = { + beforeExecute: () => { + System.init(); + }, + moduleScope(scope) { + scope.System = System; + }, + afterExecute: () => { + System.execute("(anonym)"); + } +}; diff --git a/test/configCases/target/system-export/webpack.config.js b/test/configCases/target/system-export/webpack.config.js index 2c09f946d53..063b492074e 100644 --- a/test/configCases/target/system-export/webpack.config.js +++ b/test/configCases/target/system-export/webpack.config.js @@ -1,4 +1,3 @@ -const webpack = require("../../../../"); module.exports = { output: { libraryTarget: "system" @@ -6,23 +5,5 @@ module.exports = { node: { __dirname: false, __filename: false - }, - plugins: [ - new webpack.BannerPlugin({ - raw: true, - banner: ` - global.SystemExports = { - 'export': function(exports) { - Object.assign(global.SystemExports, exports); - } - }; - global.System = { - register: function(deps, fn) { - var mod = fn(global.SystemExports['export']); - mod.execute(); - } - }; - ` - }) - ] + } }; diff --git a/test/configCases/target/system-named/index.js b/test/configCases/target/system-named/index.js index 0dafc34d9ce..5f355e1d11f 100644 --- a/test/configCases/target/system-named/index.js +++ b/test/configCases/target/system-named/index.js @@ -1,15 +1,5 @@ /* This test verifies that when output.library is specified that the compiled bundle provides -* the library name to System during the System.register -*/ + * the library name to System during the System.register + */ -afterEach(function(done) { - delete global.System; - done() -}) - -it("should call System.register with a name", function() { - var fs = require("fs"); - var source = fs.readFileSync(__filename, "utf-8"); - - expect(source).toMatch(/.*System\.register\("named-system-module", \[[^\]]*\]/); -}); \ No newline at end of file +it("should call System.register with a name", function() {}); diff --git a/test/configCases/target/system-named/test.config.js b/test/configCases/target/system-named/test.config.js new file mode 100644 index 00000000000..8b3f83a51f9 --- /dev/null +++ b/test/configCases/target/system-named/test.config.js @@ -0,0 +1,13 @@ +const System = require("../../../helpers/fakeSystem"); + +module.exports = { + beforeExecute: () => { + System.init(); + }, + moduleScope(scope) { + scope.System = System; + }, + afterExecute: () => { + System.execute("named-system-module"); + } +}; diff --git a/test/configCases/target/system-named/webpack.config.js b/test/configCases/target/system-named/webpack.config.js index 838038e8c9c..1f4b76b0ca1 100644 --- a/test/configCases/target/system-named/webpack.config.js +++ b/test/configCases/target/system-named/webpack.config.js @@ -1,4 +1,3 @@ -const webpack = require("../../../../"); module.exports = { output: { library: "named-system-module", @@ -7,19 +6,5 @@ module.exports = { node: { __dirname: false, __filename: false - }, - plugins: [ - new webpack.BannerPlugin({ - raw: true, - banner: ` - System = { - register: function(name, deps, fn) { - function dynamicExport() {} - var mod = fn(dynamicExport); - mod.execute(); - } - } - ` - }) - ] + } }; diff --git a/test/configCases/target/system-unnamed/index.js b/test/configCases/target/system-unnamed/index.js index 0991f8cda3c..7f2b1a91ee9 100644 --- a/test/configCases/target/system-unnamed/index.js +++ b/test/configCases/target/system-unnamed/index.js @@ -1,15 +1,5 @@ /* This test verifies that when there is no output.library specified that the call to -* System.register does not include a name argument. -*/ + * System.register does not include a name argument. + */ -afterEach(function(done) { - delete global.System; - done() -}) - -it("should call System.register without a name", function() { - var fs = require("fs"); - var source = fs.readFileSync(__filename, "utf-8"); - - expect(source).toMatch(/.*System\.register\(\[[^\]]*\], function\(__WEBPACK_DYNAMIC_EXPORT__\) {\s+(var .*;)?\s*return \{\s+setters: [^]+,[^]+execute: function\(\) {/); -}); \ No newline at end of file +it("should call System.register without a name", function() {}); diff --git a/test/configCases/target/system-unnamed/test.config.js b/test/configCases/target/system-unnamed/test.config.js new file mode 100644 index 00000000000..97ebf538dbe --- /dev/null +++ b/test/configCases/target/system-unnamed/test.config.js @@ -0,0 +1,13 @@ +const System = require("../../../helpers/fakeSystem"); + +module.exports = { + beforeExecute: () => { + System.init(); + }, + moduleScope(scope) { + scope.System = System; + }, + afterExecute: () => { + System.execute("(anonym)"); + } +}; diff --git a/test/configCases/target/system-unnamed/webpack.config.js b/test/configCases/target/system-unnamed/webpack.config.js index 41c2d0fcf1a..063b492074e 100644 --- a/test/configCases/target/system-unnamed/webpack.config.js +++ b/test/configCases/target/system-unnamed/webpack.config.js @@ -1,4 +1,3 @@ -const webpack = require("../../../../"); module.exports = { output: { libraryTarget: "system" @@ -6,20 +5,5 @@ module.exports = { node: { __dirname: false, __filename: false - }, - plugins: [ - new webpack.BannerPlugin({ - raw: true, - banner: ` - System = { - register: function(deps, fn) { - function dynamicExport() {} - - var mod = fn(dynamicExport) - mod.execute() - } - } - ` - }) - ] + } }; diff --git a/test/helpers/fakeSystem.js b/test/helpers/fakeSystem.js new file mode 100644 index 00000000000..2b83c4b9341 --- /dev/null +++ b/test/helpers/fakeSystem.js @@ -0,0 +1,80 @@ +const System = { + register: (name, deps, fn) => { + if (!System.registry) { + throw new Error("System is no initialized"); + } + if (typeof name !== "string") { + fn = deps; + deps = name; + name = "(anonym)"; + } + if (!Array.isArray(deps)) { + fn = deps; + deps = []; + } + const dynamicExport = result => { + if (System.registry[name] !== entry) { + throw new Error(`Module ${name} calls dynamicExport too late`); + } + entry.exports = result; + }; + if (name in System.registry) { + throw new Error(`Module ${name} is already registered`); + } + const mod = fn(dynamicExport); + if (deps.length > 0) { + if (!Array.isArray(mod.setters)) { + throw new Error( + `Module ${name} must have setters, because it has dependencies` + ); + } + if (mod.setters.length !== deps.length) { + throw new Error( + `Module ${name} has incorrect number of setters for the dependencies` + ); + } + } + const entry = { + name, + deps, + fn, + mod, + executed: false, + exports: undefined + }; + System.registry[name] = entry; + }, + registry: undefined, + init: modules => { + System.registry = {}; + if (modules) { + for (const name of Object.keys(modules)) { + System.registry[name] = { + executed: true, + exports: modules[name] + }; + } + } + }, + execute: name => { + const m = System.registry[name]; + if (!m) throw new Error(`Module ${name} not registered`); + if (m.executed) throw new Error(`Module ${name} was already executed`); + return System.ensureExecuted(name); + }, + ensureExecuted: name => { + const m = System.registry[name]; + if (!m) throw new Error(`Module ${name} not registered`); + if (!m.executed) { + m.executed = true; + for (let i = 0; i < m.deps.length; i++) { + const dep = m.deps[i]; + System.ensureExecuted(dep); + m.mod.setters[i](System.registry[dep].exports); + } + m.mod.execute(); + } + return m.exports; + } +}; +module.exports = System;