@arcote.tech/arc-cli 0.8.2 → 0.8.3
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/dist/index.js +318 -41
- package/package.json +10 -10
- package/src/builder/chunk-planner.ts +14 -0
- package/src/builder/module-builder.ts +432 -30
- package/src/commands/platform-build.ts +6 -1
- package/src/deploy/caddyfile.ts +3 -0
- package/src/index.ts +6 -2
- package/src/platform/shared.ts +68 -21
- package/src/platform/startup.ts +17 -3
package/src/platform/shared.ts
CHANGED
|
@@ -25,7 +25,11 @@ import {
|
|
|
25
25
|
import { extractAccessMap } from "../builder/access-extractor";
|
|
26
26
|
import { buildPeerShell, type PeerShellResult } from "../builder/peer-shell";
|
|
27
27
|
import type { FederationConfig } from "@arcote.tech/platform";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
assertOneModulePerPackage,
|
|
30
|
+
packageDeclaresModule,
|
|
31
|
+
planChunks,
|
|
32
|
+
} from "../builder/chunk-planner";
|
|
29
33
|
import { collectFrameworkDeps } from "../builder/dependency-collector";
|
|
30
34
|
import {
|
|
31
35
|
mtimeOf,
|
|
@@ -144,6 +148,11 @@ export function resolveWorkspace(): WorkspaceInfo {
|
|
|
144
148
|
|
|
145
149
|
export interface BuildOptions {
|
|
146
150
|
noCache?: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Minifikacja bundli przeglądarki (domyślnie true — build/deploy).
|
|
153
|
+
* Dev watcher podaje false: czytelny kod w devtools, szybszy rebuild.
|
|
154
|
+
*/
|
|
155
|
+
minify?: boolean;
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
/**
|
|
@@ -157,6 +166,7 @@ export async function buildAll(
|
|
|
157
166
|
): Promise<BuildManifest> {
|
|
158
167
|
const cache = loadBuildCache(ws.arcDir);
|
|
159
168
|
const noCache = opts.noCache ?? false;
|
|
169
|
+
const minify = opts.minify ?? true;
|
|
160
170
|
const themePath = ws.rootPkg.arc?.theme as string | undefined;
|
|
161
171
|
const federation = getFederationConfig(ws);
|
|
162
172
|
|
|
@@ -201,13 +211,20 @@ export async function buildAll(
|
|
|
201
211
|
const moduleNameOf = (name: string) => name.split("/").pop() ?? name;
|
|
202
212
|
const exposureOf = (pkgName: string): "local" | "external" | "both" =>
|
|
203
213
|
accessMap.exposure[moduleNameOf(pkgName)]?.exposure ?? "local";
|
|
214
|
+
// Tylko pakiety DEKLARUJĄCE moduł Arc są członami wchodzącymi do initial/grup.
|
|
215
|
+
// Czyste biblioteki (np. @ndt/email — szablony maili) NIE mogą być tu, bo
|
|
216
|
+
// initial robiłoby `import "@pkg"` i przy `sideEffects:true` (patch buildu)
|
|
217
|
+
// wciągałoby całe ich drzewo zależności do pierwszego wczytania. Taka libka
|
|
218
|
+
// trafia do bundla NORMALNIE — dopiero gdy realnie zaimportuje ją moduł
|
|
219
|
+
// (wtedy podlega tree-shakingowi / lazy-split).
|
|
220
|
+
const modulePackages = ws.packages.filter(packageDeclaresModule);
|
|
204
221
|
// Normalna platforma: moduły local/both (external-only ukryte w macierzystej
|
|
205
222
|
// platformie). Host bierze wszystko (nie ukrywa niczego przed sobą).
|
|
206
223
|
const localPackages = isHost
|
|
207
|
-
?
|
|
208
|
-
:
|
|
224
|
+
? modulePackages
|
|
225
|
+
: modulePackages.filter((p) => exposureOf(p.name) !== "external");
|
|
209
226
|
// Build federowany: moduły external/both → browser-fed/.
|
|
210
|
-
const fedPackages =
|
|
227
|
+
const fedPackages = modulePackages.filter((p) =>
|
|
211
228
|
["external", "both"].includes(exposureOf(p.name)),
|
|
212
229
|
);
|
|
213
230
|
const hasFederatedModules = fedPackages.length > 0;
|
|
@@ -238,31 +255,47 @@ export async function buildAll(
|
|
|
238
255
|
// zawsze external peers) + peer-shell.
|
|
239
256
|
const i18nCollector = new Map<string, Set<string>>();
|
|
240
257
|
|
|
241
|
-
|
|
258
|
+
// Dwa buildy przeglądarkowe (bundled + federated) biegną SZEREGOWO, nie w
|
|
259
|
+
// Promise.all: oba mutują TE SAME package.json (patch sideEffects na czas
|
|
260
|
+
// builda) — przy równoległym biegu `finally` szybszego builda przywraca
|
|
261
|
+
// oryginał, zanim wolniejszy skończy bundlować, i tree-shaking wolniejszego
|
|
262
|
+
// biegnie na złych metadanych. Koszt: +czas builda fed; zysk: deterministyka.
|
|
263
|
+
const browserSequence = (async () => {
|
|
242
264
|
// HOST buduje się BUNDLED (React inline, eager — bez lazy-race), a initial
|
|
243
265
|
// eksponuje runtime na globalThis; gościa obsłuży peer-shell (globalThis).
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
? buildBrowserApp(
|
|
266
|
+
const bundled = await buildBrowserApp(
|
|
267
|
+
ws.rootDir,
|
|
268
|
+
ws.browserDir,
|
|
269
|
+
plan,
|
|
270
|
+
cache,
|
|
271
|
+
noCache,
|
|
272
|
+
i18nCollector,
|
|
273
|
+
{ federated: false, exposeRuntime: isHost, minify },
|
|
274
|
+
);
|
|
275
|
+
const fed = fedPlan
|
|
276
|
+
? await buildBrowserApp(
|
|
256
277
|
ws.rootDir,
|
|
257
278
|
join(ws.arcDir, "browser-fed"),
|
|
258
279
|
fedPlan,
|
|
259
280
|
cache,
|
|
260
281
|
noCache,
|
|
261
282
|
i18nCollector,
|
|
262
|
-
{ federated: true, unitId: "browser-app-fed" },
|
|
283
|
+
{ federated: true, unitId: "browser-app-fed", minify },
|
|
263
284
|
)
|
|
264
|
-
:
|
|
265
|
-
|
|
285
|
+
: undefined;
|
|
286
|
+
return { bundled, fed };
|
|
287
|
+
})();
|
|
288
|
+
|
|
289
|
+
const [{ bundled: browserResult, fed: fedResult }, , , , shellResult] =
|
|
290
|
+
await Promise.all([
|
|
291
|
+
browserSequence,
|
|
292
|
+
buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
|
|
293
|
+
copyBrowserAssets(ws, cache, noCache),
|
|
294
|
+
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache),
|
|
295
|
+
needsPeerShell
|
|
296
|
+
? buildPeerShell(ws.rootDir, join(ws.arcDir, "shell"), cache, noCache)
|
|
297
|
+
: Promise.resolve(undefined),
|
|
298
|
+
]);
|
|
266
299
|
|
|
267
300
|
// Finalize i18n catalogs once after all chunks + initial bundle collected.
|
|
268
301
|
const { finalizeTranslations } = await import("../i18n");
|
|
@@ -493,7 +526,18 @@ async function copyBrowserAssets(
|
|
|
493
526
|
|
|
494
527
|
export async function loadServerContext(
|
|
495
528
|
ws: WorkspaceInfo,
|
|
496
|
-
): Promise<{
|
|
529
|
+
): Promise<{
|
|
530
|
+
context: any | null;
|
|
531
|
+
moduleAccess: Map<string, any>;
|
|
532
|
+
/**
|
|
533
|
+
* Set gdy import bundla serwerowego RZUCIŁ (np. fail-fast na brakującym env).
|
|
534
|
+
* Kontekst jest wtedy CZĘŚCIOWY — część modułów się nie zarejestrowała, więc
|
|
535
|
+
* ich query rzucają `Element not found`. Caller uruchamiający serwer MUSI to
|
|
536
|
+
* potraktować jako twardy błąd startu (patrz startup.ts), inaczej aplikacja
|
|
537
|
+
* wstaje połowiczna i cicho. Build/access-extractor może to zignorować.
|
|
538
|
+
*/
|
|
539
|
+
serverError?: unknown;
|
|
540
|
+
}> {
|
|
497
541
|
// Set globals for server context — framework packages (arc-auth etc.)
|
|
498
542
|
// use these at runtime to tree-shake browser/server code paths.
|
|
499
543
|
(globalThis as any).ONLY_SERVER = true;
|
|
@@ -519,15 +563,18 @@ export async function loadServerContext(
|
|
|
519
563
|
// No build yet (or a static-only workspace) — nothing to register.
|
|
520
564
|
return { context: null, moduleAccess: new Map() };
|
|
521
565
|
}
|
|
566
|
+
let serverError: unknown;
|
|
522
567
|
try {
|
|
523
568
|
await import(serverEntry);
|
|
524
569
|
} catch (e) {
|
|
525
570
|
err(`Failed to load server bundle ${SERVER_ENTRY_FILE}: ${e}`);
|
|
571
|
+
serverError = e;
|
|
526
572
|
}
|
|
527
573
|
|
|
528
574
|
const { getContext, getAllModuleAccess } = await import(platformEntry);
|
|
529
575
|
return {
|
|
530
576
|
context: getContext() ?? null,
|
|
531
577
|
moduleAccess: getAllModuleAccess(),
|
|
578
|
+
serverError,
|
|
532
579
|
};
|
|
533
580
|
}
|
package/src/platform/startup.ts
CHANGED
|
@@ -60,7 +60,7 @@ export async function startPlatform(
|
|
|
60
60
|
// missing manifest is a hard error.
|
|
61
61
|
let manifest: BuildManifest;
|
|
62
62
|
if (devMode) {
|
|
63
|
-
manifest = await buildAll(ws);
|
|
63
|
+
manifest = await buildAll(ws, { minify: false });
|
|
64
64
|
} else {
|
|
65
65
|
const manifestPath = join(ws.arcDir, "manifest.json");
|
|
66
66
|
if (!existsSync(manifestPath)) {
|
|
@@ -88,7 +88,21 @@ export async function startPlatform(
|
|
|
88
88
|
// imports the combined server bundle at .arc/platform/server/_server.js
|
|
89
89
|
// (produced by the buildServerApp step in buildAll).
|
|
90
90
|
log("Loading server context...");
|
|
91
|
-
const { context, moduleAccess } = await loadServerContext(ws);
|
|
91
|
+
const { context, moduleAccess, serverError } = await loadServerContext(ws);
|
|
92
|
+
// TWARDY STOP: import bundla serwerowego rzucił (np. fail-fast na brakującym
|
|
93
|
+
// env). Kontekst jest wtedy CZĘŚCIOWY — część agregatów się nie
|
|
94
|
+
// zarejestrowała, więc ich query rzucałyby `Element not found`, a aplikacja
|
|
95
|
+
// wyglądałaby na działającą (login OK), tyle że połowiczną. Przerywamy start
|
|
96
|
+
// GŁOŚNO, zamiast wstawać po cichu — to była realna produkcyjna pułapka.
|
|
97
|
+
if (serverError) {
|
|
98
|
+
err(
|
|
99
|
+
"Serwer NIE wstanie: moduł serwerowy nie załadował się (błąd wyżej). " +
|
|
100
|
+
"Częściowy kontekst = połowiczna aplikacja (query rzucają " +
|
|
101
|
+
"'Element not found'). Napraw przyczynę (np. brakujące zmienne " +
|
|
102
|
+
"środowiskowe) i wystartuj ponownie.",
|
|
103
|
+
);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
92
106
|
if (context) {
|
|
93
107
|
ok("Context loaded");
|
|
94
108
|
} else {
|
|
@@ -381,7 +395,7 @@ function attachDevWatcher(
|
|
|
381
395
|
isRebuilding = true;
|
|
382
396
|
log("Rebuilding...");
|
|
383
397
|
try {
|
|
384
|
-
const next = await buildAll(ws);
|
|
398
|
+
const next = await buildAll(ws, { minify: false });
|
|
385
399
|
platform.setManifest(next);
|
|
386
400
|
platform.notifyReload(next);
|
|
387
401
|
onReload?.();
|