Skip to content

Commit 2688b5b

Browse files
committed
add resolver cache
1 parent 9964a82 commit 2688b5b

File tree

3 files changed

+262
-1
lines changed

3 files changed

+262
-1
lines changed

lib/WebpackOptionsApply.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const TemplatedPathPlugin = require("./TemplatedPathPlugin");
3030
const UseStrictPlugin = require("./UseStrictPlugin");
3131
const WarnCaseSensitiveModulesPlugin = require("./WarnCaseSensitiveModulesPlugin");
3232

33+
const ResolverCachePlugin = require("./cache/ResolverCachePlugin");
34+
3335
const AMDPlugin = require("./dependencies/AMDPlugin");
3436
const CommonJsPlugin = require("./dependencies/CommonJsPlugin");
3537
const HarmonyModulesPlugin = require("./dependencies/HarmonyModulesPlugin");
@@ -474,6 +476,7 @@ class WebpackOptionsApply extends OptionsApply {
474476
throw new Error(`Unknown cache type ${options.cache.type}`);
475477
}
476478
}
479+
new ResolverCachePlugin().apply(compiler);
477480

478481
compiler.hooks.afterPlugins.call(compiler);
479482
if (!compiler.inputFileSystem) {

lib/WebpackOptionsDefaulter.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,6 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
335335
});
336336

337337
this.set("resolve", "call", value => Object.assign({}, value));
338-
this.set("resolve.unsafeCache", true);
339338
this.set("resolve.modules", ["node_modules"]);
340339
this.set("resolve.extensions", [".wasm", ".mjs", ".js", ".json"]);
341340
this.set("resolve.mainFiles", ["index"]);

