@bleedingdev/modern-js-ultramodern-create 3.9.3-ultramodern.1 → 3.9.3-ultramodern.2

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.
Files changed (30) hide show
  1. package/dist/cjs/ultramodern-workspace/add-vertical/shell-files.cjs +2 -7
  2. package/dist/cjs/ultramodern-workspace/api/client.cjs +2 -37
  3. package/dist/cjs/ultramodern-workspace/api.cjs +0 -3
  4. package/dist/cjs/ultramodern-workspace/native-app.cjs +0 -2
  5. package/dist/cjs/ultramodern-workspace/package-json.cjs +1 -3
  6. package/dist/cjs/ultramodern-workspace/validation/architecture.cjs +24 -11
  7. package/dist/cjs/ultramodern-workspace/validation/native-shell-runtime.cjs +134 -0
  8. package/dist/cjs/ultramodern-workspace/write-app.cjs +1 -3
  9. package/dist/esm/ultramodern-workspace/add-vertical/shell-files.js +3 -8
  10. package/dist/esm/ultramodern-workspace/api/client.js +4 -36
  11. package/dist/esm/ultramodern-workspace/api.js +1 -1
  12. package/dist/esm/ultramodern-workspace/native-app.js +0 -2
  13. package/dist/esm/ultramodern-workspace/package-json.js +1 -3
  14. package/dist/esm/ultramodern-workspace/validation/architecture.js +24 -11
  15. package/dist/esm/ultramodern-workspace/validation/native-shell-runtime.js +96 -0
  16. package/dist/esm/ultramodern-workspace/write-app.js +1 -3
  17. package/dist/esm-node/ultramodern-workspace/add-vertical/shell-files.js +3 -8
  18. package/dist/esm-node/ultramodern-workspace/api/client.js +4 -36
  19. package/dist/esm-node/ultramodern-workspace/api.js +1 -1
  20. package/dist/esm-node/ultramodern-workspace/native-app.js +0 -2
  21. package/dist/esm-node/ultramodern-workspace/package-json.js +1 -3
  22. package/dist/esm-node/ultramodern-workspace/validation/architecture.js +24 -11
  23. package/dist/esm-node/ultramodern-workspace/validation/native-shell-runtime.js +97 -0
  24. package/dist/esm-node/ultramodern-workspace/write-app.js +1 -3
  25. package/dist/types/ultramodern-workspace/api/client.d.ts +1 -2
  26. package/dist/types/ultramodern-workspace/api.d.ts +1 -1
  27. package/dist/types/ultramodern-workspace/validation/native-shell-runtime.d.ts +19 -0
  28. package/package.json +18 -18
  29. package/release-cohort.json +51 -51
  30. package/templates/workspace-scripts/proof-workerd-ssr.mjs +371 -38
