131 lines
3.7 KiB
JavaScript
131 lines
3.7 KiB
JavaScript
/* eslint-disable max-classes-per-file, no-underscore-dangle */
|
||
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;
|
||
|
||
/* eslint-disable class-methods-use-this */
|
||
class NoopCompiler {
|
||
constructor() {
|
||
this.enabled = false;
|
||
}
|
||
|
||
filterEntryPoints(entryPoints) {
|
||
return entryPoints;
|
||
}
|
||
|
||
logStatus() {}
|
||
|
||
setupMiddleware() {}
|
||
}
|
||
/* eslint-enable class-methods-use-this */
|
||
|
||
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();
|
||
};
|