diff --git a/lib/WebpackOptionsDefaulter.js b/lib/WebpackOptionsDefaulter.js index 62f8e9736..a33833789 100644 --- a/lib/WebpackOptionsDefaulter.js +++ b/lib/WebpackOptionsDefaulter.js @@ -21,16 +21,20 @@ const isWebLikeTarget = options => { return options.target === "web" || options.target === "webworker"; }; -const getDevtoolNamespace = library => { +const getLibraryName = library => { // if options.output.library is a string if (Array.isArray(library)) { return library.join("."); } else if (typeof library === "object") { - return getDevtoolNamespace(library.root); + return getLibraryName(library.root); } return library || ""; }; +const getLibraryIdentifier = library => { + return Template.toIdentifier(getLibraryName(library)); +}; + class WebpackOptionsDefaulter extends OptionsDefaulter { constructor() { super(); @@ -235,17 +239,17 @@ class WebpackOptionsDefaulter extends OptionsDefaulter { this.set("output.compareBeforeEmit", true); this.set("output.hotUpdateFunction", "make", options => { return Template.toIdentifier( - "webpackHotUpdate" + Template.toIdentifier(options.output.library) + "webpackHotUpdate" + getLibraryIdentifier(options.output.library) ); }); this.set("output.jsonpFunction", "make", options => { return Template.toIdentifier( - "webpackJsonp" + Template.toIdentifier(options.output.library) + "webpackJsonp" + getLibraryIdentifier(options.output.library) ); }); this.set("output.chunkCallbackName", "make", options => { return Template.toIdentifier( - "webpackChunk" + Template.toIdentifier(options.output.library) + "webpackChunk" + getLibraryIdentifier(options.output.library) ); }); this.set("output.globalObject", "make", options => { @@ -265,7 +269,7 @@ class WebpackOptionsDefaulter extends OptionsDefaulter { } }); this.set("output.devtoolNamespace", "make", options => { - return getDevtoolNamespace(options.output.library); + return getLibraryName(options.output.library); }); this.set("output.libraryTarget", "make", options => { return options.output.module ? "module" : "var"; @@ -495,9 +499,7 @@ class WebpackOptionsDefaulter extends OptionsDefaulter { this.set("resolveLoader.extensions", [".js"]); this.set("resolveLoader.mainFiles", ["index"]); - this.set("infrastructureLogging", "call", value => - Object.assign({}, value) - ); + this.set("infrastructureLogging", "call", value => ({ ...value })); this.set("infrastructureLogging.level", "info"); this.set("infrastructureLogging.debug", false); } diff --git a/package.json b/package.json index 2ac8c32bd..2e35dfaf8 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "husky": "^3.0.2", "istanbul": "^0.4.5", "jest": "^24.9.0", + "jest-diff": "^24.9.0", "jest-junit": "^8.0.0", "json-loader": "^0.5.7", "json-schema-to-typescript": "^7.0.0", @@ -67,7 +68,7 @@ "mini-svg-data-uri": "^1.1.3", "open-cli": "^5.0.0", "prettier": "^1.14.3", - "pretty-format": "^24.8.0", + "pretty-format": "^24.9.0", "pug": "^2.0.4", "pug-loader": "^2.4.0", "raw-loader": "^3.1.0", diff --git a/test/WebpackOptionsDefaulter.unittest.js b/test/WebpackOptionsDefaulter.unittest.js new file mode 100644 index 000000000..8281ce2df --- /dev/null +++ b/test/WebpackOptionsDefaulter.unittest.js @@ -0,0 +1,746 @@ +const jestDiff = require("jest-diff"); +const stripAnsi = require("strip-ansi"); +const WebpackOptionsDefaulter = require("../lib/WebpackOptionsDefaulter"); + +/** + * Escapes regular expression metacharacters + * @param {string} str String to quote + * @returns {string} Escaped string + */ +const quotemeta = str => { + return str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); +}; + +describe("WebpackOptionsDefaulter", () => { + const cwd = process.cwd(); + const cwdRegExp = new RegExp( + `${quotemeta(cwd)}((?:\\\\)?(?:[a-zA-Z.\\-_]+\\\\)*)`, + "g" + ); + const escapedCwd = JSON.stringify(cwd).slice(1, -1); + const escapedCwdRegExp = new RegExp( + `${quotemeta(escapedCwd)}((?:\\\\\\\\)?(?:[a-zA-Z.\\-_]+\\\\\\\\)*)`, + "g" + ); + const normalizeCwd = str => { + if (cwd.startsWith("/")) { + str = str.replace(new RegExp(quotemeta(cwd), "g"), ""); + } else { + str = str.replace(cwdRegExp, (m, g) => `${g.replace(/\\/g, "/")}`); + str = str.replace( + escapedCwdRegExp, + (m, g) => `${g.replace(/\\\\/g, "/")}` + ); + } + return str; + }; + + class Diff { + constructor(value) { + this.value = value; + } + } + + expect.addSnapshotSerializer({ + test(value) { + return value instanceof Diff; + }, + print(received) { + return normalizeCwd(received.value); + } + }); + + expect.addSnapshotSerializer({ + test(value) { + return typeof value === "string"; + }, + print(received) { + return JSON.stringify(normalizeCwd(received)); + } + }); + + const getDefaultConfig = config => + new WebpackOptionsDefaulter().process(config); + + const baseConfig = getDefaultConfig({ mode: "none" }); + + it("should have the correct base config", () => { + expect(baseConfig).toMatchInlineSnapshot(` + Object { + "cache": false, + "context": "", + "devtool": false, + "entry": "./src", + "experiments": Object { + "asset": false, + "asyncWebAssembly": false, + "importAsync": false, + "importAwait": false, + "mjs": false, + "outputModule": false, + "syncWebAssembly": false, + "topLevelAwait": false, + }, + "infrastructureLogging": Object { + "debug": false, + "level": "info", + }, + "mode": "none", + "module": Object { + "defaultRules": Array [ + Object { + "resolve": Object {}, + "type": "javascript/auto", + }, + Object { + "test": /\\\\\\.json\\$/i, + "type": "json", + }, + ], + "exprContextCritical": true, + "exprContextRecursive": true, + "exprContextRegExp": false, + "exprContextRequest": ".", + "rules": Array [], + "strictExportPresence": false, + "strictThisContextOnImports": false, + "unknownContextCritical": true, + "unknownContextRecursive": true, + "unknownContextRegExp": false, + "unknownContextRequest": ".", + "unsafeCache": undefined, + "wrappedContextCritical": false, + "wrappedContextRecursive": true, + "wrappedContextRegExp": /\\.\\*/, + }, + "node": Object { + "__dirname": "mock", + "__filename": "mock", + "global": true, + }, + "optimization": Object { + "checkWasmTypes": false, + "chunkIds": "natural", + "concatenateModules": false, + "flagIncludedChunks": false, + "innerGraph": false, + "mangleExports": false, + "mangleWasmImports": false, + "mergeDuplicateChunks": true, + "minimize": false, + "minimizer": Array [ + Object { + "apply": [Function], + }, + ], + "moduleIds": "natural", + "noEmitOnErrors": false, + "nodeEnv": false, + "portableRecords": false, + "providedExports": true, + "removeAvailableModules": false, + "removeEmptyChunks": true, + "runtimeChunk": undefined, + "sideEffects": false, + "splitChunks": Object { + "automaticNameDelimiter": "-", + "cacheGroups": Object { + "default": Object { + "idHint": "", + "minChunks": 2, + "priority": -20, + "reuseExistingChunk": true, + }, + "defaultVendors": Object { + "idHint": "vendors", + "priority": -10, + "reuseExistingChunk": true, + "test": /\\[\\\\\\\\/\\]node_modules\\[\\\\\\\\/\\]/i, + }, + }, + "chunks": "async", + "hidePathInfo": false, + "maxAsyncRequests": Infinity, + "maxInitialRequests": Infinity, + "minChunks": 1, + "minRemainingSize": undefined, + "minSize": 10000, + }, + "usedExports": false, + }, + "output": Object { + "assetModuleFilename": "[hash][ext]", + "chunkCallbackName": "webpackChunk", + "chunkFilename": "[name].js", + "chunkLoadTimeout": 120000, + "compareBeforeEmit": true, + "crossOriginLoading": false, + "devtoolNamespace": "", + "ecmaVersion": 6, + "filename": "[name].js", + "globalObject": "window", + "hashDigest": "hex", + "hashDigestLength": 20, + "hashFunction": "md4", + "hotUpdateChunkFilename": "[id].[fullhash].hot-update.js", + "hotUpdateFunction": "webpackHotUpdate", + "hotUpdateMainFilename": "[fullhash].hot-update.json", + "iife": true, + "jsonpFunction": "webpackJsonp", + "jsonpScriptType": false, + "library": "", + "libraryTarget": "var", + "module": false, + "path": "/dist", + "pathinfo": false, + "publicPath": "", + "sourceMapFilename": "[file].map[query]", + "strictModuleExceptionHandling": false, + "webassemblyModuleFilename": "[hash].module.wasm", + }, + "performance": false, + "resolve": Object { + "aliasFields": Array [ + "browser", + ], + "cache": false, + "extensions": Array [ + ".js", + ".json", + ".wasm", + ], + "mainFields": Array [ + "browser", + "module", + "main", + ], + "mainFiles": Array [ + "index", + ], + "modules": Array [ + "node_modules", + ], + }, + "resolveLoader": Object { + "cache": false, + "extensions": Array [ + ".js", + ], + "mainFields": Array [ + "loader", + "main", + ], + "mainFiles": Array [ + "index", + ], + }, + "target": "web", + } + `); + }); + + const test = (name, options, fn) => { + it(`should generate the correct defaults from ${name}`, () => { + if (!("mode" in options)) options.mode = "none"; + const result = getDefaultConfig(options); + + const diff = stripAnsi( + jestDiff(baseConfig, result, { expand: false, contextLines: 0 }) + ); + + fn(expect(new Diff(diff)), expect(result)); + }); + }; + + test("empty config", {}, e => + e.toMatchInlineSnapshot(`Compared values have no visual difference.`) + ); + test("none mode", { mode: "none" }, e => + e.toMatchInlineSnapshot(`Compared values have no visual difference.`) + ); + test("no mode provided", { mode: undefined }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -20,1 +20,1 @@ + - "mode": "none", + + "mode": undefined, + @@ -54,6 +54,6 @@ + - "checkWasmTypes": false, + - "chunkIds": "natural", + - "concatenateModules": false, + - "flagIncludedChunks": false, + - "innerGraph": false, + - "mangleExports": false, + + "checkWasmTypes": true, + + "chunkIds": "deterministic", + + "concatenateModules": true, + + "flagIncludedChunks": true, + + "innerGraph": true, + + "mangleExports": true, + @@ -62,1 +62,1 @@ + - "minimize": false, + + "minimize": true, + @@ -68,3 +68,3 @@ + - "moduleIds": "natural", + - "noEmitOnErrors": false, + - "nodeEnv": false, + + "moduleIds": "deterministic", + + "noEmitOnErrors": true, + + "nodeEnv": "production", + @@ -76,1 +76,1 @@ + - "sideEffects": false, + + "sideEffects": true, + @@ -94,3 +94,3 @@ + - "hidePathInfo": false, + - "maxAsyncRequests": Infinity, + - "maxInitialRequests": Infinity, + + "hidePathInfo": true, + + "maxAsyncRequests": 6, + + "maxInitialRequests": 4, + @@ -99,1 +99,1 @@ + - "minSize": 10000, + + "minSize": 30000, + @@ -101,1 +101,1 @@ + - "usedExports": false, + + "usedExports": true, + @@ -133,1 +133,5 @@ + - "performance": false, + + "performance": Object { + + "hints": "warning", + + "maxAssetSize": 250000, + + "maxEntrypointSize": 250000, + + }, + `) + ); + test("production", { mode: "production" }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -20,1 +20,1 @@ + - "mode": "none", + + "mode": "production", + @@ -54,6 +54,6 @@ + - "checkWasmTypes": false, + - "chunkIds": "natural", + - "concatenateModules": false, + - "flagIncludedChunks": false, + - "innerGraph": false, + - "mangleExports": false, + + "checkWasmTypes": true, + + "chunkIds": "deterministic", + + "concatenateModules": true, + + "flagIncludedChunks": true, + + "innerGraph": true, + + "mangleExports": true, + @@ -62,1 +62,1 @@ + - "minimize": false, + + "minimize": true, + @@ -68,3 +68,3 @@ + - "moduleIds": "natural", + - "noEmitOnErrors": false, + - "nodeEnv": false, + + "moduleIds": "deterministic", + + "noEmitOnErrors": true, + + "nodeEnv": "production", + @@ -76,1 +76,1 @@ + - "sideEffects": false, + + "sideEffects": true, + @@ -94,3 +94,3 @@ + - "hidePathInfo": false, + - "maxAsyncRequests": Infinity, + - "maxInitialRequests": Infinity, + + "hidePathInfo": true, + + "maxAsyncRequests": 6, + + "maxInitialRequests": 4, + @@ -99,1 +99,1 @@ + - "minSize": 10000, + + "minSize": 30000, + @@ -101,1 +101,1 @@ + - "usedExports": false, + + "usedExports": true, + @@ -133,1 +133,5 @@ + - "performance": false, + + "performance": Object { + + "hints": "warning", + + "maxAssetSize": 250000, + + "maxEntrypointSize": 250000, + + }, + `) + ); + test("development", { mode: "development" }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -2,1 +2,7 @@ + - "cache": false, + + "cache": Object { + + "immutablePaths": undefined, + + "managedPaths": Array [ + + "/node_modules", + + ], + + "type": "memory", + + }, + @@ -4,1 +10,1 @@ + - "devtool": false, + + "devtool": "eval", + @@ -20,1 +26,1 @@ + - "mode": "none", + + "mode": "development", + @@ -43,1 +49,1 @@ + - "unsafeCache": undefined, + + "unsafeCache": [Function anonymous], + @@ -55,1 +61,1 @@ + - "chunkIds": "natural", + + "chunkIds": "named", + @@ -68,1 +74,1 @@ + - "moduleIds": "natural", + + "moduleIds": "named", + @@ -70,1 +76,1 @@ + - "nodeEnv": false, + + "nodeEnv": "development", + @@ -98,1 +104,1 @@ + - "minRemainingSize": undefined, + + "minRemainingSize": 0, + @@ -127,1 +133,1 @@ + - "pathinfo": false, + + "pathinfo": true, + @@ -138,1 +144,1 @@ + - "cache": false, + + "cache": true, + @@ -157,1 +163,1 @@ + - "cache": false, + + "cache": true, + `) + ); + test("sync wasm", { experiments: { syncWebAssembly: true } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -13,1 +13,1 @@ + - "syncWebAssembly": false, + + "syncWebAssembly": true, + @@ -30,0 +30,4 @@ + + }, + + Object { + + "test": /\\.wasm$/i, + + "type": "webassembly/sync", + `) + ); + test("mjs", { experiments: { mjs: true } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -11,1 +11,1 @@ + - "mjs": false, + + "mjs": true, + @@ -28,0 +28,10 @@ + + "resolve": Object { + + "mainFields": Array [ + + "browser", + + "main", + + ], + + }, + + "test": /\\.mjs$/i, + + "type": "javascript/esm", + + }, + + Object { + @@ -140,0 +150,1 @@ + + ".mjs", + `) + ); + test("output module", { experiments: { outputModule: true } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -12,1 +12,1 @@ + - "outputModule": false, + + "outputModule": true, + @@ -120,1 +120,1 @@ + - "iife": true, + + "iife": false, + @@ -122,1 +122,1 @@ + - "jsonpScriptType": false, + + "jsonpScriptType": "module", + @@ -124,2 +124,2 @@ + - "libraryTarget": "var", + - "module": false, + + "libraryTarget": "module", + + "module": true, + `) + ); + test("async wasm", { experiments: { asyncWebAssembly: true } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -8,1 +8,1 @@ + - "asyncWebAssembly": false, + + "asyncWebAssembly": true, + @@ -30,0 +30,4 @@ + + }, + + Object { + + "test": /\\.wasm$/i, + + "type": "webassembly/async", + `) + ); + test( + "both wasm", + { experiments: { syncWebAssembly: true, asyncWebAssembly: true } }, + e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -8,1 +8,1 @@ + - "asyncWebAssembly": false, + + "asyncWebAssembly": true, + @@ -13,1 +13,1 @@ + - "syncWebAssembly": false, + + "syncWebAssembly": true, + @@ -30,0 +30,8 @@ + + }, + + Object { + + "test": /\\.wasm$/i, + + "type": "webassembly/sync", + + }, + + Object { + + "test": /\\.wasm$/i, + + "type": "webassembly/async", + `) + ); + test("const filename", { output: { filename: "bundle.js" } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -106,1 +106,1 @@ + - "chunkFilename": "[name].js", + + "chunkFilename": "[id].bundle.js", + @@ -112,1 +112,1 @@ + - "filename": "[name].js", + + "filename": "bundle.js", + `) + ); + test("function filename", { output: { filename: () => "bundle.js" } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -106,1 +106,1 @@ + - "chunkFilename": "[name].js", + + "chunkFilename": "[id].js", + @@ -112,1 +112,1 @@ + - "filename": "[name].js", + + "filename": [Function filename], + `) + ); + test("library", { output: { library: ["myLib", "awesome"] } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -105,1 +105,1 @@ + - "chunkCallbackName": "webpackChunk", + + "chunkCallbackName": "webpackChunkmyLib_awesome", + @@ -110,1 +110,1 @@ + - "devtoolNamespace": "", + + "devtoolNamespace": "myLib.awesome", + @@ -118,1 +118,1 @@ + - "hotUpdateFunction": "webpackHotUpdate", + + "hotUpdateFunction": "webpackHotUpdatemyLib_awesome", + @@ -121,1 +121,1 @@ + - "jsonpFunction": "webpackJsonp", + + "jsonpFunction": "webpackJsonpmyLib_awesome", + @@ -123,1 +123,4 @@ + - "library": "", + + "library": Array [ + + "myLib", + + "awesome", + + ], + `) + ); + test("target node", { target: "node" }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -49,3 +49,3 @@ + - "__dirname": "mock", + - "__filename": "mock", + - "global": true, + + "__dirname": false, + + "__filename": false, + + "global": false, + @@ -113,1 +113,1 @@ + - "globalObject": "window", + + "globalObject": "global", + @@ -135,3 +135,1 @@ + - "aliasFields": Array [ + - "browser", + - ], + + "aliasFields": Array [], + @@ -145,1 +143,0 @@ + - "browser", + @@ -169,1 +166,1 @@ + - "target": "web", + + "target": "node", + `) + ); + test("target webworker", { target: "webworker" }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -113,1 +113,1 @@ + - "globalObject": "window", + + "globalObject": "self", + @@ -169,1 +169,1 @@ + - "target": "web", + + "target": "webworker", + `) + ); + test("records", { recordsPath: "some-path" }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -71,1 +71,1 @@ + - "portableRecords": false, + + "portableRecords": true, + @@ -134,0 +134,1 @@ + + "recordsPath": "some-path", + `) + ); + test("ecamVersion", { output: { ecmaVersion: 2020 } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -111,1 +111,1 @@ + - "ecmaVersion": 6, + + "ecmaVersion": 11, + `) + ); + test("single runtimeChunk", { optimization: { runtimeChunk: "single" } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -75,1 +75,3 @@ + - "runtimeChunk": undefined, + + "runtimeChunk": Object { + + "name": "runtime", + + }, + `) + ); + test( + "single runtimeChunk", + { optimization: { runtimeChunk: "multiple" } }, + e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -75,1 +75,3 @@ + - "runtimeChunk": undefined, + + "runtimeChunk": Object { + + "name": [Function name], + + }, + `) + ); + test("single runtimeChunk", { optimization: { runtimeChunk: true } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -75,1 +75,3 @@ + - "runtimeChunk": undefined, + + "runtimeChunk": Object { + + "name": [Function name], + + }, + `) + ); + test("output shortcut", { output: "bundle.js" }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -106,1 +106,1 @@ + - "chunkFilename": "[name].js", + + "chunkFilename": "[id].bundle.js", + @@ -112,1 +112,1 @@ + - "filename": "[name].js", + + "filename": "bundle.js", + `) + ); + test("cache true", { cache: true }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -2,1 +2,7 @@ + - "cache": false, + + "cache": Object { + + "immutablePaths": undefined, + + "managedPaths": Array [ + + "/node_modules", + + ], + + "type": "memory", + + }, + @@ -43,1 +49,1 @@ + - "unsafeCache": undefined, + + "unsafeCache": [Function anonymous], + @@ -138,1 +144,1 @@ + - "cache": false, + + "cache": true, + @@ -157,1 +163,1 @@ + - "cache": false, + + "cache": true, + `) + ); + test("cache filesystem", { cache: { type: "filesystem" } }, e => + e.toMatchInlineSnapshot(` + - Expected + + Received + + @@ -2,1 +2,20 @@ + - "cache": false, + + "cache": Object { + + "buildDependencies": Object { + + "defaultWebpack": Array [ + + "/lib/", + + ], + + }, + + "cacheDirectory": "/node_modules/.cache/webpack", + + "cacheLocation": "/node_modules/.cache/webpack/default-none", + + "hashAlgorithm": "md4", + + "idleTimeout": 60000, + + "idleTimeoutForInitialStore": 0, + + "immutablePaths": undefined, + + "managedPaths": Array [ + + "/node_modules", + + ], + + "name": "default-none", + + "store": "pack", + + "type": "filesystem", + + "version": "", + + }, + @@ -43,1 +62,1 @@ + - "unsafeCache": undefined, + + "unsafeCache": [Function anonymous], + @@ -138,1 +157,1 @@ + - "cache": false, + + "cache": true, + @@ -157,1 +176,1 @@ + - "cache": false, + + "cache": true, + `) + ); +}); diff --git a/yarn.lock b/yarn.lock index de6c9d3b8..7e874dafe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4932,7 +4932,7 @@ prettier@^1.14.3, prettier@^1.18.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -pretty-format@^24.8.0, pretty-format@^24.9.0: +pretty-format@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==