Skip to content

Commit 77d6398

Browse files
committed
feat(webpack-plugin): Extract webpack rewriting to its own plugin so that a single analysis can be used in other builds -- eg for SSR builds.
1 parent 46e9537 commit 77d6398

File tree

1 file changed

+90
-23
lines changed

1 file changed

+90
-23
lines changed

packages/webpack-plugin/src/Plugin.ts

+90-23
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,59 @@ interface CompilationResult {
6464
analyses: Array<TemplateAnalysis<keyof TemplateTypes>>;
6565
}
6666

67+
export class CssBlocksRewriterPlugin
68+
extends Tapable
69+
implements WebpackPlugin
70+
{
71+
parent: CssBlocksPlugin;
72+
compilationOptions: CssBlocksOptions;
73+
outputCssFile: string;
74+
name: any;
75+
debug: debugGenerator.IDebugger;
76+
pendingResult: Promise<StyleMapping | void> | undefined;
77+
constructor(parent: CssBlocksPlugin) {
78+
super();
79+
this.debug = parent.debug;
80+
this.outputCssFile = parent.outputCssFile;
81+
this.name = parent.name;
82+
this.compilationOptions = parent.compilationOptions;
83+
this.parent = parent;
84+
parent.onCompilationExpiration(() => {
85+
this.trace(`resetting pending compilation.`);
86+
this.pendingResult = undefined;
87+
});
88+
parent.onPendingCompilation((pendingResult) => {
89+
this.trace(`received pending compilation.`);
90+
this.pendingResult = pendingResult;
91+
});
92+
}
93+
94+
apply(compiler: WebpackCompiler) {
95+
compiler.plugin("compilation", (compilation: any) => {
96+
compilation.plugin("normal-module-loader", (context: any, mod: any) => {
97+
this.trace(`preparing normal-module-loader for ${mod.resource}`);
98+
context.cssBlocks = context.cssBlocks || {mappings: {}, compilationOptions: this.compilationOptions};
99+
100+
// If we're already waiting for a css file of this name to finish compiling, throw.
101+
if (context.cssBlocks.mappings[this.outputCssFile]) {
102+
throw new Error(`css conflict detected. Multiple compiles writing to ${this.parent.outputCssFile}?`);
103+
}
104+
105+
if (this.pendingResult === undefined) {
106+
throw new Error(`No pending result is available yet.`);
107+
}
108+
context.cssBlocks.mappings[this.outputCssFile] = this.pendingResult;
109+
});
110+
});
111+
}
112+
113+
trace(message: string) {
114+
message = message.replace(this.parent.projectDir + "/", "");
115+
this.debug(`[${this.name}] ${message}`);
116+
}
117+
118+
}
119+
67120
export class CssBlocksPlugin
68121
extends Tapable
69122
implements WebpackPlugin
@@ -74,7 +127,7 @@ export class CssBlocksPlugin
74127
projectDir: string;
75128
outputCssFile: string;
76129
compilationOptions: CssBlocksOptions;
77-
debug: (message: string) => void;
130+
debug: debugGenerator.IDebugger;
78131

79132
constructor(options: CssBlocksWebpackOptions) {
80133
super();
@@ -89,13 +142,11 @@ export class CssBlocksPlugin
89142
Object.assign({}, DEFAULT_OPTIONS, options.optimization);
90143
}
91144

