@antelopejs/dms-frontend 0.8.0 → 0.9.1

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 (50) hide show
  1. package/dist/commands/build-action.js +3 -0
  2. package/dist/commands/dev-action.d.ts +2 -0
  3. package/dist/commands/dev-action.js +12 -5
  4. package/dist/commands/dev.js +3 -1
  5. package/dist/commands/start-action.js +36 -5
  6. package/dist/common.d.ts +1 -0
  7. package/dist/common.js +1 -0
  8. package/dist/config.js +3 -0
  9. package/dist/derived-outputs.d.ts +1 -5
  10. package/dist/derived-outputs.js +46 -33
  11. package/dist/dev-modules.d.ts +5 -0
  12. package/dist/dev-modules.js +88 -0
  13. package/dist/entry-weight.d.ts +24 -0
  14. package/dist/entry-weight.js +86 -0
  15. package/dist/help.js +5 -1
  16. package/dist/layer-watch.js +3 -2
  17. package/dist/layers.d.ts +1 -0
  18. package/dist/locale-files.d.ts +6 -0
  19. package/dist/locale-files.js +75 -0
  20. package/dist/manifest.js +2 -0
  21. package/dist/private-values.d.ts +1 -0
  22. package/dist/private-values.js +16 -0
  23. package/dist/project-modules-child.d.ts +13 -0
  24. package/dist/project-modules-child.js +26 -0
  25. package/dist/project-modules.d.ts +1 -0
  26. package/dist/project-modules.js +73 -38
  27. package/dist/workspace-setup.js +41 -3
  28. package/dist/workspace.d.ts +6 -0
  29. package/package.json +24 -7
  30. package/skills/dms-frontend/SKILL.md +1 -0
  31. package/templates/vue/app-runtime.ts +8 -1
  32. package/templates/vue/entry-weight.mjs +132 -0
  33. package/templates/vue/frontend-build-loader.ts +227 -9
  34. package/templates/vue/frontend-build.ts +34 -1
  35. package/templates/vue/frontend-module.ts +144 -1
  36. package/templates/vue/gitignore +1 -0
  37. package/templates/vue/main.ts +21 -0
  38. package/templates/vue/network-error.ts +3 -1
  39. package/templates/vue/package.json +1 -1
  40. package/templates/vue/patches/@vueuse__core@14.4.0.patch +24 -0
  41. package/templates/vue/pnpm-workspace.yaml +5 -0
  42. package/templates/vue/server/client-manifest.mjs +12 -0
  43. package/templates/vue/server/in-place-render.mjs +110 -0
  44. package/templates/vue/server/page-props.mjs +10 -0
  45. package/templates/vue/server/proxy-transfers.mjs +35 -9
  46. package/templates/vue/server.mjs +39 -29
  47. package/templates/vue/sfc-global-types.mjs +119 -0
  48. package/templates/vue/ssr-renderer.ts +8 -0
  49. package/templates/vue/ui-kbd.mjs +45 -0
  50. package/templates/vue/vite.config.ts +34 -2
@@ -10,6 +10,7 @@ const child_output_1 = require("../child-output");
10
10
  const common_1 = require("../common");
11
11
  const output_1 = require("../output");
12
12
  const vite_output_1 = require("../vite-output");
13
+ const entry_weight_1 = require("../entry-weight");
13
14
  const workspace_task_1 = require("./workspace-task");
14
15
  exports.DEFAULT_WORKSPACE_DIR = ".dms-frontend";
15
16
  function productionWorkspace(options) {
@@ -107,6 +108,7 @@ async function runBuild(options) {
107
108
  }
108
109
  }
