aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2021-10-05 17:00:10 +0200
committersonartech <sonartech@sonarsource.com>2021-10-06 20:03:08 +0000
commitfea5cec5eb9b26cbf42f98f6ef79a8e01028af0e (patch)
tree9e1795ed93d3e64034859daa0204ca39f91a2c52 /server
parent3901540ab1ae1bf0f5c63b4ed7abbf3c75ef290b (diff)
downloadsonarqube-fea5cec5eb9b26cbf42f98f6ef79a8e01028af0e.tar.gz
sonarqube-fea5cec5eb9b26cbf42f98f6ef79a8e01028af0e.zip
[NO-JIRA] Fix frontend caching issue
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/config/esbuild-config.js54
-rw-r--r--server/sonar-web/config/esbuild-html-plugin.js60
-rw-r--r--server/sonar-web/config/indexHtmlTemplate.js (renamed from server/sonar-web/public/index.html)25
-rw-r--r--server/sonar-web/scripts/build.js2
-rw-r--r--server/sonar-web/scripts/start.js39
5 files changed, 154 insertions, 26 deletions
diff --git a/server/sonar-web/config/esbuild-config.js b/server/sonar-web/config/esbuild-config.js
index c1cec016ba5..8497d8978da 100644
--- a/server/sonar-web/config/esbuild-config.js
+++ b/server/sonar-web/config/esbuild-config.js
@@ -22,26 +22,12 @@ const postCssPlugin = require('esbuild-plugin-postcss2').default;
const postCssCalc = require('postcss-calc');
const postCssCustomProperties = require('postcss-custom-properties');
const documentationPlugin = require('./esbuild-documentation-plugin');
+const htmlPlugin = require('./esbuild-html-plugin');
+const htmlTemplate = require('./indexHtmlTemplate');
const { getCustomProperties } = require('./utils');
-module.exports = release => ({
- entryPoints: ['src/main/js/app/index.ts'],
- tsconfig: './tsconfig.json',
- external: ['/images/*'],
- loader: {
- '.png': 'dataurl',
- '.md': 'text'
- },
- define: {
- 'process.cwd': 'dummy_process_cwd'
- },
- inject: ['config/process-shim.js'],
- bundle: true,
- minify: release,
- sourcemap: true,
- target: ['chrome58', 'firefox57', 'safari11', 'edge18'],
- outfile: 'build/webapp/js/out.js',
- plugins: [
+module.exports = release => {
+ const plugins = [
postCssPlugin({
plugins: [
autoprefixer,
@@ -53,5 +39,33 @@ module.exports = release => ({
]
}),
documentationPlugin()
- ]
-});
+ ];
+
+ if (release) {
+ // Only create index.html from template when releasing
+ // The devserver will generate its own index file from the template
+ plugins.push(htmlPlugin());
+ }
+
+ return {
+ entryPoints: ['src/main/js/app/index.ts'],
+ tsconfig: './tsconfig.json',
+ external: ['/images/*'],
+ loader: {
+ '.png': 'dataurl',
+ '.md': 'text'
+ },
+ define: {
+ 'process.cwd': 'dummy_process_cwd'
+ },
+ inject: ['config/process-shim.js'],
+ bundle: true,
+ minify: release,
+ metafile: true,
+ sourcemap: true,
+ target: ['chrome58', 'firefox57', 'safari11', 'edge18'],
+ outdir: 'build/webapp/js',
+ entryNames: release ? 'out[hash]' : 'out',
+ plugins
+ };
+};
diff --git a/server/sonar-web/config/esbuild-html-plugin.js b/server/sonar-web/config/esbuild-html-plugin.js
new file mode 100644
index 00000000000..7ab0a9ff5f0
--- /dev/null
+++ b/server/sonar-web/config/esbuild-html-plugin.js
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+const fs = require('fs-extra');
+const path = require('path');
+const paths = require('./paths');
+const htmlTemplate = require('./indexHtmlTemplate');
+
+function extractHash(filename) {
+ const regexp = /out([\w]+)\./;
+ const result = filename.match(regexp);
+ if (!result) {
+ throw Error('filename format error: could not extract hash');
+ }
+
+ return result[1];
+}
+
+/*
+ * This plugin generates a index.html file from the template,
+ * injecting the right hash values to the imported js and css files
+ */
+module.exports = () => ({
+ name: 'html-plugin',
+ setup({ onEnd }) {
+ onEnd(result => {
+ const files = result.metafile.outputs;
+
+ let cssHash;
+ let jsHash;
+ for (const filename in files) {
+ if (filename.endsWith('css')) {
+ cssHash = extractHash(filename);
+ } else if (filename.endsWith('js')) {
+ jsHash = extractHash(filename);
+ }
+ }
+
+ const htmlContents = htmlTemplate(cssHash, jsHash);
+
+ fs.writeFile(path.join(paths.appBuild, 'index.html'), htmlContents);
+ });
+ }
+});
diff --git a/server/sonar-web/public/index.html b/server/sonar-web/config/indexHtmlTemplate.js
index 7c6fb6a4194..0a0695cfc79 100644
--- a/server/sonar-web/public/index.html
+++ b/server/sonar-web/config/indexHtmlTemplate.js
@@ -1,3 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+module.exports = (cssHash, jsHash) => `
<!DOCTYPE html>
<html lang="en">
@@ -20,7 +40,7 @@
<meta name="msapplication-TileImage" content="%WEB_CONTEXT%/mstile-512x512.png" />
<title>%INSTANCE%</title>
- <link rel="stylesheet" href="%WEB_CONTEXT%/js/out.css">
+ <link rel="stylesheet" href="%WEB_CONTEXT%/js/out${cssHash}.css">
</head>
<body>
@@ -38,7 +58,8 @@
window.official = %OFFICIAL%;
</script>
- <script type="module" src="%WEB_CONTEXT%/js/out.js"></script>
+ <script type="module" src="%WEB_CONTEXT%/js/out${jsHash}.js"></script>
</body>
</html>
+`;
diff --git a/server/sonar-web/scripts/build.js b/server/sonar-web/scripts/build.js
index 42d461449f9..d81c59adb0f 100644
--- a/server/sonar-web/scripts/build.js
+++ b/server/sonar-web/scripts/build.js
@@ -39,7 +39,7 @@ async function build() {
console.log(chalk.cyan.bold(`Creating ${release ? 'optimized' : 'fast'} production build...`));
console.log();
- await esbuild.build(getConfig(release));
+ await esbuild.build(getConfig(release)).catch(() => process.exit(1));
console.log(chalk.green.bold('Compiled successfully!'));
console.log(chalk.cyan(Math.round(performance.now() - start), 'ms'));
diff --git a/server/sonar-web/scripts/start.js b/server/sonar-web/scripts/start.js
index ceb9cc1cdb4..64fc06ca851 100644
--- a/server/sonar-web/scripts/start.js
+++ b/server/sonar-web/scripts/start.js
@@ -27,6 +27,10 @@ const http = require('http');
const httpProxy = require('http-proxy');
const getConfig = require('../config/esbuild-config');
const { getMessages } = require('./utils');
+const paths = require('../config/paths');
+
+const STATUS_OK = 200;
+const STATUS_ERROR = 500;
const port = process.env.PORT || 3000;
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
@@ -38,16 +42,39 @@ const config = getConfig(false);
function handleL10n(res) {
getMessages()
.then(messages => {
- res.writeHead(200, { 'Content-Type': 'application/json' });
+ res.writeHead(STATUS_OK, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ effectiveLocale: 'en', messages }));
})
.catch(e => {
console.error(e);
- res.writeHead(500);
+ res.writeHead(STATUS_ERROR);
res.end(e);
});
}
+function handleStaticFileRequest(req, res) {
+ fs.readFile(paths.appBuild + req.url, (err, data) => {
+ if (err) {
+ // Any unknown path should go to the index.html
+ const htmlTemplate = require('../config/indexHtmlTemplate');
+
+ // Replace hash placeholders as well as all the
+ // tags that are usually replaced by the server
+ const content = htmlTemplate('', '')
+ .replace(/%WEB_CONTEXT%/g, '')
+ .replace(/%SERVER_STATUS%/g, 'UP')
+ .replace(/%INSTANCE%/g, 'SonarQube')
+ .replace(/%OFFICIAL%/g, 'true');
+
+ res.writeHead(STATUS_OK);
+ res.end(content);
+ } else {
+ res.writeHead(STATUS_OK);
+ res.end(data);
+ }
+ });
+}
+
function run() {
console.log('starting...');
esbuild
@@ -83,7 +110,11 @@ function run() {
esbuildProxy.web(req, res);
} else if (req.url.match(/l10n\/index/)) {
handleL10n(res);
- } else {
+ } else if (
+ req.url.includes('api/') ||
+ req.url.includes('images/') ||
+ req.url.includes('static/')
+ ) {
proxy.web(
req,
res,
@@ -92,6 +123,8 @@ function run() {
},
e => console.error('req error', e)
);
+ } else {
+ handleStaticFileRequest(req, res);
}
})
.listen(port);