92-
apply(compiler: WebpackCompiler) {
93-
this.projectDir = compiler.options.context || this.projectDir;
94-
let outputPath = compiler.options.output && compiler.options.output.path || this.projectDir; // TODO What is the webpack default output directory?
95-
let assets: Assets = {};
96-
97-
compiler.plugin("make", (compilation: any, cb: (error?: Error) => void) => {
145+
getRewriterPlugin(): CssBlocksRewriterPlugin {
146+
return new CssBlocksRewriterPlugin(this);
147+
}
98148

149+
private handleMake(outputPath: string, assets: Assets, compilation: any, cb: (error?: Error) => void) {
99150
// Start analysis with a clean analysis object
100151
this.trace(`starting analysis.`);
101152
this.analyzer.reset();
@@ -169,30 +220,30 @@ export class CssBlocksPlugin
169220
this.trace(`notified of compilation failure`);
170221
});
171222

172-
compilation.plugin("normal-module-loader", (context: any, mod: any) => {
173-
this.trace(`preparing normal-module-loader for ${mod.resource}`);
174-
context.cssBlocks = context.cssBlocks || {mappings: {}, compilationOptions: this.compilationOptions};
175-
176-
// If we're already waiting for a css file of this name to finish compiling, throw.
177-
if (context.cssBlocks.mappings[this.outputCssFile]) {
178-
throw new Error(`css conflict detected. Multiple compiles writing to ${this.outputCssFile}`);
179-
}
223+
this.trace(`notifying of pending compilation`);
224+
this.notifyPendingCompilation(pendingResult);
225+
this.trace(`notified of pending compilation`);
226+
}
180227

181-
context.cssBlocks.mappings[this.outputCssFile] = pendingResult;
228+
apply(compiler: WebpackCompiler) {
229+
this.projectDir = compiler.options.context || this.projectDir;
230+
let outputPath = compiler.options.output && compiler.options.output.path || this.projectDir; // TODO What is the webpack default output directory?
231+
let assets: Assets = {};
182232

183-
});
233+
compiler.plugin("this-compilation", (compilation) => {
234+
this.notifyCompilationExpiration();
184235

185236
compilation.plugin('additional-assets', (cb: () => void) => {
186237
Object.assign(compilation.assets, assets);
187238
cb();
188239
});
240+
});
189241

190-
this.trace(`notifying of pending compilation`);
191-
this.notifyPendingCompilation(pendingResult);
192-
this.trace(`notified of pending compilation`);
242+
compiler.plugin("make", this.handleMake.bind(this, outputPath, assets));
193243

194-
});
244+
this.getRewriterPlugin().apply(compiler);
195245
}
246+
196247
private compileBlocks(analysis: MetaTemplateAnalysis, cssOutputName: string): Promise<CompilationResult> {
197248
let options: CssBlocksOptions = this.compilationOptions;
198249
let reader = new CssBlocksOptionsReader(options);
@@ -236,16 +287,32 @@ export class CssBlocksPlugin
236287
message = message.replace(this.projectDir + "/", "");
237288
this.debug(`[${this.name}] ${message}`);
238289
}
290+
/**
291+
* Fires when the compilation promise is available.
292+
*/
239293
onPendingCompilation(handler: (pendingResult: Promise<StyleMapping | void>) => void) {
240294
this.plugin("block-compilation-pending", handler);
241295
}
242-
notifyPendingCompilation(pendingResult: Promise<StyleMapping | void>) {
296+
private notifyPendingCompilation(pendingResult: Promise<StyleMapping | void>) {
243297
this.applyPlugins("block-compilation-pending", pendingResult);
244298
}
299+
/**
300+
* Fires when the compilation is first started to let any listeners know that
301+
* their current promise is no longer valid.
302+
*/
303+
onCompilationExpiration(handler: () => void) {
304+
this.plugin("block-compilation-expired", handler);
305+
}
306+
private notifyCompilationExpiration() {
307+
this.applyPlugins("block-compilation-expired");
308+
}
309+
/**
310+
* Fires when the compilation is done.
311+
*/
245312
onComplete(handler: (result: BlockCompilationComplete | BlockCompilationError, cb: (err: Error) => void) => void) {
246313
this.plugin("block-compilation-complete", handler);
247314
}
248-
notifyComplete(result: BlockCompilationComplete | BlockCompilationError, cb: (err: Error) => void) {
315+
private notifyComplete(result: BlockCompilationComplete | BlockCompilationError, cb: (err: Error) => void) {
249316
this.applyPluginsAsync("block-compilation-complete", result, cb);
250317
}
251318
}

0 commit comments

Comments
 (0)