diff options
Diffstat (limited to 'config/helpers/incremental_webpack_compiler/compiler.js')
-rw-r--r-- | config/helpers/incremental_webpack_compiler/compiler.js | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/config/helpers/incremental_webpack_compiler/compiler.js b/config/helpers/incremental_webpack_compiler/compiler.js new file mode 100644 index 00000000000..480d7fa3263 --- /dev/null +++ b/config/helpers/incremental_webpack_compiler/compiler.js @@ -0,0 +1,117 @@ +/* eslint-disable max-classes-per-file */ + +const path = require('path'); +const { History, HistoryWithTTL } = require('./history'); +const log = require('./log'); + +const onRequestEntryPoint = (app, callback) => { + app.use((req, res, next) => { + const fileName = path.basename(req.url); + + /** + * We are only interested in files that have a name like `pages.foo.bar.chunk.js` + * because those are the ones corresponding to our entry points. + * + * This filters out hot update files that are for example named "pages.foo.bar.[hash].hot-update.js" + */ + if (fileName.startsWith('pages.') && fileName.endsWith('.chunk.js')) { + const entryPoint = fileName.replace(/\.chunk\.js$/, ''); + callback(entryPoint); + } + + next(); + }); +}; + +/** + * The NoopCompiler does nothing, following the null object pattern. + */ +class NoopCompiler { + constructor() { + this.enabled = false; + } + + // eslint-disable-next-line class-methods-use-this + filterEntryPoints(entryPoints) { + return entryPoints; + } + + // eslint-disable-next-line class-methods-use-this + logStatus() {} + + // eslint-disable-next-line class-methods-use-this + setupMiddleware() {} +} + +/** + * The HistoryOnlyCompiler only records which entry points have been requested. + * This is so that if the user disables incremental compilation, history is + * still recorded. If they later enable incremental compilation, that history + * can be used. + */ +class HistoryOnlyCompiler extends NoopCompiler { + constructor(historyFilePath) { + super(); + this.history = new History(historyFilePath); + } + + setupMiddleware(app) { + onRequestEntryPoint(app, (entryPoint) => { + this.history.onRequestEntryPoint(entryPoint); + }); + } +} + +// If we force a recompile immediately, the page reload doesn't seem to work. +// Five seconds seem to work fine and the user can read the message +const TIMEOUT = 5000; + +/** + * The IncrementalWebpackCompiler tracks which entry points have been + * requested, and only compiles entry points visited within the last `ttl` + * days. + */ +class IncrementalWebpackCompiler { + constructor(historyFilePath, ttl) { + this.enabled = true; + this.history = new HistoryWithTTL(historyFilePath, ttl); + } + + filterEntryPoints(entrypoints) { + return Object.fromEntries( + Object.entries(entrypoints).map(([entryPoint, paths]) => { + if (this.history.isRecentlyVisited(entryPoint)) { + return [entryPoint, paths]; + } + return [entryPoint, ['./webpack_non_compiled_placeholder.js']]; + }), + ); + } + + logStatus(totalCount) { + log(`Currently compiling route entrypoints: ${this.history.size} of ${totalCount}`); + } + + setupMiddleware(app, server) { + onRequestEntryPoint(app, (entryPoint) => { + const wasVisitedRecently = this.history.onRequestEntryPoint(entryPoint); + if (!wasVisitedRecently) { + log(`Have not visited ${entryPoint} recently. Adding to compilation.`); + + setTimeout(() => { + server.middleware.invalidate(() => { + if (server.sockets) { + server.sockWrite(server.sockets, 'content-changed'); + } + }); + }, TIMEOUT); + } + }); + } +} + +module.exports = { + NoopCompiler, + HistoryOnlyCompiler, + IncrementalWebpackCompiler, +}; |