Skip to content

Commit e2d214a

Browse files
authored
Merge pull request #15200 from webpack/feature/css-exports-in-node
allow to generate only exports for css in node
2 parents 1ed8aaf + 181a2f0 commit e2d214a

27 files changed

Lines changed: 870 additions & 129 deletions

declarations/WebpackOptions.d.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2723,6 +2723,15 @@ export interface AssetResourceGeneratorOptions {
27232723
*/
27242724
publicPath?: RawPublicPath;
27252725
}
2726+
/**
2727+
* Options for css handling.
2728+
*/
2729+
export interface CssExperimentOptions {
2730+
/**
2731+
* Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.
2732+
*/
2733+
exportsOnly?: boolean;
2734+
}
27262735
/**
27272736
* Generator options for css modules.
27282737
*/
@@ -2809,10 +2818,6 @@ export interface ExperimentsCommon {
28092818
* Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.
28102819
*/
28112820
cacheUnaffected?: boolean;
2812-
/**
2813-
* Enable css support.
2814-
*/
2815-
css?: boolean;
28162821
/**
28172822
* Apply defaults of next major version.
28182823
*/
@@ -3453,6 +3458,10 @@ export interface ExperimentsExtra {
34533458
* Build http(s): urls using a lockfile and resource content cache.
34543459
*/
34553460
buildHttp?: HttpUriAllowedUris | HttpUriOptions;
3461+
/**
3462+
* Enable css support.
3463+
*/
3464+
css?: boolean | CssExperimentOptions;
34563465
/**
34573466
* Compile entrypoints and import()s only when they are accessed.
34583467
*/
@@ -3466,6 +3475,10 @@ export interface ExperimentsNormalizedExtra {
34663475
* Build http(s): urls using a lockfile and resource content cache.
34673476
*/
34683477
buildHttp?: HttpUriOptions;
3478+
/**
3479+
* Enable css support.
3480+
*/
3481+
css?: CssExperimentOptions;
34693482
/**
34703483
* Compile entrypoints and import()s only when they are accessed.
34713484
*/

lib/Compiler.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ class Compiler {
165165
/** @type {AsyncSeriesHook<[Compilation]>} */
166166
afterCompile: new AsyncSeriesHook(["compilation"]),
167167

168+
/** @type {AsyncSeriesHook<[]>} */
169+
readRecords: new AsyncSeriesHook([]),
170+
/** @type {AsyncSeriesHook<[]>} */
171+
emitRecords: new AsyncSeriesHook([]),
172+
168173
/** @type {AsyncSeriesHook<[Compiler]>} */
169174
watchRun: new AsyncSeriesHook(["compiler"]),
170175
/** @type {SyncHook<[Error]>} */
@@ -882,8 +887,32 @@ ${other}`);
882887
* @returns {void}
883888
*/
884889
emitRecords(callback) {
885-
if (!this.recordsOutputPath) return callback();
890+
if (this.hooks.emitRecords.isUsed()) {
891+
if (this.recordsOutputPath) {
892+
asyncLib.parallel(
893+
[
894+
cb => this.hooks.emitRecords.callAsync(cb),
895+
this._emitRecords.bind(this)
896+
],
897+
err => callback(err)
898+
);
899+
} else {
900+
this.hooks.emitRecords.callAsync(callback);
901+
}
902+
} else {
903+
if (this.recordsOutputPath) {
904+
this._emitRecords(callback);
905+
} else {
906+
callback();
907+
}
908+
}
909+
}
886910

911+
/**
912+
* @param {Callback<void>} callback signals when the call finishes
913+
* @returns {void}
914+
*/
915+
_emitRecords(callback) {
887916
const writeFile = () => {
888917
this.outputFileSystem.writeFile(
889918
this.recordsOutputPath,
@@ -926,6 +955,31 @@ ${other}`);
926955
* @returns {void}
927956
*/
928957
readRecords(callback) {
958+
if (this.hooks.readRecords.isUsed()) {
959+
if (this.recordsInputPath) {
960+
asyncLib.parallel([
961+
cb => this.hooks.readRecords.callAsync(cb),
962+
this._readRecords.bind(this)
963+
]);
964+
} else {
965+
this.records = {};
966+
this.hooks.readRecords.callAsync(callback);
967+
}
968+
} else {
969+
if (this.recordsInputPath) {
970+
this._readRecords(callback);
971+
} else {
972+
this.records = {};
973+
callback();
974+
}
975+
}
976+
}
977+
978+
/**
979+
* @param {Callback<void>} callback signals when the call finishes
980+
* @returns {void}
981+
*/
982+
_readRecords(callback) {
929983
if (!this.recordsInputPath) {
930984
this.records = {};
931985
return callback();

lib/WebpackOptionsApply.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ class WebpackOptionsApply extends OptionsApply {
291291

292292
if (options.experiments.css) {
293293
const CssModulesPlugin = require("./css/CssModulesPlugin");
294-
new CssModulesPlugin().apply(compiler);
294+
new CssModulesPlugin(options.experiments.css).apply(compiler);
295295
}
296296

297297
if (options.experiments.lazyCompilation) {

lib/config/defaults.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const {
1616
} = require("./target");
1717

1818
/** @typedef {import("../../declarations/WebpackOptions").CacheOptionsNormalized} CacheOptions */
19+
/** @typedef {import("../../declarations/WebpackOptions").CssExperimentOptions} CssExperimentOptions */
1920
/** @typedef {import("../../declarations/WebpackOptions").EntryDescription} EntryDescription */
2021
/** @typedef {import("../../declarations/WebpackOptions").EntryNormalized} Entry */
2122
/** @typedef {import("../../declarations/WebpackOptions").Experiments} Experiments */
@@ -160,7 +161,11 @@ const applyWebpackOptionsDefaults = options => {
160161
D(options, "recordsInputPath", false);
161162
D(options, "recordsOutputPath", false);
162163

163-
applyExperimentsDefaults(options.experiments, { production, development });
164+
applyExperimentsDefaults(options.experiments, {
165+
production,
166+
development,
167+
targetProperties
168+
});
164169

165170
const futureDefaults = options.experiments.futureDefaults;
166171

@@ -265,9 +270,13 @@ const applyWebpackOptionsDefaults = options => {
265270
* @param {Object} options options
266271
* @param {boolean} options.production is production
267272
* @param {boolean} options.development is development mode
273+
* @param {TargetProperties | false} options.targetProperties target properties
268274
* @returns {void}
269275
*/
270-
const applyExperimentsDefaults = (experiments, { production, development }) => {
276+
const applyExperimentsDefaults = (
277+
experiments,
278+
{ production, development, targetProperties }
279+
) => {
271280
D(experiments, "futureDefaults", false);
272281
D(experiments, "backCompat", !experiments.futureDefaults);
273282
D(experiments, "topLevelAwait", experiments.futureDefaults);
@@ -278,12 +287,20 @@ const applyExperimentsDefaults = (experiments, { production, development }) => {
278287
D(experiments, "lazyCompilation", undefined);
279288
D(experiments, "buildHttp", undefined);
280289
D(experiments, "cacheUnaffected", experiments.futureDefaults);
281-
D(experiments, "css", experiments.futureDefaults);
290+
F(experiments, "css", () => (experiments.futureDefaults ? {} : undefined));
282291

283292
if (typeof experiments.buildHttp === "object") {
284293
D(experiments.buildHttp, "frozen", production);
285294
D(experiments.buildHttp, "upgrade", false);
286295
}
296+
297+
if (typeof experiments.css === "object") {
298+
D(
299+
experiments.css,
300+
"exportsOnly",
301+
!targetProperties || !targetProperties.document
302+
);
303+
}
287304
};
288305

289306
/**
@@ -461,7 +478,7 @@ const applyJavascriptParserOptionsDefaults = (
461478
* @param {boolean} options.cache is caching enabled
462479
* @param {boolean} options.syncWebAssembly is syncWebAssembly enabled
463480
* @param {boolean} options.asyncWebAssembly is asyncWebAssembly enabled
464-
* @param {boolean} options.css is css enabled
481+
* @param {CssExperimentOptions} options.css is css enabled
465482
* @param {boolean} options.futureDefaults is future defaults enabled
466483
* @returns {void}
467484
*/
@@ -1083,7 +1100,7 @@ const applyPerformanceDefaults = (performance, { production }) => {
10831100
* @param {Object} options options
10841101
* @param {boolean} options.production is production
10851102
* @param {boolean} options.development is development
1086-
* @param {boolean} options.css is css enabled
1103+
* @param {CssExperimentOptions} options.css is css enabled
10871104
* @param {boolean} options.records using records
10881105
* @returns {void}
10891106
*/

lib/config/normalization.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ const getNormalizedWebpackOptions = config => {
180180
experiments.lazyCompilation,
181181
options =>
182182
options === true ? {} : options === false ? undefined : options
183+
),
184+
css: optionalNestedConfig(experiments.css, options =>
185+
options === true ? {} : options === false ? undefined : options
183186
)
184187
})),
185188
externals: config.externals,

lib/css/CssExportsGenerator.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Sergey Melyukov @smelukov
4+
*/
5+
6+
"use strict";
7+
8+
const { ReplaceSource, RawSource, ConcatSource } = require("webpack-sources");
9+
const { UsageState } = require("../ExportsInfo");
10+
const Generator = require("../Generator");
11+
const RuntimeGlobals = require("../RuntimeGlobals");
12+
const Template = require("../Template");
13+
14+
/** @typedef {import("webpack-sources").Source} Source */
15+
/** @typedef {import("../Dependency")} Dependency */
16+
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
17+
/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
18+
/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
19+
/** @typedef {import("../NormalModule")} NormalModule */
20+
/** @typedef {import("../util/Hash")} Hash */
21+
22+
const TYPES = new Set(["javascript"]);
23+
24+
class CssExportsGenerator extends Generator {
25+
constructor() {
26+
super();
27+
}
28+
29+
// TODO add getConcatenationBailoutReason to allow concatenation
30+
// but how to make it have a module id
31+
32+
/**
33+
* @param {NormalModule} module module for which the code should be generated
34+
* @param {GenerateContext} generateContext context for generate
35+
* @returns {Source} generated code
36+
*/
37+
generate(module, generateContext) {
38+
const source = new ReplaceSource(new RawSource(""));
39+
const initFragments = [];
40+
const cssExports = new Map();
41+
42+
generateContext.runtimeRequirements.add(RuntimeGlobals.module);
43+
44+
const runtimeRequirements = new Set();
45+
46+
const templateContext = {
47+
runtimeTemplate: generateContext.runtimeTemplate,
48+
dependencyTemplates: generateContext.dependencyTemplates,
49+
moduleGraph: generateContext.moduleGraph,
50+
chunkGraph: generateContext.chunkGraph,
51+
module,
52+
runtime: generateContext.runtime,
53+
runtimeRequirements: runtimeRequirements,
54+
concatenationScope: generateContext.concatenationScope,
55+
codeGenerationResults: generateContext.codeGenerationResults,
56+
initFragments,
57+
cssExports
58+
};
59+
60+
const handleDependency = dependency => {
61+
const constructor = /** @type {new (...args: any[]) => Dependency} */ (
62+
dependency.constructor
63+
);
64+
const template = generateContext.dependencyTemplates.get(constructor);
65+
if (!template) {
66+
throw new Error(
67+
"No template for dependency: " + dependency.constructor.name
68+
);
69+
}
70+
71+
template.apply(dependency, source, templateContext);
72+
};
73+
module.dependencies.forEach(handleDependency);
74+
75+
if (generateContext.concatenationScope) {
76+
const source = new ConcatSource();
77+
const usedIdentifiers = new Set();
78+
for (const [k, v] of cssExports) {
79+
let identifier = Template.toIdentifier(k);
80+
let i = 0;
81+
while (usedIdentifiers.has(identifier)) {
82+
identifier = Template.toIdentifier(k + i);
83+
}
84+
usedIdentifiers.add(identifier);
85+
generateContext.concatenationScope.registerExport(k, identifier);
86+
source.add(
87+
`${
88+
generateContext.runtimeTemplate.supportsConst ? "const" : "var"
89+
} ${identifier} = ${JSON.stringify(v)};\n`
90+
);
91+
}
92+
return source;
93+
} else {
94+
const otherUsed =
95+
generateContext.moduleGraph
96+
.getExportsInfo(module)
97+
.otherExportsInfo.getUsed(generateContext.runtime) !==
98+
UsageState.Unused;
99+
if (otherUsed) {
100+
generateContext.runtimeRequirements.add(
101+
RuntimeGlobals.makeNamespaceObject
102+
);
103+
}
104+
return new RawSource(
105+
`${otherUsed ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${
106+
module.moduleArgument
107+
}.exports = {\n${Array.from(
108+
cssExports,
109+
([k, v]) => `\t${JSON.stringify(k)}: ${JSON.stringify(v)}`
110+
).join(",\n")}\n}${otherUsed ? ")" : ""};`
111+
);
112+
}
113+
}
114+
115+
/**
116+
* @param {NormalModule} module fresh module
117+
* @returns {Set<string>} available types (do not mutate)
118+
*/
119+
getTypes(module) {
120+
return TYPES;
121+
}
122+
123+
/**
124+
* @param {NormalModule} module the module
125+
* @param {string=} type source type
126+
* @returns {number} estimate size of the module
127+
*/
128+
getSize(module, type) {
129+
return 42;
130+
}
131+
132+
/**
133+
* @param {Hash} hash hash that will be modified
134+
* @param {UpdateHashContext} updateHashContext context for updating hash
135+
*/
136+
updateHash(hash, { module }) {}
137+
}
138+
139+
module.exports = CssExportsGenerator;

lib/css/CssLoadingRuntimeModule.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,7 @@ class CssLoadingRuntimeModule extends RuntimeModule {
192192
"exports, module",
193193
`module.exports = exports;`
194194
)}).bind(null, exports); ${
195-
withHmr
196-
? "moduleIds.push(token); target[token].cssExports = exports; "
197-
: ""
195+
withHmr ? "moduleIds.push(token); " : ""
198196
}token = ""; exports = {}; exportsWithId.length = 0; }`,
199197
`else if(cc == ${cc("\\")}) { token += data[++i] }`,
200198
`else { token += data[i]; }`

0 commit comments

Comments
 (0)