UNPKG

5.04 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const Cache = require("../Cache");
9const ProgressPlugin = require("../ProgressPlugin");
10
11/** @typedef {import("../Compiler")} Compiler */
12
13const BUILD_DEPENDENCIES_KEY = Symbol();
14
15class IdleFileCachePlugin {
16 /**
17 * @param {TODO} strategy cache strategy
18 * @param {number} idleTimeout timeout
19 * @param {number} idleTimeoutForInitialStore initial timeout
20 */
21 constructor(strategy, idleTimeout, idleTimeoutForInitialStore) {
22 this.strategy = strategy;
23 this.idleTimeout = idleTimeout;
24 this.idleTimeoutForInitialStore = idleTimeoutForInitialStore;
25 }
26
27 /**
28 * Apply the plugin
29 * @param {Compiler} compiler the compiler instance
30 * @returns {void}
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 /** @type {Map<string | typeof BUILD_DEPENDENCIES_KEY, () => Promise>} */
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 // Reset strategy
113 if (strategy.clear) strategy.clear();
114 });
115 }
116 );
117
118 /** @type {Promise<any>} */
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
180module.exports = IdleFileCachePlugin;
181
\No newline at end of file