109
110
  async function buildProduction(options, startedAt) {
111
+ (0, entry_weight_1.entryWeightThreshold)(process.env);
110
112
  const ui = (0, cli_1.getProcessUi)();
111
113
  const workspace = productionWorkspace(options);
112
114
  (0, output_1.writeHeader)("build", [options.project, options.env]);
@@ -136,6 +138,7 @@ async function buildProduction(options, startedAt) {
136
138
  for (const step of exports.BUILD_STEPS)
137
139
  await runBuildStep(step, context);
138
140
  (0, vite_output_1.reportViteWarnings)(context.warnings, isVerbose, ui);
141
+ (0, entry_weight_1.reportEntryWeight)(ui, workspaceDir, process.env);
139
142
  (0, output_1.writeBlankLine)();
140
143
  ui.summary({
141
144
  headline: "Built the production frontend",
@@ -4,5 +4,7 @@ export interface DevOptions {
4
4
  force?: boolean;
5
5
  offline?: boolean;
6
6
  bootstrapSecret?: string;
7
+ project?: string;
8
+ env?: string;
7
9
  }
8
10
  export declare function runDev(options: DevOptions): Promise<void>;
@@ -14,6 +14,7 @@ const manifest_1 = require("../manifest");
14
14
  const module_set_1 = require("../module-set");
15
15
  const project_modules_1 = require("../project-modules");
16
16
  const workspace_task_1 = require("./workspace-task");
17
+ const BASE_ENVIRONMENT = "default";
17
18
  function clientHost() {
18
19
  const host = process.env.HOST?.trim();
19
20
  if (!host || host === "0.0.0.0" || host === "::" || host === "[::]") {
@@ -41,6 +42,10 @@ async function runDev(options) {
41
42
  const offline = (0, common_1.flagOrEnv)(options.offline, "DMS_OFFLINE");
42
43
  const sessionSecret = (0, common_1.resolveSessionSecret)("dev");
43
44
  const { backendUrl, workspaceKey, projectDir } = resolveBackend(options);
45
+ const project = {
46
+ projectDir: options.project ?? projectDir ?? process.cwd(),
47
+ environment: options.env ?? BASE_ENVIRONMENT,
48
+ };
44
49
  const bootstrapSecret = (0, common_1.resolveBootstrapSecret)(options.bootstrapSecret, backendUrl);
45
50
  const ui = (0, cli_1.getProcessUi)();
46
51
  (0, output_1.writeHeader)("dev", [backendUrl, "development"]);
@@ -58,6 +63,7 @@ async function runDev(options) {
58
63
  clientUrl,
59
64
  workspaceKey,
60
65
  bootstrapSecret,
66
+ project,
61
67
  });
62
68
  if (port !== requestedPort) {
63
69
  ui.message("warn", `Port ${requestedPort} is busy, using ${port}`);
@@ -103,16 +109,17 @@ async function runDev(options) {
103
109
  clientUrl,
104
110
  workspaceKey,
105
111
  bootstrapSecret: session.bootstrapSecret,
112
+ project,
106
113
  });
107
114
  }
108
115
  }
109
- function servedModules(setup) {
116
+ function backendModuleSet(setup) {
110
117
  return (0, manifest_1.readCachedManifest)(setup.workspaceDir)?.manifest.modules ?? [];
111
118
  }
112
119
  function reportServed(session, setup) {
113
- return (0, module_set_1.reportFrontendReady)(session.backendUrl, servedModules(setup).map((module) => ({
114
- name: module.name,
115
- version: module.path ? (0, project_modules_1.readShippingVersion)(module.path) : undefined,
120
+ return (0, module_set_1.reportFrontendReady)(session.backendUrl, setup.layers.map((layer) => ({
121
+ name: layer.name ?? layer.path,
122
+ version: (0, project_modules_1.readShippingVersion)(layer.path),
116
123
  })), session.bootstrapSecret);
117
124
  }
118
125
  async function serveWorkspace(session, setup) {
@@ -126,7 +133,7 @@ async function serveWorkspace(session, setup) {
126
133
  session.bootstrapSecret = session.refreshBootstrapSecret();
127
134
  return session.bootstrapSecret;
128
135
  },
129
- fingerprint: (0, module_set_1.moduleSetFingerprint)(servedModules(setup)),
136
+ fingerprint: (0, module_set_1.moduleSetFingerprint)(backendModuleSet(setup)),
130
137
  onChange: () => restart.abort(),
131
138
  onUnchanged: () => void reportServed(session, setup),
132
139
  });
@@ -50,8 +50,10 @@ const DEV_EXAMPLES = [
50
50
  function cmdDev() {
51
51
  const command = new commander_1.Command("dev")
52
52
  .summary("Run the dev server against a local backend, with hot reload")
53
- .description("Run the dev server with hot reload. It reads the frontend modules in place, from the backend's own directories, so the backend runs on this machine.")
53
+ .description("Run the dev server with hot reload. It reads the frontend modules from the AntelopeJS project as build does, each module's frontendModule called with dev: true, and serves them in place from their sources. The backend, on this machine, gives the secrets it holds and says when the module store changes its modules.")
54
54
  .addOption(config_1.Options.backendUrl("Backend URL; when omitted, discovered from the enclosing antelope project's .antelope/dev.json"))
55
+ .addOption(new commander_1.Option("--project <dir>", "The AntelopeJS project the modules are read from (default: the discovered backend's, else the current directory)"))
56
+ .addOption(new commander_1.Option("-e, --env <environment>", "The AntelopeJS environment the backend runs, as ajs project dev -e (default: the base configuration)").env("ANTELOPEJS_LAUNCH_ENV"))
55
57
  .addOption(config_1.Options.port)
56
58
  .addOption(config_1.Options.force("Reinstall the workspace dependencies"))
57
59
  .addOption(config_1.Options.offline("Reuse the last cached manifest instead of fetching it"))
@@ -61,11 +61,40 @@ function publicOptionsById(modules) {
61
61
  ];
62
62
  }));
63
63
  }
64
+ function builtModules(workspaceDir) {
65
+ try {
66
+ const { modules } = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(workspaceDir, "generated-frontend-modules.json"), "utf8"));
67
+ return (modules ?? []).map((module) => ({
68
+ name: module.name ?? module.id,
69
+ version: module.version,
70
+ }));
71
+ }
72
+ catch {
73
+ return [];
74
+ }
75
+ }
76
+ function reportModuleSetDrift(built, modules) {
77
+ const builtNames = new Set(built.map(({ name }) => name));
78
+ const currentNames = new Set(modules.map(({ layer }) => layer.name ?? layer.path));
79
+ const added = [...currentNames].filter((name) => !builtNames.has(name));
80
+ const removed = [...builtNames].filter((name) => !currentNames.has(name));
81
+ if (added.length === 0 && removed.length === 0)
82
+ return;
83
+ const ui = (0, cli_1.getProcessUi)();
84
+ ui.message("warn", "The project's modules differ from the ones the build serves: rebuild to apply them", {
85
+ details: [
86
+ ...(added.length > 0
87
+ ? [`Not in the build: ${added.sort().join(", ")}`]
88
+ : []),
89
+ ...(removed.length > 0
90
+ ? [`Built, no longer in the project: ${removed.sort().join(", ")}`]
91
+ : []),
92
+ `${ui.symbols.levels.hint} Build again with the deployment's variables: ajs dms build`,
93
+ ],
94
+ });
95
+ }
64
96
  async function reportServedModules(backendUrl, modules) {
65
- const isTaken = await (0, module_set_1.reportFrontendReadyOnceAnswered)(backendUrl, modules.map(({ layer, version }) => ({
66
- name: layer.name ?? layer.path,
67
- version,
68
- })), process.env.DMS_BOOTSTRAP_SECRET);
97
+ const isTaken = await (0, module_set_1.reportFrontendReadyOnceAnswered)(backendUrl, modules, process.env.DMS_BOOTSTRAP_SECRET);
69
98
  const ui = (0, cli_1.getProcessUi)();
70
99
  if (isTaken) {
71
100
  ui.message("info", `[DMS] Reported ${modules.length} modules to the backend (dms-frontend ${module_set_1.OWN_RELEASE.version})`);
@@ -116,6 +145,8 @@ async function runStart(options) {
116
145
  projectDir: options.project,
117
146
  environment: options.env,
118
147
  });
148
+ const built = builtModules(workspaceDir);
149
+ reportModuleSetDrift(built, modules);
119
150
  const secrets = (0, common_1.resolveManifestSecrets)({ published: {}, conflicts: [] });
120
151
  (0, common_1.reportManifestSecrets)(secrets, (count) => `Set ${count === 1 ? "it" : "them"} in the server's environment, with the value${count === 1 ? "" : "s"} the backend uses`);
121
152
  reportUnfilledPrivateValues(workspaceDir);
@@ -148,7 +179,7 @@ async function runStart(options) {
148
179
  ]),
149
180
  footer: "Ctrl+C to stop",
150
181
  });
151
- void reportServedModules(backendUrl, modules);
182
+ void reportServedModules(backendUrl, built);
152
183
  void (0, update_check_1.reportAvailableUpdate)({ isFollowed: true });
153
184
  },
154
185
  });
