252 lines
8.2 KiB
TypeScript
252 lines
8.2 KiB
TypeScript
import { appTools, defineConfig } from "@modern-js/app-tools";
|
|
import { moduleFederationPlugin } from "@module-federation/modern-js";
|
|
|
|
const buildTarget = process.env["BUILD_TARGET"];
|
|
const isRemote = buildTarget === "remote";
|
|
const plugins = [
|
|
appTools({ bundler: "rspack" }),
|
|
...(isRemote ? [moduleFederationPlugin()] : []),
|
|
];
|
|
|
|
// Runtime env values that must reach the client bundle. Rspack resolves
|
|
// `process.env` at BUILD time to an empty-ish polyfill in the browser, so
|
|
// any value we want per-deployment (e.g. MAP_TILE_URL pointing at a tile
|
|
// service that the local ingress doesn't proxy) has to be injected into
|
|
// the HTML as a window global. getEnv() picks up window.__ENV__ on the
|
|
// client. Evaluated once at Modern.js build time → pod env → HTML.
|
|
//
|
|
// IMPORTANT: Rspack's HTML plugin preprocesses <script> children through
|
|
// a template engine that eats '{token}' pairs AND decodes \u007B / \u007D
|
|
// Unicode escapes before running the engine, so neither raw braces nor
|
|
// escaped braces survive. The Leaflet tile template literally contains
|
|
// '{z}/{x}/{y}', so we serialize the whole payload to base64 and decode
|
|
// it in the browser at runtime. Base64 output is A-Z/a-z/0-9/+/=/ — no
|
|
// braces for the template engine to grab.
|
|
const PUBLIC_ENV_KEYS = [
|
|
"MAP_TILE_URL",
|
|
"API_BASE_URL",
|
|
"SIGNALR_HUB_URL",
|
|
"REFRESH_PAUSE_MIN",
|
|
"REFRESH_STOP_MIN",
|
|
] as const;
|
|
const PUBLIC_ENV: Record<string, string> = {};
|
|
for (const k of PUBLIC_ENV_KEYS) {
|
|
const v = process.env[k];
|
|
if (typeof v === "string" && v.length > 0) PUBLIC_ENV[k] = v;
|
|
}
|
|
const publicEnvB64 = Buffer.from(
|
|
JSON.stringify(PUBLIC_ENV),
|
|
"utf8",
|
|
).toString("base64");
|
|
// Pure-expression form — NO '{' or '}' anywhere in the source. The
|
|
// base64 payload round-trips `{…}` without exposing braces to the
|
|
// Rspack HTML plugin. `Object.create(null)` replaces an empty `{}`.
|
|
const PUBLIC_ENV_SCRIPT =
|
|
`window.__ENV__=Object.assign(window.__ENV__||Object.create(null),JSON.parse(atob("${publicEnvB64}")));`;
|
|
const SUPPRESS_AEROFLOT_LOADER_WARNINGS_SCRIPT = `
|
|
window.__AFL_SHELL_CONSOLE_WARN__=window.__AFL_SHELL_CONSOLE_WARN__||console.warn.bind(console);
|
|
window.__AFL_SHELL_CONSOLE_ERROR__=window.__AFL_SHELL_CONSOLE_ERROR__||console.error.bind(console);
|
|
window.__AFL_SHELL_SHOULD_SUPPRESS__=function(args){
|
|
return Array.prototype.some.call(args,function(value){
|
|
return String(value).indexOf("Cannot find module './afl-frontend-lib/locales/")!==-1;
|
|
});
|
|
};
|
|
console.warn=function(){
|
|
if(window.__AFL_SHELL_SHOULD_SUPPRESS__(arguments)){return;}
|
|
return window.__AFL_SHELL_CONSOLE_WARN__.apply(console,arguments);
|
|
};
|
|
console.error=function(){
|
|
if(window.__AFL_SHELL_SHOULD_SUPPRESS__(arguments)){return;}
|
|
return window.__AFL_SHELL_CONSOLE_ERROR__.apply(console,arguments);
|
|
};`;
|
|
const AEROFLOT_RUNTIME_CONFIG_SCRIPT = `
|
|
window["afl-frontend-runtime-config"]=window["afl-frontend-runtime-config"]||Object.create(null);
|
|
window["afl-frontend-runtime-config"].FRONTEND_PROXY=window.location.origin;
|
|
window["afl-frontend-runtime-config"].FRONTEND_BACKEND="https://www.aeroflot.ru";`;
|
|
const FIX_AEROFLOT_SHELL_LINKS_SCRIPT = `
|
|
window.__AFL_SHELL_REWRITE_LINKS__=function(){
|
|
var anchors=document.querySelectorAll("afl-component.header a[href],afl-component.footer a[href]");
|
|
Array.prototype.forEach.call(anchors,function(anchor){
|
|
var href=anchor.getAttribute("href")||"";
|
|
if(href.indexOf("/personal/login")===0||href.indexOf("/pkl/app/")===0){
|
|
anchor.setAttribute("href","https://www.aeroflot.ru"+href);
|
|
}
|
|
});
|
|
};
|
|
window.__AFL_SHELL_LINK_OBSERVER__=new MutationObserver(function(){
|
|
window.__AFL_SHELL_REWRITE_LINKS__();
|
|
});
|
|
window.__AFL_SHELL_LINK_OBSERVER__.observe(document.documentElement,{
|
|
childList:true,
|
|
subtree:true,
|
|
attributes:true,
|
|
attributeFilter:["href"]
|
|
});
|
|
window.addEventListener("DOMContentLoaded",function(){
|
|
window.__AFL_SHELL_REWRITE_LINKS__();
|
|
});`;
|
|
|
|
const modernCommand = process.argv.join(" ");
|
|
const npmLifecycleEvent = process.env["npm_lifecycle_event"] ?? "";
|
|
const isDevServer =
|
|
/\bdev\b/.test(modernCommand) || npmLifecycleEvent.includes("dev");
|
|
const shouldUseShellLoaderProxy =
|
|
process.env["AEROFLOT_SHELL_LOADER_PROXY"] === "1";
|
|
// Angular uses full `afl-component` loader assets in production
|
|
// `index.html`, but its local `index.dev.html` keeps only shell
|
|
// placeholders. `dev:full` exposes a same-origin static proxy so local
|
|
// Chrome can hydrate the placeholders without CORS failures.
|
|
const shouldLoadStandaloneShellLoader =
|
|
!isRemote && (!isDevServer || shouldUseShellLoaderProxy);
|
|
const standaloneShellLoaderMode = shouldLoadStandaloneShellLoader
|
|
? shouldUseShellLoaderProxy
|
|
? "proxy"
|
|
: "external"
|
|
: "placeholder";
|
|
const aeroflotStaticBase = shouldUseShellLoaderProxy
|
|
? "/frontend/static"
|
|
: "https://www.aeroflot.ru/frontend/static";
|
|
|
|
const standaloneShellTags = isRemote
|
|
? []
|
|
: [
|
|
...(shouldLoadStandaloneShellLoader
|
|
? [
|
|
...(shouldUseShellLoaderProxy
|
|
? [
|
|
{
|
|
tag: "script",
|
|
head: true,
|
|
append: true,
|
|
children: SUPPRESS_AEROFLOT_LOADER_WARNINGS_SCRIPT,
|
|
},
|
|
{
|
|
tag: "script",
|
|
head: true,
|
|
append: true,
|
|
children: AEROFLOT_RUNTIME_CONFIG_SCRIPT,
|
|
},
|
|
{
|
|
tag: "script",
|
|
head: true,
|
|
append: true,
|
|
children: FIX_AEROFLOT_SHELL_LINKS_SCRIPT,
|
|
},
|
|
]
|
|
: []),
|
|
{
|
|
tag: "link",
|
|
head: true,
|
|
append: true,
|
|
attrs: {
|
|
rel: "stylesheet",
|
|
href: `${aeroflotStaticBase}/css/afl-frontend-loader.bundle.css`,
|
|
},
|
|
},
|
|
{
|
|
tag: "script",
|
|
head: false,
|
|
append: true,
|
|
attrs: {
|
|
async: true,
|
|
src: `${aeroflotStaticBase}/js/afl-frontend-loader.bundle.js`,
|
|
},
|
|
},
|
|
]
|
|
: []),
|
|
{
|
|
tag: "meta",
|
|
head: true,
|
|
append: true,
|
|
attrs: {
|
|
name: "aeroflot-shell-loader",
|
|
content: standaloneShellLoaderMode,
|
|
},
|
|
},
|
|
];
|
|
|
|
export default defineConfig({
|
|
plugins,
|
|
source: {
|
|
entriesDir: "./src",
|
|
},
|
|
html: {
|
|
favicon: "./config/public/favicon.ico",
|
|
tags: [
|
|
// Inline script runs before the main bundle so client-side getEnv()
|
|
// sees the pod's env (read when the SSR server starts).
|
|
{
|
|
tag: "script",
|
|
head: true,
|
|
append: false,
|
|
children: PUBLIC_ENV_SCRIPT,
|
|
},
|
|
...standaloneShellTags,
|
|
{
|
|
tag: "link",
|
|
attrs: {
|
|
rel: "icon",
|
|
type: "image/png",
|
|
sizes: "32x32",
|
|
href: "/assets/img/favicon-32x32.png",
|
|
},
|
|
},
|
|
{
|
|
tag: "link",
|
|
attrs: {
|
|
rel: "icon",
|
|
type: "image/png",
|
|
sizes: "16x16",
|
|
href: "/assets/img/favicon-16x16.png",
|
|
},
|
|
},
|
|
{
|
|
tag: "link",
|
|
attrs: {
|
|
rel: "apple-touch-icon",
|
|
href: "/assets/img/favicon-touch.png",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
runtime: {
|
|
router: {
|
|
future: {
|
|
v7_startTransition: true,
|
|
v7_relativeSplatPath: true,
|
|
v7_fetcherPersist: true,
|
|
v7_normalizeFormMethod: true,
|
|
v7_partialHydration: true,
|
|
v7_skipActionErrorRevalidation: true,
|
|
},
|
|
},
|
|
},
|
|
server: {
|
|
ssr: {
|
|
mode: "stream",
|
|
},
|
|
},
|
|
tools: {
|
|
rspack(config) {
|
|
config.cache = false;
|
|
},
|
|
cssLoader: {
|
|
url: false,
|
|
},
|
|
// Explicit dev-server CORS headers. Silences the MF modern-js warning
|
|
// about empty devServer.headers (auto-assigning "*"). Production is
|
|
// unaffected — the real reverse proxy manages CORS.
|
|
devServer: {
|
|
headers: {
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Headers":
|
|
"Content-Type, Authorization, Accept, Accept-Language",
|
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
},
|
|
},
|
|
},
|
|
output: {
|
|
distPath: { root: isRemote ? "dist/remote" : "dist/standalone" },
|
|
},
|
|
});
|