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
This commit is contained in:
parent
b41e2c68a5
commit
97d58d31c0
|
@ -1122,7 +1122,8 @@ export interface OutputOptions {
|
|||
| "amd-require"
|
||||
| "umd"
|
||||
| "umd2"
|
||||
| "jsonp";
|
||||
| "jsonp"
|
||||
| "system";
|
||||
/**
|
||||
* The output directory as **absolute path** (required).
|
||||
*/
|
||||
|
|
|
@ -137,6 +137,7 @@ class ExternalModule extends Module {
|
|||
case "amd-require":
|
||||
case "umd":
|
||||
case "umd2":
|
||||
case "system":
|
||||
return this.getSourceForAmdOrUmdExternal(
|
||||
this.id,
|
||||
this.optional,
|
||||
|
|
|
@ -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`);
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -986,7 +986,8 @@
|
|||
"amd-require",
|
||||
"umd",
|
||||
"umd2",
|
||||
"jsonp"
|
||||
"jsonp",
|
||||
"system"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
|
|
|
@ -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');
|
||||
});
|
|
@ -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',
|
||||
},
|
||||
}
|
||||
`
|
||||
})
|
||||
]
|
||||
};
|
|
@ -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()
|
||||
})
|
||||
});
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
`
|
||||
})
|
||||
]
|
||||
};
|
|
@ -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", \[[^\]]*\]/);
|
||||
});
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
]
|
||||
};
|
|
@ -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\(\) {/);
|
||||
});
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
]
|
||||
};
|
Loading…
Reference in New Issue