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,
]
}),
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
+ };
+};
--- /dev/null
+/*
+ * 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);
+ });
+ }
+});
--- /dev/null
+/*
+ * 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">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" charset="UTF-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <link rel="apple-touch-icon" href="%WEB_CONTEXT%/apple-touch-icon.png">
+ <link rel="apple-touch-icon" sizes="57x57" href="%WEB_CONTEXT%/apple-touch-icon-57x57.png">
+ <link rel="apple-touch-icon" sizes="60x60" href="%WEB_CONTEXT%/apple-touch-icon-60x60.png">
+ <link rel="apple-touch-icon" sizes="72x72" href="%WEB_CONTEXT%/apple-touch-icon-72x72.png">
+ <link rel="apple-touch-icon" sizes="76x76" href="%WEB_CONTEXT%/apple-touch-icon-76x76.png">
+ <link rel="apple-touch-icon" sizes="114x114" href="%WEB_CONTEXT%/apple-touch-icon-114x114.png">
+ <link rel="apple-touch-icon" sizes="120x120" href="%WEB_CONTEXT%/apple-touch-icon-120x120.png">
+ <link rel="apple-touch-icon" sizes="144x144" href="%WEB_CONTEXT%/apple-touch-icon-144x144.png">
+ <link rel="apple-touch-icon" sizes="152x152" href="%WEB_CONTEXT%/apple-touch-icon-152x152.png">
+ <link rel="apple-touch-icon" sizes="180x180" href="%WEB_CONTEXT%/apple-touch-icon-180x180.png">
+ <link rel="icon" type="image/x-icon" href="%WEB_CONTEXT%/favicon.ico">
+ <meta name="application-name" content="SonarQube" />
+ <meta name="msapplication-TileColor" content="#FFFFFF" />
+ <meta name="msapplication-TileImage" content="%WEB_CONTEXT%/mstile-512x512.png" />
+ <title>%INSTANCE%</title>
+
+ <link rel="stylesheet" href="%WEB_CONTEXT%/js/out${cssHash}.css">
+</head>
+
+<body>
+ <div id="content">
+ <div class="global-loading">
+ <i class="spinner global-loading-spinner"></i>
+ <span class="global-loading-text">Loading...</span>
+ </div>
+ </div>
+
+ <script>
+ window.baseUrl = '%WEB_CONTEXT%';
+ window.serverStatus = '%SERVER_STATUS%';
+ window.instance = '%INSTANCE%';
+ window.official = %OFFICIAL%;
+ </script>
+
+ <script type="module" src="%WEB_CONTEXT%/js/out${jsHash}.js"></script>
+</body>
+
+</html>
+`;
+++ /dev/null
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <link rel="apple-touch-icon" href="%WEB_CONTEXT%/apple-touch-icon.png">
- <link rel="apple-touch-icon" sizes="57x57" href="%WEB_CONTEXT%/apple-touch-icon-57x57.png">
- <link rel="apple-touch-icon" sizes="60x60" href="%WEB_CONTEXT%/apple-touch-icon-60x60.png">
- <link rel="apple-touch-icon" sizes="72x72" href="%WEB_CONTEXT%/apple-touch-icon-72x72.png">
- <link rel="apple-touch-icon" sizes="76x76" href="%WEB_CONTEXT%/apple-touch-icon-76x76.png">
- <link rel="apple-touch-icon" sizes="114x114" href="%WEB_CONTEXT%/apple-touch-icon-114x114.png">
- <link rel="apple-touch-icon" sizes="120x120" href="%WEB_CONTEXT%/apple-touch-icon-120x120.png">
- <link rel="apple-touch-icon" sizes="144x144" href="%WEB_CONTEXT%/apple-touch-icon-144x144.png">
- <link rel="apple-touch-icon" sizes="152x152" href="%WEB_CONTEXT%/apple-touch-icon-152x152.png">
- <link rel="apple-touch-icon" sizes="180x180" href="%WEB_CONTEXT%/apple-touch-icon-180x180.png">
- <link rel="icon" type="image/x-icon" href="%WEB_CONTEXT%/favicon.ico">
- <meta name="application-name" content="SonarQube" />
- <meta name="msapplication-TileColor" content="#FFFFFF" />
- <meta name="msapplication-TileImage" content="%WEB_CONTEXT%/mstile-512x512.png" />
- <title>%INSTANCE%</title>
-
- <link rel="stylesheet" href="%WEB_CONTEXT%/js/out.css">
-</head>
-
-<body>
- <div id="content">
- <div class="global-loading">
- <i class="spinner global-loading-spinner"></i>
- <span class="global-loading-text">Loading...</span>
- </div>
- </div>
-
- <script>
- window.baseUrl = '%WEB_CONTEXT%';
- window.serverStatus = '%SERVER_STATUS%';
- window.instance = '%INSTANCE%';
- window.official = %OFFICIAL%;
- </script>
-
- <script type="module" src="%WEB_CONTEXT%/js/out.js"></script>
-</body>
-
-</html>
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'));
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';
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
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,
},
e => console.error('req error', e)
);
+ } else {
+ handleStaticFileRequest(req, res);
}
})
.listen(port);