-
-
Notifications
You must be signed in to change notification settings - Fork 282
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: persistent cache between runs (webpack@5 only) #541
Changes from 4 commits
3139c04
7487458
d7bff39
9a11643
2477837
4b29447
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,8 +37,57 @@ class CopyPlugin { | |
this.options = options.options || {}; | ||
} | ||
|
||
static async createSnapshot(compilation, dependency) { | ||
if (!compilation.fileSystemInfo) { | ||
return; | ||
} | ||
|
||
// eslint-disable-next-line consistent-return | ||
return new Promise((resolve, reject) => { | ||
compilation.fileSystemInfo.createSnapshot( | ||
// eslint-disable-next-line no-undefined | ||
undefined, | ||
[dependency], | ||
[], | ||
[], | ||
null, | ||
(error, snapshot) => { | ||
if (error) { | ||
reject(error); | ||
|
||
return; | ||
} | ||
|
||
resolve(snapshot); | ||
} | ||
); | ||
}); | ||
} | ||
|
||
static async checkSnapshotValid(compilation, snapshot) { | ||
if (!compilation.fileSystemInfo) { | ||
return; | ||
} | ||
|
||
// eslint-disable-next-line consistent-return | ||
return new Promise((resolve, reject) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://nodejs.org/api/util.html#util_util_promisify_original const checkSnapshotValid = util.promisify((compilation, snapshot, callback) =>
compilation.fileSystemInfo.checkSnapshotValid(snapshot, callback)
);
const createSnapshot = util.promisify((compilation, dependency, callback) =>
compilation.fileSystemInfo.createSnapshot(Date.now(), [dependency], undefined, undefined, null, callback)
); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing an |
||
compilation.fileSystemInfo.checkSnapshotValid( | ||
snapshot, | ||
(error, isValid) => { | ||
if (error) { | ||
reject(error); | ||
|
||
return; | ||
} | ||
|
||
resolve(isValid); | ||
} | ||
); | ||
}); | ||
} | ||
|
||
// eslint-disable-next-line class-methods-use-this | ||
async runPattern(compiler, compilation, logger, inputPattern) { | ||
static async runPattern(compiler, compilation, logger, cache, inputPattern) { | ||
const pattern = | ||
typeof inputPattern === 'string' | ||
? { from: inputPattern } | ||
|
@@ -49,7 +98,6 @@ class CopyPlugin { | |
pattern.to = path.normalize( | ||
typeof pattern.to !== 'undefined' ? pattern.to : '' | ||
); | ||
|
||
pattern.context = path.normalize( | ||
typeof pattern.context !== 'undefined' | ||
? !path.isAbsolute(pattern.context) | ||
|
@@ -266,64 +314,129 @@ class CopyPlugin { | |
compilation.fileDependencies.add(file.absoluteFrom); | ||
} | ||
|
||
logger.debug(`reading "${file.absoluteFrom}" to write to assets`); | ||
let itemCache; | ||
let source; | ||
|
||
let data; | ||
// TODO logger | ||
if (cache) { | ||
let snapshot; | ||
|
||
try { | ||
data = await readFile(inputFileSystem, file.absoluteFrom); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
try { | ||
snapshot = await CopyPlugin.createSnapshot( | ||
compilation, | ||
file.absoluteFrom | ||
); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
|
||
return; | ||
} | ||
return; | ||
} | ||
|
||
if (snapshot) { | ||
let isValidSnapshot; | ||
|
||
if (pattern.transform) { | ||
logger.log(`transforming content for "${file.absoluteFrom}"`); | ||
|
||
if (pattern.cacheTransform) { | ||
const cacheDirectory = pattern.cacheTransform.directory | ||
? pattern.cacheTransform.directory | ||
: typeof pattern.cacheTransform === 'string' | ||
? pattern.cacheTransform | ||
: findCacheDir({ name: 'copy-webpack-plugin' }) || os.tmpdir(); | ||
let defaultCacheKeys = { | ||
version, | ||
transform: pattern.transform, | ||
contentHash: crypto.createHash('md4').update(data).digest('hex'), | ||
}; | ||
|
||
if (typeof pattern.cacheTransform.keys === 'function') { | ||
defaultCacheKeys = await pattern.cacheTransform.keys( | ||
defaultCacheKeys, | ||
file.absoluteFrom | ||
try { | ||
isValidSnapshot = await CopyPlugin.checkSnapshotValid( | ||
compilation, | ||
snapshot | ||
); | ||
} else { | ||
defaultCacheKeys = { | ||
...defaultCacheKeys, | ||
...pattern.cacheTransform.keys, | ||
}; | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
|
||
return; | ||
} | ||
|
||
const cacheKeys = serialize(defaultCacheKeys); | ||
itemCache = cache.getItemCache(file.relativeFrom, null); | ||
|
||
try { | ||
const result = await cacache.get(cacheDirectory, cacheKeys); | ||
if (isValidSnapshot) { | ||
try { | ||
source = await itemCache.getPromise(); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
|
||
logger.debug( | ||
`getting cached transformation for "${file.absoluteFrom}"` | ||
); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Here is some pseudo code: // 1. check cache first, validate snapshot in cache entry
cacheEntry = await cache.getPromise(file.relativeFrom, null);
if (cacheEntry !== undefined) {
const { snapshot, source } = cacheEntry;
if (await checkSnapshotValid(snapshot)) {
// return early when cache entry can be used
return source;
}
}
// 2. create a new snapshot
const snapshot = await createSnapshot(file.relativeFrom);
// 3. create new data
const source = await createNewSource(file.relativeFrom);
// 4. store both in cache
await cache.storePromise(file.relativeFrom, null, { source, snapshot });
// 5. return new data
return source; You are in the good position that you can call 2. before 3. Usually that's not possible, because dependencies are not known before execution. In that case one would do this: // 2a.
const startTime = Date.now();
// 2b. create new data
const source = await createNewSource(file.relativeFrom);
// 3. create a new snapshot
const snapshot = await createSnapshot(file.relativeFrom, startTime); |
||
|
||
if (!source) { | ||
logger.debug(`reading "${file.absoluteFrom}" to write to assets`); | ||
|
||
let data; | ||
|
||
try { | ||
data = await readFile(inputFileSystem, file.absoluteFrom); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
|
||
return; | ||
} | ||
|
||
if (pattern.transform) { | ||
logger.log(`transforming content for "${file.absoluteFrom}"`); | ||
|
||
if (pattern.cacheTransform) { | ||
const cacheDirectory = pattern.cacheTransform.directory | ||
? pattern.cacheTransform.directory | ||
: typeof pattern.cacheTransform === 'string' | ||
? pattern.cacheTransform | ||
: findCacheDir({ name: 'copy-webpack-plugin' }) || os.tmpdir(); | ||
let defaultCacheKeys = { | ||
version, | ||
transform: pattern.transform, | ||
contentHash: crypto | ||
.createHash('md4') | ||
.update(data) | ||
.digest('hex'), | ||
}; | ||
|
||
({ data } = result); | ||
} catch (_ignoreError) { | ||
if (typeof pattern.cacheTransform.keys === 'function') { | ||
defaultCacheKeys = await pattern.cacheTransform.keys( | ||
defaultCacheKeys, | ||
file.absoluteFrom | ||
); | ||
} else { | ||
defaultCacheKeys = { | ||
...defaultCacheKeys, | ||
...pattern.cacheTransform.keys, | ||
}; | ||
} | ||
|
||
const cacheKeys = serialize(defaultCacheKeys); | ||
|
||
try { | ||
const result = await cacache.get(cacheDirectory, cacheKeys); | ||
|
||
logger.debug( | ||
`getting cached transformation for "${file.absoluteFrom}"` | ||
); | ||
|
||
({ data } = result); | ||
} catch (_ignoreError) { | ||
data = await pattern.transform(data, file.absoluteFrom); | ||
|
||
logger.debug( | ||
`caching transformation for "${file.absoluteFrom}"` | ||
); | ||
|
||
await cacache.put(cacheDirectory, cacheKeys, data); | ||
} | ||
} else { | ||
data = await pattern.transform(data, file.absoluteFrom); | ||
} | ||
} | ||
|
||
source = new RawSource(data); | ||
|
||
logger.debug(`caching transformation for "${file.absoluteFrom}"`); | ||
if (itemCache) { | ||
try { | ||
await itemCache.storePromise(source); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
|
||
await cacache.put(cacheDirectory, cacheKeys, data); | ||
return; | ||
} | ||
} else { | ||
data = await pattern.transform(data, file.absoluteFrom); | ||
} | ||
} | ||
|
||
|
@@ -349,7 +462,7 @@ class CopyPlugin { | |
{ resourcePath: file.absoluteFrom }, | ||
file.webpackTo, | ||
{ | ||
content: data, | ||
content: source.source(), | ||
context: pattern.context, | ||
} | ||
); | ||
|
@@ -374,7 +487,7 @@ class CopyPlugin { | |
} | ||
|
||
// eslint-disable-next-line no-param-reassign | ||
file.data = data; | ||
file.source = source; | ||
// eslint-disable-next-line no-param-reassign | ||
file.targetPath = normalizePath(file.webpackTo); | ||
// eslint-disable-next-line no-param-reassign | ||
|
@@ -392,6 +505,10 @@ class CopyPlugin { | |
|
||
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => { | ||
const logger = compilation.getLogger('copy-webpack-plugin'); | ||
// eslint-disable-next-line no-undefined | ||
const cache = compilation.getCache | ||
? compilation.getCache('CopyWebpackPlugin') | ||
: undefined; | ||
|
||
compilation.hooks.additionalAssets.tapAsync( | ||
'copy-webpack-plugin', | ||
|
@@ -404,7 +521,13 @@ class CopyPlugin { | |
assets = await Promise.all( | ||
this.patterns.map((item) => | ||
limit(async () => | ||
this.runPattern(compiler, compilation, logger, item) | ||
CopyPlugin.runPattern( | ||
compiler, | ||
compilation, | ||
logger, | ||
cache, | ||
item | ||
) | ||
) | ||
) | ||
); | ||
|
@@ -426,12 +549,10 @@ class CopyPlugin { | |
absoluteFrom, | ||
targetPath, | ||
webpackTo, | ||
data, | ||
source, | ||
force, | ||
} = asset; | ||
|
||
const source = new RawSource(data); | ||
|
||
// For old version webpack 4 | ||
/* istanbul ignore if */ | ||
if (typeof compilation.emitAsset !== 'function') { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can pass
undefined
here, that saves a bit memory.