mirror of
https://github.com/HSLdevcom/digitransit-ui
synced 2025-07-05 08:00:35 +02:00
226 lines
6.4 KiB
JavaScript
226 lines
6.4 KiB
JavaScript
// Libraries
|
|
import serialize from 'serialize-javascript';
|
|
import polyfillLibrary from 'polyfill-library';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import LRU from 'lru-cache';
|
|
|
|
// configuration
|
|
import { getConfiguration } from './config';
|
|
import { getAnalyticsInitCode } from './util/analyticsUtils';
|
|
|
|
// Look up paths for various asset files
|
|
const appRoot = `${process.cwd()}/`;
|
|
|
|
// cached assets
|
|
const polyfillls = new LRU(200);
|
|
|
|
let assets;
|
|
let mainAssets;
|
|
let manifest;
|
|
|
|
if (process.env.NODE_ENV !== 'development') {
|
|
// eslint-disable-next-line global-require, import/no-unresolved
|
|
assets = require('../manifest.json');
|
|
// eslint-disable-next-line global-require, import/no-unresolved
|
|
mainAssets = require('../stats.json').entrypoints.main.assets.filter(
|
|
asset => !asset.endsWith('.map'),
|
|
);
|
|
|
|
const manifestFiles = mainAssets.filter(asset =>
|
|
asset.startsWith('js/runtime'),
|
|
);
|
|
|
|
manifest = manifestFiles
|
|
.map(manifestFile =>
|
|
fs.readFileSync(path.join(appRoot, '_static', manifestFile)),
|
|
)
|
|
.join('\n');
|
|
|
|
mainAssets = mainAssets.filter(asset => !manifestFiles.includes(asset));
|
|
}
|
|
|
|
function getPolyfills(userAgent, config) {
|
|
// Do not trust Samsung, LG
|
|
// see https://digitransit.atlassian.net/browse/DT-360 and DT-445
|
|
// Also https://github.com/Financial-Times/polyfill-service/issues/727
|
|
if (
|
|
!userAgent ||
|
|
/(IEMobile|LG-|GT-|SM-|SamsungBrowser|Google Page Speed Insights)/.test(
|
|
userAgent,
|
|
)
|
|
) {
|
|
userAgent = ''; // eslint-disable-line no-param-reassign
|
|
}
|
|
|
|
const normalizedUA = polyfillLibrary.normalizeUserAgent(userAgent);
|
|
let polyfill = polyfillls.get(normalizedUA);
|
|
|
|
if (polyfill) {
|
|
return polyfill;
|
|
}
|
|
|
|
const features = {
|
|
'caniuse:console-basic': { flags: ['gated'] },
|
|
default: { flags: ['gated'] },
|
|
es5: { flags: ['gated'] },
|
|
es6: { flags: ['gated'] },
|
|
es7: { flags: ['gated'] },
|
|
es2017: { flags: ['gated'] },
|
|
fetch: { flags: ['gated'] },
|
|
Intl: { flags: ['gated'] },
|
|
'Object.assign': { flags: ['gated'] },
|
|
matchMedia: { flags: ['gated'] },
|
|
};
|
|
|
|
config.availableLanguages.forEach(language => {
|
|
features[`Intl.~locale.${language}`] = {
|
|
flags: ['gated'],
|
|
};
|
|
});
|
|
|
|
polyfill = polyfillLibrary
|
|
.getPolyfillString({
|
|
uaString: userAgent,
|
|
features,
|
|
minify: process.env.NODE_ENV !== 'development',
|
|
unknown: 'polyfill',
|
|
})
|
|
.then(polyfills =>
|
|
// no sourcemaps for inlined js
|
|
polyfills.replace(/^\/\/# sourceMappingURL=.*$/gm, ''),
|
|
);
|
|
|
|
polyfillls.set(normalizedUA, polyfill);
|
|
return polyfill;
|
|
}
|
|
|
|
export default async function serve(req, res, next) {
|
|
try {
|
|
const config = getConfiguration(req);
|
|
const agent = req.headers['user-agent'];
|
|
|
|
// TODO: Move this to PreferencesStore
|
|
// 1. use locale from cookie (user selected) or default
|
|
let locale = req.cookies.lang || config.defaultLanguage;
|
|
|
|
if (config.availableLanguages.indexOf(locale) === -1) {
|
|
locale = config.defaultLanguage;
|
|
}
|
|
|
|
if (req.cookies.lang === undefined || req.cookies.lang !== locale) {
|
|
res.cookie('lang', locale);
|
|
}
|
|
|
|
const polyfills = await getPolyfills(agent, config);
|
|
|
|
const spriteName = config.sprites;
|
|
|
|
const ASSET_URL = process.env.ASSET_URL || config.APP_PATH;
|
|
|
|
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
res.write('<!doctype html>\n');
|
|
res.write(`<html lang="${locale}">\n`);
|
|
res.write('<head>\n');
|
|
|
|
// Write preload hints before doing anything else
|
|
if (process.env.NODE_ENV !== 'development') {
|
|
res.write(getAnalyticsInitCode(config, req));
|
|
|
|
const preloads = [
|
|
{ as: 'style', href: config.URL.FONT },
|
|
{
|
|
as: 'style',
|
|
href: `${ASSET_URL}/${assets[`${config.CONFIG}_theme.css`]}`,
|
|
crossorigin: true,
|
|
},
|
|
...mainAssets.map(asset => ({
|
|
as: asset.endsWith('.css') ? 'style' : 'script',
|
|
href: `${ASSET_URL}/${asset}`,
|
|
crossorigin: true,
|
|
})),
|
|
];
|
|
|
|
preloads.forEach(({ as, href, crossorigin }) =>
|
|
res.write(
|
|
`<link rel="preload" as="${as}" ${
|
|
crossorigin ? 'crossorigin' : ''
|
|
} href="${href}">\n`,
|
|
),
|
|
);
|
|
|
|
const preconnects = [config.URL.API_URL, config.URL.MAP_URL];
|
|
|
|
if (config.staticMessagesUrl) {
|
|
preconnects.push(config.staticMessagesUrl);
|
|
}
|
|
|
|
preconnects.forEach(href =>
|
|
res.write(`<link rel="preconnect" crossorigin href="${href}">\n`),
|
|
);
|
|
|
|
res.write(
|
|
`<link rel="stylesheet" type="text/css" crossorigin href="${ASSET_URL}/${
|
|
assets[`${config.CONFIG}_theme.css`]
|
|
}"/>\n`,
|
|
);
|
|
mainAssets
|
|
.filter(asset => asset.endsWith('.css'))
|
|
.forEach(asset =>
|
|
res.write(
|
|
`<link rel="stylesheet" type="text/css" crossorigin href="${ASSET_URL}/${asset}"/>\n`,
|
|
),
|
|
);
|
|
}
|
|
res.write(
|
|
`<link rel="stylesheet" type="text/css" href="${config.URL.FONT}"/>\n`,
|
|
);
|
|
|
|
res.write(`<script>\n${polyfills}\n</script>\n`);
|
|
|
|
res.write(`<script>\nwindow.config=${serialize(config)};\n</script>\n`);
|
|
|
|
res.write('</head>\n');
|
|
res.write('<body>\n');
|
|
|
|
if (process.env.NODE_ENV !== 'development') {
|
|
res.write('<script>\n');
|
|
res.write(`fetch('${ASSET_URL}/${assets[spriteName]}')
|
|
.then(function(response) {return response.text();}).then(function(blob) {
|
|
var div = document.createElement('div');
|
|
div.innerHTML = blob;
|
|
document.body.insertBefore(div, document.body.childNodes[0]);
|
|
});`);
|
|
res.write('</script>\n');
|
|
} else {
|
|
res.write('<div>\n');
|
|
res.write(fs.readFileSync(`${appRoot}_static/${spriteName}`).toString());
|
|
res.write('</div>\n');
|
|
}
|
|
|
|
res.write('<div id="app" />');
|
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
res.write('<script async src="/proxy/js/main.js"></script>\n');
|
|
} else {
|
|
res.write('<script>');
|
|
res.write(
|
|
manifest.replace(/\/\/# sourceMappingURL=/g, `$&${ASSET_URL}/js/`),
|
|
);
|
|
res.write('\n</script>\n');
|
|
res.write(`<script>window.ASSET_URL="${ASSET_URL}/"</script>\n`);
|
|
mainAssets
|
|
.filter(asset => !asset.endsWith('.css'))
|
|
.forEach(asset =>
|
|
res.write(
|
|
`<script src="${ASSET_URL}/${asset}" crossorigin defer></script>\n`,
|
|
),
|
|
);
|
|
}
|
|
res.write('</body>\n');
|
|
res.write('</html>\n');
|
|
res.end();
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|