package/dist/common.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from "./manifest-secrets";
8
8
  export * from "./layer-watch";
9
9
  export * from "./materialize";
10
10
  export * from "./derived-outputs";
11
+ export * from "./locale-files";
11
12
  export * from "./app-configs";
12
13
  export * from "./renderer-range";
13
14
  export * from "./workspace";
package/dist/common.js CHANGED
@@ -24,6 +24,7 @@ __exportStar(require("./manifest-secrets"), exports);
24
24
  __exportStar(require("./layer-watch"), exports);
25
25
  __exportStar(require("./materialize"), exports);
26
26
  __exportStar(require("./derived-outputs"), exports);
27
+ __exportStar(require("./locale-files"), exports);
27
28
  __exportStar(require("./app-configs"), exports);
28
29
  __exportStar(require("./renderer-range"), exports);
29
30
  __exportStar(require("./workspace"), exports);
package/dist/config.js CHANGED
@@ -63,11 +63,14 @@ exports.TEMPLATE_FILES = [
63
63
  ["compress-assets.mjs", "compress-assets.mjs"],
64
64
  ["head-order.mjs", "head-order.mjs"],
65
65
  ["client-chunking.mjs", "client-chunking.mjs"],
66
+ ["entry-weight.mjs", "entry-weight.mjs"],
66
67
  ["client-server-guard.mjs", "client-server-guard.mjs"],
67
68
  ["ui-inertia-page.mjs", "ui-inertia-page.mjs"],
68
69
  ["ui-request-state.mjs", "ui-request-state.mjs"],
70
+ ["sfc-global-types.mjs", "sfc-global-types.mjs"],
69
71
  ["icon-api.mjs", "icon-api.mjs"],
70
72
  ["icon-scan.mjs", "icon-scan.mjs"],
73
+ ["ui-kbd.mjs", "ui-kbd.mjs"],
71
74
  ["email-renderer.ts", "email-renderer.ts"],
72
75
  ["email-runtime.ts", "email-runtime.ts"],
73
76
  ["email-locales.ts", "email-locales.ts"],
@@ -3,6 +3,7 @@ export interface FrontendModuleRegistryEntry {
3
3
  id: string;
4
4
  name?: string;
5
5
  packageName?: string;
6
+ version?: string;
6
7
  root: string;
7
8
  priority: number;
8
9
  entry?: string;
@@ -14,11 +15,6 @@ export interface FrontendModuleRegistry {
14
15
  modules: FrontendModuleRegistryEntry[];
15
16
  }
16
17
  export declare function createFrontendModuleRegistry(workspaceDir: string, layers: ResolvedLayer[]): FrontendModuleRegistry;
17
- export declare class InvalidLocaleFileError extends Error {
18
- readonly path: string;
19
- readonly reason: string;
20
- constructor(path: string, reason: string);
21
- }
22
18
  export declare function affectsLocaleMessages(relativePath: string): boolean;
23
19
  export declare function writeLocaleMessages(workspaceDir: string, registry: FrontendModuleRegistry): boolean;
24
20
  export interface DerivedOutput {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DERIVED_OUTPUTS = exports.InvalidLocaleFileError = void 0;
3
+ exports.DERIVED_OUTPUTS = void 0;
4
4
  exports.createFrontendModuleRegistry = createFrontendModuleRegistry;
5
5
  exports.affectsLocaleMessages = affectsLocaleMessages;
6
6
  exports.writeLocaleMessages = writeLocaleMessages;
@@ -8,9 +8,11 @@ const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = require("node:path");
9
9
  const config_1 = require("./config");
10
10
  const app_configs_1 = require("./app-configs");
11
+ const locale_files_1 = require("./locale-files");
11
12
  const asset_roots_1 = require("./asset-roots");
12
13
  const layer_aliases_1 = require("./layer-aliases");
13
14
  const layers_1 = require("./layers");
15
+ const project_modules_1 = require("./project-modules");
14
16
  const fs_sync_1 = require("./fs-sync");
15
17
  const GENERATED_MODULE_ID = /^[A-Za-z0-9_-]+$/;
16
18
  function validateFrontendModuleRegistry(registry, workspaceDir) {
@@ -42,6 +44,7 @@ function createFrontendModuleRegistry(workspaceDir, layers) {
42
44
  id: (0, layers_1.getLayerSafeName)(layer),
43
45
  name: layer.name ?? layer.packageName,
44
46
  packageName: layer.packageName,
47
+ version: (0, project_modules_1.readShippingVersion)(layer.sourcePath ?? layer.path),
45
48
  root: (0, layers_1.getLayerWorkspacePath)(workspaceDir, layer),
46
49
  priority: layer.priority ?? 0,
47
50
  entry: (0, node_fs_1.existsSync)(sourceEntry) ? config_1.FRONTEND_MODULE_ENTRY : undefined,
@@ -148,24 +151,6 @@ function mergeLocaleMessages(source, existing) {
148
151
  return merged;
149
152
  }
150
153
  const LOCALE_DIRECTORY = "i18n/locales";
151
- class InvalidLocaleFileError extends Error {
152
- constructor(path, reason) {
153
- super(`Invalid locale file ${path}: ${reason}`);
154
- this.path = path;
155
- this.reason = reason;
156
- this.name = "InvalidLocaleFileError";
157
- }
158
- }
159
- exports.InvalidLocaleFileError = InvalidLocaleFileError;
160
- function readLocaleFile(path) {
161
- try {
162
- return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
163
- }
164
- catch (error) {
165
- const reason = error instanceof Error ? error.message : String(error);
166
- throw new InvalidLocaleFileError(path, reason);
167
- }
168
- }
169
154
  function readLocaleMessages(assets, registry) {
170
155
  const roots = new Map(registry.modules.map((module) => [module.id, module.root]));
171
156
  const messages = {};
@@ -174,7 +159,7 @@ function readLocaleMessages(assets, registry) {
174
159
  const root = roots.get(asset.moduleId);
175
160
  if (!locale || !root)
176
161
  continue;
177
- const value = readLocaleFile((0, node_path_1.join)(root, asset.relativePath));
162
+ const value = (0, locale_files_1.readLocaleFile)((0, node_path_1.join)(root, asset.relativePath));
178
163
  messages[locale] = mergeLocaleMessages(value, messages[locale] ?? {});
179
164
  }
180
165
  return messages;
@@ -182,13 +167,28 @@ function readLocaleMessages(assets, registry) {
182
167
  function affectsLocaleMessages(relativePath) {
183
168
  return (0, asset_roots_1.affectsAssetDirectory)(LOCALE_DIRECTORY, relativePath);
184
169
  }
170
+ const FALLBACK_LOCALE = "en";
171
+ const RENDERER_TRANSLATED_KEYS = ["error.network"];
172
+ function withoutMessage(messages, key) {
173
+ const [head, ...rest] = key.split(".");
174
+ if (!(head in messages))
175
+ return messages;
176
+ const { [head]: value, ...others } = messages;
177
+ if (!rest.length)
178
+ return others;
179
+ if (!isRecord(value))
180
+ return messages;
181
+ const remaining = withoutMessage(value, rest.join("."));
182
+ return Object.keys(remaining).length
183
+ ? { ...others, [head]: remaining }
184
+ : others;
185
+ }
185
186
  function localeModuleSource(supportedLocales) {
186
- const loaders = supportedLocales
187
- .filter((locale) => locale !== "en")
188
- .map((locale) => ` ${JSON.stringify(locale)}: () => import(${JSON.stringify(`./locales.generated/${locale}`)}),`);
189
- return `import defaultMessages from "./locales.generated/en";
190
-
191
- type LocaleMessages = Record<string, unknown>;
187
+ const loaders = supportedLocales.map((locale) => ` ${JSON.stringify(locale)}: () => import(${JSON.stringify(`./locales.generated/${locale}`)}),`);
188
+ const fallback = supportedLocales.includes(FALLBACK_LOCALE)
189
+ ? FALLBACK_LOCALE
190
+ : supportedLocales[0];
191
+ return `type LocaleMessages = Record<string, unknown>;
192
192
  type LocaleLoader = () => Promise<{ default: LocaleMessages }>;
193
193
  interface LocaleMessagesTarget {
194
194
  setLocaleMessage(locale: string, messages: LocaleMessages): void;
@@ -200,13 +200,13 @@ interface LocaleState {
200
200
  }
201
201
 
202
202
  export const supportedLocales = ${JSON.stringify(supportedLocales)};
203
+ const fallbackLocale: string | undefined = ${JSON.stringify(fallback)};
203
204
  const localeLoaders: Record<string, LocaleLoader> = {
204
- "en": async () => ({ default: defaultMessages }),
205
205
  ${loaders.join("\n")}
206
206
  };
207
207
  const shared: LocaleState | undefined = import.meta.hot?.data.state;
208
208
  const state: LocaleState = shared ?? {
209
- messages: { en: defaultMessages },
209
+ messages: {},
210
210
  targets: new Set(),
211
211
  version: 0,
212
212
  };
@@ -216,11 +216,20 @@ async function readLocaleMessages(locale: string): Promise<LocaleMessages> {
216
216
  return (await localeLoaders[locale]()).default;
217
217
  }
218
218
 
219
- export async function loadLocaleMessages(locale: string): Promise<LocaleMessages> {
219
+ /**
220
+ * The catalog that serves \`locale\`: its own when the application has one,
221
+ * else the fallback locale's.
222
+ */
223
+ export function catalogLocale(locale: string): string | undefined {
220
224
  const normalized = locale.slice(0, 2);
221
- if (!localeLoaders[normalized]) return localeMessages.en;
222
- localeMessages[normalized] ??= await readLocaleMessages(normalized);
223
- return localeMessages[normalized];
225
+ return normalized in localeLoaders ? normalized : fallbackLocale;
226
+ }
227
+
228
+ export async function loadLocaleMessages(locale: string): Promise<LocaleMessages> {
229
+ const catalog = catalogLocale(locale);
230
+ if (!catalog) return {};
231
+ localeMessages[catalog] ??= await readLocaleMessages(catalog);
232
+ return localeMessages[catalog];
224
233
  }
225
234
 
226
235
  /** Development only: applies every regenerated catalog to \`target\`. */
@@ -262,10 +271,14 @@ function writeLocaleMessages(workspaceDir, registry) {
262
271
  changed = (0, fs_sync_1.applyContent)(content, path) || changed;
263
272
  };
264
273
  (0, node_fs_1.mkdirSync)(outputDir, { recursive: true });
274
+ const fallback = RENDERER_TRANSLATED_KEYS.reduce(withoutMessage, messages[FALLBACK_LOCALE] ?? {});
265
275
  for (const [locale, value] of Object.entries(messages)) {
266
276
  const catalog = JSON.stringify(value);
277
+ const complete = locale === FALLBACK_LOCALE
278
+ ? catalog
279
+ : JSON.stringify(mergeLocaleMessages(fallback, value));
267
280
  apply(catalog, (0, node_path_1.join)(outputDir, `${locale}.json`));
268
- apply(`const messages = ${catalog};\nexport default messages;\n`, (0, node_path_1.join)(outputDir, `${locale}.ts`));
281
+ apply(`const messages = ${complete};\nexport default messages;\n`, (0, node_path_1.join)(outputDir, `${locale}.ts`));
269
282
  outputs.add(`${locale}.json`).add(`${locale}.ts`);
270
283
  }
271
284
  const supportedLocales = Object.keys(messages);
@@ -0,0 +1,5 @@
1
+ import type { ProjectFrontendModule, ProjectModulesOptions } from "./project-modules";
2
+ import type { Manifest, ResolvedLayer } from "./workspace";
3
+ export declare function readProjectModulesFresh(options: ProjectModulesOptions): Promise<ProjectFrontendModule[]>;
4
+ export declare function warnModuleDrift(layers: readonly ResolvedLayer[], manifest: Manifest): void;
5
+ export declare function withBackendValues(modules: readonly ProjectFrontendModule[], manifest: Manifest): ResolvedLayer[];
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readProjectModulesFresh = readProjectModulesFresh;
4
+ exports.warnModuleDrift = warnModuleDrift;
5
+ exports.withBackendValues = withBackendValues;
6
+ const node_child_process_1 = require("node:child_process");
7
+ const cli_1 = require("@antelopejs/core/cli");
8
+ const private_values_1 = require("./private-values");
9
+ function readProjectModulesFresh(options) {
10
+ return new Promise((resolve, reject) => {
11
+ const child = (0, node_child_process_1.fork)(require.resolve("./project-modules-child"), [], {
12
+ stdio: ["ignore", "inherit", "inherit", "ipc"],
13
+ });
14
+ let reply;
15
+ child.once("message", (message) => {
16
+ reply = message;
17
+ });
18
+ child.once("error", reject);
19
+ child.once("exit", (code, signal) => {
20
+ if (!reply) {
21
+ reject(new Error(`Reading the project's modules stopped (${signal ?? `exit code ${code}`})`));
22
+ return;
23
+ }
24
+ const ui = (0, cli_1.getProcessUi)();
25
+ for (const message of reply.messages)
26
+ ui.message(...message);
27
+ if ("modules" in reply)
28
+ resolve(reply.modules);
29
+ else if ("problem" in reply)
30
+ reject(new cli_1.CliError(reply.problem));
31
+ else
32
+ reject(new Error(reply.failure));
33
+ });
34
+ child.send(options);
35
+ });
36
+ }
37
+ const URL_BOUNDARY = "(?=[/?#]|$)";
38
+ function escapeRegExp(value) {
39
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
40
+ }
41
+ function rewriteOrigins(options, rewrites) {
42
+ if (rewrites.length === 0)
43
+ return options;
44
+ const replacements = new Map(rewrites.map(({ from, to }) => [from, to]));
45
+ const alternation = [...replacements.keys()]
46
+ .sort((left, right) => right.length - left.length)
47
+ .map(escapeRegExp)
48
+ .join("|");
49
+ const pattern = new RegExp(`(?:${alternation})${URL_BOUNDARY}`, "g");
50
+ const rewrite = (value) => {
51
+ if (typeof value === "string")
52
+ return value.replace(pattern, (match) => replacements.get(match) ?? match);
53
+ if (Array.isArray(value))
54
+ return value.map(rewrite);
55
+ if (value !== null && typeof value === "object")
56
+ return rewriteObject(value);
57
+ return value;
58
+ };
59
+ const rewriteObject = (value) => Object.fromEntries(Object.entries(value).map(([key, item]) => [key, rewrite(item)]));
60
+ return rewriteObject(options);
61
+ }
62
+ function warnModuleDrift(layers, manifest) {
63
+ const served = new Set(layers.map((layer) => layer.name ?? ""));
64
+ const registered = new Set(manifest.modules.map((module) => module.name));
65
+ const ui = (0, cli_1.getProcessUi)();
66
+ for (const name of served) {
67
+ if (!registered.has(name))
68
+ ui.message("warn", `${name} is served from the project, but the backend does not register it`, {
69
+ detail: "Its frontendModule returns a registration the running backend did not add: check the module's configuration and startup",
70
+ });
71
+ }
72
+ for (const name of registered) {
73
+ if (!served.has(name))
74
+ ui.message("warn", `${name} is registered by the backend, but not in the project: its pages are left out`, {
75
+ detail: `A build leaves it out too. Declare its frontendModule (dmsFrontend in its package.json), or add the module to antelope.config.ts`,
76
+ });
77
+ }
78
+ }
79
+ function withBackendValues(modules, manifest) {
80
+ const registered = new Map(manifest.modules.map((module) => [module.name, module]));
81
+ return modules.map(({ layer }) => ({
82
+ ...layer,
83
+ options: layer.options &&
84
+ rewriteOrigins(layer.options, manifest.originRewrites ?? []),
85
+ privateOptions: layer.privateOptions &&
86
+ (0, private_values_1.filledOptions)(layer.privateOptions, registered.get(layer.name ?? "")?.privateOptions),
87
+ }));
88
+ }
@@ -0,0 +1,24 @@
1
+ export declare const ENTRY_WEIGHT_VARIABLE = "DMS_ENTRY_WEIGHT_WARNING";
2
+ export declare const DEFAULT_ENTRY_WEIGHT_WARNING_KB = 50;
3
+ export interface EntryShare {
4
+ owner: string;
5
+ bytes: number;
6
+ brotli: number;
7
+ }
8
+ export interface EntryWeight {
9
+ bytes: number;
10
+ brotli: number;
11
+ shares: EntryShare[];
12
+ }
13
+ export declare function readEntryWeight(workspaceDir: string): EntryWeight | undefined;
14
+ export declare function entryWeightThreshold(env: NodeJS.ProcessEnv): number;
15
+ export declare function heavyEntryModules(weight: EntryWeight, threshold: number): EntryShare[];
16
+ export declare function describeEntryShares(weight: EntryWeight): string[];
17
+ interface ReportUi {
18
+ message(level: "info" | "warn", text: string, options?: {
19
+ detail?: string;
20
+ details?: string[];
21
+ }): void;
22
+ }
23
+ export declare function reportEntryWeight(ui: ReportUi, workspaceDir: string, env: NodeJS.ProcessEnv): void;
24
+ export {};
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_ENTRY_WEIGHT_WARNING_KB = exports.ENTRY_WEIGHT_VARIABLE = void 0;
4
+ exports.readEntryWeight = readEntryWeight;
5
+ exports.entryWeightThreshold = entryWeightThreshold;
6
+ exports.heavyEntryModules = heavyEntryModules;
7
+ exports.describeEntryShares = describeEntryShares;
8
+ exports.reportEntryWeight = reportEntryWeight;
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const node_zlib_1 = require("node:zlib");
12
+ const cli_1 = require("@antelopejs/core/cli");
13
+ const output_1 = require("./output");
14
+ exports.ENTRY_WEIGHT_VARIABLE = "DMS_ENTRY_WEIGHT_WARNING";
15
+ exports.DEFAULT_ENTRY_WEIGHT_WARNING_KB = 50;
16
+ const ENTRY_WEIGHT_FILE = (0, node_path_1.join)("dist", "client", ".vite", "entry-weight.json");
17
+ const SHARED_OWNER = "shared";
18
+ const TRANSLATIONS_OWNER = "translations";
19
+ const NON_MODULE_OWNERS = new Set([SHARED_OWNER, TRANSLATIONS_OWNER]);
20
+ const BYTES_PER_KB = 1024;
21
+ function brotliSize(file) {
22
+ const compressed = `${file}.br`;
23
+ if ((0, node_fs_1.existsSync)(compressed))
24
+ return (0, node_fs_1.statSync)(compressed).size;
25
+ return (0, node_zlib_1.brotliCompressSync)((0, node_fs_1.readFileSync)(file), {
26
+ params: { [node_zlib_1.constants.BROTLI_PARAM_QUALITY]: node_zlib_1.constants.BROTLI_MAX_QUALITY },
27
+ }).length;
28
+ }
29
+ function readEntryWeight(workspaceDir) {
30
+ const path = (0, node_path_1.join)(workspaceDir, ENTRY_WEIGHT_FILE);
31
+ if (!(0, node_fs_1.existsSync)(path))
32
+ return undefined;
33
+ const recorded = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
34
+ const brotli = recorded.files.reduce((sum, file) => sum + brotliSize((0, node_path_1.join)(workspaceDir, "dist", "client", file)), 0);
35
+ const ratio = recorded.bytes ? brotli / recorded.bytes : 0;
36
+ const shares = Object.entries(recorded.owners)
37
+ .map(([owner, bytes]) => ({
38
+ owner,
39
+ bytes,
40
+ brotli: Math.round(bytes * ratio),
41
+ }))
42
+ .sort((left, right) => right.bytes - left.bytes);
43
+ return { bytes: recorded.bytes, brotli, shares };
44
+ }
45
+ function entryWeightThreshold(env) {
46
+ const value = env[exports.ENTRY_WEIGHT_VARIABLE]?.trim();
47
+ if (!value)
48
+ return exports.DEFAULT_ENTRY_WEIGHT_WARNING_KB * BYTES_PER_KB;
49
+ const kilobytes = Number(value);
50
+ if (!Number.isFinite(kilobytes) || kilobytes < 0)
51
+ throw new cli_1.CliError({
52
+ title: `${exports.ENTRY_WEIGHT_VARIABLE} is not a size`,
53
+ reason: `It is ${JSON.stringify(value)}; it takes a number of KB of Brotli.`,
54
+ fixes: [
55
+ `Set ${exports.ENTRY_WEIGHT_VARIABLE}=${exports.DEFAULT_ENTRY_WEIGHT_WARNING_KB}, or 0 to turn the warning off`,
56
+ ],
57
+ });
58
+ return kilobytes * BYTES_PER_KB;
59
+ }
60
+ function heavyEntryModules(weight, threshold) {
61
+ if (threshold <= 0)
62
+ return [];
63
+ return weight.shares.filter((share) => !NON_MODULE_OWNERS.has(share.owner) && share.brotli > threshold);
64
+ }
65
+ function describeEntryShares(weight) {
66
+ return weight.shares.map((share) => `${share.owner} ${(0, output_1.formatSize)(share.brotli)} (${(0, output_1.formatSize)(share.bytes)})`);
67
+ }
68
+ function reportEntryWeight(ui, workspaceDir, env) {
69
+ const threshold = entryWeightThreshold(env);
70
+ const weight = readEntryWeight(workspaceDir);
71
+ if (!weight)
72
+ return;
73
+ ui.message("info", `Client entry ${(0, output_1.formatSize)(weight.brotli)} Brotli (${(0, output_1.formatSize)(weight.bytes)}), loaded first by every page`, { details: describeEntryShares(weight) });
74
+ const heavy = heavyEntryModules(weight, threshold);
75
+ heavy.forEach((share, index) => {
76
+ const isLast = index === heavy.length - 1;
77
+ ui.message("warn", `${share.owner} adds ${(0, output_1.formatSize)(share.brotli)} Brotli to the client entry`, {
78
+ details: isLast
79
+ ? [
80
+ "What a module's dms.frontend.ts and its plugins import is loaded by every page: render a component they only show on some pages by its registered name or with defineAsyncComponent",
81
+ `The threshold is ${(0, output_1.formatSize)(threshold)} (${exports.ENTRY_WEIGHT_VARIABLE}, in KB; 0 turns the warning off)`,
82
+ ]
83
+ : [],
84
+ });
85
+ });
86
+ }
package/dist/help.js CHANGED
@@ -17,9 +17,13 @@ exports.ENVIRONMENT_VARIABLES = [
17
17
  ["DMS_WORKSPACE_DIR", "Same as build and start --workspace"],
18
18
  [
19
19
  "ANTELOPEJS_LAUNCH_ENV",
20
- "AntelopeJS environment build and start evaluate the project in, same as -e",
20
+ "AntelopeJS environment dev, build and start evaluate the project in, same as -e",
21
21
  ],
22
22
  ["DMS_PREPARE_STRICT", "Same as prepare --strict, read like DMS_OFFLINE"],
23
+ [
24
+ "DMS_ENTRY_WEIGHT_WARNING",
25
+ "KB of Brotli a module may add to the client entry before build warns (default: 50; 0: never)",
26
+ ],
23
27
  ["PORT", "Same as -p"],
24
28
  [
25
29
  "HOST",
@@ -12,6 +12,7 @@ const child_output_1 = require("./child-output");
12
12
  const config_1 = require("./config");
13
13
  const layers_1 = require("./layers");
14
14
  const derived_outputs_1 = require("./derived-outputs");
15
+ const locale_files_1 = require("./locale-files");
15
16
  const output_1 = require("./output");
16
17
  const fs_sync_1 = require("./fs-sync");
17
18
  const WATCH_DEBOUNCE_MS = 80;
@@ -128,11 +129,11 @@ function createLayerSync(src, dest, reporter, onStaged) {
128
129
  }
129
130
  function describeOutputFailure(output, err, reporter) {
130
131
  const kept = `The dev server keeps the previous ${output.name} until the file is fixed.`;
131
- if (err instanceof derived_outputs_1.InvalidLocaleFileError) {
132
+ if (err instanceof locale_files_1.InvalidLocaleFileError) {
132
133
  const file = reporter.show(err.path);
133
134
  return {
134
135
  text: `${file} is not a valid locale file`,
135
- options: { details: [err.reason, kept] },
136
+ options: { details: [...err.reason.split("\n"), kept] },
136
137
  fixed: [`${file} fixed`, `${output.name} regenerated`].join((0, cli_1.getProcessUi)().symbols.separator),
137
138
  };
138
139
  }
package/dist/layers.d.ts CHANGED
@@ -11,6 +11,7 @@ export interface LayerPackage {
11
11
  name?: unknown;
12
12
  engines?: Record<string, unknown>;
13
13
  dmsFrontend?: unknown;
14
+ antelopeJs?: unknown;
14
15
  }
15
16
  export declare function readLayerPackage(layerPath: string): LayerPackage | undefined;
16
17
  export declare function buildLayersFromPaths(modules: ServedWithPath[]): ResolvedLayer[];
@@ -0,0 +1,6 @@
1
+ export declare class InvalidLocaleFileError extends Error {
2
+ readonly path: string;
3
+ readonly reason: string;
4
+ constructor(path: string, reason: string);
5
+ }
6
+ export declare function readLocaleFile(path: string): Record<string, unknown>;