lib/cache/ResolverCachePlugin.js

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Tobias Koppers @sokra
4+
*/
5+
6+
"use strict";
7+
8+
const asyncLib = require("neo-async");
9+
10+
/** @typedef {import("enhanced-resolve/lib/Resolver")} Resolver */
11+
/** @typedef {import("../Compiler")} Compiler */
12+
/** @typedef {import("../FileSystemInfo")} FileSystemInfo */
13+
14+
const INVALID = {};
15+
16+
class ResolverCachePlugin {
17+
/**
18+
* @param {Compiler} compiler Webpack compiler
19+
* @returns {void}
20+
*/
21+
apply(compiler) {
22+
const cache = compiler.cache;
23+
/** @type {FileSystemInfo} */
24+
let fileSystemInfo;
25+
compiler.hooks.thisCompilation.tap("ResolverCachePlugin", compilation => {
26+
fileSystemInfo = compilation.fileSystemInfo;
27+
});
28+
const doRealResolve = (
29+
identifier,
30+
type,
31+
resolver,
32+
resolveContext,
33+
request,
34+
callback
35+
) => {
36+
const newRequest = Object.assign(
37+
{
38+
_ResolverCachePluginCacheMiss: true
39+
},
40+
request
41+
);
42+
const newResolveContext = Object.assign({}, resolveContext, {
43+
stack: new Set(),
44+
missing: new Set(),
45+
fileDependencies: new Set(),
46+
contextDependencies: new Set()
47+
});
48+
const propagate = key => {
49+
if (resolveContext[key]) {
50+
for (const dep of newResolveContext[key]) {
51+
resolveContext[key].add(dep);
52+
}
53+
}
54+
};
55+
const resolveTime = Date.now();
56+
resolver.doResolve(
57+
resolver.hooks.resolve,
58+
newRequest,
59+
"Cache miss",
60+
newResolveContext,
61+
(err, result) => {
62+
propagate("missing");
63+
propagate("fileDependencies");
64+
propagate("contextDependencies");
65+
if (err) return callback(err);
66+
const fileDependencies = new Set(newResolveContext.fileDependencies);
67+
if (newResolveContext.missing) {
68+
for (const missing of newResolveContext.missing) {
69+
fileDependencies.add(missing);
70+
}
71+
}
72+
const contextDependencies = new Set(
73+
newResolveContext.contextDependencies
74+
);
75+
if (result && result.path) {
76+
if (type === "normal" || type === "loader") {
77+
fileDependencies.add(result.path);
78+
} else if (type === "context") {
79+
contextDependencies.add(result.path);
80+
}
81+
}
82+
const fileTimestamps = new Map();
83+
const contextTimestamps = new Map();
84+
const store = () => {
85+
cache.store(
86+
identifier,
87+
null,
88+
{
89+
result,
90+
resolveTime,
91+
fileTimestamps,
92+
contextTimestamps
93+
},
94+
restoreErr => {
95+
if (restoreErr) return callback(restoreErr);
96+
if (result) return callback(null, result);
97+
callback();
98+
}
99+
);
100+
};
101+
asyncLib.parallel(
102+
[
103+
asyncLib.each.bind(
104+
asyncLib,
105+
fileDependencies,
106+
(dep, callback) => {
107+
fileSystemInfo.getFileTimestamp(dep, (err, entry) => {
108+
if (err) {
109+
fileTimestamps.set(dep, "error");
110+
} else {
111+
fileTimestamps.set(dep, entry && entry.timestamp);
112+
}
113+
callback();
114+
});
115+
}
116+
),
117+
asyncLib.each.bind(
118+
asyncLib,
119+
contextDependencies,
120+
(dep, callback) => {
121+
fileSystemInfo.getContextTimestamp(dep, (err, entry) => {
122+
contextTimestamps.set(dep, "error");
123+
// TODO: getContextTimestamp is not implemented yet
124+
callback();
125+
});
126+
}
127+
)
128+
],
129+
err => {
130+
if (err) return callback(err);
131+
store();
132+
}
133+
);
134+
}
135+
);
136+
};
137+
compiler.resolverFactory.hooks.resolver.intercept({
138+
factory(type, hook) {
139+
hook.tap(
140+
"ResolverCachePlugin",
141+
/**
142+
* @param {Resolver} resolver the resolver
143+
* @param {Object} options resolve options
144+
* @returns {void}
145+
*/
146+
(resolver, options) => {
147+
resolver.hooks.resolve.tapAsync(
148+
{
149+
name: "ResolverCachePlugin",
150+
stage: -100
151+
},
152+
(request, resolveContext, callback) => {
153+
if (request._ResolverCachePluginCacheMiss || !fileSystemInfo) {
154+
return callback();
155+
}
156+
const identifier = `/resolve/${type}/${JSON.stringify(
157+
request
158+
)}`;
159+
cache.get(identifier, null, (err, cacheEntry) => {
160+
if (err) return callback(err);
161+
162+
if (cacheEntry) {
163+
const {
164+
result,
165+
resolveTime,
166+
fileTimestamps,
167+
contextTimestamps
168+
} = cacheEntry;
169+
asyncLib.parallel(
170+
[
171+
asyncLib.each.bind(
172+
asyncLib,
173+
fileTimestamps,
174+
([dep, ts], callback) => {
175+
fileSystemInfo.getFileTimestamp(
176+
dep,
177+
(err, entry) => {
178+
if (err) return callback(err);
179+
if (ts === "error") {
180+
return callback(
181+
!entry || entry.safeTime > resolveTime
182+
? INVALID
183+
: null
184+
);
185+
}
186+
if (!entry !== !ts) return callback(INVALID);
187+
if (entry && entry.timestamp) {
188+
return callback(
189+
entry.timestamp !== ts ? INVALID : null
190+
);
191+
}
192+
callback();
193+
}
194+
);
195+
}
196+
),
197+
asyncLib.each.bind(
198+
asyncLib,
199+
contextTimestamps,
200+
([dep, ts], callback) => {
201+
fileSystemInfo.getContextTimestamp(
202+
dep,
203+
(err, entry) => {
204+
if (err) return callback(err);
205+
if (ts === "error") {
206+
return callback(
207+
!entry || entry.safeTime > resolveTime
208+
? INVALID
209+
: null
210+
);
211+
}
212+
if (!entry !== !ts) return callback(INVALID);
213+
if (entry && entry.timestamp) {
214+
return callback(
215+
entry.timestamp !== ts ? INVALID : null
216+
);
217+
}
218+
callback();
219+
}
220+
);
221+
}
222+
)
223+
],
224+
err => {
225+
if (err) {
226+
return doRealResolve(
227+
identifier,
228+
type,
229+
resolver,
230+
resolveContext,
231+
request,
232+
callback
233+
);
234+
}
235+
callback(null, result);
236+
}
237+
);
238+
} else {
239+
doRealResolve(
240+
identifier,
241+
type,
242+
resolver,
243+
resolveContext,
244+
request,
245+
callback
246+
);
247+
}
248+
});
249+
}
250+
);
251+
}
252+
);
253+
return hook;
254+
}
255+
});
256+
}
257+
}
258+
259+
module.exports = ResolverCachePlugin;

0 commit comments

Comments
 (0)