Skip to content

Commit c1dc12c

Browse files
committed
feat: use webpack 5 cache system and remove child compilation
BREAKING CHANGE: file based cache will only work if you configure webpacks filesystem cache
1 parent 8bae0b1 commit c1dc12c

22 files changed

+1097
-293
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
/.nyc_output/
33
/.vscode/
44
/example/**/public/
5-
**/.wwp-cache/
5+
**/.cache/
66
**/node_modules/

example/no-inject/webpack.config.js

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ module.exports = (env, args) => {
1111
publicPath: '/',
1212
filename: 'app.js'
1313
},
14+
cache: {
15+
type: 'filesystem',
16+
},
1417
plugins: [
1518
new HtmlWebpackPlugin({
1619
filename: 'index.html',

src/cache.js

+246
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/// @ts-check
2+
3+
// Import types
4+
/** @typedef {ReturnType<import("webpack").Compiler['getCache']>} WebpackCacheFacade */
5+
/** @typedef {import("webpack").Compilation} WebpackCompilation */
6+
/** @typedef {any} Snapshot */
7+
8+
/** @typedef {{
9+
tags: string[],
10+
assets: Array<{
11+
name: string,
12+
contents: import('webpack').sources.RawSource
13+
}>
14+
}} FaviconsCompilationResult */
15+
16+
const path = require('path');
17+
const {
18+
replaceContentHash,
19+
resolvePublicPath,
20+
getContentHash
21+
} = require('./hash');
22+
23+
/** @type {WeakMap<any, Snapshot>} */
24+
const snapshots = new WeakMap();
25+
/** @type {WeakMap<Snapshot, Promise<FaviconsCompilationResult>>} */
26+
const faviconCache = new WeakMap();
27+
28+
/**
29+
* Executes the generator function and caches the result in memory
30+
* The cache will be invalidated after the logo source file was modified
31+
*
32+
* @param {import('./options').FaviconWebpackPlugionInternalOptions} faviconOptions
33+
* @param {string} context - the compiler.context patth
34+
* @param {WebpackCompilation} compilation - the current webpack compilation
35+
* @param {any} pluginInstance - the plugin instance to use as cache key
36+
* @param {(
37+
logoSource: Buffer | string,
38+
compilation: WebpackCompilation,
39+
resolvedPublicPath: string,
40+
outputPath: string
41+
) => Promise<FaviconsCompilationResult>
42+
} generator
43+
*
44+
* @returns {Promise<FaviconsCompilationResult>}
45+
*/
46+
function runCached(
47+
faviconOptions,
48+
context,
49+
compilation,
50+
pluginInstance,
51+
generator
52+
) {
53+
const { logo } = faviconOptions;
54+
55+
const latestSnapShot = snapshots.get(pluginInstance);
56+
const cachedFavicons = latestSnapShot && faviconCache.get(latestSnapShot);
57+
58+
if (latestSnapShot && cachedFavicons) {
59+
return isSnapShotValid(latestSnapShot, compilation).then(isValid => {
60+
// If the source files have changed clear all caches
61+
// and try again
62+
if (!isValid) {
63+
faviconCache.delete(latestSnapShot);
64+
return runCached(
65+
faviconOptions,
66+
context,
67+
compilation,
68+
pluginInstance,
69+
generator
70+
);
71+
}
72+
// If the cache is valid return the result directly from cache
73+
return cachedFavicons;
74+
});
75+
}
76+
77+
// Store a snapshot of the filesystem
78+
// to find out if the logo was changed
79+
snapshots.set(
80+
pluginInstance,
81+
createSnapshot(
82+
{
83+
fileDependencies: [logo],
84+
contextDependencies: [],
85+
missingDependencies: []
86+
},
87+
compilation
88+
)
89+
);
90+
91+
// Start generating the favicons
92+
const faviconsGenerationsPromise = runWithFileCache(
93+
faviconOptions,
94+
context,
95+
compilation,
96+
generator
97+
);
98+
99+
// Store the promise of the favicon compilation in cache
100+
faviconCache.set(
101+
snapshots.get(pluginInstance) || latestSnapShot,
102+
faviconsGenerationsPromise
103+
);
104+
105+
return faviconsGenerationsPromise;
106+
}
107+
108+
/**
109+
* Create a snapshot
110+
* @param {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} fileDependencies
111+
* @param {WebpackCompilation} mainCompilation
112+
* @returns {Promise<Snapshot>}
113+
*/
114+
function createSnapshot(fileDependencies, mainCompilation) {
115+
return new Promise((resolve, reject) => {
116+
mainCompilation.fileSystemInfo.createSnapshot(
117+
new Date().getTime(),
118+
fileDependencies.fileDependencies,
119+
fileDependencies.contextDependencies,
120+
fileDependencies.missingDependencies,
121+
{},
122+
(err, snapshot) => {
123+
if (err) {
124+
return reject(err);
125+
}
126+
resolve(snapshot);
127+
}
128+
);
129+
});
130+
}
131+
132+
/**
133+
* Executes the generator function and stores it in the webpack file cache
134+
*
135+
* @param {import('./options').FaviconWebpackPlugionInternalOptions} faviconOptions
136+
* @param {string} context - the compiler.context patth
137+
* @param {WebpackCompilation} compilation - the current webpack compilation
138+
* @param {(
139+
logoSource: Buffer | string,
140+
compilation: WebpackCompilation,
141+
resolvedPublicPath: string,
142+
outputPath: string
143+
) => Promise<FaviconsCompilationResult>
144+
} generator
145+
*
146+
* @returns {Promise<FaviconsCompilationResult>}
147+
*/
148+
async function runWithFileCache(
149+
faviconOptions,
150+
context,
151+
compilation,
152+
generator
153+
) {
154+
const { logo } = faviconOptions;
155+
const logoSource = await new Promise((resolve, reject) =>
156+
compilation.inputFileSystem.readFile(
157+
path.resolve(context, logo),
158+
(error, fileBuffer) => {
159+
if (error) {
160+
reject(error);
161+
} else {
162+
resolve(fileBuffer);
163+
}
164+
}
165+
)
166+
);
167+
168+
const compilationOutputPath = compilation.outputOptions.path || '';
169+
/**
170+
* the relative output path to the folder where the favicon files should be generated to
171+
* it might include tokens like [fullhash] or [contenthash]
172+
*/
173+
const relativeOutputPath = faviconOptions.outputPath
174+
? path.relative(
175+
compilationOutputPath,
176+
path.resolve(compilationOutputPath, faviconOptions.outputPath)
177+
)
178+
: faviconOptions.prefix;
179+
180+
const logoContentHash = getContentHash(logoSource);
181+
const executeGenerator = () => {
182+
const outputPath = replaceContentHash(
183+
compilation,
184+
relativeOutputPath,
185+
logoContentHash
186+
);
187+
const resolvedPublicPath = replaceContentHash(
188+
compilation,
189+
resolvePublicPath(
190+
compilation,
191+
faviconOptions.publicPath || compilation.outputOptions.publicPath,
192+
faviconOptions.prefix
193+
),
194+
logoContentHash
195+
);
196+
197+
return generator(logoSource, compilation, resolvedPublicPath, outputPath);
198+
};
199+
200+
if (faviconOptions.cache === false) {
201+
return executeGenerator();
202+
}
203+
204+
const webpackCache = compilation.getCache('favicons-webpack-plugin');
205+
// Cache invalidation token
206+
const eTag = [
207+
JSON.stringify(faviconOptions.publicPath),
208+
JSON.stringify(faviconOptions.mode),
209+
// Recompile filesystem cache if the user change the favicon options
210+
JSON.stringify(faviconOptions.favicons),
211+
// Recompile filesystem cache if the logo source changes:
212+
logoContentHash
213+
].join('\n');
214+
// Use the webpack cache which supports filesystem caching to improve build speed
215+
// See also https://webpack.js.org/configuration/other-options/#cache
216+
// Create one cache for every output target
217+
return webpackCache.providePromise(
218+
relativeOutputPath,
219+
eTag,
220+
executeGenerator
221+
);
222+
}
223+
224+
/**
225+
* Returns true if the files inside this snapshot
226+
* have not been changed
227+
*
228+
* @param {Snapshot} snapshot
229+
* @param {WebpackCompilation} mainCompilation
230+
* @returns {Promise<boolean>}
231+
*/
232+
function isSnapShotValid(snapshot, mainCompilation) {
233+
return new Promise((resolve, reject) => {
234+
mainCompilation.fileSystemInfo.checkSnapshotValid(
235+
snapshot,
236+
(err, isValid) => {
237+
if (err) {
238+
reject(err);
239+
}
240+
resolve(Boolean(isValid));
241+
}
242+
);
243+
});
244+
}
245+
246+
module.exports = { runCached };

src/compat.d.ts

-6
This file was deleted.

0 commit comments

Comments
 (0)