@moku-labs/web 2.3.3 → 2.4.0

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.cjs CHANGED
@@ -2357,10 +2357,11 @@ const CSS_EXTERNAL_FONT_GLOBS = [
2357
2357
  * @param options.naming.chunk - Naming template for lazy split chunks.
2358
2358
  * @param options.naming.asset - Naming template for additional emitted assets.
2359
2359
  * @param options.external - Import/url() globs left unresolved in the output.
2360
+ * @param options.define - Global names replaced with constant expressions.
2360
2361
  * @returns The structural build result.
2361
2362
  * @example
2362
2363
  * ```ts
2363
- * await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [] });
2364
+ * await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [], define: {} });
2364
2365
  * ```
2365
2366
  */
2366
2367
  async function defaultRunner(options) {
@@ -2372,6 +2373,33 @@ async function defaultRunner(options) {
2372
2373
  return bun.build(options);
2373
2374
  }
2374
2375
  /**
2376
+ * The bundle-time constants for the `build.env` names. Each name becomes `process.env.<NAME>` and
2377
+ * `import.meta.env.<NAME>`, and the whole `import.meta.env` object is set too, because
2378
+ * `browserEnv()` spreads it. The value is the build process's own variable, or `""` when unset.
2379
+ * With `minify`, a branch on an unset flag is removed together with the dynamic `import()` chunk
2380
+ * it guards, so developer-only code never ships in a build without the flag.
2381
+ *
2382
+ * @param names - The `build.env` names.
2383
+ * @param source - Where the values come from; the build process's environment by default.
2384
+ * @returns The `define` map for `Bun.build`; empty without names.
2385
+ * @example
2386
+ * ```ts
2387
+ * envDefines(["IS_DEVELOPMENT"], { IS_DEVELOPMENT: "true" });
2388
+ * // { "process.env.IS_DEVELOPMENT": "\"true\"", "import.meta.env.IS_DEVELOPMENT": "\"true\"",
2389
+ * // "import.meta.env": "{\"IS_DEVELOPMENT\":\"true\"}" }
2390
+ * ```
2391
+ */
2392
+ function envDefines(names, source = process.env) {
2393
+ if (names.length === 0) return {};
2394
+ const values = Object.fromEntries(names.map((name) => [name, source[name] ?? ""]));
2395
+ const define = { "import.meta.env": JSON.stringify(values) };
2396
+ for (const [name, value] of Object.entries(values)) {
2397
+ define[`process.env.${name}`] = JSON.stringify(value);
2398
+ define[`import.meta.env.${name}`] = JSON.stringify(value);
2399
+ }
2400
+ return define;
2401
+ }
2402
+ /**
2375
2403
  * Resolve the first existing entry file from a candidate list (project-relative).
2376
2404
  *
2377
2405
  * @param candidates - Ordered candidate paths to probe.
@@ -2440,12 +2468,13 @@ function normalizeAssetPath(absolutePath, outDir) {
2440
2468
  * @param outDir - The publish root; stored asset paths are made relative to it.
2441
2469
  * @param outdir - The bundler output directory (`<outDir>/assets`).
2442
2470
  * @param minify - Whether to minify.
2471
+ * @param define - Global names replaced with constant expressions.
2443
2472
  * @example
2444
2473
  * ```ts
2445
- * await runOne(ctx, runner, "css", ["a.css"], "dist", true);
2474
+ * await runOne(ctx, runner, "css", ["a.css"], "dist", true, {});
2446
2475
  * ```
2447
2476
  */
2448
- async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify) {
2477
+ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify, define) {
2449
2478
  if (entrypoints.length === 0) return;
2450
2479
  const result = await runner({
2451
2480
  entrypoints,
@@ -2454,7 +2483,8 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify) {
2454
2483
  splitting: true,
2455
2484
  target: "browser",
2456
2485
  naming: FINGERPRINT_NAMING,
2457
- external: kind === "css" ? [...CSS_EXTERNAL_FONT_GLOBS] : []
2486
+ external: kind === "css" ? [...CSS_EXTERNAL_FONT_GLOBS] : [],
2487
+ define
2458
2488
  });
2459
2489
  if (!result.success) throw new Error(`[web] build.bundle ${kind} build failed`);
2460
2490
  const hashed = {};
@@ -2489,10 +2519,11 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify) {
2489
2519
  async function bundle(ctx, options = {}) {
2490
2520
  const runner = options.runner ?? defaultRunner;
2491
2521
  const { minify, outDir } = ctx.config;
2522
+ const define = envDefines(ctx.config.env ?? []);
2492
2523
  const assetsDir = node_path$1.default.join(outDir, "assets");
2493
2524
  const cssEntrypoints = options.cssEntrypoints ?? resolveEntrypoints(CSS_ENTRY_CANDIDATES);
2494
2525
  const jsEntrypoints = options.jsEntrypoints ?? resolveJsEntrypoints(ctx);
2495
- await Promise.all([runOne(ctx, runner, "css", cssEntrypoints, outDir, assetsDir, minify), runOne(ctx, runner, "js", jsEntrypoints, outDir, assetsDir, minify)]);
2526
+ await Promise.all([runOne(ctx, runner, "css", cssEntrypoints, outDir, assetsDir, minify, define), runOne(ctx, runner, "js", jsEntrypoints, outDir, assetsDir, minify, define)]);
2496
2527
  }
2497
2528
  //#endregion
2498
2529
  //#region src/plugins/build/phases/asset-tags.ts
@@ -5821,6 +5852,22 @@ function validateFonts(og) {
5821
5852
  if (isMissingFontDir(og.fontDir)) throw new Error(`${ERROR_PREFIX$7}.ogImage: fontDir "${og.fontDir}" does not exist — provide a directory with at least one font.`);
5822
5853
  if (!(0, node_fs.readdirSync)(og.fontDir).some((name) => FONT_EXTENSIONS.some((extension) => name.endsWith(extension)))) throw new Error(`${ERROR_PREFIX$7}.ogImage: fontDir "${og.fontDir}" contains no .ttf/.otf/.woff font files.`);
5823
5854
  }
5855
+ /** An environment variable name: letters, digits and `_`, not starting with a digit. */
5856
+ const ENV_NAME = /^[A-Z_a-z]\w*$/;
5857
+ /**
5858
+ * Whether a `build.env` value lists environment variable names.
5859
+ *
5860
+ * @param value - The configured `build.env`.
5861
+ * @returns True for an array of valid names.
5862
+ * @example
5863
+ * ```ts
5864
+ * isEnvNameList(["IS_DEVELOPMENT"]); // true
5865
+ * isEnvNameList(["process.env.X"]); // false
5866
+ * ```
5867
+ */
5868
+ function isEnvNameList(value) {
5869
+ return Array.isArray(value) && value.every((name) => typeof name === "string" && ENV_NAME.test(name));
5870
+ }
5824
5871
  /**
5825
5872
  * Validates `build` config synchronously in `onInit` (return value discarded).
5826
5873
  * Throws an actionable `[web] build.<field>` error when `outDir` is empty, or
@@ -5837,6 +5884,7 @@ function validateConfig$2(config) {
5837
5884
  if (config.publicDir !== void 0 && typeof config.publicDir !== "string") throw new Error(`${ERROR_PREFIX$7}.publicDir: must be a string when set.`);
5838
5885
  if (config.template !== void 0 && typeof config.template !== "string") throw new Error(`${ERROR_PREFIX$7}.template: must be a string path when set.`);
5839
5886
  if (config.clientEntry !== void 0 && typeof config.clientEntry !== "string") throw new Error(`${ERROR_PREFIX$7}.clientEntry: must be a string path when set.`);
5887
+ if (config.env !== void 0 && !isEnvNameList(config.env)) throw new Error(`${ERROR_PREFIX$7}.env: must list environment variable names.\n Use names like "IS_DEVELOPMENT".`);
5840
5888
  if (config.ogImage) validateFonts(config.ogImage);
5841
5889
  }
5842
5890
  //#endregion
package/dist/index.d.cts CHANGED
@@ -1715,6 +1715,13 @@ type Config$3 = {
1715
1715
  }; /** Emit per-path i18n bare-path redirect HTML pages. Default `false`. */
1716
1716
  localeRedirects?: boolean; /** Authoritative client bundle entry path (overrides the conventional scan). */
1717
1717
  clientEntry?: string;
1718
+ /**
1719
+ * Environment variable names the client bundle reads, e.g. `["IS_DEVELOPMENT", "IS_LOCAL"]`.
1720
+ * Each becomes a constant under `process.env.<NAME>` and `import.meta.env.<NAME>`: the value
1721
+ * the build process has, or `""` when unset. With `minify`, a branch on an unset flag is dropped
1722
+ * together with the dynamic `import()` chunk it guards. Default: none.
1723
+ */
1724
+ env?: readonly string[];
1718
1725
  /**
1719
1726
  * Path to a custom HTML document shell, giving the app full control over the
1720
1727
  * scaffold (charset, viewport, `<html lang>`, body attributes, wrapper markup).
package/dist/index.d.mts CHANGED
@@ -1713,6 +1713,13 @@ type Config$3 = {
1713
1713
  }; /** Emit per-path i18n bare-path redirect HTML pages. Default `false`. */
1714
1714
  localeRedirects?: boolean; /** Authoritative client bundle entry path (overrides the conventional scan). */
1715
1715
  clientEntry?: string;
1716
+ /**
1717
+ * Environment variable names the client bundle reads, e.g. `["IS_DEVELOPMENT", "IS_LOCAL"]`.
1718
+ * Each becomes a constant under `process.env.<NAME>` and `import.meta.env.<NAME>`: the value
1719
+ * the build process has, or `""` when unset. With `minify`, a branch on an unset flag is dropped
1720
+ * together with the dynamic `import()` chunk it guards. Default: none.
1721
+ */
1722
+ env?: readonly string[];
1716
1723
  /**
1717
1724
  * Path to a custom HTML document shell, giving the app full control over the
1718
1725
  * scaffold (charset, viewport, `<html lang>`, body attributes, wrapper markup).
package/dist/index.mjs CHANGED
@@ -2313,10 +2313,11 @@ const CSS_EXTERNAL_FONT_GLOBS = [
2313
2313
  * @param options.naming.chunk - Naming template for lazy split chunks.
2314
2314
  * @param options.naming.asset - Naming template for additional emitted assets.
2315
2315
  * @param options.external - Import/url() globs left unresolved in the output.
2316
+ * @param options.define - Global names replaced with constant expressions.
2316
2317
  * @returns The structural build result.
2317
2318
  * @example
2318
2319
  * ```ts
2319
- * await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [] });
2320
+ * await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [], define: {} });
2320
2321
  * ```
2321
2322
  */
2322
2323
  async function defaultRunner(options) {
@@ -2328,6 +2329,33 @@ async function defaultRunner(options) {
2328
2329
  return bun.build(options);
2329
2330
  }
2330
2331
  /**
2332
+ * The bundle-time constants for the `build.env` names. Each name becomes `process.env.<NAME>` and
2333
+ * `import.meta.env.<NAME>`, and the whole `import.meta.env` object is set too, because
2334
+ * `browserEnv()` spreads it. The value is the build process's own variable, or `""` when unset.
2335
+ * With `minify`, a branch on an unset flag is removed together with the dynamic `import()` chunk
2336
+ * it guards, so developer-only code never ships in a build without the flag.
2337
+ *
2338
+ * @param names - The `build.env` names.
2339
+ * @param source - Where the values come from; the build process's environment by default.
2340
+ * @returns The `define` map for `Bun.build`; empty without names.
2341
+ * @example
2342
+ * ```ts
2343
+ * envDefines(["IS_DEVELOPMENT"], { IS_DEVELOPMENT: "true" });
2344
+ * // { "process.env.IS_DEVELOPMENT": "\"true\"", "import.meta.env.IS_DEVELOPMENT": "\"true\"",
2345
+ * // "import.meta.env": "{\"IS_DEVELOPMENT\":\"true\"}" }
2346
+ * ```
2347
+ */
2348
+ function envDefines(names, source = process.env) {
2349
+ if (names.length === 0) return {};
2350
+ const values = Object.fromEntries(names.map((name) => [name, source[name] ?? ""]));
2351
+ const define = { "import.meta.env": JSON.stringify(values) };
2352
+ for (const [name, value] of Object.entries(values)) {
2353
+ define[`process.env.${name}`] = JSON.stringify(value);
2354
+ define[`import.meta.env.${name}`] = JSON.stringify(value);
2355
+ }
2356
+ return define;
2357
+ }
2358
+ /**
2331
2359
  * Resolve the first existing entry file from a candidate list (project-relative).
2332
2360
  *
2333
2361
  * @param candidates - Ordered candidate paths to probe.
@@ -2396,12 +2424,13 @@ function normalizeAssetPath(absolutePath, outDir) {
2396
2424
  * @param outDir - The publish root; stored asset paths are made relative to it.
2397
2425
  * @param outdir - The bundler output directory (`<outDir>/assets`).
2398
2426
  * @param minify - Whether to minify.
2427
+ * @param define - Global names replaced with constant expressions.
2399
2428
  * @example
2400
2429
  * ```ts
2401
- * await runOne(ctx, runner, "css", ["a.css"], "dist", true);
2430
+ * await runOne(ctx, runner, "css", ["a.css"], "dist", true, {});
2402
2431
  * ```
2403
2432
  */
2404
- async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify) {
2433
+ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify, define) {
2405
2434
  if (entrypoints.length === 0) return;
2406
2435
  const result = await runner({
2407
2436
  entrypoints,
@@ -2410,7 +2439,8 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify) {
2410
2439
  splitting: true,
2411
2440
  target: "browser",
2412
2441
  naming: FINGERPRINT_NAMING,
2413
- external: kind === "css" ? [...CSS_EXTERNAL_FONT_GLOBS] : []
2442
+ external: kind === "css" ? [...CSS_EXTERNAL_FONT_GLOBS] : [],
2443
+ define
2414
2444
  });
2415
2445
  if (!result.success) throw new Error(`[web] build.bundle ${kind} build failed`);
2416
2446
  const hashed = {};
@@ -2445,10 +2475,11 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify) {
2445
2475
  async function bundle(ctx, options = {}) {
2446
2476
  const runner = options.runner ?? defaultRunner;
2447
2477
  const { minify, outDir } = ctx.config;
2478
+ const define = envDefines(ctx.config.env ?? []);
2448
2479
  const assetsDir = path.join(outDir, "assets");
2449
2480
  const cssEntrypoints = options.cssEntrypoints ?? resolveEntrypoints(CSS_ENTRY_CANDIDATES);
2450
2481
  const jsEntrypoints = options.jsEntrypoints ?? resolveJsEntrypoints(ctx);
2451
- await Promise.all([runOne(ctx, runner, "css", cssEntrypoints, outDir, assetsDir, minify), runOne(ctx, runner, "js", jsEntrypoints, outDir, assetsDir, minify)]);
2482
+ await Promise.all([runOne(ctx, runner, "css", cssEntrypoints, outDir, assetsDir, minify, define), runOne(ctx, runner, "js", jsEntrypoints, outDir, assetsDir, minify, define)]);
2452
2483
  }
2453
2484
  //#endregion
2454
2485
  //#region src/plugins/build/phases/asset-tags.ts
@@ -5777,6 +5808,22 @@ function validateFonts(og) {
5777
5808
  if (isMissingFontDir(og.fontDir)) throw new Error(`${ERROR_PREFIX$7}.ogImage: fontDir "${og.fontDir}" does not exist — provide a directory with at least one font.`);
5778
5809
  if (!readdirSync(og.fontDir).some((name) => FONT_EXTENSIONS.some((extension) => name.endsWith(extension)))) throw new Error(`${ERROR_PREFIX$7}.ogImage: fontDir "${og.fontDir}" contains no .ttf/.otf/.woff font files.`);
5779
5810
  }
5811
+ /** An environment variable name: letters, digits and `_`, not starting with a digit. */
5812
+ const ENV_NAME = /^[A-Z_a-z]\w*$/;
5813
+ /**
5814
+ * Whether a `build.env` value lists environment variable names.
5815
+ *
5816
+ * @param value - The configured `build.env`.
5817
+ * @returns True for an array of valid names.
5818
+ * @example
5819
+ * ```ts
5820
+ * isEnvNameList(["IS_DEVELOPMENT"]); // true
5821
+ * isEnvNameList(["process.env.X"]); // false
5822
+ * ```
5823
+ */
5824
+ function isEnvNameList(value) {
5825
+ return Array.isArray(value) && value.every((name) => typeof name === "string" && ENV_NAME.test(name));
5826
+ }
5780
5827
  /**
5781
5828
  * Validates `build` config synchronously in `onInit` (return value discarded).
5782
5829
  * Throws an actionable `[web] build.<field>` error when `outDir` is empty, or
@@ -5793,6 +5840,7 @@ function validateConfig$2(config) {
5793
5840
  if (config.publicDir !== void 0 && typeof config.publicDir !== "string") throw new Error(`${ERROR_PREFIX$7}.publicDir: must be a string when set.`);
5794
5841
  if (config.template !== void 0 && typeof config.template !== "string") throw new Error(`${ERROR_PREFIX$7}.template: must be a string path when set.`);
5795
5842
  if (config.clientEntry !== void 0 && typeof config.clientEntry !== "string") throw new Error(`${ERROR_PREFIX$7}.clientEntry: must be a string path when set.`);
5843
+ if (config.env !== void 0 && !isEnvNameList(config.env)) throw new Error(`${ERROR_PREFIX$7}.env: must list environment variable names.\n Use names like "IS_DEVELOPMENT".`);
5796
5844
  if (config.ogImage) validateFonts(config.ogImage);
5797
5845
  }
5798
5846
  //#endregion
package/package.json CHANGED
@@ -137,5 +137,5 @@
137
137
  "release:doctor": "moku-release doctor",
138
138
  "release": "moku-release"
139
139
  },
140
- "version": "2.3.3"
140
+ "version": "2.4.0"
141
141
  }