Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support css esModule generator options #18357

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions declarations/WebpackOptions.d.ts
Expand Up @@ -755,6 +755,10 @@ export type AssetParserDataUrlFunction = (
source: string | Buffer,
context: {filename: string; module: import("../lib/Module")}
) => boolean;
/**
* Configure the generated JS modules that use the ES modules syntax.
*/
export type CssGeneratorEsModule = boolean;
/**
* Specifies the convention of exported names.
*/
Expand Down Expand Up @@ -2862,6 +2866,10 @@ export interface AssetResourceGeneratorOptions {
* Generator options for css/auto modules.
*/
export interface CssAutoGeneratorOptions {
/**
* Configure the generated JS modules that use the ES modules syntax.
*/
esModule?: CssGeneratorEsModule;
/**
* Specifies the convention of exported names.
*/
Expand All @@ -2888,6 +2896,10 @@ export interface CssAutoParserOptions {
* Generator options for css modules.
*/
export interface CssGeneratorOptions {
/**
* Configure the generated JS modules that use the ES modules syntax.
*/
esModule?: CssGeneratorEsModule;
/**
* Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.
*/
Expand All @@ -2897,6 +2909,10 @@ export interface CssGeneratorOptions {
* Generator options for css/global modules.
*/
export interface CssGlobalGeneratorOptions {
/**
* Configure the generated JS modules that use the ES modules syntax.
*/
esModule?: CssGeneratorEsModule;
/**
* Specifies the convention of exported names.
*/
Expand All @@ -2923,6 +2939,10 @@ export interface CssGlobalParserOptions {
* Generator options for css/module modules.
*/
export interface CssModuleGeneratorOptions {
/**
* Configure the generated JS modules that use the ES modules syntax.
*/
esModule?: CssGeneratorEsModule;
/**
* Specifies the convention of exported names.
*/
Expand Down
8 changes: 7 additions & 1 deletion lib/DependencyTemplate.js
Expand Up @@ -41,7 +41,13 @@

/**
* @typedef {Object} CssDependencyTemplateContextExtras
* @property {Map<string, string>} cssExports the css exports
* @property {CssExportsData} cssExportsData the css exports data
*/

/**
* @typedef {Object} CssExportsData
* @property {boolean} esModule whether export __esModule
* @property {Map<string, string>} exports the css exports
*/

/** @typedef {DependencyTemplateContext & CssDependencyTemplateContextExtras} CssDependencyTemplateContext */
Expand Down
1 change: 1 addition & 0 deletions lib/config/defaults.js
Expand Up @@ -553,6 +553,7 @@ const applyCssGeneratorOptionsDefaults = (
"exportsOnly",
!targetProperties || !targetProperties.document
);
D(generatorOptions, "esModule", true);
};

/**
Expand Down
56 changes: 41 additions & 15 deletions lib/css/CssExportsGenerator.js
Expand Up @@ -16,6 +16,8 @@ const { cssExportConvention } = require("../util/conventions");
/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */
/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorLocalIdentName} CssGeneratorLocalIdentName */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../DependencyTemplate").CssExportsData} CssExportsData */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
Expand All @@ -33,17 +35,36 @@ class CssExportsGenerator extends Generator {
/**
* @param {CssGeneratorExportsConvention | undefined} convention the convention of the exports name
* @param {CssGeneratorLocalIdentName | undefined} localIdentName css export local ident name
* @param {boolean} esModule whether to use ES modules syntax
*/
constructor(convention, localIdentName) {
constructor(convention, localIdentName, esModule) {
super();
/** @type {CssGeneratorExportsConvention | undefined} */
this.convention = convention;
/** @type {CssGeneratorLocalIdentName | undefined} */
this.localIdentName = localIdentName;
/** @type {boolean} */
this.esModule = esModule;
}

// TODO add getConcatenationBailoutReason to allow concatenation
// but how to make it have a module id
/**
* @param {NormalModule} module module for which the bailout reason should be determined
* @param {ConcatenationBailoutReasonContext} context context
* @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
*/
getConcatenationBailoutReason(module, context) {
if (!this.esModule) {
return "Module is not an ECMAScript module";
}
// TODO webpack 6: remove /\[moduleid\]/.test
if (
/\[id\]/.test(this.localIdentName) ||
/\[moduleid\]/.test(this.localIdentName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can improve it in future PRs

) {
return "The localIdentName includes moduleId ([id] or [moduleid])";
}
return undefined;
}

/**
* @param {NormalModule} module module for which the code should be generated
Expand All @@ -54,14 +75,18 @@ class CssExportsGenerator extends Generator {
const source = new ReplaceSource(new RawSource(""));
/** @type {InitFragment<TODO>[]} */
const initFragments = [];
/** @type {Map<string, string>} */
const cssExports = new Map();
/** @type {CssExportsData} */
const cssExportsData = {
esModule: this.esModule,
exports: new Map()
};

generateContext.runtimeRequirements.add(RuntimeGlobals.module);

let chunkInitFragments;
const runtimeRequirements = new Set();

/** @type {DependencyTemplateContext} */
const templateContext = {
runtimeTemplate: generateContext.runtimeTemplate,
dependencyTemplates: generateContext.dependencyTemplates,
Expand All @@ -73,7 +98,7 @@ class CssExportsGenerator extends Generator {
concatenationScope: generateContext.concatenationScope,
codeGenerationResults: generateContext.codeGenerationResults,
initFragments,
cssExports,
cssExportsData,
get chunkInitFragments() {
if (!chunkInitFragments) {
const data = generateContext.getData();
Expand Down Expand Up @@ -109,7 +134,7 @@ class CssExportsGenerator extends Generator {
if (generateContext.concatenationScope) {
const source = new ConcatSource();
const usedIdentifiers = new Set();
for (const [name, v] of cssExports) {
for (const [name, v] of cssExportsData.exports) {
for (let k of cssExportConvention(name, this.convention)) {
let identifier = Template.toIdentifier(k);
let i = 0;
Expand All @@ -127,26 +152,27 @@ class CssExportsGenerator extends Generator {
}
return source;
} else {
const otherUsed =
const needNsObj =
this.esModule &&
generateContext.moduleGraph
.getExportsInfo(module)
.otherExportsInfo.getUsed(generateContext.runtime) !==
UsageState.Unused;
if (otherUsed) {
UsageState.Unused;
if (needNsObj) {
generateContext.runtimeRequirements.add(
RuntimeGlobals.makeNamespaceObject
);
}
const newCssExports = [];
for (let [k, v] of cssExports) {
const newExports = [];
for (let [k, v] of cssExportsData.exports) {
for (let name of cssExportConvention(k, this.convention)) {
newCssExports.push(`\t${JSON.stringify(name)}: ${JSON.stringify(v)}`);
newExports.push(`\t${JSON.stringify(name)}: ${JSON.stringify(v)}`);
}
}
return new RawSource(
`${otherUsed ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${
`${needNsObj ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${
module.moduleArgument
}.exports = {\n${newCssExports.join(",\n")}\n}${otherUsed ? ")" : ""};`
}.exports = {\n${newExports.join(",\n")}\n}${needNsObj ? ")" : ""};`
);
}
}
Expand Down
30 changes: 20 additions & 10 deletions lib/css/CssGenerator.js
Expand Up @@ -15,6 +15,8 @@ const { cssExportConvention } = require("../util/conventions");
/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */
/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorLocalIdentName} CssGeneratorLocalIdentName */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../DependencyTemplate").CssExportsData} CssExportsData */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../NormalModule")} NormalModule */
Expand All @@ -26,13 +28,16 @@ class CssGenerator extends Generator {
/**
* @param {CssGeneratorExportsConvention | undefined} convention the convention of the exports name
* @param {CssGeneratorLocalIdentName | undefined} localIdentName css export local ident name
* @param {boolean} esModule whether to use ES modules syntax
*/
constructor(convention, localIdentName) {
constructor(convention, localIdentName, esModule) {
super();
/** @type {CssGeneratorExportsConvention | undefined} */
this.convention = convention;
/** @type {CssGeneratorLocalIdentName | undefined} */
this.localIdentName = localIdentName;
/** @type {boolean} */
this.esModule = esModule;
}

/**
Expand All @@ -45,12 +50,16 @@ class CssGenerator extends Generator {
const source = new ReplaceSource(originalSource);
/** @type {InitFragment[]} */
const initFragments = [];
/** @type {Map<string, string>} */
const cssExports = new Map();
/** @type {CssExportsData} */
const cssExportsData = {
esModule: this.esModule,
exports: new Map()
};

generateContext.runtimeRequirements.add(RuntimeGlobals.hasCssModules);

let chunkInitFragments;
/** @type {DependencyTemplateContext} */
const templateContext = {
runtimeTemplate: generateContext.runtimeTemplate,
dependencyTemplates: generateContext.dependencyTemplates,
Expand All @@ -62,7 +71,7 @@ class CssGenerator extends Generator {
concatenationScope: generateContext.concatenationScope,
codeGenerationResults: generateContext.codeGenerationResults,
initFragments,
cssExports,
cssExportsData,
get chunkInitFragments() {
if (!chunkInitFragments) {
const data = generateContext.getData();
Expand Down Expand Up @@ -97,16 +106,17 @@ class CssGenerator extends Generator {
if (module.presentationalDependencies !== undefined)
module.presentationalDependencies.forEach(handleDependency);

if (cssExports.size > 0) {
const newCssExports = new Map();
for (let [name, v] of cssExports) {
if (cssExportsData.exports.size > 0) {
const newExports = new Map();
for (let [name, v] of cssExportsData.exports) {
for (let newName of cssExportConvention(name, this.convention)) {
newCssExports.set(newName, v);
newExports.set(newName, v);
}
}
const data = generateContext.getData();
data.set("css-exports", newCssExports);
cssExportsData.exports = newExports;
}
const data = generateContext.getData();
data.set("css-exports", cssExportsData);

return InitFragment.addToSource(source, initFragments, generateContext);
}
Expand Down
11 changes: 7 additions & 4 deletions lib/css/CssLoadingRuntimeModule.js
Expand Up @@ -224,7 +224,9 @@ class CssLoadingRuntimeModule extends RuntimeModule {
withCompression
? Template.asString([
// LZW decode
`var map = {}, char = data[0], oldPhrase = char, decoded = char, code = 256, maxCode = "\uffff".charCodeAt(0), phrase;`,
`var map = {}, char = data[0], oldPhrase = char, decoded = char, code = 256, maxCode = ${"\uffff".charCodeAt(
0
)}, phrase;`,
"for (i = 1; i < data.length; i++) {",
Template.indent([
"cc = data[i].charCodeAt(0);",
Expand All @@ -246,11 +248,12 @@ class CssLoadingRuntimeModule extends RuntimeModule {
`else if(cc == ${cc(
"/"
)}) { token = token.replace(/^_/, ""); token2 = token2.replace(/^_/, ""); exports[token2] = token; token = ""; token2 = ""; }`,
`else if(cc == ${cc("&")}) { ${
RuntimeGlobals.makeNamespaceObject
}(exports); }`,
`else if(!cc || cc == ${cc(
","
)}) { token = token.replace(/^_/, ""); ${
RuntimeGlobals.makeNamespaceObject
}(exports); target[token] = (${runtimeTemplate.basicFunction(
)}) { token = token.replace(/^_/, ""); target[token] = (${runtimeTemplate.basicFunction(
"exports, module",
`module.exports = exports;`
)}).bind(null, exports); ${
Expand Down
15 changes: 10 additions & 5 deletions lib/css/CssModulesPlugin.js
Expand Up @@ -39,6 +39,7 @@ const CssParser = require("./CssParser");
/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../DependencyTemplate").CssExportsData} CssExportsData */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../util/memoize")} Memoize */

Expand Down Expand Up @@ -244,11 +245,13 @@ class CssModulesPlugin {
return generatorOptions.exportsOnly
? new CssExportsGenerator(
generatorOptions.exportsConvention,
generatorOptions.localIdentName
generatorOptions.localIdentName,
generatorOptions.esModule
)
: new CssGenerator(
generatorOptions.exportsConvention,
generatorOptions.localIdentName
generatorOptions.localIdentName,
generatorOptions.esModule
);
});
normalModuleFactory.hooks.createModuleClass
Expand Down Expand Up @@ -642,9 +645,11 @@ class CssModulesPlugin {
source.add(moduleSource);
source.add("\n");
}
/** @type {Map<string, string> | undefined} */
const exports =
/** @type {CssExportsData | undefined} */
const cssExportsData =
codeGenResult.data && codeGenResult.data.get("css-exports");
const exports = cssExportsData && cssExportsData.exports;
const esModule = cssExportsData && cssExportsData.esModule;
let moduleId = chunkGraph.getModuleId(module) + "";

// When `optimization.moduleIds` is `named` the module id is a path, so we need to normalize it between platforms
Expand All @@ -660,7 +665,7 @@ class CssModulesPlugin {
([n, v]) => `${escapeCss(n)}:${escapeCss(v)}/`
).join("")
: ""
}${escapeCss(moduleId)}`
}${esModule ? "&" : ""}${escapeCss(moduleId)}`
);
} catch (e) {
/** @type {Error} */
Expand Down
4 changes: 2 additions & 2 deletions lib/dependencies/CssExportDependency.js
Expand Up @@ -79,9 +79,9 @@ CssExportDependency.Template = class CssExportDependencyTemplate extends (
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(dependency, source, { cssExports }) {
apply(dependency, source, { cssExportsData }) {
const dep = /** @type {CssExportDependency} */ (dependency);
cssExports.set(dep.name, dep.value);
cssExportsData.exports.set(dep.name, dep.value);
}
};

Expand Down
11 changes: 9 additions & 2 deletions lib/dependencies/CssLocalIdentifierDependency.js
Expand Up @@ -157,7 +157,14 @@ CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTempla
apply(
dependency,
source,
{ module, moduleGraph, chunkGraph, runtime, runtimeTemplate, cssExports }
{
module,
moduleGraph,
chunkGraph,
runtime,
runtimeTemplate,
cssExportsData
}
) {
const dep = /** @type {CssLocalIdentifierDependency} */ (dependency);
const used = moduleGraph
Expand All @@ -179,7 +186,7 @@ CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTempla
dep.range[1] - 1,
escapeCssIdentifier(localIdent, dep.prefix)
);
if (used) cssExports.set(used, localIdent);
if (used) cssExportsData.exports.set(used, localIdent);
}
};

Expand Down
2 changes: 1 addition & 1 deletion schemas/WebpackOptions.check.js

Large diffs are not rendered by default.