@@ -0,0 +1,96 @@
1
+ const FEDERATION_RUNTIME = '@module-federation/modern-js-v3/runtime';
2
+ const FEDERATION_MEMBER_LOADERS = new Set([
3
+ 'loadRemote',
4
+ 'loadShare'
5
+ ]);
6
+ const DECLARATION_FILE = /\.d\.[cm]?ts$/u;
7
+ function createNativeShellRuntimeEvidence(typescript, typescriptApi, checker, sourceFile) {
8
+ const unwrapParentheses = (node)=>{
9
+ while(typescript.isParenthesizedExpression(node))node = node.expression;
10
+ return node;
11
+ };
12
+ const isAmbientProcess = (node)=>{
13
+ if (!typescript.isIdentifier(node) || 'process' !== node.text) return false;
14
+ const symbol = checker.getSymbolAtLocation(node);
15
+ return void 0 === symbol || symbol.declarations.every((declaration)=>DECLARATION_FILE.test(declaration.path));
16
+ };
17
+ const isBuildTargetAccess = (node)=>typescript.isPropertyAccessExpression(node) && void 0 === node.questionDotToken && 'MODERN_TARGET' === node.name.text && typescript.isPropertyAccessExpression(node.expression) && void 0 === node.expression.questionDotToken && 'env' === node.expression.name.text && isAmbientProcess(node.expression.expression);
18
+ const isBuildTargetLiteral = (node)=>typescript.isStringLiteral(node) && ('browser' === node.text || 'node' === node.text);
19
+ const buildTargetComparison = (node)=>{
20
+ node = unwrapParentheses(node);
21
+ if (!typescript.isBinaryExpression(node) || node.operatorToken.kind !== typescript.SyntaxKind.EqualsEqualsEqualsToken) return;
22
+ const literal = isBuildTargetAccess(node.left) ? node.right : isBuildTargetAccess(node.right) ? node.left : void 0;
23
+ return void 0 !== literal && isBuildTargetLiteral(literal) ? literal.text : void 0;
24
+ };
25
+ const isConditionOf = (node)=>{
26
+ let condition = node;
27
+ for(;;){
28
+ const parent = condition.parent;
29
+ if (typescript.isParenthesizedExpression(parent) || typescript.isPrefixUnaryExpression(parent) && parent.operator === typescript.SyntaxKind.ExclamationToken || typescript.isBinaryExpression(parent) && (parent.operatorToken.kind === typescript.SyntaxKind.AmpersandAmpersandToken || parent.operatorToken.kind === typescript.SyntaxKind.BarBarToken)) {
30
+ condition = parent;
31
+ continue;
32
+ }
33
+ return typescript.isIfStatement(parent) && parent.expression === condition || typescript.isConditionalExpression(parent) && parent.condition === condition;
34
+ }
35
+ };
36
+ const isBuildTargetCondition = (node)=>{
37
+ const access = node.parent;
38
+ if (!isBuildTargetAccess(access) || access.expression !== node) return false;
39
+ const comparison = access.parent;
40
+ return void 0 !== buildTargetComparison(comparison) && isConditionOf(comparison);
41
+ };
42
+ const conjuncts = (node)=>{
43
+ node = unwrapParentheses(node);
44
+ return typescript.isBinaryExpression(node) && node.operatorToken.kind === typescript.SyntaxKind.AmpersandAmpersandToken ? [
45
+ ...conjuncts(node.left),
46
+ ...conjuncts(node.right)
47
+ ] : [
48
+ node
49
+ ];
50
+ };
51
+ const requiresBrowserTarget = (condition)=>conjuncts(condition).some((conjunct)=>'browser' === buildTargetComparison(conjunct));
52
+ const isBrowserGuarded = (node)=>{
53
+ for(let child = node; void 0 !== child.parent; child = child.parent){
54
+ const parent = child.parent;
55
+ if (typescript.isIfStatement(parent) && parent.thenStatement === child && requiresBrowserTarget(parent.expression)) return true;
56
+ if (typescript.isConditionalExpression(parent) && parent.whenTrue === child && requiresBrowserTarget(parent.condition)) return true;
57
+ }
58
+ return false;
59
+ };
60
+ let federationInstanceTypeIds;
61
+ const federationInstanceTypes = ()=>{
62
+ if (void 0 !== federationInstanceTypeIds) return federationInstanceTypeIds;
63
+ const typeIds = new Set();
64
+ const recordRuntime = (specifier)=>{
65
+ const runtime = checker.getSymbolAtLocation(specifier);
66
+ const getInstance = void 0 === runtime ? void 0 : checker.getMemberInModuleExports(runtime, 'getInstance');
67
+ const getInstanceType = void 0 === getInstance ? void 0 : checker.getTypeOfSymbol(getInstance);
68
+ if (void 0 === getInstanceType) return;
69
+ for (const signature of checker.getSignaturesOfType(getInstanceType, typescriptApi.SignatureKind.Call)){
70
+ const returnType = checker.getReturnTypeOfSignature(signature);
71
+ const instanceType = void 0 === returnType ? void 0 : checker.getNonNullableType(returnType);
72
+ if (void 0 !== instanceType && instanceType.getSymbol()?.name === 'ModuleFederation') typeIds.add(instanceType.id);
73
+ }
74
+ };
75
+ const visit = (node)=>{
76
+ if (typescript.isImportDeclaration(node) && typescript.isStringLiteralLikeNode(node.moduleSpecifier) && node.moduleSpecifier.text === FEDERATION_RUNTIME && node.importClause?.phaseModifier !== typescript.SyntaxKind.TypeKeyword) recordRuntime(node.moduleSpecifier);
77
+ else if (typescript.isCallExpression(node) && node.expression.kind === typescript.SyntaxKind.ImportKeyword && 1 === node.arguments.length && typescript.isStringLiteralLikeNode(node.arguments[0]) && node.arguments[0].text === FEDERATION_RUNTIME) recordRuntime(node.arguments[0]);
78
+ node.forEachChild(visit);
79
+ };
80
+ visit(sourceFile);
81
+ federationInstanceTypeIds = typeIds;
82
+ return typeIds;
83
+ };
84
+ const isBrowserFederationMemberLoad = (node)=>{
85
+ if (!typescript.isCallExpression(node)) return false;
86
+ const callee = unwrapParentheses(node.expression);
87
+ if (!typescript.isPropertyAccessExpression(callee) || !FEDERATION_MEMBER_LOADERS.has(callee.name.text)) return false;
88
+ const receiverType = checker.getTypeAtLocation(callee.expression);
89
+ return void 0 !== receiverType && federationInstanceTypes().has(receiverType.id) && isBrowserGuarded(node);
90
+ };
91
+ return {
92
+ isBrowserFederationMemberLoad,
93
+ isBuildTargetCondition
94
+ };
95
+ }
96
+ export { createNativeShellRuntimeEvidence };
@@ -1,4 +1,3 @@
1
- import { createShellApiClient } from "./api.js";
2
1
  import { writeAppApiFiles } from "./api/write-app-api.js";
3
2
  import { createAppEnvDts, createAppRuntimeConfig, createAppStyles, createShellFrameComponent, createTailwindConfig } from "./app-files.js";
4
3
  import { createFederatedComponentsRegistry, createLayout, createRemoteEntry, createRemoteExposeComponent, createRemoteExposeFragmentPage, createRemotePage, createShellPage, createShellRemoteComponents, createShellWorkerRemoteComponents, remoteComponentOutputPath } from "./demo-components.js";
