From e0891eeea0a10b545ac022be115a1c033d9c44ba Mon Sep 17 00:00:00 2001 From: hai-x <98948357+hai-x@users.noreply.github.com> Date: Sun, 6 Apr 2025 20:53:42 +0800 Subject: [PATCH] fix(css): avoid extra `module.export` output for css module (#19265) --- lib/Module.js | 1 + lib/ModuleSourceTypesConstants.js | 11 +++ lib/css/CssGenerator.js | 48 +++++++++++-- lib/css/CssModulesPlugin.js | 5 +- lib/css/CssParser.js | 3 + lib/optimize/ModuleConcatenationPlugin.js | 3 +- test/HotTestCases.template.js | 27 ++++++-- .../css/basic-dynamic-only/index.js | 2 +- .../css/basic-esm-target-node/index.js | 4 +- .../css/basic-esm-target-web/index.js | 4 +- .../css/basic-initial-only/index.js | 2 +- test/configCases/css/basic-web-async/index.js | 4 +- test/configCases/css/basic/index.js | 4 +- test/configCases/css/contenthash/index.js | 4 +- .../configCases/css/external-in-node/index.js | 2 +- test/configCases/css/external/index.js | 2 +- .../css/import-different-case/index.js | 2 +- .../css/no-extra-js-exports-output/a1.css | 3 + .../no-extra-js-exports-output/a1.module.css | 3 + .../css/no-extra-js-exports-output/a2.css | 3 + .../no-extra-js-exports-output/a2.module.css | 3 + .../no-extra-js-exports-output/a3.module.css | 3 + .../css/no-extra-js-exports-output/main.css | 5 ++ .../css/no-extra-js-exports-output/main1.js | 4 ++ .../css/no-extra-js-exports-output/main2.js | 3 + .../no-extra-js-exports-output/test.config.js | 12 ++++ .../css/no-extra-js-exports-output/test.js | 30 ++++++++ .../webpack.config.js | 69 +++++++++++++++++++ test/configCases/css/pathinfo/index.js | 4 +- .../css/prefer-relative-css-import/index.js | 2 +- test/configCases/css/prefer-relative/index.js | 2 +- test/configCases/css/universal/index.js | 4 +- .../url-and-asset-module-filename/index.js | 2 +- test/helpers/FakeDocument.js | 9 ++- test/hotCases/css/imported-css/a.css | 4 ++ test/hotCases/css/imported-css/index.css | 9 +++ test/hotCases/css/imported-css/index.js | 21 ++++++ test/hotCases/css/imported-css/test.filter.js | 5 ++ .../css/imported-css/webpack.config.js | 8 +++ test/hotCases/css/single-css-entry/index.css | 11 +++ test/hotCases/css/single-css-entry/index.js | 19 +++++ .../css/single-css-entry/test.filter.js | 5 ++ .../css/single-css-entry/webpack.config.js | 29 ++++++++ .../css/with-lazy-compilation/index.js | 39 +++++++++++ .../css/with-lazy-compilation/style.css | 11 +++ .../css/with-lazy-compilation/test.filter.js | 5 ++ .../with-lazy-compilation/webpack.config.js | 19 +++++ types.d.ts | 1 + 48 files changed, 435 insertions(+), 40 deletions(-) create mode 100644 test/configCases/css/no-extra-js-exports-output/a1.css create mode 100644 test/configCases/css/no-extra-js-exports-output/a1.module.css create mode 100644 test/configCases/css/no-extra-js-exports-output/a2.css create mode 100644 test/configCases/css/no-extra-js-exports-output/a2.module.css create mode 100644 test/configCases/css/no-extra-js-exports-output/a3.module.css create mode 100644 test/configCases/css/no-extra-js-exports-output/main.css create mode 100644 test/configCases/css/no-extra-js-exports-output/main1.js create mode 100644 test/configCases/css/no-extra-js-exports-output/main2.js create mode 100644 test/configCases/css/no-extra-js-exports-output/test.config.js create mode 100644 test/configCases/css/no-extra-js-exports-output/test.js create mode 100644 test/configCases/css/no-extra-js-exports-output/webpack.config.js create mode 100644 test/hotCases/css/imported-css/a.css create mode 100644 test/hotCases/css/imported-css/index.css create mode 100644 test/hotCases/css/imported-css/index.js create mode 100644 test/hotCases/css/imported-css/test.filter.js create mode 100644 test/hotCases/css/imported-css/webpack.config.js create mode 100644 test/hotCases/css/single-css-entry/index.css create mode 100644 test/hotCases/css/single-css-entry/index.js create mode 100644 test/hotCases/css/single-css-entry/test.filter.js create mode 100644 test/hotCases/css/single-css-entry/webpack.config.js create mode 100644 test/hotCases/css/with-lazy-compilation/index.js create mode 100644 test/hotCases/css/with-lazy-compilation/style.css create mode 100644 test/hotCases/css/with-lazy-compilation/test.filter.js create mode 100644 test/hotCases/css/with-lazy-compilation/webpack.config.js diff --git a/lib/Module.js b/lib/Module.js index 5c958ba74..f7cb0a54f 100644 --- a/lib/Module.js +++ b/lib/Module.js @@ -105,6 +105,7 @@ const makeSerializable = require("./util/makeSerializable"); * @property {boolean=} async * @property {boolean=} sideEffectFree * @property {Record=} exportsFinalName + * @property {boolean=} isCSSModule */ /** diff --git a/lib/ModuleSourceTypesConstants.js b/lib/ModuleSourceTypesConstants.js index ec5b6706d..f78d70a57 100644 --- a/lib/ModuleSourceTypesConstants.js +++ b/lib/ModuleSourceTypesConstants.js @@ -34,6 +34,11 @@ const ASSET_AND_JS_AND_CSS_URL_TYPES = new Set([ "css-url" ]); +/** + * @type {"javascript"} + */ +const JS_TYPE = "javascript"; + /** * @type {ReadonlySet<"javascript">} */ @@ -54,6 +59,10 @@ const JS_AND_CSS_URL_TYPES = new Set(["javascript", "css-url"]); */ const JS_AND_CSS_TYPES = new Set(["javascript", "css"]); +/** + * @type {"css"} + */ +const CSS_TYPE = "css"; /** * @type {ReadonlySet<"css">} */ @@ -94,6 +103,7 @@ const CONSUME_SHARED_TYPES = new Set(["consume-shared"]); const SHARED_INIT_TYPES = new Set(["share-init"]); module.exports.NO_TYPES = NO_TYPES; +module.exports.JS_TYPE = JS_TYPE; module.exports.JS_TYPES = JS_TYPES; module.exports.JS_AND_CSS_TYPES = JS_AND_CSS_TYPES; module.exports.JS_AND_CSS_URL_TYPES = JS_AND_CSS_URL_TYPES; @@ -102,6 +112,7 @@ module.exports.ASSET_TYPES = ASSET_TYPES; module.exports.ASSET_AND_JS_TYPES = ASSET_AND_JS_TYPES; module.exports.ASSET_AND_CSS_URL_TYPES = ASSET_AND_CSS_URL_TYPES; module.exports.ASSET_AND_JS_AND_CSS_URL_TYPES = ASSET_AND_JS_AND_CSS_URL_TYPES; +module.exports.CSS_TYPE = CSS_TYPE; module.exports.CSS_TYPES = CSS_TYPES; module.exports.CSS_URL_TYPES = CSS_URL_TYPES; module.exports.CSS_IMPORT_TYPES = CSS_IMPORT_TYPES; diff --git a/lib/css/CssGenerator.js b/lib/css/CssGenerator.js index b3b7ad81c..7df10bbbf 100644 --- a/lib/css/CssGenerator.js +++ b/lib/css/CssGenerator.js @@ -11,7 +11,10 @@ const Generator = require("../Generator"); const InitFragment = require("../InitFragment"); const { JS_AND_CSS_EXPORT_TYPES, - JS_AND_CSS_TYPES + JS_AND_CSS_TYPES, + CSS_TYPES, + JS_TYPE, + CSS_TYPE } = require("../ModuleSourceTypesConstants"); const RuntimeGlobals = require("../RuntimeGlobals"); const Template = require("../Template"); @@ -27,21 +30,25 @@ const Template = require("../Template"); /** @typedef {import("../Generator").GenerateContext} GenerateContext */ /** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */ /** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ /** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ /** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../NormalModule")} NormalModule */ /** @typedef {import("../util/Hash")} Hash */ class CssGenerator extends Generator { /** * @param {CssAutoGeneratorOptions | CssGlobalGeneratorOptions | CssModuleGeneratorOptions} options options + * @param {ModuleGraph} moduleGraph the module graph */ - constructor(options) { + constructor(options, moduleGraph) { super(); this.convention = options.exportsConvention; this.localIdentName = options.localIdentName; this.exportsOnly = options.exportsOnly; this.esModule = options.esModule; + this._moduleGraph = moduleGraph; } /** @@ -169,6 +176,13 @@ class CssGenerator extends Generator { return source; } + if ( + cssData.exports.size === 0 && + !(/** @type {BuildMeta} */ (module.buildMeta).isCSSModule) + ) { + return new RawSource(""); + } + const needNsObj = this.esModule && generateContext.moduleGraph @@ -237,7 +251,22 @@ class CssGenerator extends Generator { */ getTypes(module) { // TODO, find a better way to prevent the original module from being removed after concatenation, maybe it is a bug - return this.exportsOnly ? JS_AND_CSS_EXPORT_TYPES : JS_AND_CSS_TYPES; + if (this.exportsOnly) { + return JS_AND_CSS_EXPORT_TYPES; + } + const sourceTypes = new Set(); + const connections = this._moduleGraph.getIncomingConnections(module); + for (const connection of connections) { + if (!connection.originModule) { + continue; + } + if (connection.originModule.type.split("/")[0] !== CSS_TYPE) + sourceTypes.add(JS_TYPE); + } + if (sourceTypes.has(JS_TYPE)) { + return JS_AND_CSS_TYPES; + } + return CSS_TYPES; } /** @@ -248,12 +277,17 @@ class CssGenerator extends Generator { getSize(module, type) { switch (type) { case "javascript": { - const buildInfo = /** @type {BuildInfo} */ (module.buildInfo); - if (!buildInfo.cssData) { + const cssData = /** @type {BuildInfo} */ (module.buildInfo).cssData; + if (!cssData) { return 42; } - - const exports = buildInfo.cssData.exports; + if (cssData.exports.size === 0) { + if (/** @type {BuildMeta} */ (module.buildMeta).isCSSModule) { + return 42; + } + return 0; + } + const exports = cssData.exports; const stringifiedExports = JSON.stringify( Array.from(exports).reduce((obj, [key, value]) => { obj[key] = value; diff --git a/lib/css/CssModulesPlugin.js b/lib/css/CssModulesPlugin.js index 830cd7eac..b2d60bb6d 100644 --- a/lib/css/CssModulesPlugin.js +++ b/lib/css/CssModulesPlugin.js @@ -301,7 +301,10 @@ class CssModulesPlugin { .tap(PLUGIN_NAME, generatorOptions => { validateGeneratorOptions[type](generatorOptions); - return new CssGenerator(generatorOptions); + return new CssGenerator( + generatorOptions, + compilation.moduleGraph + ); }); normalModuleFactory.hooks.createModuleClass .for(type) diff --git a/lib/css/CssParser.js b/lib/css/CssParser.js index 7d0b9be0a..76c4a595b 100644 --- a/lib/css/CssParser.js +++ b/lib/css/CssParser.js @@ -361,6 +361,9 @@ class CssParser extends Parser { const isModules = mode === "global" || mode === "local"; + /** @type {BuildMeta} */ + (module.buildMeta).isCSSModule = isModules; + const locConverter = new LocConverter(source); /** @type {number} */ diff --git a/lib/optimize/ModuleConcatenationPlugin.js b/lib/optimize/ModuleConcatenationPlugin.js index 1644bd0b6..11b609f39 100644 --- a/lib/optimize/ModuleConcatenationPlugin.js +++ b/lib/optimize/ModuleConcatenationPlugin.js @@ -8,6 +8,7 @@ const asyncLib = require("neo-async"); const ChunkGraph = require("../ChunkGraph"); const ModuleGraph = require("../ModuleGraph"); +const { JS_TYPE } = require("../ModuleSourceTypesConstants"); const { STAGE_DEFAULT } = require("../OptimizationStages"); const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency"); const { compareModulesByIdentifier } = require("../util/comparators"); @@ -452,7 +453,7 @@ class ModuleConcatenationPlugin { chunkGraph.disconnectChunkAndModule(chunk, m); } else { const newSourceTypes = new Set(sourceTypes); - newSourceTypes.delete("javascript"); + newSourceTypes.delete(JS_TYPE); chunkGraph.setChunkModuleSourceTypes( chunk, m, diff --git a/test/HotTestCases.template.js b/test/HotTestCases.template.js index 5ebad6f68..c5c5f8b9a 100644 --- a/test/HotTestCases.template.js +++ b/test/HotTestCases.template.js @@ -8,6 +8,7 @@ const vm = require("vm"); const rimraf = require("rimraf"); const checkArrayExpectation = require("./checkArrayExpectation"); const createLazyTestEnv = require("./helpers/createLazyTestEnv"); +const FakeDocument = require("./helpers/FakeDocument"); const casesPath = path.join(__dirname, "hotCases"); let categories = fs @@ -108,8 +109,7 @@ const describeCases = config => { // ignored } - compiler = webpack(options); - compiler.run((err, stats) => { + const onCompiled = (err, stats) => { if (err) return done(err); const jsonStats = stats.toJson({ errorDetails: true @@ -179,9 +179,8 @@ const describeCases = config => { }, document: { createElement(type) { - return { + const ele = { _type: type, - sheet: {}, getAttribute(name) { return this[name]; }, @@ -199,6 +198,11 @@ const describeCases = config => { } } }; + ele.sheet = + type === "link" + ? new FakeDocument.FakeSheet(ele, outputDirectory) + : {}; + return ele; }, head: { appendChild(element) { @@ -353,8 +357,15 @@ const describeCases = config => { let promise = Promise.resolve(); const info = stats.toJson({ all: false, entrypoints: true }); if (config.target === "web") { - for (const file of info.entrypoints.main.assets) - _require(`./${file.name}`); + for (const file of info.entrypoints.main.assets) { + if (file.name.endsWith(".css")) { + const link = window.document.createElement("link"); + link.href = path.join(outputDirectory, file.name); + window.document.head.appendChild(link); + } else { + _require(`./${file.name}`); + } + } } else { const assets = info.entrypoints.main.assets; const result = _require( @@ -375,7 +386,9 @@ const describeCases = config => { done(err); } ); - }); + }; + compiler = webpack(options); + compiler.run(onCompiled); }, 20000); const { diff --git a/test/configCases/css/basic-dynamic-only/index.js b/test/configCases/css/basic-dynamic-only/index.js index ca673e386..0e786f4cc 100644 --- a/test/configCases/css/basic-dynamic-only/index.js +++ b/test/configCases/css/basic-dynamic-only/index.js @@ -1,6 +1,6 @@ it("should compile and load style on demand", (done) => { import("./style.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); const style = getComputedStyle(document.body); expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("margin")).toBe(" 10px"); diff --git a/test/configCases/css/basic-esm-target-node/index.js b/test/configCases/css/basic-esm-target-node/index.js index 49a5dd1fd..4f3f829ec 100644 --- a/test/configCases/css/basic-esm-target-node/index.js +++ b/test/configCases/css/basic-esm-target-node/index.js @@ -1,9 +1,9 @@ import * as style from "./style.css"; it("should compile and load style on demand", done => { - expect(style).toEqual(nsObj({})); + expect(style).toEqual({}); import("./style2.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); done(); }, done); }); diff --git a/test/configCases/css/basic-esm-target-web/index.js b/test/configCases/css/basic-esm-target-web/index.js index c15078254..eb4c93a7f 100644 --- a/test/configCases/css/basic-esm-target-web/index.js +++ b/test/configCases/css/basic-esm-target-web/index.js @@ -1,9 +1,9 @@ import * as style from "./style.css"; it("should compile and load style on demand", done => { - expect(style).toEqual(nsObj({})); + expect(style).toEqual({}); import("./style2.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); const style = getComputedStyle(document.body); expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("margin")).toBe(" 10px"); diff --git a/test/configCases/css/basic-initial-only/index.js b/test/configCases/css/basic-initial-only/index.js index cba22192d..ea3660d24 100644 --- a/test/configCases/css/basic-initial-only/index.js +++ b/test/configCases/css/basic-initial-only/index.js @@ -1,7 +1,7 @@ import * as style from "./style.css"; it("should compile and load initial style", () => { - expect(style).toEqual(nsObj({})); + expect(style).toEqual({}); const computedStyle = getComputedStyle(document.body); expect(computedStyle.getPropertyValue("background")).toBe(" red"); expect(computedStyle.getPropertyValue("margin")).toBe(" 10px"); diff --git a/test/configCases/css/basic-web-async/index.js b/test/configCases/css/basic-web-async/index.js index c15078254..eb4c93a7f 100644 --- a/test/configCases/css/basic-web-async/index.js +++ b/test/configCases/css/basic-web-async/index.js @@ -1,9 +1,9 @@ import * as style from "./style.css"; it("should compile and load style on demand", done => { - expect(style).toEqual(nsObj({})); + expect(style).toEqual({}); import("./style2.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); const style = getComputedStyle(document.body); expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("margin")).toBe(" 10px"); diff --git a/test/configCases/css/basic/index.js b/test/configCases/css/basic/index.js index c15078254..eb4c93a7f 100644 --- a/test/configCases/css/basic/index.js +++ b/test/configCases/css/basic/index.js @@ -1,9 +1,9 @@ import * as style from "./style.css"; it("should compile and load style on demand", done => { - expect(style).toEqual(nsObj({})); + expect(style).toEqual({}); import("./style2.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); const style = getComputedStyle(document.body); expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("margin")).toBe(" 10px"); diff --git a/test/configCases/css/contenthash/index.js b/test/configCases/css/contenthash/index.js index 6215ea756..f2504f3a7 100644 --- a/test/configCases/css/contenthash/index.js +++ b/test/configCases/css/contenthash/index.js @@ -8,7 +8,7 @@ it("should work with js", done => { }); it("should work with css", done => { - expect(style).toEqual(nsObj({})); + expect(style).toEqual({}); const computedStyle = getComputedStyle(document.body); @@ -16,7 +16,7 @@ it("should work with css", done => { expect(computedStyle.getPropertyValue("color")).toBe(" yellow"); import("./async.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); const style = getComputedStyle(document.body); diff --git a/test/configCases/css/external-in-node/index.js b/test/configCases/css/external-in-node/index.js index 526b3c0a8..827a002ff 100644 --- a/test/configCases/css/external-in-node/index.js +++ b/test/configCases/css/external-in-node/index.js @@ -1,6 +1,6 @@ it("should import an external css", done => { import("../external/style.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); done(); }, done); }); diff --git a/test/configCases/css/external/index.js b/test/configCases/css/external/index.js index fb100cf0d..ec3b1155e 100644 --- a/test/configCases/css/external/index.js +++ b/test/configCases/css/external/index.js @@ -1,6 +1,6 @@ it("should import an external css", done => { import("./style.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); const style = getComputedStyle(document.body); expect(style.getPropertyValue("color")).toBe(" green"); expect(style.getPropertyValue("background")).toBe( diff --git a/test/configCases/css/import-different-case/index.js b/test/configCases/css/import-different-case/index.js index 652fef343..f2d6eae5a 100644 --- a/test/configCases/css/import-different-case/index.js +++ b/test/configCases/css/import-different-case/index.js @@ -1,7 +1,7 @@ import * as style from "./style.css"; it("should compile and load style on demand", () => { - expect(style).toEqual(nsObj({})); + expect(style).toEqual({}); const computedStyle = getComputedStyle(document.body); expect(computedStyle.getPropertyValue("background")).toBe(" red"); expect(computedStyle.getPropertyValue("margin")).toBe(" 10px"); diff --git a/test/configCases/css/no-extra-js-exports-output/a1.css b/test/configCases/css/no-extra-js-exports-output/a1.css new file mode 100644 index 000000000..be9d5269a --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/a1.css @@ -0,0 +1,3 @@ +.bar { + background-color: black; +} diff --git a/test/configCases/css/no-extra-js-exports-output/a1.module.css b/test/configCases/css/no-extra-js-exports-output/a1.module.css new file mode 100644 index 000000000..be9d5269a --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/a1.module.css @@ -0,0 +1,3 @@ +.bar { + background-color: black; +} diff --git a/test/configCases/css/no-extra-js-exports-output/a2.css b/test/configCases/css/no-extra-js-exports-output/a2.css new file mode 100644 index 000000000..be9d5269a --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/a2.css @@ -0,0 +1,3 @@ +.bar { + background-color: black; +} diff --git a/test/configCases/css/no-extra-js-exports-output/a2.module.css b/test/configCases/css/no-extra-js-exports-output/a2.module.css new file mode 100644 index 000000000..be9d5269a --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/a2.module.css @@ -0,0 +1,3 @@ +.bar { + background-color: black; +} diff --git a/test/configCases/css/no-extra-js-exports-output/a3.module.css b/test/configCases/css/no-extra-js-exports-output/a3.module.css new file mode 100644 index 000000000..be9d5269a --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/a3.module.css @@ -0,0 +1,3 @@ +.bar { + background-color: black; +} diff --git a/test/configCases/css/no-extra-js-exports-output/main.css b/test/configCases/css/no-extra-js-exports-output/main.css new file mode 100644 index 000000000..f56551dd6 --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/main.css @@ -0,0 +1,5 @@ +@import url("./a1.css"); + +.foo { + background-color: red; +} diff --git a/test/configCases/css/no-extra-js-exports-output/main1.js b/test/configCases/css/no-extra-js-exports-output/main1.js new file mode 100644 index 000000000..dd349e5cf --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/main1.js @@ -0,0 +1,4 @@ +import "./main.css" +require("./a2.css") +import("./a2.css").then(() => {}) + diff --git a/test/configCases/css/no-extra-js-exports-output/main2.js b/test/configCases/css/no-extra-js-exports-output/main2.js new file mode 100644 index 000000000..4d2835fe7 --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/main2.js @@ -0,0 +1,3 @@ +import a1 from "./a1.module.css" +const a2 = require("./a2.module.css") +import("./a3.module.css").then(() => {}) diff --git a/test/configCases/css/no-extra-js-exports-output/test.config.js b/test/configCases/css/no-extra-js-exports-output/test.config.js new file mode 100644 index 000000000..cf9bce19b --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/test.config.js @@ -0,0 +1,12 @@ +module.exports = { + findBundle: function (i) { + switch (i) { + case 0: + return ["test.js"]; + case 1: + return ["test.js", `1/main.js`]; + case 2: + return ["test.js", `2/main.js`]; + } + } +}; diff --git a/test/configCases/css/no-extra-js-exports-output/test.js b/test/configCases/css/no-extra-js-exports-output/test.js new file mode 100644 index 000000000..691d2ce7e --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/test.js @@ -0,0 +1,30 @@ +it("should work", () => { + const stats = __STATS__.children[__STATS_I__]; + + expect(stats.assets.findIndex(a => a.name === "test.js") > -1).toBe(true); + + expect( + stats.assets.findIndex(a => a.name === `${__STATS_I__}/main.css`) > -1 + ).toBe(true); + + if (__STATS_I__ === 0) { + // ./main.css + // ./a.css + // and it still output two runtime module: + // 'webpack/runtime/make namespace object' + // 'webpack/runtime/css loading' + expect(stats.modules.length).toBe(4); + } else if (__STATS_I__ === 1) { + stats.modules + .filter(module => module.moduleType === "css/auto") + .forEach(module => { + expect(module.sizes["javascript"] === 1).toBe(true); + }); + } else if (__STATS_I__ === 2) { + stats.modules + .filter(module => module.moduleType === "css/auto") + .forEach(module => { + expect(module.sizes["javascript"] === 1).toBe(false); + }); + } +}); diff --git a/test/configCases/css/no-extra-js-exports-output/webpack.config.js b/test/configCases/css/no-extra-js-exports-output/webpack.config.js new file mode 100644 index 000000000..46a012257 --- /dev/null +++ b/test/configCases/css/no-extra-js-exports-output/webpack.config.js @@ -0,0 +1,69 @@ +const path = require("path"); +const fs = require("fs"); +const webpack = require("../../../../"); + +const entry = i => { + switch (i) { + case 0: + return { + main: ["./main.css"] + }; + case 1: + return { + main: ["./main1.js"] + }; + case 2: + return { + main: ["./main2.js"] + }; + } +}; + +/** + * @param {number} i param + * @returns {import("../../../../").Configuration} return + */ +const common = i => ({ + entry: { + ...entry(i) + }, + target: "web", + devtool: false, + experiments: { + css: true + }, + output: { + filename: `${i}/[name].js`, + chunkFilename: `${i}/[name].js`, + cssFilename: `${i}/[name].css`, + cssChunkFilename: `${i}/[name].css` + }, + plugins: [ + { + apply(compiler) { + compiler.hooks.compilation.tap("Test", compilation => { + compilation.hooks.processAssets.tap( + { + name: "copy-webpack-plugin", + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + const data = fs.readFileSync( + path.resolve(__dirname, "./test.js") + ); + + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource(data) + ); + } + ); + }); + } + } + ] +}); + +/** @type {import("../../../../").Configuration[]} */ +module.exports = [...[0, 1].map(i => common(i))]; diff --git a/test/configCases/css/pathinfo/index.js b/test/configCases/css/pathinfo/index.js index c15078254..eb4c93a7f 100644 --- a/test/configCases/css/pathinfo/index.js +++ b/test/configCases/css/pathinfo/index.js @@ -1,9 +1,9 @@ import * as style from "./style.css"; it("should compile and load style on demand", done => { - expect(style).toEqual(nsObj({})); + expect(style).toEqual({}); import("./style2.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); const style = getComputedStyle(document.body); expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("margin")).toBe(" 10px"); diff --git a/test/configCases/css/prefer-relative-css-import/index.js b/test/configCases/css/prefer-relative-css-import/index.js index 5910b3412..06444c6a1 100644 --- a/test/configCases/css/prefer-relative-css-import/index.js +++ b/test/configCases/css/prefer-relative-css-import/index.js @@ -2,7 +2,7 @@ import * as styles1 from "./style.less"; import * as styles2 from "./style.modules.less"; it("should prefer relative", () => { - expect(styles1).toEqual(nsObj({})); + expect(styles1).toEqual({}); expect(styles2).toEqual(nsObj({ "style-module": "_style_modules_less-style-module", })); diff --git a/test/configCases/css/prefer-relative/index.js b/test/configCases/css/prefer-relative/index.js index 9f40cbd7b..9701a0453 100644 --- a/test/configCases/css/prefer-relative/index.js +++ b/test/configCases/css/prefer-relative/index.js @@ -2,7 +2,7 @@ import * as styles1 from "./style.css"; import * as styles2 from "./style.modules.css"; it("should prefer relative", () => { - expect(styles1).toEqual(nsObj({})); + expect(styles1).toEqual({}); expect(styles2).toEqual(nsObj({ "style-module": "_style_modules_css-style-module", })); diff --git a/test/configCases/css/universal/index.js b/test/configCases/css/universal/index.js index c97676906..4c7821b40 100644 --- a/test/configCases/css/universal/index.js +++ b/test/configCases/css/universal/index.js @@ -2,13 +2,13 @@ import * as pureStyle from "./style.css"; import * as styles from "./style.modules.css"; it("should work", done => { - expect(pureStyle).toEqual(nsObj({})); + expect(pureStyle).toEqual({}); const style = getComputedStyle(document.body); expect(style.getPropertyValue("background")).toBe(" red"); expect(styles.foo).toBe('_style_modules_css-foo'); import(/* webpackPrefetch: true */ "./style2.css").then(x => { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); const style = getComputedStyle(document.body); expect(style.getPropertyValue("color")).toBe(" blue"); diff --git a/test/configCases/css/url-and-asset-module-filename/index.js b/test/configCases/css/url-and-asset-module-filename/index.js index d7371181e..db8244a0d 100644 --- a/test/configCases/css/url-and-asset-module-filename/index.js +++ b/test/configCases/css/url-and-asset-module-filename/index.js @@ -7,7 +7,7 @@ it(`should generate correct url public path with css filename`, done => { document.body.appendChild(h1); import("./index.css").then(x => { try { - expect(x).toEqual(nsObj({})); + expect(x).toEqual({}); const style1 = getComputedStyle(h1); expect(style1).toMatchSnapshot(); const style2 = getComputedStyle(h2); diff --git a/test/helpers/FakeDocument.js b/test/helpers/FakeDocument.js index fdd526d65..5ce19ffff 100644 --- a/test/helpers/FakeDocument.js +++ b/test/helpers/FakeDocument.js @@ -5,7 +5,7 @@ function getPropertyValue(property) { return this[property]; } -module.exports = class FakeDocument { +class FakeDocument { constructor(basePath) { this.head = this.createElement("head"); this.body = this.createElement("body"); @@ -54,7 +54,7 @@ module.exports = class FakeDocument { } return style; } -}; +} class FakeElement { constructor(document, type, basePath) { @@ -252,3 +252,8 @@ class FakeSheet { return rules; } } + +FakeDocument.FakeSheet = FakeSheet; +FakeDocument.FakeElement = FakeDocument; + +module.exports = FakeDocument; diff --git a/test/hotCases/css/imported-css/a.css b/test/hotCases/css/imported-css/a.css new file mode 100644 index 000000000..84a857d54 --- /dev/null +++ b/test/hotCases/css/imported-css/a.css @@ -0,0 +1,4 @@ + +.html { + color: green; +} diff --git a/test/hotCases/css/imported-css/index.css b/test/hotCases/css/imported-css/index.css new file mode 100644 index 000000000..78a24438d --- /dev/null +++ b/test/hotCases/css/imported-css/index.css @@ -0,0 +1,9 @@ +@import url("./a.css"); +--- +html { + color: blue; +} +--- +html { + color: yellow; +} diff --git a/test/hotCases/css/imported-css/index.js b/test/hotCases/css/imported-css/index.js new file mode 100644 index 000000000..0103a1149 --- /dev/null +++ b/test/hotCases/css/imported-css/index.js @@ -0,0 +1,21 @@ +import "./index.css" + +it("should work", done => { + const links = window.document.getElementsByTagName("link"); + expect(links[0].sheet.css).toContain("color: green;"); + + NEXT( + require("../../update")(done, true, () => { + const links = window.document.getElementsByTagName("link"); + expect(links[0].sheet.css).toContain("color: blue;"); + + NEXT( + require("../../update")(done, true, () => { + const links = window.document.getElementsByTagName("link"); + expect(links[0].sheet.css).toContain("color: yellow;"); + done(); + }) + ); + }) + ); +}); diff --git a/test/hotCases/css/imported-css/test.filter.js b/test/hotCases/css/imported-css/test.filter.js new file mode 100644 index 000000000..7701a43cf --- /dev/null +++ b/test/hotCases/css/imported-css/test.filter.js @@ -0,0 +1,5 @@ +module.exports = function (config) { + if (config.target !== "web") { + return false; + } +}; diff --git a/test/hotCases/css/imported-css/webpack.config.js b/test/hotCases/css/imported-css/webpack.config.js new file mode 100644 index 000000000..14df4b565 --- /dev/null +++ b/test/hotCases/css/imported-css/webpack.config.js @@ -0,0 +1,8 @@ +/** @type {import("../../../../").Configuration} */ +module.exports = { + mode: "development", + devtool: false, + experiments: { + css: true + } +}; diff --git a/test/hotCases/css/single-css-entry/index.css b/test/hotCases/css/single-css-entry/index.css new file mode 100644 index 000000000..39fb52cc4 --- /dev/null +++ b/test/hotCases/css/single-css-entry/index.css @@ -0,0 +1,11 @@ +.html { + color: red; +} +--- +html { + color: blue; +} +--- +html { + color: yellow; +} diff --git a/test/hotCases/css/single-css-entry/index.js b/test/hotCases/css/single-css-entry/index.js new file mode 100644 index 000000000..9864cf854 --- /dev/null +++ b/test/hotCases/css/single-css-entry/index.js @@ -0,0 +1,19 @@ +it("should work", done => { + const links = window.document.getElementsByTagName("link"); + expect(links[0].sheet.css).toContain("color: red;"); + + NEXT( + require("../../update")(done, true, () => { + const links = window.document.getElementsByTagName("link"); + expect(links[0].sheet.css).toContain("color: blue;"); + + NEXT( + require("../../update")(done, true, () => { + const links = window.document.getElementsByTagName("link"); + expect(links[0].sheet.css).toContain("color: yellow;"); + done(); + }) + ); + }) + ); +}); diff --git a/test/hotCases/css/single-css-entry/test.filter.js b/test/hotCases/css/single-css-entry/test.filter.js new file mode 100644 index 000000000..7701a43cf --- /dev/null +++ b/test/hotCases/css/single-css-entry/test.filter.js @@ -0,0 +1,5 @@ +module.exports = function (config) { + if (config.target !== "web") { + return false; + } +}; diff --git a/test/hotCases/css/single-css-entry/webpack.config.js b/test/hotCases/css/single-css-entry/webpack.config.js new file mode 100644 index 000000000..26f2eae1e --- /dev/null +++ b/test/hotCases/css/single-css-entry/webpack.config.js @@ -0,0 +1,29 @@ +const webpack = require("../../../../"); + +/** @type {import("../../../../").Configuration} */ +module.exports = { + mode: "development", + devtool: false, + entry: ["./index.js", "./index.css"], + experiments: { + css: true + }, + plugins: [ + { + apply(compiler) { + compiler.hooks.compilation.tap("Test", compilation => { + compilation.hooks.additionalTreeRuntimeRequirements.tap( + "Test", + (module, set, context) => { + // To prevent the runtime error `ReferenceError: __webpack_exports__ is not defined`, + // which occurs because the default `output.library` setting is `commonjs2`, + // resulting in adding `module.exports = __webpack_exports__;`. + set.add(webpack.RuntimeGlobals.startup); + set.add(webpack.RuntimeGlobals.exports); + } + ); + }); + } + } + ] +}; diff --git a/test/hotCases/css/with-lazy-compilation/index.js b/test/hotCases/css/with-lazy-compilation/index.js new file mode 100644 index 000000000..58d336fd5 --- /dev/null +++ b/test/hotCases/css/with-lazy-compilation/index.js @@ -0,0 +1,39 @@ +const getFile = name => + __non_webpack_require__("fs").readFileSync( + __non_webpack_require__("path").join(__dirname, name), + "utf-8" + ); + +it("should work", async function (done) { + let promise = import("./style.css"); + + NEXT( + require("../../update")(done, true, () => { + promise.then(res => { + const links = window.document.getElementsByTagName("link"); + let href = links[0].href; + expect(href).toBe("https://test.cases/path/style_css.css"); + href = href + .replace(/^https:\/\/test\.cases\/path\//, "") + .replace(/^https:\/\/example\.com\//, ""); + let sheet = getFile(href); + expect(sheet).toContain("color: red;"); + + module.hot.accept("./style.css", () => { + const links = window.document.getElementsByTagName("link"); + let href = links[0].href; + expect(href).toContain("https://test.cases/path/style_css.css?hmr"); + href = href + .replace(/^https:\/\/test\.cases\/path\//, "") + .replace(/^https:\/\/example\.com\//, "") + .split("?")[0]; + let sheet = getFile(href); + expect(sheet).toContain("color: blue;"); + done(); + }); + + NEXT(require("../../update")(done)); + }); + }) + ); +}); diff --git a/test/hotCases/css/with-lazy-compilation/style.css b/test/hotCases/css/with-lazy-compilation/style.css new file mode 100644 index 000000000..22c549999 --- /dev/null +++ b/test/hotCases/css/with-lazy-compilation/style.css @@ -0,0 +1,11 @@ +html { + color: red; +} +--- +html { + color: red; +} +--- +html { + color: blue; +} diff --git a/test/hotCases/css/with-lazy-compilation/test.filter.js b/test/hotCases/css/with-lazy-compilation/test.filter.js new file mode 100644 index 000000000..7701a43cf --- /dev/null +++ b/test/hotCases/css/with-lazy-compilation/test.filter.js @@ -0,0 +1,5 @@ +module.exports = function (config) { + if (config.target !== "web") { + return false; + } +}; diff --git a/test/hotCases/css/with-lazy-compilation/webpack.config.js b/test/hotCases/css/with-lazy-compilation/webpack.config.js new file mode 100644 index 000000000..01b5b9066 --- /dev/null +++ b/test/hotCases/css/with-lazy-compilation/webpack.config.js @@ -0,0 +1,19 @@ +/** @type {import("../../../../").Configuration} */ +module.exports = { + mode: "development", + devtool: false, + output: { + cssFilename: "[name].css", + cssChunkFilename: "[name].css" + }, + experiments: { + css: true, + lazyCompilation: { + entries: false, + imports: true + } + }, + node: { + __dirname: false + } +}; diff --git a/types.d.ts b/types.d.ts index a757817c6..8dbca5fb9 100644 --- a/types.d.ts +++ b/types.d.ts @@ -7617,6 +7617,7 @@ declare interface KnownBuildMeta { async?: boolean; sideEffectFree?: boolean; exportsFinalName?: Record; + isCSSModule?: boolean; } declare interface KnownCreateStatsOptionsContext { forToString?: boolean;