Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 16:18:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 16:18:24 +0300
commit0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch)
tree4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /config/helpers
parent744144d28e3e7fddc117924fef88de5d9674fe4c (diff)
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'config/helpers')
-rw-r--r--config/helpers/incremental_webpack_compiler.js131
-rw-r--r--config/helpers/incremental_webpack_compiler/compiler.js117
-rw-r--r--config/helpers/incremental_webpack_compiler/history.js176
-rw-r--r--config/helpers/incremental_webpack_compiler/index.js17
-rw-r--r--config/helpers/incremental_webpack_compiler/log.js3
5 files changed, 313 insertions, 131 deletions
diff --git a/config/helpers/incremental_webpack_compiler.js b/config/helpers/incremental_webpack_compiler.js
deleted file mode 100644
index 5d4f9bd040d..00000000000
--- a/config/helpers/incremental_webpack_compiler.js
+++ /dev/null
@@ -1,131 +0,0 @@
-/* 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();
-};
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,
+};
diff --git a/config/helpers/incremental_webpack_compiler/history.js b/config/helpers/incremental_webpack_compiler/history.js
new file mode 100644
index 00000000000..24599900011
--- /dev/null
+++ b/config/helpers/incremental_webpack_compiler/history.js
@@ -0,0 +1,176 @@
+/* eslint-disable max-classes-per-file, no-underscore-dangle */
+
+const fs = require('fs');
+const log = require('./log');
+
+const ESSENTIAL_ENTRY_POINTS = [
+ // Login page
+ 'pages.sessions.new',
+ // Explore page
+ 'pages.root',
+];
+
+// TODO: Find a way to keep this list up-to-date/relevant.
+const COMMON_ENTRY_POINTS = [
+ ...ESSENTIAL_ENTRY_POINTS,
+ 'pages.admin',
+ 'pages.admin.dashboard',
+ 'pages.dashboard.groups.index',
+ 'pages.dashboard.projects.index',
+ 'pages.groups.new',
+ 'pages.groups.show',
+ 'pages.profiles.preferences.show',
+ 'pages.projects.commit.show',
+ 'pages.projects.edit',
+ 'pages.projects.issues.index',
+ 'pages.projects.issues.new',
+ 'pages.projects.issues.show',
+ 'pages.projects.jobs.show',
+ 'pages.projects.merge_requests.index',
+ 'pages.projects.merge_requests.show',
+ 'pages.projects.milestones.index',
+ 'pages.projects.new',
+ 'pages.projects.pipelines.index',
+ 'pages.projects.pipelines.show',
+ 'pages.projects.settings.ci_cd.show',
+ 'pages.projects.settings.repository.show',
+ 'pages.projects.show',
+ 'pages.users',
+];
+
+/**
+ * The History class is responsible for tracking which entry points have been
+ * requested, and persisting/loading the history to/from disk.
+ */
+class History {
+ constructor(historyFilePath) {
+ this._historyFilePath = historyFilePath;
+ this._history = {};
+
+ this._loadHistoryFile();
+ }
+
+ onRequestEntryPoint(entryPoint) {
+ const wasVisitedRecently = this.isRecentlyVisited(entryPoint);
+
+ this._addEntryPoint(entryPoint);
+
+ this._writeHistoryFile();
+
+ return wasVisitedRecently;
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ isRecentlyVisited() {
+ return true;
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ get size() {
+ return 0;
+ }
+
+ // Private methods
+
+ _addEntryPoint(entryPoint) {
+ if (!this._history[entryPoint]) {
+ this._history[entryPoint] = { lastVisit: null, count: 0 };
+ }
+
+ this._history[entryPoint].lastVisit = Date.now();
+ this._history[entryPoint].count += 1;
+ }
+
+ _writeHistoryFile() {
+ try {
+ fs.writeFileSync(this._historyFilePath, JSON.stringify(this._history), 'utf8');
+ } catch (error) {
+ log('Warning – Could not write to history', error.message);
+ }
+ }
+
+ _loadHistoryFile() {
+ try {
+ fs.accessSync(this._historyFilePath);
+ } catch (e) {
+ // History file doesn't exist; attempt to seed it, and return early
+ this._seedHistory();
+ return;
+ }
+
+ // History file already exists; attempt to load its contents into memory
+ try {
+ this._history = JSON.parse(fs.readFileSync(this._historyFilePath, 'utf8'));
+ const historySize = Object.keys(this._history).length;
+ log(`Successfully loaded history containing ${historySize} entry points`);
+ } catch (error) {
+ log('Could not load history', error.message);
+ }
+ }
+
+ /**
+ * Seeds a reasonable set of approximately the most common entry points to
+ * seed the history. This helps to avoid fresh GDK installs showing the
+ * compiling overlay too often.
+ */
+ _seedHistory() {
+ log('Seeding history...');
+ COMMON_ENTRY_POINTS.forEach((entryPoint) => this._addEntryPoint(entryPoint));
+ this._writeHistoryFile();
+ }
+}
+
+const MS_PER_DAY = 1000 * 60 * 60 * 24;
+
+/**
+ * The HistoryWithTTL class adds LRU-like behaviour onto the base History
+ * behaviour. Entry points visited within the last `ttl` days are considered
+ * "recent", and therefore should be eagerly compiled.
+ */
+class HistoryWithTTL extends History {
+ constructor(historyFilePath, ttl) {
+ super(historyFilePath);
+ this._ttl = ttl;
+ this._calculateRecentEntryPoints();
+ }
+
+ onRequestEntryPoint(entryPoint) {
+ const wasVisitedRecently = super.onRequestEntryPoint(entryPoint);
+
+ this._calculateRecentEntryPoints();
+
+ return wasVisitedRecently;
+ }
+
+ isRecentlyVisited(entryPoint) {
+ return this._recentEntryPoints.has(entryPoint);
+ }
+
+ get size() {
+ return this._recentEntryPoints.size;
+ }
+
+ // Private methods
+
+ _calculateRecentEntryPoints() {
+ const oldestVisitAllowed = Date.now() - MS_PER_DAY * this._ttl;
+
+ const recentEntryPoints = Object.entries(this._history).reduce(
+ (acc, [entryPoint, { lastVisit }]) => {
+ if (lastVisit > oldestVisitAllowed) {
+ acc.push(entryPoint);
+ }
+
+ return acc;
+ },
+ [],
+ );
+
+ this._recentEntryPoints = new Set([...ESSENTIAL_ENTRY_POINTS, ...recentEntryPoints]);
+ }
+}
+
+module.exports = {
+ History,
+ HistoryWithTTL,
+};
diff --git a/config/helpers/incremental_webpack_compiler/index.js b/config/helpers/incremental_webpack_compiler/index.js
new file mode 100644
index 00000000000..81826607490
--- /dev/null
+++ b/config/helpers/incremental_webpack_compiler/index.js
@@ -0,0 +1,17 @@
+const { NoopCompiler, HistoryOnlyCompiler, IncrementalWebpackCompiler } = require('./compiler');
+const log = require('./log');
+
+module.exports = (recordHistory, enabled, historyFilePath, ttl) => {
+ if (!recordHistory) {
+ log(`Status – disabled`);
+ return new NoopCompiler();
+ }
+
+ if (enabled) {
+ log(`Status – enabled, ttl=${ttl}`);
+ return new IncrementalWebpackCompiler(historyFilePath, ttl);
+ }
+
+ log(`Status – history-only`);
+ return new HistoryOnlyCompiler(historyFilePath);
+};
diff --git a/config/helpers/incremental_webpack_compiler/log.js b/config/helpers/incremental_webpack_compiler/log.js
new file mode 100644
index 00000000000..6336cb0a78b
--- /dev/null
+++ b/config/helpers/incremental_webpack_compiler/log.js
@@ -0,0 +1,3 @@
+const log = (msg, ...rest) => console.log(`IncrementalWebpackCompiler: ${msg}`, ...rest);
+
+module.exports = log;