@@ -78,7 +77,7 @@ function writeAppFederationConfigFiles({ targetDir, scope, resolvedApp, emitsUi,
78
77
  if (emitsUi) writeFile(targetDir, `${resolvedApp.directory}/module-federation.config.ts`, 'shell' === resolvedApp.kind ? createShellModuleFederationConfig(scope, resolvedApp, remotes) : createRemoteModuleFederationConfig(scope, resolvedApp, remotes));
79
78
  if (appHasApi(resolvedApp)) writeFile(targetDir, `${resolvedApp.directory}/backend-federation.config.ts`, createBackendModuleFederationConfig(resolvedApp));
80
79
  }
81
- function writeAppRouteAndShellFiles({ targetDir, scope, resolvedApp, emitsUi, remotes, availableRemotes, publicWeb, writeAppFile }) {
80
+ function writeAppRouteAndShellFiles({ targetDir, scope, resolvedApp, emitsUi, remotes, publicWeb, writeAppFile }) {
82
81
  if (!emitsUi) return;
83
82
  if ((resolvedApp.verticalRefs?.length ?? 0) > 0) {
84
83
  writeAppFile('src/federated-components.tsx', createFederatedComponentsRegistry(scope, resolvedApp, remotes));
@@ -92,7 +91,6 @@ function writeAppRouteAndShellFiles({ targetDir, scope, resolvedApp, emitsUi, re
92
91
  writeAppFile('src/routes/vertical-components.tsx', createShellRemoteComponents(resolvedApp, remotes.filter(appEmitsBrowserUi)));
93
92
  writeAppFile('src/routes/vertical-components.worker.tsx', createShellWorkerRemoteComponents(resolvedApp, remotes.filter(appEmitsBrowserUi)));
94
93
  writeAppFile('src/routes/shell-frame.tsx', createShellFrameComponent(resolvedApp));
95
- writeFile(targetDir, `${resolvedApp.directory}/src/api/vertical-clients.ts`, createShellApiClient(scope, availableRemotes));
96
94
  } else for (const expose of distributedSsrExposes(resolvedApp))writeAppFile(`src/routes/[lang]/_mf/fragment/${distributedSsrFragmentSlug(expose)}/page.tsx`, createRemoteExposeFragmentPage(resolvedApp, expose));
97
95
  }
98
96
  function writeAppRemoteExposeFiles({ resolvedApp, emitsUi, writeAppFile }) {
@@ -1,10 +1,9 @@
1
1
  import "node:module";
2
2
  import node_fs from "node:fs";
3
3
  import node_path from "node:path";
4
- import { createShellApiClient } from "../api.js";
5
4
  import { createAppEnvDts, createAppRuntimeConfig, createShellFrameComponent } from "../app-files.js";
6
5
  import { createShellPage, createShellRemoteComponents, createShellWorkerRemoteComponents } from "../demo-components.js";
7
- import { appEmitsBrowserUi, appHasApi, appI18nNamespace, resolveRemoteRefs, shellApp } from "../descriptors.js";
6
+ import { appEmitsBrowserUi, appI18nNamespace, resolveRemoteRefs, shellApp } from "../descriptors.js";
8
7
  import { readJsonFile, writeJsonFile } from "../fs-io.js";
9
8
  import { createAppPublicLocaleMessages } from "../locales.js";
10
9
  import { createAppModernConfig, createShellModuleFederationConfig } from "../module-federation.js";
@@ -41,10 +40,7 @@ function shellAppArtifacts(scope, packageSource, enableTailwind, remotes, bridge
41
40
  ...Object.fromEntries(createNativeFederationArtifacts(scope, shellHost, remotes).map((artifact)=>[
42
41
  `${shellHost.directory}/${artifact.path}`,
43
42
  artifact.content
44
- ])),
45
- ...remotes.some(appHasApi) ? {
46
- [`${shellHost.directory}/src/api/vertical-clients.ts`]: createShellApiClient(scope, remotes)
47
- } : {}
43
+ ]))
48
44
  };
49
45
  return {
50
46
  shellHost,
@@ -84,8 +80,7 @@ function shellAppArtifacts(scope, packageSource, enableTailwind, remotes, bridge
84
80
  [`${shellHost.directory}/src/routes/[lang]/page.tsx`]: createShellPage(shellHost, uiRemotes),
85
81
  [`${shellHost.directory}/src/routes/vertical-components.tsx`]: createShellRemoteComponents(shellHost, uiRemotes),
86
82
  [`${shellHost.directory}/src/routes/vertical-components.worker.tsx`]: createShellWorkerRemoteComponents(shellHost, uiRemotes),
87
- [`${shellHost.directory}/src/routes/shell-frame.tsx`]: createShellFrameComponent(shellHost),
88
- [`${shellHost.directory}/src/api/vertical-clients.ts`]: createShellApiClient(scope, remotes)
83
+ [`${shellHost.directory}/src/routes/shell-frame.tsx`]: createShellFrameComponent(shellHost)
89
84
  };
90
85
  return {
91
86
  shellHost,
@@ -1,7 +1,7 @@
1
1
  import "node:module";
2
- import { resolveApiProtocol, resolveApiStem, verticalApiApps } from "../descriptors.js";
3
- import { packageName, toPascalCase } from "../naming.js";
4
- import { createCheckoutCartClientExports, serviceHasCheckoutCartState } from "./checkout-cart.js";
2
+ import { resolveApiStem } from "../descriptors.js";
3
+ import { toPascalCase } from "../naming.js";
4
+ import { createCheckoutCartClientExports } from "./checkout-cart.js";
5
5
  import { verticalApiErrorStem, verticalApiExport, verticalApiGroupName, verticalApiNotFoundErrorExport } from "./names.js";
6
6
  function createApiClient(service, contractImportPath, options) {
7
7
  const apiExport = verticalApiExport(service);
@@ -149,36 +149,4 @@ export const ${createName} = (
149
149
  );${checkoutCartClientExports}
150
150
  `;
151
151
  }
152
- function createShellApiClient(scope, remotes = []) {
153
- const exports = verticalApiApps(remotes).map((remote)=>{
154
- const stem = resolveApiStem(remote);
155
- const pascalStem = toPascalCase(stem);
156
- const rpc = 'rpc' === resolveApiProtocol(remote);
157
- const pascalSingular = toPascalCase(verticalApiErrorStem(remote));
158
- const checkoutCartExports = !rpc && serviceHasCheckoutCartState(remote) ? ` addCheckoutCartItem,
159
- clearCheckoutCart,
160
- getCheckoutCart,
161
- removeCheckoutCartItem,
162
- type CheckoutAddCartItemInput,
163
- type CheckoutCart,
164
- type CheckoutCartLine,
165
- ` : '';
166
- if (rpc) return `export {
167
- get${pascalStem}Rpc,
168
- list${pascalStem}Rpc,
169
- make${pascalStem}RpcClient,
170
- type ${pascalStem}RpcClientOptions,
171
- } from '${packageName(scope, remote.packageSuffix)}/api/rpc-client';`;
172
- return `export {
173
- ${checkoutCartExports} create${pascalSingular},
174
- create${pascalStem}Client,
175
- get${pascalSingular},
176
- get${pascalStem}Readiness,
177
- list${pascalStem},
178
- type ${pascalStem}ClientOptions,
179
- } from '${packageName(scope, remote.packageSuffix)}/api/client';`;
180
- }).join('\n\n');
181
- return exports ? `${exports}\n` : `export const ultramodernVerticalClients = [] as const;
182
- `;
183
- }
184
- export { createApiClient, createShellApiClient };
152
+ export { createApiClient };
@@ -1,5 +1,5 @@
1
1
  import "node:module";
2
- export { createApiClient, createShellApiClient } from "./api/client.js";
2
+ export { createApiClient } from "./api/client.js";
3
3
  export { apiTopologyMetadata } from "./api/contracts.js";
4
4
  export { verticalApiCreatePayloadSchemaExport, verticalApiErrorStem, verticalApiExport, verticalApiGroupName, verticalApiMarkerSchemaExport, verticalApiName, verticalApiNotFoundErrorExport, verticalApiNotFoundSchemaExport, verticalApiReadinessSchemaExport, verticalApiSchemaExport } from "./api/names.js";
5
5
  export { createRpcApiServiceEntry, createRpcClientFile, createRpcContractFile, rpcPath, verticalRpcContractExport, verticalRpcGroupExport } from "./api/rpc.js";
@@ -1,5 +1,4 @@
1
1
  import "node:module";
2
- import { createShellApiClient } from "./api.js";
3
2
  import { writeAppApiFiles } from "./api/write-app-api.js";
4
3
  import { createAppStyles, createTailwindConfig } from "./app-files.js";
5
4
  import { appHasApi } from "./descriptors.js";
@@ -62,6 +61,5 @@ function writeNativeApp(targetDir, scope, app, packageSource, enableTailwind, re
62
61
  resolvedApp: app,
63
62
  emitsUi: true
64
63
  });
65
- if ('shell' === app.kind && remotes.some(appHasApi)) write('src/api/vertical-clients.ts', createShellApiClient(scope, remotes));
66
64
  }
67
65
  export { writeNativeApp };
@@ -270,9 +270,7 @@ function createAppPackage(scope, app, packageSource, enableTailwind, remotes = [
270
270
  './api': './shared/api.ts',
271
271
  './api/client': `./${clientDirectory}/${app.api.stem}-client.ts`
272
272
  });
273
- } else if ('shell' === app.kind && ('react' === resolveWorkspaceRenderer(app) || verticalApiApps(remotes).length > 0)) Object.assign(packageExports, {
274
- './api/clients': './src/api/vertical-clients.ts'
275
- });
273
+ }
276
274
  if (Object.keys(packageExports).length > 0) packageJson.exports = packageExports;
277
275
  return packageJson;
278
276
  }
@@ -5,6 +5,7 @@ import node_path from "node:path";
5
5
  import { hasNativeAppGeneration } from "../renderer-generations.js";
6
6
  import { getRendererGenerationProfile, isApplicationRenderer } from "../renderer-profile.js";
7
7
  import { assert, sameJson, selfCheckFailure } from "./assertions.js";
8
+ import { createNativeShellRuntimeEvidence } from "./native-shell-runtime.js";
8
9
  import { isForeignRendererPackage } from "./renderer.js";
9
10
  function assertCompilerArchitecture(root, workspaceValidationContract) {
10
11
  const fullStackVerticals = workspaceValidationContract.apps.filter((app)=>'shell' !== app.kind);
@@ -63,12 +64,16 @@ function assertCompilerArchitecture(root, workspaceValidationContract) {
63
64
  ]
64
65
  });
65
66
  };
66
- const parseCompilerInput = (absolutePath)=>{
67
+ const compilerInput = (absolutePath)=>{
67
68
  const project = compilerSnapshot?.getDefaultProjectForFile(absolutePath);
68
69
  const sourceFile = project?.program.getSourceFile(absolutePath);
69
- assert(void 0 !== sourceFile, `Native TypeScript compiler could not parse ${node_path.relative(root, absolutePath)}`);
70
- return sourceFile;
70
+ assert(void 0 !== project && void 0 !== sourceFile, `Native TypeScript compiler could not parse ${node_path.relative(root, absolutePath)}`);
71
+ return {
72
+ checker: project.checker,
73
+ sourceFile
74
+ };
71
75
  };
76
+ const parseCompilerInput = (absolutePath)=>compilerInput(absolutePath).sourceFile;
72
77
  const nodeName = (node)=>{
73
78
  if (!node) return;
74
79
  if (typescript.isIdentifier(node) || typescript.isPrivateIdentifier(node) || typescript.isStringLiteralLikeNode(node) || typescript.isNumericLiteral(node)) return node.text;
@@ -156,6 +161,12 @@ function assertCompilerArchitecture(root, workspaceValidationContract) {
156
161
  const accessPath = propertyAccessPath(node.expression);
157
162
  return void 0 !== accessPath && names.has(accessPath.at(-1));
158
163
  };
164
+ const calleeName = (node)=>{
165
+ let callee = node.expression;
166
+ while(typescript.isParenthesizedExpression(callee) || typescript.isNonNullExpression(callee) || typescript.isAsExpression(callee))callee = callee.expression;
167
+ if (typescript.isIdentifier(callee)) return callee.text;
168
+ if (typescript.isPropertyAccessExpression(callee)) return callee.name.text;
169
+ };
159
170
  const objectLiteralProperty = (objectLiteral, name)=>objectLiteral.properties.find((property)=>typescript.isPropertyAssignment(property) && nodeName(property.name) === name);
160
171
  const moduleFederationConfigArgument = (sourceFile)=>{
161
172
  let argument;
@@ -303,11 +314,12 @@ function assertCompilerArchitecture(root, workspaceValidationContract) {
303
314
  ...configFiles.map((relativePath)=>node_path.join(root, relativePath)).filter((absolutePath)=>node_fs.existsSync(absolutePath))
304
315
  ]);
305
316
  for (const absolutePath of checkedFiles){
306
- const sourceFile = parseCompilerInput(absolutePath);
317
+ const { checker, sourceFile } = compilerInput(absolutePath);
307
318
  const isShellRoute = shellRouteDirectories.some((relativeDirectory)=>{
308
319
  const absoluteDirectory = node_path.join(root, relativeDirectory);
309
320
  return absolutePath.startsWith(absoluteDirectory + node_path.sep);
310
321
  });
322
+ const shellRuntime = isShellRoute ? createNativeShellRuntimeEvidence(typescript, typescriptApi, checker, sourceFile) : void 0;
311
323
  const moduleFederationAppPath = moduleFederationConfigAppPaths.get(absolutePath);
312
324
  const nativeFederation = void 0 === moduleFederationAppPath ? void 0 : nativeModuleFederationApps.get(moduleFederationAppPath);
313
325
  const tailwindFactories = new Set();
@@ -330,6 +342,12 @@ function assertCompilerArchitecture(root, workspaceValidationContract) {
330
342
  ].includes(imported)) presetFactories.add(binding.name.text);
331
343
  }
332
344
  }
345
+ const manualLoadingCallees = new Set([
346
+ 'hydrateRoot',
347
+ 'loadRemote',
348
+ 'loadShare',
349
+ ...nativeRemoteLoaders
350
+ ]);
333
351
  const modernConfigApp = workspaceValidationContract.apps.find((app)=>absolutePath === node_path.join(root, app.path, 'modern.config.ts'));
334
352
  let insideDefaultExport = false;
335
353
  const visit = (node)=>{
@@ -357,7 +375,7 @@ function assertCompilerArchitecture(root, workspaceValidationContract) {
357
375
  if (sameJson(accessPath, [
358
376
  'process',
359
377
  'env'
360
- ])) compilerFailure(sourceFile, node, 'framework config API direct-process-env-access', 'Generated config must use the framework config environment API instead of process.env.', 'use getBuildConfigEnvironment');
378
+ ]) && !shellRuntime?.isBuildTargetCondition(node)) compilerFailure(sourceFile, node, 'framework config API direct-process-env-access', 'Generated config must use the framework config environment API instead of process.env.', 'use getBuildConfigEnvironment');
361
379
  if (typescript.isIdentifier(node) && 'ULTRAMODERN_ZEPHYR' === node.text) compilerFailure(sourceFile, node, 'zephyr gating ultramodern-zephyr-environment-gate', 'Generated Zephyr integration must not be gated through ULTRAMODERN_ZEPHYR.', 'use Zephyr native deploy-token behavior');
362
380
  if (typescript.isPropertyAssignment(node) || typescript.isShorthandPropertyAssignment(node)) {
363
381
  const property = nodeName(node.name);
@@ -365,12 +383,7 @@ function assertCompilerArchitecture(root, workspaceValidationContract) {
365
383
  }
366
384
  if (isShellRoute) {
367
385
  if (typescript.isJsxOpeningElement(node) && 'a' === jsxTagName(node.tagName) && void 0 !== jsxAttribute(node.attributes, 'onClick')) compilerFailure(sourceFile, node, 'shell routing synthetic-anchor-click-interception', 'Shell routing must not intercept anchor clicks synthetically.', 'use the router Link primitive');
368
- if (isCallNamed(node, new Set([
369
- 'hydrateRoot',
370
- 'loadRemote',
371
- 'loadShare',
372
- ...nativeRemoteLoaders
373
- ])) && !nativeLoaderCalls.has(node)) compilerFailure(sourceFile, node, 'module federation native loading', 'Shell routing must use framework Module Federation loading primitives.', 'remove manual hydration or remote-loading wrappers');
386
+ if (typescript.isCallExpression(node) && manualLoadingCallees.has(calleeName(node) ?? '') && !nativeLoaderCalls.has(node) && !shellRuntime?.isBrowserFederationMemberLoad(node)) compilerFailure(sourceFile, node, 'module federation native loading', 'Shell routing must use framework Module Federation loading primitives.', 'remove manual hydration or remote-loading wrappers');
374
387
  if (typescript.isBinaryExpression(node) && node.operatorToken.kind === typescript.SyntaxKind.EqualsToken && propertyAccessPath(node.left)?.slice(0, 2).join('.') === 'window.location' || typescript.isCallExpression(node) && propertyAccessPath(node.expression)?.slice(0, 2).join('.') === 'window.location') compilerFailure(sourceFile, node, 'shell routing window-location-navigation', 'Shell routing must use router navigation instead of window.location.', 'use the router navigation primitive');
375
388
  }
376
389
  node.forEachChild(visit);
@@ -0,0 +1,97 @@
1
+ import "node:module";
2
+ const FEDERATION_RUNTIME = '@module-federation/modern-js-v3/runtime';
3
+ const FEDERATION_MEMBER_LOADERS = new Set([
4
+ 'loadRemote',
5
+ 'loadShare'
6
+ ]);
7
+ const DECLARATION_FILE = /\.d\.[cm]?ts$/u;
8
+ function createNativeShellRuntimeEvidence(typescript, typescriptApi, checker, sourceFile) {
9
+ const unwrapParentheses = (node)=>{
10
+ while(typescript.isParenthesizedExpression(node))node = node.expression;
11
+ return node;
12
+ };
13
+ const isAmbientProcess = (node)=>{
14
+ if (!typescript.isIdentifier(node) || 'process' !== node.text) return false;
15
+ const symbol = checker.getSymbolAtLocation(node);
16
+ return void 0 === symbol || symbol.declarations.every((declaration)=>DECLARATION_FILE.test(declaration.path));
17
+ };
18
+ const isBuildTargetAccess = (node)=>typescript.isPropertyAccessExpression(node) && void 0 === node.questionDotToken && 'MODERN_TARGET' === node.name.text && typescript.isPropertyAccessExpression(node.expression) && void 0 === node.expression.questionDotToken && 'env' === node.expression.name.text && isAmbientProcess(node.expression.expression);
19
+ const isBuildTargetLiteral = (node)=>typescript.isStringLiteral(node) && ('browser' === node.text || 'node' === node.text);
20
+ const buildTargetComparison = (node)=>{
21
+ node = unwrapParentheses(node);
22
+ if (!typescript.isBinaryExpression(node) || node.operatorToken.kind !== typescript.SyntaxKind.EqualsEqualsEqualsToken) return;
23
+ const literal = isBuildTargetAccess(node.left) ? node.right : isBuildTargetAccess(node.right) ? node.left : void 0;
24
+ return void 0 !== literal && isBuildTargetLiteral(literal) ? literal.text : void 0;
25
+ };
26
+ const isConditionOf = (node)=>{
27
+ let condition = node;
28
+ for(;;){
29
+ const parent = condition.parent;
30
+ if (typescript.isParenthesizedExpression(parent) || typescript.isPrefixUnaryExpression(parent) && parent.operator === typescript.SyntaxKind.ExclamationToken || typescript.isBinaryExpression(parent) && (parent.operatorToken.kind === typescript.SyntaxKind.AmpersandAmpersandToken || parent.operatorToken.kind === typescript.SyntaxKind.BarBarToken)) {
31
+ condition = parent;
32
+ continue;
33
+ }
34
+ return typescript.isIfStatement(parent) && parent.expression === condition || typescript.isConditionalExpression(parent) && parent.condition === condition;
35
+ }
36
+ };
37
+ const isBuildTargetCondition = (node)=>{
38
+ const access = node.parent;
39
+ if (!isBuildTargetAccess(access) || access.expression !== node) return false;
40
+ const comparison = access.parent;
41
+ return void 0 !== buildTargetComparison(comparison) && isConditionOf(comparison);
42
+ };
43
+ const conjuncts = (node)=>{
44
+ node = unwrapParentheses(node);
45
+ return typescript.isBinaryExpression(node) && node.operatorToken.kind === typescript.SyntaxKind.AmpersandAmpersandToken ? [
46
+ ...conjuncts(node.left),
47
+ ...conjuncts(node.right)
48
+ ] : [
49
+ node
50
+ ];
51
+ };
52
+ const requiresBrowserTarget = (condition)=>conjuncts(condition).some((conjunct)=>'browser' === buildTargetComparison(conjunct));
53
+ const isBrowserGuarded = (node)=>{
54
+ for(let child = node; void 0 !== child.parent; child = child.parent){
55
+ const parent = child.parent;
56
+ if (typescript.isIfStatement(parent) && parent.thenStatement === child && requiresBrowserTarget(parent.expression)) return true;
57
+ if (typescript.isConditionalExpression(parent) && parent.whenTrue === child && requiresBrowserTarget(parent.condition)) return true;
58
+ }
59
+ return false;
60
+ };
61
+ let federationInstanceTypeIds;
62
+ const federationInstanceTypes = ()=>{
63
+ if (void 0 !== federationInstanceTypeIds) return federationInstanceTypeIds;
64
+ const typeIds = new Set();
65
+ const recordRuntime = (specifier)=>{
66
+ const runtime = checker.getSymbolAtLocation(specifier);
67
+ const getInstance = void 0 === runtime ? void 0 : checker.getMemberInModuleExports(runtime, 'getInstance');
68
+ const getInstanceType = void 0 === getInstance ? void 0 : checker.getTypeOfSymbol(getInstance);
69
+ if (void 0 === getInstanceType) return;
70
+ for (const signature of checker.getSignaturesOfType(getInstanceType, typescriptApi.SignatureKind.Call)){
71
+ const returnType = checker.getReturnTypeOfSignature(signature);
72
+ const instanceType = void 0 === returnType ? void 0 : checker.getNonNullableType(returnType);
73
+ if (void 0 !== instanceType && instanceType.getSymbol()?.name === 'ModuleFederation') typeIds.add(instanceType.id);
74
+ }
75
+ };
76
+ const visit = (node)=>{
77
+ if (typescript.isImportDeclaration(node) && typescript.isStringLiteralLikeNode(node.moduleSpecifier) && node.moduleSpecifier.text === FEDERATION_RUNTIME && node.importClause?.phaseModifier !== typescript.SyntaxKind.TypeKeyword) recordRuntime(node.moduleSpecifier);
78
+ else if (typescript.isCallExpression(node) && node.expression.kind === typescript.SyntaxKind.ImportKeyword && 1 === node.arguments.length && typescript.isStringLiteralLikeNode(node.arguments[0]) && node.arguments[0].text === FEDERATION_RUNTIME) recordRuntime(node.arguments[0]);
79
+ node.forEachChild(visit);
80
+ };
81
+ visit(sourceFile);
82
+ federationInstanceTypeIds = typeIds;
83
+ return typeIds;
84
+ };
85
+ const isBrowserFederationMemberLoad = (node)=>{
86
+ if (!typescript.isCallExpression(node)) return false;
87
+ const callee = unwrapParentheses(node.expression);
88
+ if (!typescript.isPropertyAccessExpression(callee) || !FEDERATION_MEMBER_LOADERS.has(callee.name.text)) return false;
89
+ const receiverType = checker.getTypeAtLocation(callee.expression);
90
+ return void 0 !== receiverType && federationInstanceTypes().has(receiverType.id) && isBrowserGuarded(node);
91
+ };
92
+ return {
93
+ isBrowserFederationMemberLoad,
94
+ isBuildTargetCondition
95
+ };
96
+ }
97
+ export { createNativeShellRuntimeEvidence };
@@ -1,5 +1,4 @@
1
1
  import "node:module";
2
- import { createShellApiClient } from "./api.js";
3
2
  import { writeAppApiFiles } from "./api/write-app-api.js";
4
3
  import { createAppEnvDts, createAppRuntimeConfig, createAppStyles, createShellFrameComponent, createTailwindConfig } from "./app-files.js";
5
4
  import { createFederatedComponentsRegistry, createLayout, createRemoteEntry, createRemoteExposeComponent, createRemoteExposeFragmentPage, createRemotePage, createShellPage, createShellRemoteComponents, createShellWorkerRemoteComponents, remoteComponentOutputPath } from "./demo-components.js";
@@ -79,7 +78,7 @@ function writeAppFederationConfigFiles({ targetDir, scope, resolvedApp, emitsUi,
79
78
  if (emitsUi) writeFile(targetDir, `${resolvedApp.directory}/module-federation.config.ts`, 'shell' === resolvedApp.kind ? createShellModuleFederationConfig(scope, resolvedApp, remotes) : createRemoteModuleFederationConfig(scope, resolvedApp, remotes));
80
79
  if (appHasApi(resolvedApp)) writeFile(targetDir, `${resolvedApp.directory}/backend-federation.config.ts`, createBackendModuleFederationConfig(resolvedApp));
81
80
  }
82
- function writeAppRouteAndShellFiles({ targetDir, scope, resolvedApp, emitsUi, remotes, availableRemotes, publicWeb, writeAppFile }) {
81
+ function writeAppRouteAndShellFiles({ targetDir, scope, resolvedApp, emitsUi, remotes, publicWeb, writeAppFile }) {
83
82
  if (!emitsUi) return;
84
83
  if ((resolvedApp.verticalRefs?.length ?? 0) > 0) {
85
84
  writeAppFile('src/federated-components.tsx', createFederatedComponentsRegistry(scope, resolvedApp, remotes));
@@ -93,7 +92,6 @@ function writeAppRouteAndShellFiles({ targetDir, scope, resolvedApp, emitsUi, re
93
92
  writeAppFile('src/routes/vertical-components.tsx', createShellRemoteComponents(resolvedApp, remotes.filter(appEmitsBrowserUi)));
94
93
  writeAppFile('src/routes/vertical-components.worker.tsx', createShellWorkerRemoteComponents(resolvedApp, remotes.filter(appEmitsBrowserUi)));
95
94
  writeAppFile('src/routes/shell-frame.tsx', createShellFrameComponent(resolvedApp));
96
- writeFile(targetDir, `${resolvedApp.directory}/src/api/vertical-clients.ts`, createShellApiClient(scope, availableRemotes));
97
95
  } else for (const expose of distributedSsrExposes(resolvedApp))writeAppFile(`src/routes/[lang]/_mf/fragment/${distributedSsrFragmentSlug(expose)}/page.tsx`, createRemoteExposeFragmentPage(resolvedApp, expose));
98
96
  }
99
97
  function writeAppRemoteExposeFiles({ resolvedApp, emitsUi, writeAppFile }) {
@@ -1,8 +1,7 @@
1
- import type { WorkspaceApi, WorkspaceApp } from '../types.js';
1
+ import type { WorkspaceApi } from '../types.js';
2
2
  export declare function createApiClient(service: {
3
3
  id: string;
4
4
  api?: WorkspaceApi;
5
5
  }, contractImportPath: string, options?: {
6
6
  readonly scope: string;
7
7
  }): string;
8
- export declare function createShellApiClient(scope: string, remotes?: WorkspaceApp[]): string;
@@ -1,4 +1,4 @@
1
- export { createApiClient, createShellApiClient } from './api/client.js';
1
+ export { createApiClient } from './api/client.js';
2
2
  export { apiTopologyMetadata } from './api/contracts.js';
3
3
  export { verticalApiCreatePayloadSchemaExport, verticalApiErrorStem, verticalApiExport, verticalApiGroupName, verticalApiMarkerSchemaExport, verticalApiName, verticalApiNotFoundErrorExport, verticalApiNotFoundSchemaExport, verticalApiReadinessSchemaExport, verticalApiSchemaExport, } from './api/names.js';
4
4
  export { createRpcApiServiceEntry, createRpcClientFile, createRpcContractFile, rpcPath, verticalRpcContractExport, verticalRpcGroupExport, } from './api/rpc.js';
@@ -0,0 +1,19 @@
1
+ import type * as ts from '@typescript/native/unstable/ast';
2
+ import type { Checker } from '@typescript/native/unstable/sync';
3
+ type TypeScriptAst = typeof import('@typescript/native/unstable/ast');
4
+ type TypeScriptApi = typeof import('@typescript/native/unstable/sync');
5
+ export type ModernBuildTarget = 'browser' | 'node';
6
+ /**
7
+ * Compiler evidence for shell source that branches on the framework build
8
+ * target and loads remotes through the native Module Federation instance.
9
+ *
10
+ * `process.env.MODERN_TARGET` is not ambient environment access: the Modern.js
11
+ * runtime plugin defines it as `'browser'` or `'node'` for every bundler
12
+ * environment, so a strict comparison is a compile-time constant that dead-code
13
+ * elimination folds per target.
14
+ */
15
+ export declare function createNativeShellRuntimeEvidence(typescript: TypeScriptAst, typescriptApi: TypeScriptApi, checker: Checker, sourceFile: ts.SourceFile): {
16
+ isBrowserFederationMemberLoad: (node: ts.Node) => boolean;
17
+ isBuildTargetCondition: (node: ts.Node) => boolean;
18
+ };
19
+ export {};
package/package.json CHANGED
@@ -26,7 +26,7 @@
26
26
  "engines": {
27
27
  "node": ">=26.10.0"
28
28
  },
29
- "version": "3.9.3-ultramodern.1",
29
+ "version": "3.9.3-ultramodern.2",
30
30
  "types": "./dist/types/index.d.ts",
31
31
  "main": "./dist/esm-node/index.js",
32
32
  "bin": {
@@ -86,22 +86,22 @@
86
86
  "@babel/core": "7.29.7",
87
87
  "@babel/parser": "^8.0.6",
88
88
  "@effect/tsgo": "0.47.2",
89
- "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.9.3-ultramodern.1",
90
- "@modern-js/app-tools-extensions": "npm:@bleedingdev/modern-js-app-tools-extensions@3.9.3-ultramodern.1",
91
- "@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.3-ultramodern.1",
89
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.9.3-ultramodern.2",
90
+ "@modern-js/app-tools-extensions": "npm:@bleedingdev/modern-js-app-tools-extensions@3.9.3-ultramodern.2",
91
+ "@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.3-ultramodern.2",
92
92
  "@modern-js/codesmith": "2.6.9",
93
- "@modern-js/i18n-integration": "npm:@bleedingdev/modern-js-i18n-integration@3.9.3-ultramodern.1",
94
- "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.9.3-ultramodern.1",
95
- "@modern-js/plugin-bff-build-extensions": "npm:@bleedingdev/modern-js-plugin-bff-build-extensions@3.9.3-ultramodern.1",
96
- "@modern-js/plugin-i18n": "npm:@bleedingdev/modern-js-plugin-i18n@3.9.3-ultramodern.1",
97
- "@modern-js/plugin-tanstack": "npm:@bleedingdev/modern-js-plugin-tanstack@3.9.3-ultramodern.1",
98
- "@modern-js/renderer-core": "npm:@bleedingdev/modern-js-renderer-core@3.9.3-ultramodern.1",
99
- "@modern-js/renderer-octane": "npm:@bleedingdev/modern-js-renderer-octane@3.9.3-ultramodern.1",
100
- "@modern-js/renderer-solid": "npm:@bleedingdev/modern-js-renderer-solid@3.9.3-ultramodern.1",
101
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.3-ultramodern.1",
102
- "@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.3-ultramodern.1",
103
- "@modern-js/ultramodern-app-tools": "npm:@bleedingdev/modern-js-ultramodern-app-tools@3.9.3-ultramodern.1",
104
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.3-ultramodern.1",
93
+ "@modern-js/i18n-integration": "npm:@bleedingdev/modern-js-i18n-integration@3.9.3-ultramodern.2",
94
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.9.3-ultramodern.2",
95
+ "@modern-js/plugin-bff-build-extensions": "npm:@bleedingdev/modern-js-plugin-bff-build-extensions@3.9.3-ultramodern.2",
96
+ "@modern-js/plugin-i18n": "npm:@bleedingdev/modern-js-plugin-i18n@3.9.3-ultramodern.2",
97
+ "@modern-js/plugin-tanstack": "npm:@bleedingdev/modern-js-plugin-tanstack@3.9.3-ultramodern.2",
98
+ "@modern-js/renderer-core": "npm:@bleedingdev/modern-js-renderer-core@3.9.3-ultramodern.2",
99
+ "@modern-js/renderer-octane": "npm:@bleedingdev/modern-js-renderer-octane@3.9.3-ultramodern.2",
100
+ "@modern-js/renderer-solid": "npm:@bleedingdev/modern-js-renderer-solid@3.9.3-ultramodern.2",
101
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.3-ultramodern.2",
102
+ "@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.3-ultramodern.2",
103
+ "@modern-js/ultramodern-app-tools": "npm:@bleedingdev/modern-js-ultramodern-app-tools@3.9.3-ultramodern.2",
104
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.3-ultramodern.2",
105
105
  "@module-federation/modern-js-v3": "npm:@bleedingdev/mf-modern-js-v3@2.9.4",
106
106
  "@rsbuild/plugin-tailwindcss": "2.0.3",
107
107
  "@typescript/native": "npm:typescript@7.0.2",
@@ -118,7 +118,7 @@
118
118
  "zephyr-rspack-plugin": "1.4.2"
119
119
  },
120
120
  "devDependencies": {
121
- "@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.3-ultramodern.1",
121
+ "@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.3-ultramodern.2",
122
122
  "@octanejs/rspack-plugin": "0.1.55",
123
123
  "@rslib/core": "1.0.3",
124
124
  "@scripts/rstest-config": "2.66.0",
@@ -142,6 +142,6 @@
142
142
  },
143
143
  "ultramodern": {
144
144
  "sourceName": "@modern-js/ultramodern-create",
145
- "frameworkVersion": "3.9.3-ultramodern.1"
145
+ "frameworkVersion": "3.9.3-ultramodern.2"
146
146
  }
147
147
  }