@bleedingdev/modern-js-create 3.2.0-ultramodern.120 → 3.2.0-ultramodern.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -12
- package/dist/cjs/create-package-root.cjs +7 -9
- package/dist/cjs/index.cjs +74 -44
- package/dist/cjs/locale/en.cjs +6 -7
- package/dist/cjs/locale/zh.cjs +6 -7
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +337 -0
- package/dist/cjs/ultramodern-workspace/app-files.cjs +223 -0
- package/dist/cjs/ultramodern-workspace/contracts.cjs +836 -0
- package/dist/cjs/ultramodern-workspace/demo-components.cjs +422 -0
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +222 -0
- package/dist/cjs/ultramodern-workspace/effect-api.cjs +952 -0
- package/dist/cjs/ultramodern-workspace/fs-io.cjs +191 -0
- package/dist/cjs/ultramodern-workspace/index.cjs +48 -0
- package/dist/cjs/ultramodern-workspace/locales.cjs +173 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +487 -0
- package/dist/cjs/ultramodern-workspace/naming.cjs +161 -0
- package/dist/cjs/ultramodern-workspace/package-json.cjs +406 -0
- package/dist/cjs/ultramodern-workspace/package-source.cjs +59 -0
- package/dist/cjs/ultramodern-workspace/policy.cjs +248 -0
- package/dist/cjs/ultramodern-workspace/public-surface.cjs +268 -0
- package/dist/cjs/ultramodern-workspace/routes.cjs +375 -0
- package/dist/cjs/ultramodern-workspace/types.cjs +61 -0
- package/dist/cjs/ultramodern-workspace/versions.cjs +153 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +153 -0
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +175 -0
- package/dist/esm/create-package-root.js +7 -9
- package/dist/esm/index.js +72 -42
- package/dist/esm/locale/en.js +6 -7
- package/dist/esm/locale/zh.js +6 -7
- package/dist/esm/ultramodern-workspace/add-vertical.js +252 -0
- package/dist/esm/ultramodern-workspace/app-files.js +149 -0
- package/dist/esm/ultramodern-workspace/contracts.js +741 -0
- package/dist/esm/ultramodern-workspace/demo-components.js +363 -0
- package/dist/esm/ultramodern-workspace/descriptors.js +133 -0
- package/dist/esm/ultramodern-workspace/effect-api.js +854 -0
- package/dist/esm/ultramodern-workspace/fs-io.js +90 -0
- package/dist/esm/ultramodern-workspace/index.js +3 -0
- package/dist/esm/ultramodern-workspace/locales.js +122 -0
- package/dist/esm/ultramodern-workspace/module-federation.js +415 -0
- package/dist/esm/ultramodern-workspace/naming.js +71 -0
- package/dist/esm/ultramodern-workspace/package-json.js +338 -0
- package/dist/esm/ultramodern-workspace/package-source.js +21 -0
- package/dist/esm/ultramodern-workspace/policy.js +183 -0
- package/dist/esm/ultramodern-workspace/public-surface.js +183 -0
- package/dist/esm/ultramodern-workspace/routes.js +280 -0
- package/dist/esm/ultramodern-workspace/types.js +16 -0
- package/dist/esm/ultramodern-workspace/versions.js +34 -0
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +91 -0
- package/dist/esm/ultramodern-workspace/write-workspace.js +121 -0
- package/dist/esm-node/create-package-root.js +7 -9
- package/dist/esm-node/index.js +72 -42
- package/dist/esm-node/locale/en.js +6 -7
- package/dist/esm-node/locale/zh.js +6 -7
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +253 -0
- package/dist/esm-node/ultramodern-workspace/app-files.js +150 -0
- package/dist/esm-node/ultramodern-workspace/contracts.js +742 -0
- package/dist/esm-node/ultramodern-workspace/demo-components.js +364 -0
- package/dist/esm-node/ultramodern-workspace/descriptors.js +134 -0
- package/dist/esm-node/ultramodern-workspace/effect-api.js +855 -0
- package/dist/esm-node/ultramodern-workspace/fs-io.js +91 -0
- package/dist/esm-node/ultramodern-workspace/index.js +4 -0
- package/dist/esm-node/ultramodern-workspace/locales.js +123 -0
- package/dist/esm-node/ultramodern-workspace/module-federation.js +416 -0
- package/dist/esm-node/ultramodern-workspace/naming.js +72 -0
- package/dist/esm-node/ultramodern-workspace/package-json.js +339 -0
- package/dist/esm-node/ultramodern-workspace/package-source.js +22 -0
- package/dist/esm-node/ultramodern-workspace/policy.js +184 -0
- package/dist/esm-node/ultramodern-workspace/public-surface.js +184 -0
- package/dist/esm-node/ultramodern-workspace/routes.js +281 -0
- package/dist/esm-node/ultramodern-workspace/types.js +17 -0
- package/dist/esm-node/ultramodern-workspace/versions.js +35 -0
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +92 -0
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +122 -0
- package/dist/types/locale/en.d.ts +4 -5
- package/dist/types/locale/index.d.ts +8 -10
- package/dist/types/locale/zh.d.ts +4 -5
- package/dist/types/ultramodern-workspace/add-vertical.d.ts +19 -0
- package/dist/types/ultramodern-workspace/app-files.d.ts +14 -0
- package/dist/types/ultramodern-workspace/contracts.d.ts +21 -0
- package/dist/types/ultramodern-workspace/demo-components.d.ts +9 -0
- package/dist/types/ultramodern-workspace/descriptors.d.ts +39 -0
- package/dist/types/ultramodern-workspace/effect-api.d.ts +73 -0
- package/dist/types/ultramodern-workspace/fs-io.d.ts +18 -0
- package/dist/types/ultramodern-workspace/index.d.ts +4 -0
- package/dist/types/ultramodern-workspace/locales.d.ts +183 -0
- package/dist/types/ultramodern-workspace/module-federation.d.ts +16 -0
- package/dist/types/ultramodern-workspace/naming.d.ts +16 -0
- package/dist/types/ultramodern-workspace/package-json.d.ts +12 -0
- package/dist/types/ultramodern-workspace/package-source.d.ts +2 -0
- package/dist/types/ultramodern-workspace/policy.d.ts +60 -0
- package/dist/types/ultramodern-workspace/public-surface.d.ts +37 -0
- package/dist/types/ultramodern-workspace/routes.d.ts +25 -0
- package/dist/types/ultramodern-workspace/types.d.ts +95 -0
- package/dist/types/ultramodern-workspace/versions.d.ts +38 -0
- package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +10 -0
- package/dist/types/ultramodern-workspace/write-workspace.d.ts +4 -0
- package/package.json +4 -3
- package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +1 -4
- package/template-workspace/.mise.toml.handlebars +1 -0
- package/template-workspace/{AGENTS.md → AGENTS.md.handlebars} +12 -7
- package/template-workspace/README.md.handlebars +40 -24
- package/template-workspace/{pnpm-workspace.yaml → pnpm-workspace.yaml.handlebars} +2 -2
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +31 -51
- package/templates/app/shell-frame.tsx +49 -0
- package/templates/app/ultramodern-route-head.tsx.handlebars +142 -0
- package/templates/packages/shared-contracts-index.ts +466 -0
- package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +69 -0
- package/templates/workspace-scripts/check-ultramodern-i18n-boundaries.mjs +9 -0
- package/templates/workspace-scripts/generate-public-surface-assets.mjs +529 -0
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +125 -0
- package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +851 -0
- package/templates/workspace-scripts/ultramodern-performance-readiness.config.mjs +7 -0
- package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +223 -0
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +593 -0
- package/dist/cjs/ultramodern-workspace.cjs +0 -6797
- package/dist/esm/ultramodern-workspace.js +0 -6738
- package/dist/esm-node/ultramodern-workspace.js +0 -6739
- package/dist/types/ultramodern-workspace.d.ts +0 -29
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import node_crypto from "node:crypto";
|
|
2
|
+
import node_fs from "node:fs";
|
|
3
|
+
import node_path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { resolveCreatePackageRoot } from "../create-package-root.js";
|
|
6
|
+
import { normalizePath } from "./naming.js";
|
|
7
|
+
const fs_io_dirname = node_path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const createPackageRoot = resolveCreatePackageRoot(fs_io_dirname);
|
|
9
|
+
const workspaceTemplateDir = node_path.join(createPackageRoot, 'template-workspace');
|
|
10
|
+
const fileTemplatesDir = node_path.join(createPackageRoot, 'templates');
|
|
11
|
+
function readFileTemplate(relativePath) {
|
|
12
|
+
return node_fs.readFileSync(node_path.join(fileTemplatesDir, relativePath), 'utf-8');
|
|
13
|
+
}
|
|
14
|
+
function renderFileTemplate(relativePath, data) {
|
|
15
|
+
return renderTemplate(readFileTemplate(`${relativePath}.handlebars`), data);
|
|
16
|
+
}
|
|
17
|
+
function assertSafeRelativePath(relativePath) {
|
|
18
|
+
if (0 === relativePath.length || node_path.isAbsolute(relativePath) || relativePath.split(/[\\/]+/).includes('..')) throw new Error(`Unsafe workspace template path: ${relativePath}`);
|
|
19
|
+
}
|
|
20
|
+
function ensureInsideRoot(root, targetPath) {
|
|
21
|
+
const relativePath = node_path.relative(root, targetPath);
|
|
22
|
+
if (relativePath.startsWith('..') || node_path.isAbsolute(relativePath)) throw new Error(`Refusing to write outside workspace root: ${targetPath}`);
|
|
23
|
+
}
|
|
24
|
+
function writeFile(targetDir, relativePath, content) {
|
|
25
|
+
assertSafeRelativePath(relativePath);
|
|
26
|
+
const filePath = node_path.join(targetDir, relativePath);
|
|
27
|
+
ensureInsideRoot(targetDir, filePath);
|
|
28
|
+
if (node_fs.existsSync(filePath)) throw new Error(`Refusing to overwrite generated workspace file: ${relativePath}`);
|
|
29
|
+
node_fs.mkdirSync(node_path.dirname(filePath), {
|
|
30
|
+
recursive: true
|
|
31
|
+
});
|
|
32
|
+
node_fs.writeFileSync(filePath, content, 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
function writeFileReplacing(targetDir, relativePath, content) {
|
|
35
|
+
assertSafeRelativePath(relativePath);
|
|
36
|
+
const filePath = node_path.join(targetDir, relativePath);
|
|
37
|
+
ensureInsideRoot(targetDir, filePath);
|
|
38
|
+
node_fs.mkdirSync(node_path.dirname(filePath), {
|
|
39
|
+
recursive: true
|
|
40
|
+
});
|
|
41
|
+
node_fs.writeFileSync(filePath, content, 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
function writeJson(targetDir, relativePath, value) {
|
|
44
|
+
writeFile(targetDir, relativePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
45
|
+
}
|
|
46
|
+
function renderTemplate(template, data) {
|
|
47
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key)=>data[key] ?? match);
|
|
48
|
+
}
|
|
49
|
+
function collectTemplateFiles(dir) {
|
|
50
|
+
const files = [];
|
|
51
|
+
function collect(currentDir) {
|
|
52
|
+
for (const entry of node_fs.readdirSync(currentDir, {
|
|
53
|
+
withFileTypes: true
|
|
54
|
+
}).sort((a, b)=>a.name.localeCompare(b.name))){
|
|
55
|
+
const entryPath = node_path.join(currentDir, entry.name);
|
|
56
|
+
if (entry.isDirectory()) collect(entryPath);
|
|
57
|
+
else if (entry.isFile()) files.push(normalizePath(node_path.relative(dir, entryPath)));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
collect(dir);
|
|
61
|
+
return files;
|
|
62
|
+
}
|
|
63
|
+
function hashFile(filePath) {
|
|
64
|
+
return node_crypto.createHash('sha256').update(node_fs.readFileSync(filePath)).digest('hex');
|
|
65
|
+
}
|
|
66
|
+
function hashTemplateTree(dir) {
|
|
67
|
+
const hash = node_crypto.createHash('sha256');
|
|
68
|
+
for (const relativePath of collectTemplateFiles(dir)){
|
|
69
|
+
hash.update(relativePath);
|
|
70
|
+
hash.update('\0');
|
|
71
|
+
hash.update(hashFile(node_path.join(dir, relativePath)));
|
|
72
|
+
hash.update('\0');
|
|
73
|
+
}
|
|
74
|
+
return hash.digest('hex');
|
|
75
|
+
}
|
|
76
|
+
function copyRootTemplate(targetDir, data) {
|
|
77
|
+
for (const relativePath of collectTemplateFiles(workspaceTemplateDir)){
|
|
78
|
+
const sourcePath = node_path.join(workspaceTemplateDir, relativePath);
|
|
79
|
+
const outputPath = relativePath.replace(/\.handlebars$/, '');
|
|
80
|
+
const content = relativePath.endsWith('.handlebars') ? renderTemplate(node_fs.readFileSync(sourcePath, 'utf-8'), data) : node_fs.readFileSync(sourcePath, 'utf-8');
|
|
81
|
+
writeFile(targetDir, outputPath, content);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function readJsonFile(filePath) {
|
|
85
|
+
return JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
|
|
86
|
+
}
|
|
87
|
+
function writeJsonFile(filePath, value) {
|
|
88
|
+
node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
|
|
89
|
+
}
|
|
90
|
+
export { assertSafeRelativePath, collectTemplateFiles, copyRootTemplate, createPackageRoot, ensureInsideRoot, fileTemplatesDir, hashFile, hashTemplateTree, readFileTemplate, readJsonFile, renderFileTemplate, renderTemplate, workspaceTemplateDir, writeFile, writeFileReplacing, writeJson, writeJsonFile };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
const commonLocaleMessages = {
|
|
2
|
+
cs: {
|
|
3
|
+
language: {
|
|
4
|
+
cs: 'Čeština',
|
|
5
|
+
en: 'Angličtina',
|
|
6
|
+
switcher: 'Jazyk'
|
|
7
|
+
},
|
|
8
|
+
routes: {
|
|
9
|
+
home: 'Domů'
|
|
10
|
+
},
|
|
11
|
+
seo: {
|
|
12
|
+
description: 'Route-owned UltraModern plocha s lokalizovaným SSR a frameworkem řízenými public metadata.'
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
en: {
|
|
16
|
+
language: {
|
|
17
|
+
cs: 'Czech',
|
|
18
|
+
en: 'English',
|
|
19
|
+
switcher: 'Language'
|
|
20
|
+
},
|
|
21
|
+
routes: {
|
|
22
|
+
home: 'Home'
|
|
23
|
+
},
|
|
24
|
+
seo: {
|
|
25
|
+
description: 'Route-owned UltraModern surface with localized SSR and framework-owned public metadata.'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const generatedLocaleResources = {
|
|
30
|
+
cs: {
|
|
31
|
+
shell: {
|
|
32
|
+
boundaries: {
|
|
33
|
+
toggle: 'zobrazit hranice týmů'
|
|
34
|
+
},
|
|
35
|
+
hero: {
|
|
36
|
+
cardOne: 'Přidejte první business vertical příkazem create <domain> --vertical, až ho opravdu potřebujete.',
|
|
37
|
+
cardOneKicker: 'Verticaly',
|
|
38
|
+
cardTwo: 'Plný markup, styly a lokalizovaný obsah se vykreslí před hydratací.',
|
|
39
|
+
cardTwoKicker: 'Vykreslení',
|
|
40
|
+
empty: 'Zatím nejsou připojené žádné MicroVerticaly.',
|
|
41
|
+
eyebrow: 'Shell SuperApp starter',
|
|
42
|
+
lede: 'Začněte s produkčně připraveným shellem. MicroVerticaly přidávejte až podle skutečných business domén.',
|
|
43
|
+
primary: 'Shell je připraven',
|
|
44
|
+
secondary: 'Přidejte vertical, až bude potřeba'
|
|
45
|
+
},
|
|
46
|
+
language: commonLocaleMessages.cs.language,
|
|
47
|
+
remoteUnavailable: 'Remote vertical je nedostupný',
|
|
48
|
+
remotes: {},
|
|
49
|
+
routes: {
|
|
50
|
+
home: commonLocaleMessages.cs.routes.home
|
|
51
|
+
},
|
|
52
|
+
seo: {
|
|
53
|
+
description: 'UltraModern shell SuperApp s lokalizovaným SSR, Module Federation a frameworkem řízenými public metadata.'
|
|
54
|
+
},
|
|
55
|
+
title: 'UltraModern Workspace'
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
en: {
|
|
59
|
+
shell: {
|
|
60
|
+
boundaries: {
|
|
61
|
+
toggle: 'show team boundaries'
|
|
62
|
+
},
|
|
63
|
+
hero: {
|
|
64
|
+
cardOne: 'Add the first business vertical with create <domain> --vertical when the product needs one.',
|
|
65
|
+
cardOneKicker: 'Verticals',
|
|
66
|
+
cardTwo: 'Full page markup, styles, and localized content render before hydration.',
|
|
67
|
+
cardTwoKicker: 'Rendering',
|
|
68
|
+
empty: 'No MicroVerticals are connected yet.',
|
|
69
|
+
eyebrow: 'Shell SuperApp starter',
|
|
70
|
+
lede: 'Start with a production-ready shell. Add MicroVerticals later for real business domains.',
|
|
71
|
+
primary: 'Shell ready',
|
|
72
|
+
secondary: 'Add a vertical when needed'
|
|
73
|
+
},
|
|
74
|
+
language: commonLocaleMessages.en.language,
|
|
75
|
+
remoteUnavailable: 'Remote vertical unavailable',
|
|
76
|
+
remotes: {},
|
|
77
|
+
routes: {
|
|
78
|
+
home: commonLocaleMessages.en.routes.home
|
|
79
|
+
},
|
|
80
|
+
seo: {
|
|
81
|
+
description: 'UltraModern shell SuperApp with localized SSR, Module Federation, and framework-owned public metadata.'
|
|
82
|
+
},
|
|
83
|
+
title: 'UltraModern Workspace'
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const verticalLocaleCopy = {
|
|
88
|
+
cs: {
|
|
89
|
+
federatedSurface: 'Federovaná plocha vlastněná tímto verticalem.',
|
|
90
|
+
remoteUnavailable: 'Remote vertical je nedostupný',
|
|
91
|
+
routeSurface: 'Routovaná plocha vlastněná tímto verticalem.',
|
|
92
|
+
widgetBody: 'Vlastní routovanou plochu verticalu.'
|
|
93
|
+
},
|
|
94
|
+
en: {
|
|
95
|
+
federatedSurface: 'Federated surface owned by this vertical.',
|
|
96
|
+
remoteUnavailable: 'Remote vertical unavailable',
|
|
97
|
+
routeSurface: 'Route surface owned by this vertical.',
|
|
98
|
+
widgetBody: 'Owns a vertical route surface.'
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const createFallbackLocaleMessages = (app, language)=>({
|
|
102
|
+
...commonLocaleMessages[language],
|
|
103
|
+
federatedSurface: verticalLocaleCopy[language].federatedSurface,
|
|
104
|
+
remoteUnavailable: verticalLocaleCopy[language].remoteUnavailable,
|
|
105
|
+
role: app.domain ?? app.kind,
|
|
106
|
+
routeSurface: verticalLocaleCopy[language].routeSurface,
|
|
107
|
+
title: app.displayName,
|
|
108
|
+
widgetBody: verticalLocaleCopy[language].widgetBody
|
|
109
|
+
});
|
|
110
|
+
function createAppLocaleMessages(app, language) {
|
|
111
|
+
const domain = app.domain ?? app.id;
|
|
112
|
+
const messageKey = 'shell' === app.kind ? 'shell' : domain;
|
|
113
|
+
const messages = 'shell' === app.kind ? generatedLocaleResources[language].shell : createFallbackLocaleMessages(app, language);
|
|
114
|
+
return {
|
|
115
|
+
[messageKey]: messages
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function createAppPublicLocaleMessages(app, language, remotes = []) {
|
|
119
|
+
if ('shell' !== app.kind) return createAppLocaleMessages(app, language);
|
|
120
|
+
return Object.assign({}, createAppLocaleMessages(app, language), ...remotes.map((remote)=>createAppLocaleMessages(remote, language)));
|
|
121
|
+
}
|
|
122
|
+
export { commonLocaleMessages, createAppLocaleMessages, createAppPublicLocaleMessages, createFallbackLocaleMessages, generatedLocaleResources };
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import node_crypto from "node:crypto";
|
|
2
|
+
import { appHasEffectApi, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createRemoteManifestEnv, effectApiPrefix, remoteDependencyAlias, resolveRemoteRefs, shellApp } from "./descriptors.js";
|
|
3
|
+
import { createRspackChunkLoadingGlobal, createRspackUniqueName, packageName } from "./naming.js";
|
|
4
|
+
import { createCloudflareSecurityContract, formatTsJsonValue } from "./policy.js";
|
|
5
|
+
import { sortJsonValue } from "./types.js";
|
|
6
|
+
import { CLOUDFLARE_COMPATIBILITY_DATE } from "./versions.js";
|
|
7
|
+
function createAppModernConfig(scope, app) {
|
|
8
|
+
const bffImport = appHasEffectApi(app) ? "import { bffPlugin } from '@modern-js/plugin-bff';\n" : '';
|
|
9
|
+
const bffConfig = appHasEffectApi(app) ? ` bff: {
|
|
10
|
+
effect: {
|
|
11
|
+
entry: './api/effect/index',
|
|
12
|
+
openapi: {
|
|
13
|
+
path: '/openapi.json',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
prefix: '${effectApiPrefix(app)}',
|
|
17
|
+
runtimeFramework: 'effect',
|
|
18
|
+
},
|
|
19
|
+
` : '';
|
|
20
|
+
const bffPluginEntry = appHasEffectApi(app) ? ' bffPlugin(),\n' : '';
|
|
21
|
+
return `// @effect-diagnostics processEnv:off
|
|
22
|
+
import {
|
|
23
|
+
appTools,
|
|
24
|
+
defineConfig,
|
|
25
|
+
presetUltramodern,
|
|
26
|
+
} from '@modern-js/app-tools';
|
|
27
|
+
${bffImport}import { i18nPlugin } from '@modern-js/plugin-i18n';
|
|
28
|
+
import { tanstackRouterPlugin } from '@modern-js/plugin-tanstack';
|
|
29
|
+
import { moduleFederationPlugin } from '@module-federation/modern-js-v3';
|
|
30
|
+
import { withZephyr as withZephyrRspack } from 'zephyr-rspack-plugin';
|
|
31
|
+
import { ultramodernLocalisedUrls } from './src/routes/ultramodern-route-metadata';
|
|
32
|
+
|
|
33
|
+
type ZephyrRspackConfig = Parameters<ReturnType<typeof withZephyrRspack>>[0];
|
|
34
|
+
|
|
35
|
+
const zephyrEnabled = process.env['ULTRAMODERN_ZEPHYR'] !== 'false';
|
|
36
|
+
const cloudflareDeployEnabled =
|
|
37
|
+
process.env['MODERNJS_DEPLOY'] === 'cloudflare';
|
|
38
|
+
|
|
39
|
+
const zephyrRspackPlugin = () => ({
|
|
40
|
+
name: 'ultramodern-zephyr-rspack-plugin',
|
|
41
|
+
pre: ['@modern-js/plugin-module-federation-config'],
|
|
42
|
+
setup(api: {
|
|
43
|
+
modifyRspackConfig: (
|
|
44
|
+
handler: (
|
|
45
|
+
config: ZephyrRspackConfig,
|
|
46
|
+
) => ZephyrRspackConfig | Promise<ZephyrRspackConfig>,
|
|
47
|
+
) => void;
|
|
48
|
+
}) {
|
|
49
|
+
if (!zephyrEnabled) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
api.modifyRspackConfig(config => withZephyrRspack()(config));
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const appId = '${app.id}';
|
|
57
|
+
const cloudflareWorkerName = '${createCloudflareWorkerName(scope, app)}';
|
|
58
|
+
const port = Number(process.env['${app.portEnv}'] ?? ${app.port});
|
|
59
|
+
const envValue = (name: string) => {
|
|
60
|
+
const value = process.env[name]?.trim();
|
|
61
|
+
return value !== undefined && value.length > 0 ? value : undefined;
|
|
62
|
+
};
|
|
63
|
+
const configuredSiteUrl = envValue('MODERN_PUBLIC_SITE_URL');
|
|
64
|
+
const configuredCloudflareUrl = envValue('${createCloudflarePublicUrlEnv(app)}');
|
|
65
|
+
const configuredUltramodernAssetPrefix = envValue('ULTRAMODERN_ASSET_PREFIX');
|
|
66
|
+
const configuredModernAssetPrefix = envValue('MODERN_ASSET_PREFIX');
|
|
67
|
+
const cloudflareWorkersDevSubdomain = envValue(
|
|
68
|
+
'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN',
|
|
69
|
+
);
|
|
70
|
+
const inferredCloudflareUrl =
|
|
71
|
+
cloudflareDeployEnabled && cloudflareWorkersDevSubdomain !== undefined
|
|
72
|
+
? \`https://\${cloudflareWorkerName}.\${cloudflareWorkersDevSubdomain}.workers.dev\`
|
|
73
|
+
: undefined;
|
|
74
|
+
// Site origin (SEO: canonical/hreflang URLs) prefers the site-wide public URL;
|
|
75
|
+
// the per-app deployment URL only fills in when no site origin is configured.
|
|
76
|
+
const siteUrl =
|
|
77
|
+
configuredSiteUrl ||
|
|
78
|
+
configuredCloudflareUrl ||
|
|
79
|
+
inferredCloudflareUrl ||
|
|
80
|
+
\`http://localhost:\${port}\`;
|
|
81
|
+
// Asset loading is intentionally independent from the canonical site URL and
|
|
82
|
+
// deployment public URL. Without an explicit asset prefix, assets stay
|
|
83
|
+
// origin-relative so self-hosted apps, tunnels, and reverse proxies never leak
|
|
84
|
+
// localhost URLs into production HTML.
|
|
85
|
+
const assetPrefix =
|
|
86
|
+
configuredModernAssetPrefix || configuredUltramodernAssetPrefix || '/';
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
cloudflareDeployEnabled &&
|
|
90
|
+
process.env['ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS'] === 'true' &&
|
|
91
|
+
configuredCloudflareUrl === undefined &&
|
|
92
|
+
configuredSiteUrl === undefined &&
|
|
93
|
+
inferredCloudflareUrl === undefined
|
|
94
|
+
) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
\`Cloudflare deploy for \${appId} needs ${createCloudflarePublicUrlEnv(app)}, MODERN_PUBLIC_SITE_URL, or ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN.\`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default defineConfig(
|
|
101
|
+
presetUltramodern(
|
|
102
|
+
{
|
|
103
|
+
${bffConfig} ...(cloudflareDeployEnabled
|
|
104
|
+
? {
|
|
105
|
+
deploy: {
|
|
106
|
+
worker: {
|
|
107
|
+
compatibilityDate: '${CLOUDFLARE_COMPATIBILITY_DATE}',
|
|
108
|
+
name: cloudflareWorkerName,
|
|
109
|
+
security: ${formatTsJsonValue(sortJsonValue(createCloudflareSecurityContract()), 16)},
|
|
110
|
+
ssr: true,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
: {}),
|
|
115
|
+
dev: {
|
|
116
|
+
// Keep dev assets origin-relative too; the default absolute
|
|
117
|
+
// http://localhost:<port> prefix breaks pages served through tunnels.
|
|
118
|
+
assetPrefix: '/',
|
|
119
|
+
},
|
|
120
|
+
html: {
|
|
121
|
+
outputStructure: 'flat',
|
|
122
|
+
},
|
|
123
|
+
output: {
|
|
124
|
+
assetPrefix,
|
|
125
|
+
disableTsChecker: true,
|
|
126
|
+
distPath: {
|
|
127
|
+
html: './',
|
|
128
|
+
},
|
|
129
|
+
polyfill: 'off',
|
|
130
|
+
splitRouteChunks: true,
|
|
131
|
+
},
|
|
132
|
+
performance: {
|
|
133
|
+
rsdoctor: {
|
|
134
|
+
disableClientServer: true,
|
|
135
|
+
enabled: process.env['ULTRAMODERN_RSDOCTOR'] === 'true',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
plugins: [
|
|
139
|
+
appTools(),
|
|
140
|
+
tanstackRouterPlugin(),
|
|
141
|
+
i18nPlugin({
|
|
142
|
+
backend: {
|
|
143
|
+
enabled: true,
|
|
144
|
+
loadPath: '/locales/{{lng}}/{{ns}}.json',
|
|
145
|
+
},
|
|
146
|
+
localeDetection: {
|
|
147
|
+
fallbackLanguage: 'en',
|
|
148
|
+
ignoreRedirectRoutes: [
|
|
149
|
+
'/@mf-types',
|
|
150
|
+
'/assets',
|
|
151
|
+
'/bundles',
|
|
152
|
+
'${effectApiPrefix(app)}',
|
|
153
|
+
'/locales',
|
|
154
|
+
'/mf-manifest.json',
|
|
155
|
+
'/mf-stats.json',
|
|
156
|
+
'/remoteEntry.js',
|
|
157
|
+
'/robots.txt',
|
|
158
|
+
'/site.webmanifest',
|
|
159
|
+
'/sitemap.xml',
|
|
160
|
+
'/static',
|
|
161
|
+
'/zephyr-manifest.json',
|
|
162
|
+
],
|
|
163
|
+
languages: ['en', 'cs'],
|
|
164
|
+
localePathRedirect: true,
|
|
165
|
+
localisedUrls: ultramodernLocalisedUrls as Record<string, Record<string, string>>,
|
|
166
|
+
},
|
|
167
|
+
reactI18next: false,
|
|
168
|
+
}),
|
|
169
|
+
${bffPluginEntry} moduleFederationPlugin(),
|
|
170
|
+
zephyrRspackPlugin(),
|
|
171
|
+
],
|
|
172
|
+
server: {
|
|
173
|
+
port,
|
|
174
|
+
publicDir: ['./locales', './assets'],
|
|
175
|
+
ssr: {
|
|
176
|
+
mode: 'string',
|
|
177
|
+
moduleFederationAppSSR: true,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
source: {
|
|
181
|
+
globalVars: {
|
|
182
|
+
ULTRAMODERN_SITE_URL: siteUrl,
|
|
183
|
+
},
|
|
184
|
+
mainEntryName: 'index',
|
|
185
|
+
},
|
|
186
|
+
tools: {
|
|
187
|
+
autoprefixer: {
|
|
188
|
+
overrideBrowserslist: ['defaults'],
|
|
189
|
+
},
|
|
190
|
+
bundlerChain: chain => {
|
|
191
|
+
chain.output
|
|
192
|
+
.uniqueName('${createRspackUniqueName(app)}')
|
|
193
|
+
.chunkLoadingGlobal('${createRspackChunkLoadingGlobal(app)}');
|
|
194
|
+
chain.ignoreWarnings([
|
|
195
|
+
{
|
|
196
|
+
message: /the request of a dependency is an expression/u,
|
|
197
|
+
module: /modern-js-plugin-i18n/u,
|
|
198
|
+
},
|
|
199
|
+
]);
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
appId,
|
|
205
|
+
enableBffRequestId: true,
|
|
206
|
+
enableModuleFederationSSR: true,
|
|
207
|
+
enableTelemetryExporters: true,
|
|
208
|
+
telemetryFailLoudStartup: false,
|
|
209
|
+
},
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
function createSharedModuleFederationConfig() {
|
|
215
|
+
return ` shared: {
|
|
216
|
+
'@modern-js/plugin-i18n/runtime': {
|
|
217
|
+
requiredVersion: pluginI18nVersion,
|
|
218
|
+
singleton: true,
|
|
219
|
+
treeShaking: false,
|
|
220
|
+
},
|
|
221
|
+
'@modern-js/plugin-tanstack/runtime': {
|
|
222
|
+
requiredVersion: pluginTanstackVersion,
|
|
223
|
+
singleton: true,
|
|
224
|
+
treeShaking: false,
|
|
225
|
+
},
|
|
226
|
+
'@modern-js/runtime': {
|
|
227
|
+
requiredVersion: runtimeVersion,
|
|
228
|
+
singleton: true,
|
|
229
|
+
treeShaking: false,
|
|
230
|
+
},
|
|
231
|
+
'@tanstack/react-router': {
|
|
232
|
+
requiredVersion: dependencies['@tanstack/react-router'],
|
|
233
|
+
singleton: true,
|
|
234
|
+
treeShaking: false,
|
|
235
|
+
},
|
|
236
|
+
react: {
|
|
237
|
+
requiredVersion: reactVersion,
|
|
238
|
+
singleton: true,
|
|
239
|
+
treeShaking: false,
|
|
240
|
+
},
|
|
241
|
+
'react-dom': {
|
|
242
|
+
requiredVersion: reactDomVersion,
|
|
243
|
+
singleton: true,
|
|
244
|
+
treeShaking: false,
|
|
245
|
+
},
|
|
246
|
+
'react-dom/client': {
|
|
247
|
+
requiredVersion: reactDomVersion,
|
|
248
|
+
singleton: true,
|
|
249
|
+
treeShaking: false,
|
|
250
|
+
},
|
|
251
|
+
}`;
|
|
252
|
+
}
|
|
253
|
+
function formatTsObjectLiteral(value) {
|
|
254
|
+
const entries = Object.entries(value).sort(([left], [right])=>left.localeCompare(right));
|
|
255
|
+
if (0 === entries.length) return '{}';
|
|
256
|
+
return `{
|
|
257
|
+
${entries.map(([key, entryValue])=>` '${key}': '${entryValue}',`).join('\n')}
|
|
258
|
+
}`;
|
|
259
|
+
}
|
|
260
|
+
function createModuleFederationRemoteUrlHelpers(app, remotes = []) {
|
|
261
|
+
if (0 === resolveRemoteRefs(app, remotes).length) return '';
|
|
262
|
+
return `const cloudflareDeployEnabled =
|
|
263
|
+
process.env['MODERNJS_DEPLOY'] === 'cloudflare';
|
|
264
|
+
const cloudflareWorkersDevSubdomain =
|
|
265
|
+
process.env['ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN']?.trim();
|
|
266
|
+
const requireCloudflarePublicUrls =
|
|
267
|
+
process.env['ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS'] === 'true';
|
|
268
|
+
|
|
269
|
+
const createRemoteManifestUrl = (options: {
|
|
270
|
+
manifestEnv: string;
|
|
271
|
+
mfName: string;
|
|
272
|
+
port: number;
|
|
273
|
+
publicUrlEnv: string;
|
|
274
|
+
workerName: string;
|
|
275
|
+
}) => {
|
|
276
|
+
const configuredManifest = process.env[options.manifestEnv]?.trim();
|
|
277
|
+
if (configuredManifest !== undefined && configuredManifest.length > 0) {
|
|
278
|
+
return configuredManifest;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const configuredPublicUrl = process.env[options.publicUrlEnv]?.trim();
|
|
282
|
+
if (configuredPublicUrl !== undefined && configuredPublicUrl.length > 0) {
|
|
283
|
+
return \`\${options.mfName}@\${configuredPublicUrl.replace(/\\/+$/u, '')}/mf-manifest.json\`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (cloudflareDeployEnabled && cloudflareWorkersDevSubdomain !== undefined) {
|
|
287
|
+
return \`\${options.mfName}@https://\${options.workerName}.\${cloudflareWorkersDevSubdomain}.workers.dev/mf-manifest.json\`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (cloudflareDeployEnabled && requireCloudflarePublicUrls) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
\`Cloudflare deploy needs \${options.publicUrlEnv}, \${options.manifestEnv}, or ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN for remote \${options.mfName}.\`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return \`\${options.mfName}@http://localhost:\${options.port}/mf-manifest.json\`;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
function createModuleFederationRemotesConfig(scope, app, remotes = []) {
|
|
302
|
+
const remoteEntries = resolveRemoteRefs(app, remotes).toSorted((left, right)=>remoteDependencyAlias(left).localeCompare(remoteDependencyAlias(right))).map((remote)=>{
|
|
303
|
+
const key = remoteDependencyAlias(remote);
|
|
304
|
+
return ` ${key}: createRemoteManifestUrl({
|
|
305
|
+
manifestEnv: '${createRemoteManifestEnv(remote)}',
|
|
306
|
+
mfName: '${remote.mfName}',
|
|
307
|
+
port: ${remote.port},
|
|
308
|
+
publicUrlEnv: '${createCloudflarePublicUrlEnv(remote)}',
|
|
309
|
+
workerName: '${createCloudflareWorkerName(scope, remote)}',
|
|
310
|
+
}),`;
|
|
311
|
+
}).join('\n');
|
|
312
|
+
if (!remoteEntries) return '';
|
|
313
|
+
return ` remotes: {
|
|
314
|
+
${remoteEntries}
|
|
315
|
+
},
|
|
316
|
+
`;
|
|
317
|
+
}
|
|
318
|
+
function createShellModuleFederationConfig(scope, remotes = []) {
|
|
319
|
+
const shellHost = {
|
|
320
|
+
...shellApp,
|
|
321
|
+
verticalRefs: remotes.map((remote)=>remote.id)
|
|
322
|
+
};
|
|
323
|
+
return `// @effect-diagnostics nodeBuiltinImport:off processEnv:off
|
|
324
|
+
import { createRequire } from 'node:module';
|
|
325
|
+
import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
|
|
326
|
+
import { dependencies } from './package.json';
|
|
327
|
+
|
|
328
|
+
const require = createRequire(import.meta.url);
|
|
329
|
+
const pluginI18nVersion = (require('@modern-js/plugin-i18n/package.json') as { version: string }).version;
|
|
330
|
+
const pluginTanstackVersion = (require('@modern-js/plugin-tanstack/package.json') as { version: string }).version;
|
|
331
|
+
const runtimeVersion = (require('@modern-js/runtime/package.json') as { version: string }).version;
|
|
332
|
+
const reactVersion = (require('react/package.json') as { version: string }).version;
|
|
333
|
+
const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
|
|
334
|
+
|
|
335
|
+
${createModuleFederationRemoteUrlHelpers(shellHost, remotes)}
|
|
336
|
+
export default createModuleFederationConfig({
|
|
337
|
+
bridge: {
|
|
338
|
+
enableBridgeRouter: false,
|
|
339
|
+
},
|
|
340
|
+
dev: {
|
|
341
|
+
disableDynamicRemoteTypeHints: true,
|
|
342
|
+
},
|
|
343
|
+
dts: {
|
|
344
|
+
displayErrorInTerminal: true,
|
|
345
|
+
generateTypes: {
|
|
346
|
+
compilerInstance: 'tsgo',
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
filename: 'remoteEntry.js',
|
|
350
|
+
name: '${shellApp.mfName}',
|
|
351
|
+
${createModuleFederationRemotesConfig(scope, shellHost, remotes)}${createSharedModuleFederationConfig()},
|
|
352
|
+
treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
|
|
353
|
+
});
|
|
354
|
+
`;
|
|
355
|
+
}
|
|
356
|
+
function createBuildMarker(scope, app) {
|
|
357
|
+
return node_crypto.createHash('sha256').update(`${scope}:${app.packageSuffix}:${app.id}:0.1.0`).digest('hex').slice(0, 16);
|
|
358
|
+
}
|
|
359
|
+
function createUltramodernBuildModule(scope, app) {
|
|
360
|
+
return `export const ultramodernVerticalIdentity = {
|
|
361
|
+
appId: '${app.id}',
|
|
362
|
+
build: '${createBuildMarker(scope, app)}',
|
|
363
|
+
deployProfile: 'cloudflare-ssr-mf-effect-v1',
|
|
364
|
+
packageName: '${packageName(scope, app.packageSuffix)}',
|
|
365
|
+
version: '0.1.0',
|
|
366
|
+
} as const;
|
|
367
|
+
|
|
368
|
+
export const ultramodernUiMarker = {
|
|
369
|
+
...ultramodernVerticalIdentity,
|
|
370
|
+
surface: 'ui',
|
|
371
|
+
} as const;
|
|
372
|
+
|
|
373
|
+
export const ultramodernApiMarker = {
|
|
374
|
+
...ultramodernVerticalIdentity,
|
|
375
|
+
surface: 'effect-bff',
|
|
376
|
+
} as const;
|
|
377
|
+
`;
|
|
378
|
+
}
|
|
379
|
+
function createRemoteModuleFederationConfig(scope, app, remotes = []) {
|
|
380
|
+
const exposes = formatTsObjectLiteral(app.exposes ?? {});
|
|
381
|
+
return `// @effect-diagnostics nodeBuiltinImport:off
|
|
382
|
+
import { createRequire } from 'node:module';
|
|
383
|
+
import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
|
|
384
|
+
import { dependencies } from './package.json';
|
|
385
|
+
|
|
386
|
+
const require = createRequire(import.meta.url);
|
|
387
|
+
const pluginI18nVersion = (require('@modern-js/plugin-i18n/package.json') as { version: string }).version;
|
|
388
|
+
const pluginTanstackVersion = (require('@modern-js/plugin-tanstack/package.json') as { version: string }).version;
|
|
389
|
+
const runtimeVersion = (require('@modern-js/runtime/package.json') as { version: string }).version;
|
|
390
|
+
const reactVersion = (require('react/package.json') as { version: string }).version;
|
|
391
|
+
const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
|
|
392
|
+
|
|
393
|
+
${createModuleFederationRemoteUrlHelpers(app, remotes)}
|
|
394
|
+
export default createModuleFederationConfig({
|
|
395
|
+
bridge: {
|
|
396
|
+
enableBridgeRouter: false,
|
|
397
|
+
},
|
|
398
|
+
dev: {
|
|
399
|
+
disableDynamicRemoteTypeHints: true,
|
|
400
|
+
},
|
|
401
|
+
dts: {
|
|
402
|
+
displayErrorInTerminal: true,
|
|
403
|
+
generateTypes: {
|
|
404
|
+
compilerInstance: 'tsgo',
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
exposes: ${exposes},
|
|
408
|
+
filename: 'remoteEntry.js',
|
|
409
|
+
name: '${app.mfName}',
|
|
410
|
+
${createModuleFederationRemotesConfig(scope, app, remotes)}${createSharedModuleFederationConfig()},
|
|
411
|
+
treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
|
|
412
|
+
});
|
|
413
|
+
`;
|
|
414
|
+
}
|
|
415
|
+
export { createAppModernConfig, createBuildMarker, createModuleFederationRemoteUrlHelpers, createModuleFederationRemotesConfig, createRemoteModuleFederationConfig, createSharedModuleFederationConfig, createShellModuleFederationConfig, createUltramodernBuildModule, formatTsObjectLiteral };
|