1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const Cache = require("../Cache");
|
9 | const ProgressPlugin = require("../ProgressPlugin");
|
10 |
|
11 |
|
12 |
|
13 | const BUILD_DEPENDENCIES_KEY = Symbol();
|
14 |
|
15 | class IdleFileCachePlugin {
|
16 | |
17 |
|
18 |
|
19 |
|
20 |
|
21 | constructor(strategy, idleTimeout, idleTimeoutForInitialStore) {
|
22 | this.strategy = strategy;
|
23 | this.idleTimeout = idleTimeout;
|
24 | this.idleTimeoutForInitialStore = idleTimeoutForInitialStore;
|
25 | }
|
26 |
|
27 | |
28 |
|
29 |
|
30 |
|
31 |
|
32 | apply(compiler) {
|
33 | let strategy = this.strategy;
|
34 | const idleTimeout = this.idleTimeout;
|
35 | const idleTimeoutForInitialStore = Math.min(
|
36 | idleTimeout,
|
37 | this.idleTimeoutForInitialStore
|
38 | );
|
39 |
|
40 | const resolvedPromise = Promise.resolve();
|
41 |
|
42 |
|
43 | const pendingIdleTasks = new Map();
|
44 |
|
45 | compiler.cache.hooks.store.tap(
|
46 | { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
|
47 | (identifier, etag, data) => {
|
48 | pendingIdleTasks.set(identifier, () =>
|
49 | strategy.store(identifier, etag, data)
|
50 | );
|
51 | }
|
52 | );
|
53 |
|
54 | compiler.cache.hooks.get.tapPromise(
|
55 | { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
|
56 | (identifier, etag, gotHandlers) => {
|
57 | const restore = () =>
|
58 | strategy.restore(identifier, etag).then(cacheEntry => {
|
59 | if (cacheEntry === undefined) {
|
60 | gotHandlers.push((result, callback) => {
|
61 | if (result !== undefined) {
|
62 | pendingIdleTasks.set(identifier, () =>
|
63 | strategy.store(identifier, etag, result)
|
64 | );
|
65 | }
|
66 | callback();
|
67 | });
|
68 | } else {
|
69 | return cacheEntry;
|
70 | }
|
71 | });
|
72 | const pendingTask = pendingIdleTasks.get(identifier);
|
73 | if (pendingTask !== undefined) {
|
74 | pendingIdleTasks.delete(identifier);
|
75 | return pendingTask().then(restore);
|
76 | }
|
77 | return restore();
|
78 | }
|
79 | );
|
80 |
|
81 | compiler.cache.hooks.storeBuildDependencies.tap(
|
82 | { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
|
83 | dependencies => {
|
84 | pendingIdleTasks.set(BUILD_DEPENDENCIES_KEY, () =>
|
85 | strategy.storeBuildDependencies(dependencies)
|
86 | );
|
87 | }
|
88 | );
|
89 |
|
90 | compiler.cache.hooks.shutdown.tapPromise(
|
91 | { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
|
92 | () => {
|
93 | if (idleTimer) {
|
94 | clearTimeout(idleTimer);
|
95 | idleTimer = undefined;
|
96 | }
|
97 | isIdle = false;
|
98 | const reportProgress = ProgressPlugin.getReporter(compiler);
|
99 | const jobs = Array.from(pendingIdleTasks.values());
|
100 | if (reportProgress) reportProgress(0, "process pending cache items");
|
101 | const promises = jobs.map(fn => fn());
|
102 | pendingIdleTasks.clear();
|
103 | promises.push(currentIdlePromise);
|
104 | const promise = Promise.all(promises);
|
105 | currentIdlePromise = promise.then(() => strategy.afterAllStored());
|
106 | if (reportProgress) {
|
107 | currentIdlePromise = currentIdlePromise.then(() => {
|
108 | reportProgress(1, `stored`);
|
109 | });
|
110 | }
|
111 | return currentIdlePromise.then(() => {
|
112 |
|
113 | if (strategy.clear) strategy.clear();
|
114 | });
|
115 | }
|
116 | );
|
117 |
|
118 |
|
119 | let currentIdlePromise = resolvedPromise;
|
120 | let isIdle = false;
|
121 | let isInitialStore = true;
|
122 | const processIdleTasks = () => {
|
123 | if (isIdle) {
|
124 | if (pendingIdleTasks.size > 0) {
|
125 | const promises = [currentIdlePromise];
|
126 | const maxTime = Date.now() + 100;
|
127 | let maxCount = 100;
|
128 | for (const [filename, factory] of pendingIdleTasks) {
|
129 | pendingIdleTasks.delete(filename);
|
130 | promises.push(factory());
|
131 | if (maxCount-- <= 0 || Date.now() > maxTime) break;
|
132 | }
|
133 | currentIdlePromise = Promise.all(promises);
|
134 | currentIdlePromise.then(() => {
|
135 | // Allow to exit the process between
|
136 | setTimeout(processIdleTasks, 0).unref();
|
137 | });
|
138 | return;
|
139 | }
|
140 | currentIdlePromise = currentIdlePromise
|
141 | .then(() => strategy.afterAllStored())
|
142 | .catch(err => {
|
143 | const logger = compiler.getInfrastructureLogger(
|
144 | "IdleFileCachePlugin"
|
145 | );
|
146 | logger.warn(`Background tasks during idle failed: ${err.message}`);
|
147 | logger.debug(err.stack);
|
148 | });
|
149 | isInitialStore = false;
|
150 | }
|
151 | };
|
152 | let idleTimer = undefined;
|
153 | compiler.cache.hooks.beginIdle.tap(
|
154 | { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
|
155 | () => {
|
156 | idleTimer = setTimeout(
|
157 | () => {
|
158 | idleTimer = undefined;
|
159 | isIdle = true;
|
160 | resolvedPromise.then(processIdleTasks);
|
161 | },
|
162 | isInitialStore ? idleTimeoutForInitialStore : idleTimeout
|
163 | );
|
164 | idleTimer.unref();
|
165 | }
|
166 | );
|
167 | compiler.cache.hooks.endIdle.tap(
|
168 | { name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK },
|
169 | () => {
|
170 | if (idleTimer) {
|
171 | clearTimeout(idleTimer);
|
172 | idleTimer = undefined;
|
173 | }
|
174 | isIdle = false;
|
175 | }
|
176 | );
|
177 | }
|
178 | }
|
179 |
|
180 | module.exports = IdleFileCachePlugin;
|
181 |
|
\ | No newline at end of file |