diff --git a/declarations/WebpackOptions.d.ts b/declarations/WebpackOptions.d.ts index 2b211080c..9af66f58f 100644 --- a/declarations/WebpackOptions.d.ts +++ b/declarations/WebpackOptions.d.ts @@ -1100,6 +1100,7 @@ export interface OutputOptions { | "commonjs2" | "commonjs-module" | "amd" + | "amd-require" | "umd" | "umd2" | "jsonp"; diff --git a/declarations/plugins/DllReferencePlugin.d.ts b/declarations/plugins/DllReferencePlugin.d.ts index 2354a7e63..73dfa522a 100644 --- a/declarations/plugins/DllReferencePlugin.d.ts +++ b/declarations/plugins/DllReferencePlugin.d.ts @@ -78,6 +78,7 @@ export type DllReferencePluginOptionsSourceType = | "commonjs2" | "commonjs-module" | "amd" + | "amd-require" | "umd" | "umd2" | "jsonp"; diff --git a/lib/AmdMainTemplatePlugin.js b/lib/AmdMainTemplatePlugin.js index 7184f5c7c..24417f6ba 100644 --- a/lib/AmdMainTemplatePlugin.js +++ b/lib/AmdMainTemplatePlugin.js @@ -11,13 +11,24 @@ const Template = require("./Template"); /** @typedef {import("./Compilation")} Compilation */ +/** + * @typedef {Object} AmdMainTemplatePluginOptions + * @param {string=} name the library name + * @property {boolean=} requireAsWrapper + */ + class AmdMainTemplatePlugin { /** - * @param {string=} name the library name + * @param {AmdMainTemplatePluginOptions} options the plugin options */ - constructor(name) { - /** @type {string=} */ - this.name = name; + constructor(options) { + if (!options || typeof options === "string") { + this.name = options; + this.requireAsWrapper = false; + } else { + this.name = options.name; + this.requireAsWrapper = options.requireAsWrapper; + } } /** @@ -50,7 +61,13 @@ class AmdMainTemplatePlugin { ) .join(", "); - if (this.name) { + if (this.requireAsWrapper) { + return new ConcatSource( + `require(${externalsDepsArray}, function(${externalsArguments}) { return `, + source, + "});" + ); + } else if (this.name) { const name = mainTemplate.getAssetPath(this.name, { hash, chunk diff --git a/lib/Compilation.js b/lib/Compilation.js index 55aa7e0df..1d9d0609a 100644 --- a/lib/Compilation.js +++ b/lib/Compilation.js @@ -94,6 +94,7 @@ const { arrayToSetDeprecation } = require("./util/deprecation"); * @typedef {Object} AvailableModulesChunkGroupMapping * @property {ChunkGroup} chunkGroup * @property {Set} availableModules + * @property {boolean} needCopy */ /** @@ -1344,7 +1345,7 @@ class Compilation { const moduleGraph = this.moduleGraph; - /** @typedef {{block: AsyncDependenciesBlock, chunkGroup: ChunkGroup}} ChunkGroupDep */ + /** @typedef {{block: AsyncDependenciesBlock, chunkGroup: ChunkGroup, couldBeFiltered: boolean}} ChunkGroupDep */ /** @type {Map} */ const chunkDependencies = new Map(); @@ -1530,7 +1531,8 @@ class Compilation { if (!deps) chunkDependencies.set(chunkGroup, (deps = [])); deps.push({ block: b, - chunkGroup: c + chunkGroup: c, + couldBeFiltered: true }); // 3. We enqueue the DependenciesBlock for traversal @@ -1652,12 +1654,14 @@ class Compilation { // PART TWO /** @type {Set} */ let availableModules; + /** @type {Set} */ let newAvailableModules; /** @type {Queue} */ const queue2 = new Queue( inputChunkGroups.map(chunkGroup => ({ chunkGroup, - availableModules: new Set() + availableModules: new Set(), + needCopy: true })) ); @@ -1685,8 +1689,12 @@ class Compilation { */ const filterFn = dep => { const depChunkGroup = dep.chunkGroup; + if (!dep.couldBeFiltered) return true; if (blocksWithNestedBlocks.has(dep.block)) return true; - if (areModulesAvailable(depChunkGroup, newAvailableModules)) return false; // break all modules are already available + if (areModulesAvailable(depChunkGroup, newAvailableModules)) { + return false; // break all modules are already available + } + dep.couldBeFiltered = false; return true; }; @@ -1705,7 +1713,10 @@ class Compilation { // the list didn't shrink. let minAvailableModules = minAvailableModulesMap.get(chunkGroup); if (minAvailableModules === undefined) { - minAvailableModulesMap.set(chunkGroup, new Set(availableModules)); + minAvailableModulesMap.set( + chunkGroup, + queueItem.needCopy ? new Set(availableModules) : availableModules + ); } else { let deletedModules = false; for (const m of minAvailableModules) { @@ -1731,30 +1742,33 @@ class Compilation { } } - // 4. Filter edges with available modules - const filteredDeps = deps.filter(filterFn); - - // 5. Foreach remaining edge + // 4. Foreach remaining edge const nextChunkGroups = new Set(); - for (let i = 0; i < filteredDeps.length; i++) { - const dep = filteredDeps[i]; + for (let i = 0; i < deps.length; i++) { + const dep = deps[i]; + + // Filter inline, rather than creating a new array from `.filter()` + if (!filterFn(dep)) { + continue; + } const depChunkGroup = dep.chunkGroup; const depBlock = dep.block; - // 6. Connect block with chunk + // 5. Connect block with chunk chunkGraph.connectBlockAndChunkGroup(depBlock, depChunkGroup); - // 7. Connect chunk with parent + // 6. Connect chunk with parent connectChunkGroupParentAndChild(chunkGroup, depChunkGroup); nextChunkGroups.add(depChunkGroup); } - // 8. Enqueue further traversal + // 7. Enqueue further traversal for (const nextChunkGroup of nextChunkGroups) { queue2.enqueue({ chunkGroup: nextChunkGroup, - availableModules: newAvailableModules + availableModules: newAvailableModules, + needCopy: nextChunkGroup.size !== 1 }); } } diff --git a/lib/ExternalModule.js b/lib/ExternalModule.js index fad8d4517..f4a018bf1 100644 --- a/lib/ExternalModule.js +++ b/lib/ExternalModule.js @@ -199,13 +199,14 @@ class ExternalModule extends Module { return getSourceForGlobalVariableExternal(request, this.externalType); case "global": return getSourceForGlobalVariableExternal( - runtimeTemplate.outputOptions.globalObject, - this.externalType + request, + runtimeTemplate.outputOptions.globalObject ); case "commonjs": case "commonjs2": return getSourceForCommonJsExternal(request); case "amd": + case "amd-require": case "umd": case "umd2": return getSourceForAmdOrUmdExternal( diff --git a/lib/LibraryTemplatePlugin.js b/lib/LibraryTemplatePlugin.js index 7f2f3c459..f79511930 100644 --- a/lib/LibraryTemplatePlugin.js +++ b/lib/LibraryTemplatePlugin.js @@ -27,7 +27,9 @@ const accessorToObjectAccess = accessor => { */ const accessorAccess = (base, accessor, umdProperty, joinWith = "; ") => { const normalizedAccessor = - typeof accessor === "object" ? accessor[umdProperty] : accessor; + typeof accessor === "object" && !Array.isArray(accessor) + ? accessor[umdProperty] + : accessor; const accessors = Array.isArray(normalizedAccessor) ? normalizedAccessor : [normalizedAccessor]; @@ -139,15 +141,16 @@ class LibraryTemplatePlugin { compilation ); break; - case "amd": { + case "amd": + case "amd-require": { const AmdMainTemplatePlugin = require("./AmdMainTemplatePlugin"); - if (this.name) { - if (typeof this.name !== "string") - throw new Error("library name must be a string for amd target"); - new AmdMainTemplatePlugin(this.name).apply(compilation); - } else { - new AmdMainTemplatePlugin().apply(compilation); + if (this.name && typeof this.name !== "string") { + throw new Error("library name must be a string for amd target"); } + new AmdMainTemplatePlugin({ + name: this.name, + requireAsWrapper: this.target === "amd-require" + }).apply(compilation); break; } case "umd": diff --git a/lib/MainTemplate.js b/lib/MainTemplate.js index a50a937cc..038c43d6c 100644 --- a/lib/MainTemplate.js +++ b/lib/MainTemplate.js @@ -249,7 +249,7 @@ module.exports = class MainTemplate { this.hooks.requireExtensions.tap( "MainTemplate", (source, renderContext) => { - const { chunk, hash } = renderContext; + const { chunk, hash, chunkGraph } = renderContext; const buf = []; const chunkMaps = chunk.getChunkMaps(); // Check if there are non initial chunks which need to be imported using require-ensure @@ -265,6 +265,22 @@ module.exports = class MainTemplate { ); buf.push(Template.indent("return Promise.all(promises);")); buf.push("};"); + } else if ( + chunkGraph.hasModuleInGraph(chunk, m => + m.blocks.some(b => { + const chunkGroup = chunkGraph.getBlockChunkGroup(b); + return chunkGroup && chunkGroup.chunks.length > 0; + }) + ) + ) { + // There async blocks in the graph, so we need to add an empty requireEnsure + // function anyway. This can happen with multiple entrypoints. + buf.push("// The chunk loading function for additional chunks"); + buf.push("// Since all referenced chunks are already included"); + buf.push("// in this file, this function is empty here."); + buf.push(`${this.requireFn}.e = function requireEnsure() {`); + buf.push(Template.indent("return Promise.resolve();")); + buf.push("};"); } buf.push(""); buf.push("// expose the modules object (__webpack_modules__)"); diff --git a/lib/web/JsonpMainTemplate.runtime.js b/lib/web/JsonpMainTemplate.runtime.js index b85dfa3dc..3febeb9b0 100644 --- a/lib/web/JsonpMainTemplate.runtime.js +++ b/lib/web/JsonpMainTemplate.runtime.js @@ -20,7 +20,7 @@ module.exports = function() { var script = document.createElement("script"); script.charset = "utf-8"; script.src = $require$.p + $hotChunkFilename$; - $crossOriginLoading$; + if ($crossOriginLoading$) script.crossOrigin = $crossOriginLoading$; head.appendChild(script); } diff --git a/lib/web/JsonpMainTemplatePlugin.js b/lib/web/JsonpMainTemplatePlugin.js index 1e8ac1ca2..ff3b3bd3f 100644 --- a/lib/web/JsonpMainTemplatePlugin.js +++ b/lib/web/JsonpMainTemplatePlugin.js @@ -585,9 +585,7 @@ class JsonpMainTemplatePlugin { .replace(/\$require\$/g, mainTemplate.requireFn) .replace( /\$crossOriginLoading\$/g, - crossOriginLoading - ? `script.crossOrigin = ${JSON.stringify(crossOriginLoading)}` - : "" + crossOriginLoading ? JSON.stringify(crossOriginLoading) : "null" ) .replace(/\$hotMainFilename\$/g, currentHotUpdateMainFilename) .replace(/\$hotChunkFilename\$/g, currentHotUpdateChunkFilename) diff --git a/package.json b/package.json index f1cb17a8b..16925e401 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "husky": "^1.0.0-rc.6", "istanbul": "^0.4.5", "jest": "^23.4.1", - "jest-silent-reporter": "^0.0.5", "json-loader": "^0.5.7", "json-schema-to-typescript": "^6.0.1", "less": "^2.5.1", diff --git a/schemas/WebpackOptions.json b/schemas/WebpackOptions.json index de8c00803..88bf44558 100644 --- a/schemas/WebpackOptions.json +++ b/schemas/WebpackOptions.json @@ -963,6 +963,7 @@ "commonjs2", "commonjs-module", "amd", + "amd-require", "umd", "umd2", "jsonp" diff --git a/schemas/ajv.absolutePath.js b/schemas/ajv.absolutePath.js index 2d7c92fdd..8ef11f435 100644 --- a/schemas/ajv.absolutePath.js +++ b/schemas/ajv.absolutePath.js @@ -35,7 +35,7 @@ module.exports = ajv => data, `The provided value ${JSON.stringify( data - )} contans exclamation mark (!) which is not allowed because it's reserved for loader syntax.` + )} contains exclamation mark (!) which is not allowed because it's reserved for loader syntax.` ) ]; passes = false; diff --git a/schemas/plugins/DllReferencePlugin.json b/schemas/plugins/DllReferencePlugin.json index a79255405..1e078b977 100644 --- a/schemas/plugins/DllReferencePlugin.json +++ b/schemas/plugins/DllReferencePlugin.json @@ -88,6 +88,7 @@ "commonjs2", "commonjs-module", "amd", + "amd-require", "umd", "umd2", "jsonp" diff --git a/test/Validation.test.js b/test/Validation.test.js index 676b4a3a5..9d306bcba 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -239,7 +239,7 @@ describe("Validation", () => { } }, message: [ - ' - configuration.output.path: The provided value "/somepath/!test" contans exclamation mark (!) which is not allowed because it\'s reserved for loader syntax.', + ' - configuration.output.path: The provided value "/somepath/!test" contains exclamation mark (!) which is not allowed because it\'s reserved for loader syntax.', " -> The output directory as **absolute path** (required)." ] }, diff --git a/test/__snapshots__/StatsTestCases.test.js.snap b/test/__snapshots__/StatsTestCases.test.js.snap index c80d8d034..db544ef53 100644 --- a/test/__snapshots__/StatsTestCases.test.js.snap +++ b/test/__snapshots__/StatsTestCases.test.js.snap @@ -1163,13 +1163,13 @@ Child `; exports[`StatsTestCases should print correct stats for limit-chunk-count-plugin 1`] = ` -"Hash: 05e860caf71fe3b2d19aee833b1335bf5cefe6d3074451f01c7cf41c58065d34f2c917d77c3f99ff +"Hash: 4c55553bd94205710a5fee833b1335bf5cefe6d3074451f01c7cf41c58065d34f2c917d77c3f99ff Child 1 chunks: - Hash: 05e860caf71fe3b2d19a + Hash: 4c55553bd94205710a5f Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names - bundle.js 6.39 KiB 0 [emitted] main + bundle.js 6.67 KiB 0 [emitted] main Entrypoint main = bundle.js chunk {0} bundle.js (main) 191 bytes <{0}> >{0}< [entry] [rendered] [0] ./index.js 73 bytes {0} [built] diff --git a/test/configCases/entry/issue-8110/a.js b/test/configCases/entry/issue-8110/a.js new file mode 100644 index 000000000..95aac3b54 --- /dev/null +++ b/test/configCases/entry/issue-8110/a.js @@ -0,0 +1,8 @@ +import run from "./c"; +import "./d"; + +it("should not crash", () => { + return run().then(result => { + expect(result.default).toBe("ok"); + }); +}) diff --git a/test/configCases/entry/issue-8110/b.js b/test/configCases/entry/issue-8110/b.js new file mode 100644 index 000000000..b4c710a82 --- /dev/null +++ b/test/configCases/entry/issue-8110/b.js @@ -0,0 +1,3 @@ +import run from "./c"; + +run(); diff --git a/test/configCases/entry/issue-8110/c.js b/test/configCases/entry/issue-8110/c.js new file mode 100644 index 000000000..c8bc53d94 --- /dev/null +++ b/test/configCases/entry/issue-8110/c.js @@ -0,0 +1,3 @@ +export default function run() { + return import("./d"); +} diff --git a/test/configCases/entry/issue-8110/d.js b/test/configCases/entry/issue-8110/d.js new file mode 100644 index 000000000..5c6b89abf --- /dev/null +++ b/test/configCases/entry/issue-8110/d.js @@ -0,0 +1 @@ +export default "ok"; diff --git a/test/configCases/entry/issue-8110/webpack.config.js b/test/configCases/entry/issue-8110/webpack.config.js new file mode 100644 index 000000000..ca8fd308d --- /dev/null +++ b/test/configCases/entry/issue-8110/webpack.config.js @@ -0,0 +1,9 @@ +module.exports = { + entry: { + bundle0: "./a", + other: "./b" + }, + output: { + filename: "[name].js" + } +}; diff --git a/test/configCases/externals/global/index.js b/test/configCases/externals/global/index.js new file mode 100644 index 000000000..821f2376e --- /dev/null +++ b/test/configCases/externals/global/index.js @@ -0,0 +1,11 @@ +afterEach(done => { + delete global.EXTERNAL_TEST_GLOBAL; + done(); +}); + +it("should move externals in chunks into entry chunk", function() { + global.EXTERNAL_TEST_GLOBAL = 42; + // eslint-disable-next-line node/no-missing-require + const result = require("external"); + expect(result).toBe(42); +}); diff --git a/test/configCases/externals/global/webpack.config.js b/test/configCases/externals/global/webpack.config.js new file mode 100644 index 000000000..5e9889bf3 --- /dev/null +++ b/test/configCases/externals/global/webpack.config.js @@ -0,0 +1,5 @@ +module.exports = { + externals: { + external: "global EXTERNAL_TEST_GLOBAL" + } +}; diff --git a/test/configCases/library/array-global/index.js b/test/configCases/library/array-global/index.js new file mode 100644 index 000000000..274e87f90 --- /dev/null +++ b/test/configCases/library/array-global/index.js @@ -0,0 +1,3 @@ +it("should define global object with property", function() { + expect(a["b"]).toBeDefined(); +}); diff --git a/test/configCases/library/array-global/webpack.config.js b/test/configCases/library/array-global/webpack.config.js new file mode 100644 index 000000000..bc177f6b5 --- /dev/null +++ b/test/configCases/library/array-global/webpack.config.js @@ -0,0 +1,5 @@ +module.exports = { + output: { + library: ["a", "b"] + } +}; diff --git a/test/configCases/library/array-window/index.js b/test/configCases/library/array-window/index.js new file mode 100644 index 000000000..6c539d7a3 --- /dev/null +++ b/test/configCases/library/array-window/index.js @@ -0,0 +1,3 @@ +it("should define property in 'window' object", function() { + expect(window["a"]["b"]).toBeDefined(); +}); diff --git a/test/configCases/library/array-window/webpack.config.js b/test/configCases/library/array-window/webpack.config.js new file mode 100644 index 000000000..010ed97f1 --- /dev/null +++ b/test/configCases/library/array-window/webpack.config.js @@ -0,0 +1,7 @@ +module.exports = { + target: "web", + output: { + library: ["a", "b"], + libraryTarget: "window" + } +}; diff --git a/test/configCases/target/amd-require/index.js b/test/configCases/target/amd-require/index.js new file mode 100644 index 000000000..16e308760 --- /dev/null +++ b/test/configCases/target/amd-require/index.js @@ -0,0 +1,10 @@ +it("should run", function() { + +}); + +it("should name require", function() { + var fs = nodeRequire("fs"); + var source = fs.readFileSync(__filename, "utf-8"); + + expect(source).toMatch(/require\(\[[^\]]*\], function\(/); +}); diff --git a/test/configCases/target/amd-require/webpack.config.js b/test/configCases/target/amd-require/webpack.config.js new file mode 100644 index 000000000..1bb3b0ac2 --- /dev/null +++ b/test/configCases/target/amd-require/webpack.config.js @@ -0,0 +1,17 @@ +const webpack = require("../../../../"); +module.exports = { + output: { + libraryTarget: "amd-require" + }, + node: { + __dirname: false, + __filename: false + }, + plugins: [ + new webpack.BannerPlugin({ + raw: true, + banner: + "var nodeRequire = require;\nvar require = function(deps, fn) { fn(); }\n" + }) + ] +}; diff --git a/test/configCases/target/amd-unnamed/index.js b/test/configCases/target/amd-unnamed/index.js index 85d11d914..1341eea61 100644 --- a/test/configCases/target/amd-unnamed/index.js +++ b/test/configCases/target/amd-unnamed/index.js @@ -6,5 +6,5 @@ it("should name define", function() { var fs = require("fs"); var source = fs.readFileSync(__filename, "utf-8"); - expect(source).toMatch("define(function("); + expect(source).toMatch(/define\(\[[^\]]*\], function\(/); }); diff --git a/yarn.lock b/yarn.lock index 2d1e170d2..13122ca76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3842,17 +3842,6 @@ jest-matcher-utils@^23.2.0: jest-get-type "^22.1.0" pretty-format "^23.2.0" -jest-message-util@^23.0.0: - version "23.3.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.3.0.tgz#bc07b11cec6971fb5dd9de2dfb60ebc22150c160" - integrity sha1-vAexHOxpcftd2d4t+2DrwiFQwWA= - dependencies: - "@babel/code-frame" "^7.0.0-beta.35" - chalk "^2.0.1" - micromatch "^3.1.10" - slash "^1.0.0" - stack-utils "^1.0.1" - jest-message-util@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f" @@ -3942,14 +3931,6 @@ jest-serializer@^23.0.1: resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165" integrity sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU= -jest-silent-reporter@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/jest-silent-reporter/-/jest-silent-reporter-0.0.5.tgz#14139b7a991b7bcca880dd8a69c33a91723a8f1f" - integrity sha512-LWNEJEeI9jbqOaNyqmnLWyEJXaNlDMCDqX6NbY89pzgerb3iExky56zD5oBvh+nvgzEND9cjL57EhifaAJBhjw== - dependencies: - chalk "^2.3.1" - jest-util "^23.0.0" - jest-snapshot@^23.4.1: version "23.4.1" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.4.1.tgz#090de9acae927f6a3af3005bda40d912b83e9c96" @@ -3967,19 +3948,6 @@ jest-snapshot@^23.4.1: pretty-format "^23.2.0" semver "^5.5.0" -jest-util@^23.0.0: - version "23.0.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.0.1.tgz#68ea5bd7edb177d3059f9797259f8e0dacce2f99" - integrity sha1-aOpb1+2xd9MFn5eXJZ+ODazOL5k= - dependencies: - callsites "^2.0.0" - chalk "^2.0.1" - graceful-fs "^4.1.11" - is-ci "^1.0.10" - jest-message-util "^23.0.0" - mkdirp "^0.5.1" - source-map "^0.6.0" - jest-util@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561" @@ -4652,7 +4620,7 @@ micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: +micromatch@^3.1.4, micromatch@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==