diff --git a/lib/ExternalModule.js b/lib/ExternalModule.js index f306710e4fb..8cb5e071c0b 100644 --- a/lib/ExternalModule.js +++ b/lib/ExternalModule.js @@ -74,7 +74,9 @@ class ExternalModule extends Module { .slice(1) .map(r => `[${JSON.stringify(r)}]`) .join(""); - return `module.exports = require(${moduleName})${objectLookup};`; + return `module.exports = require(${JSON.stringify( + moduleName + )})${objectLookup};`; } checkExternalVariable(variableToCheck, request) { @@ -94,15 +96,25 @@ class ExternalModule extends Module { } getSourceForDefaultCase(optional, request) { + if (!Array.isArray(request)) { + // make it an array as the look up works the same basically + request = [request]; + } + + const variableName = request[0]; const missingModuleError = optional - ? this.checkExternalVariable(request, request) + ? this.checkExternalVariable(variableName, request.join(".")) : ""; - return `${missingModuleError}module.exports = ${request};`; + const objectLookup = request + .slice(1) + .map(r => `[${JSON.stringify(r)}]`) + .join(""); + return `${missingModuleError}module.exports = ${variableName}${objectLookup};`; } getSourceString(runtime) { const request = - typeof this.request === "object" + typeof this.request === "object" && !Array.isArray(this.request) ? this.request[this.externalType] : this.request; switch (this.externalType) { diff --git a/schemas/WebpackOptions.json b/schemas/WebpackOptions.json index 01513196134..ac11bfcdd3d 100644 --- a/schemas/WebpackOptions.json +++ b/schemas/WebpackOptions.json @@ -123,6 +123,9 @@ { "type": "object" }, + { + "$ref": "#/definitions/common.arrayOfStringValues" + }, { "type": "boolean" } diff --git a/test/ExternalModule.unittest.js b/test/ExternalModule.unittest.js deleted file mode 100644 index 075c4600919..00000000000 --- a/test/ExternalModule.unittest.js +++ /dev/null @@ -1,308 +0,0 @@ -/* globals describe, it, beforeEach */ -"use strict"; - -const ExternalModule = require("../lib/ExternalModule"); -const OriginalSource = require("webpack-sources").OriginalSource; -const RawSource = require("webpack-sources").RawSource; - -describe("ExternalModule", () => { - let externalModule; - let request; - let type; - beforeEach(() => { - request = "some/request"; - type = "some-type"; - externalModule = new ExternalModule(request, type, `${type} ${request}`); - }); - describe("#identifier", () => { - it("returns an identifier for this module", () => { - const expected = `external "${request}"`; - expect(externalModule.identifier()).toBe(expected); - }); - }); - - describe("#readableIdentifier", () => { - it("returns an identifier for this module", () => { - const expected = `external "${request}"`; - expect(externalModule.identifier()).toBe(expected); - }); - }); - - describe("#needRebuild", () => { - it("always returns false", () => { - expect(externalModule.needRebuild()).toBe(false); - }); - }); - - describe("#size", () => { - it("always returns 42", () => { - expect(externalModule.size()).toBe(42); - }); - }); - - describe("#source", () => { - it("calls getSource with the result of getSourceString", () => { - // set up - const expectedString = "something expected stringy"; - const expectedSource = "something expected source"; - externalModule.getSource = jest.fn(() => expectedSource); - externalModule.getSourceString = jest.fn(() => expectedString); - - // invoke - const result = externalModule.source(); - - // check - expect(externalModule.getSource.mock.calls.length).toBe(1); - expect(externalModule.getSourceString.mock.calls.length).toBe(1); - expect(externalModule.getSource.mock.calls[0][0]).toBe(expectedString); - expect(result).toEqual(expectedSource); - }); - }); - - describe("#getSource", () => { - describe("given it should use source maps", () => { - beforeEach(() => { - externalModule.useSourceMap = true; - }); - it("returns an instance of OriginalSource", () => { - // set up - const someSourceString = "some source string"; - - // invoke - const result = externalModule.getSource(someSourceString); - - // check - expect(result).toBeInstanceOf(OriginalSource); - }); - }); - describe("given it does not use source maps", () => { - beforeEach(() => { - externalModule.useSourceMap = false; - }); - it("returns an instance of RawSource", () => { - // set up - const someSourceString = "some source string"; - - // invoke - const result = externalModule.getSource(someSourceString); - - // check - expect(result).toBeInstanceOf(RawSource); - }); - }); - }); - - describe("#getSourceForGlobalVariableExternal", () => { - describe("given an array as variable name in the global namespace", () => { - it("use the array as lookup in the global object", () => { - // set up - const type = "window"; - const varName = ["foo", "bar"]; - const expected = - '(function() { module.exports = window["foo"]["bar"]; }());'; - - // invoke - const result = externalModule.getSourceForGlobalVariableExternal( - varName, - type - ); - - // check - expect(result).toEqual(expected); - }); - }); - describe("given an single variable name", () => { - it("look it up in the global namespace", () => { - // set up - const type = "window"; - const varName = "foo"; - const expected = '(function() { module.exports = window["foo"]; }());'; - - // invoke - const result = externalModule.getSourceForGlobalVariableExternal( - varName, - type - ); - - // check - expect(result).toEqual(expected); - }); - }); - }); - - describe("#getSourceForCommonJsExternal", () => { - describe("given an array as names in the global namespace", () => { - it("use the first to require a module and the rest as lookup on the required module", () => { - // set up - const varName = ["module", "look", "up"]; - const expected = 'module.exports = require(module)["look"]["up"];'; - - // invoke - const result = externalModule.getSourceForCommonJsExternal( - varName, - type - ); - - // check - expect(result).toEqual(expected); - }); - }); - describe("given an single variable name", () => { - it("require a module with that name", () => { - // set up - const type = "window"; - const varName = "foo"; - const expected = 'module.exports = require("foo");'; - - // invoke - const result = externalModule.getSourceForCommonJsExternal( - varName, - type - ); - - // check - expect(result).toEqual(expected); - }); - }); - }); - - describe("#checkExternalVariable", () => { - it("creates a check that fails if a variable does not exist", () => { - // set up - const variableToCheck = "foo"; - const request = "bar"; - const expected = `if(typeof foo === 'undefined') {var e = new Error("Cannot find module 'bar'"); e.code = 'MODULE_NOT_FOUND'; throw e;} -`; - - // invoke - const result = externalModule.checkExternalVariable( - variableToCheck, - request - ); - - // check - expect(result).toEqual(expected); - }); - }); - - describe("#getSourceForAmdOrUmdExternal", () => { - it("looks up a global variable as specified by the id", () => { - // set up - const id = "someId"; - const optional = false; - const expected = "module.exports = __WEBPACK_EXTERNAL_MODULE_someId__;"; - - // invoke - const result = externalModule.getSourceForAmdOrUmdExternal( - id, - optional, - request - ); - - // check - expect(result).toEqual(expected); - }); - describe("given an optional check is set", function() { - it("ads a check for the existence of the variable before looking it up", () => { - // set up - const id = "someId"; - const optional = true; - const expected = `if(typeof __WEBPACK_EXTERNAL_MODULE_someId__ === 'undefined') {var e = new Error("Cannot find module 'some/request'"); e.code = 'MODULE_NOT_FOUND'; throw e;} -module.exports = __WEBPACK_EXTERNAL_MODULE_someId__;`; - - // invoke - const result = externalModule.getSourceForAmdOrUmdExternal( - id, - optional, - request - ); - - // check - expect(result).toEqual(expected); - }); - }); - }); - - describe("#getSourceForDefaultCase", () => { - it("returns the given request as lookup", () => { - // set up - const optional = false; - const expected = "module.exports = some/request;"; - - // invoke - const result = externalModule.getSourceForDefaultCase(optional, request); - - // check - expect(result).toEqual(expected); - }); - describe("given an optional check is requested", function() { - it("checks for the existence of the request setting it", () => { - // set up - const optional = true; - const expected = `if(typeof some/request === 'undefined') {var e = new Error("Cannot find module 'some/request'"); e.code = 'MODULE_NOT_FOUND'; throw e;} -module.exports = some/request;`; - - // invoke - const result = externalModule.getSourceForDefaultCase( - optional, - request - ); - - // check - expect(result).toEqual(expected); - }); - }); - }); - - describe("#updateHash", () => { - let hashedText; - let hash; - beforeEach(() => { - hashedText = ""; - hash = { - update: text => { - hashedText += text; - } - }; - externalModule.id = 12345678; - externalModule.updateHash(hash); - }); - it("updates hash with request", () => { - expect(hashedText).toMatch("some/request"); - }); - it("updates hash with type", () => { - expect(hashedText).toMatch("some-type"); - }); - it("updates hash with module id", () => { - expect(hashedText).toMatch("12345678"); - }); - }); - - describe("#updateHash without optional", () => { - let hashedText; - let hash; - beforeEach(() => { - hashedText = ""; - hash = { - update: text => { - hashedText += text; - } - }; - // Note no set of `externalModule.optional`, which crashed externals in 3.7.0 - externalModule.id = 12345678; - externalModule.updateHash(hash); - }); - it("updates hash with request", () => { - expect(hashedText).toMatch("some/request"); - }); - it("updates hash with type", () => { - expect(hashedText).toMatch("some-type"); - }); - it("updates hash with optional flag", () => { - expect(hashedText).toMatch("false"); - }); - it("updates hash with module id", () => { - expect(hashedText).toMatch("12345678"); - }); - }); -}); diff --git a/test/configCases/externals/externals-array/index.js b/test/configCases/externals/externals-array/index.js new file mode 100644 index 00000000000..a7dedba652b --- /dev/null +++ b/test/configCases/externals/externals-array/index.js @@ -0,0 +1,4 @@ +it("should not fail on optional externals", function() { + const external = require("external"); + expect(external).toBe(EXPECTED); +}); diff --git a/test/configCases/externals/externals-array/webpack.config.js b/test/configCases/externals/externals-array/webpack.config.js new file mode 100644 index 00000000000..af6b62d059c --- /dev/null +++ b/test/configCases/externals/externals-array/webpack.config.js @@ -0,0 +1,26 @@ +const webpack = require("../../../../"); +module.exports = [ + { + output: { + libraryTarget: "commonjs2" + }, + externals: { + external: ["webpack", "version"] + }, + plugins: [ + new webpack.DefinePlugin({ + EXPECTED: JSON.stringify(webpack.version) + }) + ] + }, + { + externals: { + external: ["Array", "isArray"] + }, + plugins: [ + new webpack.DefinePlugin({ + EXPECTED: "Array.isArray" + }) + ] + } +];