task(settings): Support CDN in fxa-settings

Because:
- Settings is not being served from a CDN

This Commit:
- Configures webpack so that it can build assets that will work with a CDN
- Updates docker build to produce static bundle for stage cdn
- Updates docker build to produce static bundle for prod cdn
- Makes a couple changes to target these new build assets in content server
- Uses the full hash in bundle names to reduce the chance of conflict

Note, that we currently do a string replacement on index.html to inject some config and a flow id. If we didn't do this, we could serve this as a SPA directly off the CDN which would be faster. This should be a future consideration.

To test what this would look like once deployed on stage / production, override the following in local.json config.

  "static_directory": "dist"
  "page_template_subdirectory": "dist"
  "proxy_settings": false
This commit is contained in:
dschom 2024-08-27 14:00:17 -07:00
Родитель b88e6fa4b4
Коммит 6d79b616ff
Не найден ключ, соответствующий данной подписи
9 изменённых файлов: 92 добавлений и 32 удалений

Просмотреть файл

@ -32,7 +32,7 @@ done
# `npx yarn` because `npm i -g yarn` needs sudo
npx yarn install
npx yarn gql:allowlist
NODE_OPTIONS="--max-old-space-size=7168" CHOKIDAR_USEPOLLING=true SKIP_PREFLIGHT_CHECK=true npx nx run-many -t build --all --verbose --skip-nx-cache
NODE_OPTIONS="--max-old-space-size=7168" CHOKIDAR_USEPOLLING=true SKIP_PREFLIGHT_CHECK=true BUILD_TARGETS=stage,prod,dev npx nx run-many -t build --all --verbose --skip-nx-cache
# This will reduce packages to only production dependencies
npx yarn workspaces focus --production --all

Просмотреть файл

@ -211,18 +211,13 @@ function makeApp() {
);
}
if (config.get('env') === 'production') {
app.get(settingsPath, modifySettingsStatic);
addNonSettingsRoutes(modifySettingsStatic);
app.get(settingsPath + '/*', modifySettingsStatic);
}
if (config.get('env') === 'development') {
if (config.get('proxy_settings')) {
app.use(settingsPath, createSettingsProxy);
addNonSettingsRoutes(createSettingsProxy);
} else {
app.get(settingsPath, modifySettingsStatic);
addNonSettingsRoutes(modifySettingsStatic);
app.get(settingsPath + '/*', modifySettingsStatic);
}
// it's a four-oh-four not found.

Просмотреть файл

@ -40,7 +40,9 @@
}
}
},
"proxy_settings": true,
"static_directory": "app",
"page_template_subdirectory": "src",
"subscriptions": {
"enabled": true
},

Просмотреть файл

