fix(css): avoid extra `module.export` output for css module (#19265)

This commit is contained in:
hai-x 2025-04-06 20:53:42 +08:00 committed by GitHub
parent e5c3f95b84
commit e0891eeea0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 435 additions and 40 deletions

View File

@ -105,6 +105,7 @@ const makeSerializable = require("./util/makeSerializable");
* @property {boolean=} async * @property {boolean=} async
* @property {boolean=} sideEffectFree * @property {boolean=} sideEffectFree
* @property {Record<string, string>=} exportsFinalName * @property {Record<string, string>=} exportsFinalName
* @property {boolean=} isCSSModule
*/ */
/** /**

View File

@ -34,6 +34,11 @@ const ASSET_AND_JS_AND_CSS_URL_TYPES = new Set([
"css-url" "css-url"
]); ]);
/**
* @type {"javascript"}
*/
const JS_TYPE = "javascript";
/** /**
* @type {ReadonlySet<"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"]); const JS_AND_CSS_TYPES = new Set(["javascript", "css"]);
/**
* @type {"css"}
*/
const CSS_TYPE = "css";
/** /**
* @type {ReadonlySet<"css">} * @type {ReadonlySet<"css">}
*/ */
@ -94,6 +103,7 @@ const CONSUME_SHARED_TYPES = new Set(["consume-shared"]);
const SHARED_INIT_TYPES = new Set(["share-init"]); const SHARED_INIT_TYPES = new Set(["share-init"]);
module.exports.NO_TYPES = NO_TYPES; module.exports.NO_TYPES = NO_TYPES;
module.exports.JS_TYPE = JS_TYPE;
module.exports.JS_TYPES = JS_TYPES; module.exports.JS_TYPES = JS_TYPES;
module.exports.JS_AND_CSS_TYPES = JS_AND_CSS_TYPES; module.exports.JS_AND_CSS_TYPES = JS_AND_CSS_TYPES;
module.exports.JS_AND_CSS_URL_TYPES = JS_AND_CSS_URL_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_JS_TYPES = ASSET_AND_JS_TYPES;
module.exports.ASSET_AND_CSS_URL_TYPES = ASSET_AND_CSS_URL_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.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_TYPES = CSS_TYPES;
module.exports.CSS_URL_TYPES = CSS_URL_TYPES; module.exports.CSS_URL_TYPES = CSS_URL_TYPES;
module.exports.CSS_IMPORT_TYPES = CSS_IMPORT_TYPES; module.exports.CSS_IMPORT_TYPES = CSS_IMPORT_TYPES;

View File

@ -11,7 +11,10 @@ const Generator = require("../Generator");
const InitFragment = require("../InitFragment"); const InitFragment = require("../InitFragment");
const { const {
JS_AND_CSS_EXPORT_TYPES, JS_AND_CSS_EXPORT_TYPES,
JS_AND_CSS_TYPES JS_AND_CSS_TYPES,
CSS_TYPES,
JS_TYPE,
CSS_TYPE
} = require("../ModuleSourceTypesConstants"); } = require("../ModuleSourceTypesConstants");
const RuntimeGlobals = require("../RuntimeGlobals"); const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template"); const Template = require("../Template");
@ -27,21 +30,25 @@ const Template = require("../Template");
/** @typedef {import("../Generator").GenerateContext} GenerateContext */ /** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */ /** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../Module").BuildInfo} BuildInfo */ /** @typedef {import("../Module").BuildInfo} BuildInfo */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ /** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
/** @typedef {import("../Module").SourceTypes} SourceTypes */ /** @typedef {import("../Module").SourceTypes} SourceTypes */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../NormalModule")} NormalModule */ /** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../util/Hash")} Hash */ /** @typedef {import("../util/Hash")} Hash */
class CssGenerator extends Generator { class CssGenerator extends Generator {
/** /**
* @param {CssAutoGeneratorOptions | CssGlobalGeneratorOptions | CssModuleGeneratorOptions} options options * @param {CssAutoGeneratorOptions | CssGlobalGeneratorOptions | CssModuleGeneratorOptions} options options
* @param {ModuleGraph} moduleGraph the module graph
*/ */
constructor(options) { constructor(options, moduleGraph) {
super(); super();
this.convention = options.exportsConvention; this.convention = options.exportsConvention;
this.localIdentName = options.localIdentName; this.localIdentName = options.localIdentName;
this.exportsOnly = options.exportsOnly; this.exportsOnly = options.exportsOnly;
this.esModule = options.esModule; this.esModule = options.esModule;
this._moduleGraph = moduleGraph;
} }
/** /**
@ -169,6 +176,13 @@ class CssGenerator extends Generator {
return source; return source;
} }
if (
cssData.exports.size === 0 &&
!(/** @type {BuildMeta} */ (module.buildMeta).isCSSModule)
) {
return new RawSource("");
}
const needNsObj = const needNsObj =
this.esModule && this.esModule &&
generateContext.moduleGraph generateContext.moduleGraph
@ -237,7 +251,22 @@ class CssGenerator extends Generator {
*/ */
getTypes(module) { getTypes(module) {
// TODO, find a better way to prevent the original module from being removed after concatenation, maybe it is a bug // 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) { getSize(module, type) {
switch (type) { switch (type) {
case "javascript": { case "javascript": {
const buildInfo = /** @type {BuildInfo} */ (module.buildInfo); const cssData = /** @type {BuildInfo} */ (module.buildInfo).cssData;
if (!buildInfo.cssData) { if (!cssData) {
return 42; return 42;
} }
if (cssData.exports.size === 0) {
const exports = buildInfo.cssData.exports; if (/** @type {BuildMeta} */ (module.buildMeta).isCSSModule) {
return 42;
}
return 0;
}
const exports = cssData.exports;
const stringifiedExports = JSON.stringify( const stringifiedExports = JSON.stringify(
Array.from(exports).reduce((obj, [key, value]) => { Array.from(exports).reduce((obj, [key, value]) => {
obj[key] = value; obj[key] = value;

View File

@ -301,7 +301,10 @@ class CssModulesPlugin {
.tap(PLUGIN_NAME, generatorOptions => { .tap(PLUGIN_NAME, generatorOptions => {
validateGeneratorOptions[type](generatorOptions); validateGeneratorOptions[type](generatorOptions);
return new CssGenerator(generatorOptions); return new CssGenerator(
generatorOptions,
compilation.moduleGraph
);
}); });
normalModuleFactory.hooks.createModuleClass normalModuleFactory.hooks.createModuleClass
.for(type) .for(type)

View File

@ -361,6 +361,9 @@ class CssParser extends Parser {
const isModules = mode === "global" || mode === "local"; const isModules = mode === "global" || mode === "local";
/** @type {BuildMeta} */
(module.buildMeta).isCSSModule = isModules;
const locConverter = new LocConverter(source); const locConverter = new LocConverter(source);
/** @type {number} */ /** @type {number} */

View File

@ -8,6 +8,7 @@
const asyncLib = require("neo-async"); const asyncLib = require("neo-async");
const ChunkGraph = require("../ChunkGraph"); const ChunkGraph = require("../ChunkGraph");
const ModuleGraph = require("../ModuleGraph"); const ModuleGraph = require("../ModuleGraph");
const { JS_TYPE } = require("../ModuleSourceTypesConstants");
const { STAGE_DEFAULT } = require("../OptimizationStages"); const { STAGE_DEFAULT } = require("../OptimizationStages");
const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency"); const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
const { compareModulesByIdentifier } = require("../util/comparators"); const { compareModulesByIdentifier } = require("../util/comparators");
@ -452,7 +453,7 @@ class ModuleConcatenationPlugin {
chunkGraph.disconnectChunkAndModule(chunk, m); chunkGraph.disconnectChunkAndModule(chunk, m);
} else { } else {
const newSourceTypes = new Set(sourceTypes); const newSourceTypes = new Set(sourceTypes);
newSourceTypes.delete("javascript"); newSourceTypes.delete(JS_TYPE);
chunkGraph.setChunkModuleSourceTypes( chunkGraph.setChunkModuleSourceTypes(
chunk, chunk,
m, m,

View File

@ -8,6 +8,7 @@ const vm = require("vm");
const rimraf = require("rimraf"); const rimraf = require("rimraf");
const checkArrayExpectation = require("./checkArrayExpectation"); const checkArrayExpectation = require("./checkArrayExpectation");
const createLazyTestEnv = require("./helpers/createLazyTestEnv"); const createLazyTestEnv = require("./helpers/createLazyTestEnv");
const FakeDocument = require("./helpers/FakeDocument");
const casesPath = path.join(__dirname, "hotCases"); const casesPath = path.join(__dirname, "hotCases");
let categories = fs let categories = fs
@ -108,8 +109,7 @@ const describeCases = config => {
// ignored // ignored
} }
compiler = webpack(options); const onCompiled = (err, stats) => {
compiler.run((err, stats) => {
if (err) return done(err); if (err) return done(err);
const jsonStats = stats.toJson({ const jsonStats = stats.toJson({
errorDetails: true errorDetails: true
@ -179,9 +179,8 @@ const describeCases = config => {
}, },
document: { document: {
createElement(type) { createElement(type) {
return { const ele = {
_type: type, _type: type,
sheet: {},
getAttribute(name) { getAttribute(name) {
return this[name]; return this[name];
}, },
@ -199,6 +198,11 @@ const describeCases = config => {
} }
} }
}; };
ele.sheet =
type === "link"
? new FakeDocument.FakeSheet(ele, outputDirectory)
: {};
return ele;
}, },
head: { head: {
appendChild(element) { appendChild(element) {
@ -353,8 +357,15 @@ const describeCases = config => {
let promise = Promise.resolve(); let promise = Promise.resolve();
const info = stats.toJson({ all: false, entrypoints: true }); const info = stats.toJson({ all: false, entrypoints: true });
if (config.target === "web") { if (config.target === "web") {
for (const file of info.entrypoints.main.assets) 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}`); _require(`./${file.name}`);
}
}
} else { } else {
const assets = info.entrypoints.main.assets; const assets = info.entrypoints.main.assets;
const result = _require( const result = _require(
@ -375,7 +386,9 @@ const describeCases = config => {
done(err); done(err);
} }
); );
}); };
compiler = webpack(options);
compiler.run(onCompiled);
}, 20000); }, 20000);
const { const {

View File

@ -1,6 +1,6 @@
it("should compile and load style on demand", (done) => { it("should compile and load style on demand", (done) => {
import("./style.css").then(x => { import("./style.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("background")).toBe(" red");
expect(style.getPropertyValue("margin")).toBe(" 10px"); expect(style.getPropertyValue("margin")).toBe(" 10px");

View File

@ -1,9 +1,9 @@
import * as style from "./style.css"; import * as style from "./style.css";
it("should compile and load style on demand", done => { it("should compile and load style on demand", done => {
expect(style).toEqual(nsObj({})); expect(style).toEqual({});
import("./style2.css").then(x => { import("./style2.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
done(); done();
}, done); }, done);
}); });

View File

@ -1,9 +1,9 @@
import * as style from "./style.css"; import * as style from "./style.css";
it("should compile and load style on demand", done => { it("should compile and load style on demand", done => {
expect(style).toEqual(nsObj({})); expect(style).toEqual({});
import("./style2.css").then(x => { import("./style2.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("background")).toBe(" red");
expect(style.getPropertyValue("margin")).toBe(" 10px"); expect(style.getPropertyValue("margin")).toBe(" 10px");

View File

@ -1,7 +1,7 @@
import * as style from "./style.css"; import * as style from "./style.css";
it("should compile and load initial style", () => { it("should compile and load initial style", () => {
expect(style).toEqual(nsObj({})); expect(style).toEqual({});
const computedStyle = getComputedStyle(document.body); const computedStyle = getComputedStyle(document.body);
expect(computedStyle.getPropertyValue("background")).toBe(" red"); expect(computedStyle.getPropertyValue("background")).toBe(" red");
expect(computedStyle.getPropertyValue("margin")).toBe(" 10px"); expect(computedStyle.getPropertyValue("margin")).toBe(" 10px");

View File

@ -1,9 +1,9 @@
import * as style from "./style.css"; import * as style from "./style.css";
it("should compile and load style on demand", done => { it("should compile and load style on demand", done => {
expect(style).toEqual(nsObj({})); expect(style).toEqual({});
import("./style2.css").then(x => { import("./style2.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("background")).toBe(" red");
expect(style.getPropertyValue("margin")).toBe(" 10px"); expect(style.getPropertyValue("margin")).toBe(" 10px");

View File

@ -1,9 +1,9 @@
import * as style from "./style.css"; import * as style from "./style.css";
it("should compile and load style on demand", done => { it("should compile and load style on demand", done => {
expect(style).toEqual(nsObj({})); expect(style).toEqual({});
import("./style2.css").then(x => { import("./style2.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("background")).toBe(" red");
expect(style.getPropertyValue("margin")).toBe(" 10px"); expect(style.getPropertyValue("margin")).toBe(" 10px");

View File

@ -8,7 +8,7 @@ it("should work with js", done => {
}); });
it("should work with css", done => { it("should work with css", done => {
expect(style).toEqual(nsObj({})); expect(style).toEqual({});
const computedStyle = getComputedStyle(document.body); const computedStyle = getComputedStyle(document.body);
@ -16,7 +16,7 @@ it("should work with css", done => {
expect(computedStyle.getPropertyValue("color")).toBe(" yellow"); expect(computedStyle.getPropertyValue("color")).toBe(" yellow");
import("./async.css").then(x => { import("./async.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);

View File

@ -1,6 +1,6 @@
it("should import an external css", done => { it("should import an external css", done => {
import("../external/style.css").then(x => { import("../external/style.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
done(); done();
}, done); }, done);
}); });

View File

@ -1,6 +1,6 @@
it("should import an external css", done => { it("should import an external css", done => {
import("./style.css").then(x => { import("./style.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
expect(style.getPropertyValue("color")).toBe(" green"); expect(style.getPropertyValue("color")).toBe(" green");
expect(style.getPropertyValue("background")).toBe( expect(style.getPropertyValue("background")).toBe(

View File

@ -1,7 +1,7 @@
import * as style from "./style.css"; import * as style from "./style.css";
it("should compile and load style on demand", () => { it("should compile and load style on demand", () => {
expect(style).toEqual(nsObj({})); expect(style).toEqual({});
const computedStyle = getComputedStyle(document.body); const computedStyle = getComputedStyle(document.body);
expect(computedStyle.getPropertyValue("background")).toBe(" red"); expect(computedStyle.getPropertyValue("background")).toBe(" red");
expect(computedStyle.getPropertyValue("margin")).toBe(" 10px"); expect(computedStyle.getPropertyValue("margin")).toBe(" 10px");

View File

@ -0,0 +1,3 @@
.bar {
background-color: black;
}

View File

@ -0,0 +1,3 @@
.bar {
background-color: black;
}

View File

@ -0,0 +1,3 @@
.bar {
background-color: black;
}

View File

@ -0,0 +1,3 @@
.bar {
background-color: black;
}

View File

@ -0,0 +1,3 @@
.bar {
background-color: black;
}

View File

@ -0,0 +1,5 @@
@import url("./a1.css");
.foo {
background-color: red;
}

View File

@ -0,0 +1,4 @@
import "./main.css"
require("./a2.css")
import("./a2.css").then(() => {})

View File

@ -0,0 +1,3 @@
import a1 from "./a1.module.css"
const a2 = require("./a2.module.css")
import("./a3.module.css").then(() => {})

View File

@ -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`];
}
}
};

View File

@ -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);
});
}
});

View File

@ -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))];

View File

@ -1,9 +1,9 @@
import * as style from "./style.css"; import * as style from "./style.css";
it("should compile and load style on demand", done => { it("should compile and load style on demand", done => {
expect(style).toEqual(nsObj({})); expect(style).toEqual({});
import("./style2.css").then(x => { import("./style2.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("background")).toBe(" red");
expect(style.getPropertyValue("margin")).toBe(" 10px"); expect(style.getPropertyValue("margin")).toBe(" 10px");

View File

@ -2,7 +2,7 @@ import * as styles1 from "./style.less";
import * as styles2 from "./style.modules.less"; import * as styles2 from "./style.modules.less";
it("should prefer relative", () => { it("should prefer relative", () => {
expect(styles1).toEqual(nsObj({})); expect(styles1).toEqual({});
expect(styles2).toEqual(nsObj({ expect(styles2).toEqual(nsObj({
"style-module": "_style_modules_less-style-module", "style-module": "_style_modules_less-style-module",
})); }));

View File

@ -2,7 +2,7 @@ import * as styles1 from "./style.css";
import * as styles2 from "./style.modules.css"; import * as styles2 from "./style.modules.css";
it("should prefer relative", () => { it("should prefer relative", () => {
expect(styles1).toEqual(nsObj({})); expect(styles1).toEqual({});
expect(styles2).toEqual(nsObj({ expect(styles2).toEqual(nsObj({
"style-module": "_style_modules_css-style-module", "style-module": "_style_modules_css-style-module",
})); }));

View File

@ -2,13 +2,13 @@ import * as pureStyle from "./style.css";
import * as styles from "./style.modules.css"; import * as styles from "./style.modules.css";
it("should work", done => { it("should work", done => {
expect(pureStyle).toEqual(nsObj({})); expect(pureStyle).toEqual({});
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
expect(style.getPropertyValue("background")).toBe(" red"); expect(style.getPropertyValue("background")).toBe(" red");
expect(styles.foo).toBe('_style_modules_css-foo'); expect(styles.foo).toBe('_style_modules_css-foo');
import(/* webpackPrefetch: true */ "./style2.css").then(x => { import(/* webpackPrefetch: true */ "./style2.css").then(x => {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
const style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
expect(style.getPropertyValue("color")).toBe(" blue"); expect(style.getPropertyValue("color")).toBe(" blue");

View File

@ -7,7 +7,7 @@ it(`should generate correct url public path with css filename`, done => {
document.body.appendChild(h1); document.body.appendChild(h1);
import("./index.css").then(x => { import("./index.css").then(x => {
try { try {
expect(x).toEqual(nsObj({})); expect(x).toEqual({});
const style1 = getComputedStyle(h1); const style1 = getComputedStyle(h1);
expect(style1).toMatchSnapshot(); expect(style1).toMatchSnapshot();
const style2 = getComputedStyle(h2); const style2 = getComputedStyle(h2);

View File

@ -5,7 +5,7 @@ function getPropertyValue(property) {
return this[property]; return this[property];
} }
module.exports = class FakeDocument { class FakeDocument {
constructor(basePath) { constructor(basePath) {
this.head = this.createElement("head"); this.head = this.createElement("head");
this.body = this.createElement("body"); this.body = this.createElement("body");
@ -54,7 +54,7 @@ module.exports = class FakeDocument {
} }
return style; return style;
} }
}; }
class FakeElement { class FakeElement {
constructor(document, type, basePath) { constructor(document, type, basePath) {
@ -252,3 +252,8 @@ class FakeSheet {
return rules; return rules;
} }
} }
FakeDocument.FakeSheet = FakeSheet;
FakeDocument.FakeElement = FakeDocument;
module.exports = FakeDocument;

View File

@ -0,0 +1,4 @@
.html {
color: green;
}

View File

@ -0,0 +1,9 @@
@import url("./a.css");
---
html {
color: blue;
}
---
html {
color: yellow;
}

View File

@ -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();
})
);
})
);
});

View File

@ -0,0 +1,5 @@
module.exports = function (config) {
if (config.target !== "web") {
return false;
}
};

View File

@ -0,0 +1,8 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "development",
devtool: false,
experiments: {
css: true
}
};

View File

@ -0,0 +1,11 @@
.html {
color: red;
}
---
html {
color: blue;
}
---
html {
color: yellow;
}

View File

@ -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();
})
);
})
);
});

View File

@ -0,0 +1,5 @@
module.exports = function (config) {
if (config.target !== "web") {
return false;
}
};

View File

@ -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);
}
);
});
}
}
]
};

View File

@ -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));
});
})
);
});

View File

@ -0,0 +1,11 @@
html {
color: red;
}
---
html {
color: red;
}
---
html {
color: blue;
}

View File

@ -0,0 +1,5 @@
module.exports = function (config) {
if (config.target !== "web") {
return false;
}
};

View File

@ -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
}
};

1
types.d.ts vendored
View File

@ -7617,6 +7617,7 @@ declare interface KnownBuildMeta {
async?: boolean; async?: boolean;
sideEffectFree?: boolean; sideEffectFree?: boolean;
exportsFinalName?: Record<string, string>; exportsFinalName?: Record<string, string>;
isCSSModule?: boolean;
} }
declare interface KnownCreateStatsOptionsContext { declare interface KnownCreateStatsOptionsContext {
forToString?: boolean; forToString?: boolean;