diff options
Diffstat (limited to 'config/helpers/incremental_webpack_compiler.js')
-rw-r--r-- | config/helpers/incremental_webpack_compiler.js | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/config/helpers/incremental_webpack_compiler.js b/config/helpers/incremental_webpack_compiler.js new file mode 100644 index 00000000000..786bb6071fa --- /dev/null +++ b/config/helpers/incremental_webpack_compiler.js @@ -0,0 +1,128 @@ +const fs = require('fs'); +const path = require('path'); + +const log = (msg, ...rest) => console.log(`IncrementalWebpackCompiler: ${msg}`, ...rest); + +// 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; + +class NoopCompiler { + constructor() { + this.enabled = false; + } + + filterEntryPoints(entryPoints) { + return entryPoints; + } + + logStatus() {} + + setupMiddleware() {} +} + +class IncrementalWebpackCompiler { + constructor(historyFilePath) { + this.enabled = true; + this.history = {}; + this.compiledEntryPoints = new Set([ + // Login page + 'pages.sessions.new', + // Explore page + 'pages.root', + ]); + this.historyFilePath = historyFilePath; + this._loadFromHistory(); + } + + filterEntryPoints(entrypoints) { + return Object.fromEntries( + Object.entries(entrypoints).map(([key, val]) => { + if (this.compiledEntryPoints.has(key)) { + return [key, val]; + } + return [key, ['./webpack_non_compiled_placeholder.js']]; + }), + ); + } + + logStatus(totalCount) { + const current = this.compiledEntryPoints.size; + log(`Currently compiling route entrypoints: ${current} of ${totalCount}`); + } + + setupMiddleware(app, server) { + 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 chunk = fileName.replace(/\.chunk\.js$/, ''); + + this._addToHistory(chunk); + + if (!this.compiledEntryPoints.has(chunk)) { + log(`First time we are seeing ${chunk}. Adding to compilation.`); + + this.compiledEntryPoints.add(chunk); + + setTimeout(() => { + server.middleware.invalidate(() => { + if (server.sockets) { + server.sockWrite(server.sockets, 'content-changed'); + } + }); + }, TIMEOUT); + } + } + + next(); + }); + } + + // private methods + + _addToHistory(chunk) { + if (!this.history[chunk]) { + this.history[chunk] = { lastVisit: null, count: 0 }; + } + this.history[chunk].lastVisit = Date.now(); + this.history[chunk].count += 1; + + try { + fs.writeFileSync(this.historyFilePath, JSON.stringify(this.history), 'utf8'); + } catch (e) { + log('Warning – Could not write to history', e.message); + } + } + + _loadFromHistory() { + try { + this.history = JSON.parse(fs.readFileSync(this.historyFilePath, 'utf8')); + const entryPoints = Object.keys(this.history); + log(`Successfully loaded history containing ${entryPoints.length} entry points`); + /* + TODO: Let's ask a few folks to give us their history file after a milestone of usage + Then we can make smarter decisions on when to throw out rather than rendering everything + Something like top 20/30/40 entries visited in the last 7/10/15 days might be sufficient + */ + this.compiledEntryPoints = new Set([...this.compiledEntryPoints, ...entryPoints]); + } catch (e) { + log(`No history found...`); + } + } +} + +module.exports = (enabled, historyFilePath) => { + log(`Status – ${enabled ? 'enabled' : 'disabled'}`); + + if (enabled) { + return new IncrementalWebpackCompiler(historyFilePath); + } + return new NoopCompiler(); +}; |