@ -1,5 +1,6 @@
{
"env": "production",
"proxy_settings": false,
"static_directory": "dist",
"page_template_subdirectory": "dist",
"csp": {

Просмотреть файл

@ -9,21 +9,30 @@ const config = require('./configuration');
const FLOW_ID_KEY = config.get('flow_id_key');
const flowMetrics = require('./flow-metrics');
let settingsIndexFile;
const env = config.get('env');
const settingsIndexPath = join(
__dirname,
'..',
'..',
config.get('static_directory'),
'settings',
'index.html'
);
if (env !== 'development') {
settingsIndexFile = readFileSync(settingsIndexPath, {
encoding: 'utf-8',
});
let settingsIndexFile;
function getSettingsIndexFile() {
if (settingsIndexFile === undefined) {
const proxy_settings = config.get('proxy_settings');
if (!proxy_settings) {
const static_directory = config.get('static_directory');
const static_settings_directory = config.get('static_settings_directory');
const settingsIndexPath = join(
__dirname,
'..',
'..',
static_directory,
'settings',
static_settings_directory,
'index.html'
);
settingsIndexFile = readFileSync(settingsIndexPath, {
encoding: 'utf-8',
});
}
}
return settingsIndexFile;
}
const settingsConfig = {
@ -162,9 +171,14 @@ const createSettingsProxy = createProxyMiddleware({
// Modify the static settings page by replacing __SERVER_CONFIG__ with the config object
const modifySettingsStatic = function (req, res) {
const indexFile = getSettingsIndexFile();
if (indexFile === undefined) {
throw new Error('Could not locate settings index file.');
}
const flowEventData = flowMetrics.create(FLOW_ID_KEY);
return res.send(
swapBetaMeta(settingsIndexFile, {
swapBetaMeta(indexFile, {
__SERVER_CONFIG__: settingsConfig,
__FLOW_ID__: flowEventData.flowId,
__FLOW_BEGIN_TIME__: flowEventData.flowBeginTime,

Просмотреть файл

@ -642,7 +642,7 @@ const conf = (module.exports = convict({
doc: 'The root path of server-rendered page templates',
},
page_template_subdirectory: {
default: 'src',
default: 'dist',
doc: 'Subdirectory of page_template_root for server-rendered page templates',
env: 'PAGE_TEMPLATE_SUBDIRECTORY',
format: ['src', 'dist'],
@ -890,12 +890,24 @@ const conf = (module.exports = convict({
env: 'METRIC_PREFIX',
},
},
proxy_settings: {
default: false,
doc: 'Indicates if settings requests should proxy to fxa-settings. This should only be true for local development.',
env: 'PROXY_SETTINGS',
format: Boolean,
},
static_directory: {
default: 'app',
default: 'dist',
doc: 'Directory that static files are served from.',
env: 'STATIC_DIRECTORY',
format: String,
},
static_settings_directory: {
default: 'prod',
doc: 'Directory in fxa-settings build folder that contains the target output.',
env: 'STATIC_SETTINGS_DIRECTORY',
format: ['dev', 'stage', 'prod'],
},
static_max_age: {
default: '10 minutes',
doc: 'Cache max age for static assets, in ms',

Просмотреть файл

@ -220,11 +220,11 @@ module.exports = function (webpackEnv) {
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
? 'static/js/[name].[contenthash].js'
: isEnvDevelopment && 'static/js/bundle.js',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
? 'static/js/[name].[contenthash].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext]',
// webpack uses `publicPath` to determine where the app is being served from.

Просмотреть файл

@ -10,7 +10,10 @@
"build-css": "NODE_ENV=production tailwindcss -i ./src/styles/tailwind.css -o ./src/styles/tailwind.out.css --postcss",
"build-storybook": "NODE_ENV=production STORYBOOK_BUILD=1 yarn build-css && NODE_OPTIONS=--openssl-legacy-provider sb build && cp -r public/locales ./storybook-static/locales",
"build-l10n": "nx l10n-merge && nx l10n-bundle && nx l10n-merge-test",
"build-react": "SKIP_PREFLIGHT_CHECK=true INLINE_RUNTIME_CHUNK=false NODE_OPTIONS=--openssl-legacy-provider node scripts/build.js",
"build-react": "nx build-react-dev && nx build-react-stage && nx build-react-prod",
"build-react-dev": "SKIP_PREFLIGHT_CHECK=true INLINE_RUNTIME_CHUNK=false NODE_OPTIONS=--openssl-legacy-provider BUILD_PATH=build/dev node scripts/build.js",
"build-react-stage": "SKIP_PREFLIGHT_CHECK=true INLINE_RUNTIME_CHUNK=false NODE_OPTIONS=--openssl-legacy-provider BUILD_PATH=build/stage node scripts/build.js",
"build-react-prod": "SKIP_PREFLIGHT_CHECK=true INLINE_RUNTIME_CHUNK=false NODE_OPTIONS=--openssl-legacy-provider BUILD_PATH=build/prod node scripts/build.js",
"clean": "rimraf dist",
"compile": "tsc --noEmit",
"gql-extract": "persistgraphql src ../../configs/gql/allowlist/fxa-settings.json --js --extension=ts ",
@ -20,7 +23,7 @@
"l10n-merge-test": "yarn grunt merge-ftl:test",
"legal-prime": "yarn legal:clone fxa-settings",
"lint": "eslint . .storybook",
"start": "pm2 start pm2.config.js && yarn check:url localhost:3000/settings/static/js/bundle.js",
"start": "BUILD_PATH=build/dev pm2 start pm2.config.js && yarn check:url localhost:3000/settings/static/js/bundle.js",
"stop": "pm2 stop pm2.config.js",
"restart": "pm2 restart pm2.config.js",
"delete": "pm2 delete pm2.config.js",

Просмотреть файл

@ -1,6 +1,39 @@
// This file was created by react-scripts' (create-react-app) eject script.
'use strict';
// Here we determine which builds to create. Webpack will configure assets
// to use a specific URL. In our case, for stage and production builds, we
// want this be a CDN url, and for dev it can just be the default relative
// path.
//
// This following is controlled by setting the BUILD_TARGETS env. By default
// we build 'dev', but if you wanted to build stage and prod assets then
// you'd do this: BUILD_TARGTES=stage,prod yarn build
const buildTargets = (process.env.BUILD_TARGETS || 'dev').split(',');
const buildDirTarget = (process.env.BUILD_PATH || 'build/dev').replace(
'build/',
''
);
if (!buildTargets.includes(buildDirTarget)) {
console.log(`Skipping ${buildDirTarget} build.`);
return;
}
switch (buildDirTarget) {
case 'prod':
process.env.PUBLIC_URL = 'https://accounts-cdn.moz.aws.net';
break;
case 'stage':
process.env.PUBLIC_URL = 'https://accounts-cdn.stage.moz.aws.net';
break;
default:
// This is for local development, and will result in everything being relative.
process.env.PUBLIC_URL = undefined;
break;
}
console.log(
`Building outputs for: ${buildDirTarget} that will be hosted at ${
process.env.PUBLIC_URL || '/'
}`
);
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';