bun-plugin-stx 0.2.674 → 0.2.675

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.
@@ -0,0 +1,13 @@
1
+ import type { StxOptions } from '@stacksjs/stx';
2
+ /**
3
+ * Walk literal template dependencies, not cwd. No template code is executed:
4
+ * conditional includes count conservatively; runtime-computed paths need
5
+ * imageWarmup: true. Unused resource definitions are never entry points.
6
+ */
7
+ export declare function servedTemplatesUseImageBuiltins(root: string, scope: ImageTemplateScope): Promise<boolean>;
8
+ export declare const IMAGE_BUILTIN_PATTERN: boolean;
9
+ export declare interface ImageTemplateScope {
10
+ pages: readonly string[]
11
+ options: Partial<StxOptions>
12
+ pluginComponentDirs?: readonly string[]
13
+ }
package/dist/serve.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { serve as bunServe } from 'bun';
2
2
  import { compileDomainRoutes, describeDomainRoutes, requestHost, resolveDomainElsewhere, resolveDomainRoute } from './domain-routes';
3
3
  import type { DomainRoutesOption } from './domain-routes';
4
+ import type { ImageTemplateScope } from './image-warmup-scope';
4
5
  import type { MiddlewareContext, MiddlewareHandler, MiddlewareRequest, PageMiddleware, PrepareMiddlewareRequest } from './page-middleware';
5
6
  export type { DomainRoute, DomainRoutesOption } from './domain-routes';
6
7
  export type { MiddlewareContext, MiddlewareDefinition, MiddlewareHandler, MiddlewareRequest, PageMiddleware, PrepareMiddlewareRequest } from './page-middleware';
@@ -274,8 +275,12 @@ export declare function serve(options: ServeOptions): Promise<void>;
274
275
  * plain `<img>` tags spent over seven minutes of each cold boot encoding
275
276
  * 213MB of variants that no page could ask for. A use is followed through
276
277
  * wrappers of wrappers, and a page, layout or partial using one counts.
278
+ *
279
+ * serve() supplies a scope of discovered pages and their resource directories
280
+ * so unserved templates do not count. Omitting it retains the legacy project
281
+ * scan for callers that explicitly want to inspect a whole directory tree.
277
282
  */
278
- export declare function projectUsesImageBuiltins(root: string): Promise<boolean>;
283
+ export declare function projectUsesImageBuiltins(root: string, scope?: ImageTemplateScope): Promise<boolean>;
279
284
  /**
280
285
  * Stands in for a dynamic route's params in a cached shell. Not valid
281
286
  * JavaScript on its own, so a shell that escaped without being filled fails
package/dist/serve.js CHANGED
@@ -8861,6 +8861,165 @@ function resolveDomainElsewhere(host, pathname, search, routes, isAsset) {
8861
8861
  function describeDomainRoutes(routes) {
8862
8862
  return routes.map((route) => `${route.pattern}/ \u2192 ${route.to}${route.elsewhere ? ` (other pages: ${route.elsewhere})` : ""}`).join(", ");
8863
8863
  }
8864
+
8865
+ // src/image-warmup-scope.ts
8866
+ import path from "path";
8867
+ import { readdir as readdir4, readFile, stat as stat4 } from "fs/promises";
8868
+ import { componentImportCandidates, pascalToKebab } from "@stacksjs/stx/component-resolution";
8869
+ import { splitTopLevelArgs } from "@stacksjs/stx/directive-arguments";
8870
+ import { extractParenthesizedExpression } from "@stacksjs/stx/parser/directive-parser";
8871
+ import { resolveTemplatePath } from "@stacksjs/stx/utils";
8872
+ var IMAGE_BUILTIN_PATTERN = /<(?:StxImage|Image|stx-image|stx-img)[\s/>]|@image\s*\(/;
8873
+ async function servedTemplatesUseImageBuiltins(root, scope) {
8874
+ const options = { ...scope.options, root };
8875
+ const absolute = (file) => path.resolve(root, file);
8876
+ for (const key of ["componentsDir", "layoutsDir", "fallbackLayoutsDir", "fallbackComponentsDir", "partialsDir"]) {
8877
+ if (options[key])
8878
+ options[key] = absolute(options[key]);
8879
+ }
8880
+ const directories = new Map;
8881
+ const sources = new Map;
8882
+ async function firstFile(candidates) {
8883
+ for (const candidate of candidates) {
8884
+ const file = absolute(candidate);
8885
+ if (await stat4(file).then((result) => result.isFile(), () => false))
8886
+ return file;
8887
+ }
8888
+ }
8889
+ function componentDirectories(directory) {
8890
+ const dir = absolute(directory);
8891
+ let files = directories.get(dir);
8892
+ if (!files) {
8893
+ files = (async () => {
8894
+ const result = [];
8895
+ const pending = [{ dir, depth: 0 }];
8896
+ while (pending.length) {
8897
+ const current = pending.pop();
8898
+ if (current.depth >= 8)
8899
+ continue;
8900
+ const entries = await readdir4(current.dir, { withFileTypes: true }).catch(() => []);
8901
+ for (const entry of entries) {
8902
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
8903
+ const child = path.join(current.dir, entry.name);
8904
+ result.push(child);
8905
+ pending.push({ dir: child, depth: current.depth + 1 });
8906
+ }
8907
+ }
8908
+ }
8909
+ return result;
8910
+ })();
8911
+ directories.set(dir, files);
8912
+ }
8913
+ return files;
8914
+ }
8915
+ async function component(name, file, page, imported) {
8916
+ const pascal = name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
8917
+ for (const alias of [name, name.toLowerCase(), pascal, pascalToKebab(name)]) {
8918
+ if (imported.has(alias))
8919
+ return imported.get(alias);
8920
+ }
8921
+ if (name.startsWith("./") || name.startsWith("../"))
8922
+ return firstFile([path.resolve(path.dirname(file), name.endsWith(".stx") ? name : `${name}.stx`)]);
8923
+ const names = [...new Set([name, pascal, pascalToKebab(name)].map((value) => `${value.replace(/\.stx$/, "")}.stx`))];
8924
+ const dirs = [
8925
+ path.join(path.dirname(page), "components"),
8926
+ options.componentsDir,
8927
+ path.join(path.dirname(file), "components"),
8928
+ path.join(root, "resources/views/components"),
8929
+ path.join(root, "resources/components"),
8930
+ path.join(root, "src/components"),
8931
+ path.join(root, "components"),
8932
+ ...scope.pluginComponentDirs ?? [],
8933
+ options.fallbackComponentsDir
8934
+ ].filter((dir) => typeof dir === "string" && dir.length > 0);
8935
+ for (const dir of new Set(dirs)) {
8936
+ const direct = await firstFile(names.map((name) => path.join(dir, name)));
8937
+ if (direct)
8938
+ return direct;
8939
+ for (const child of await componentDirectories(dir)) {
8940
+ const nested = await firstFile(names.map((name) => path.join(child, name)));
8941
+ if (nested)
8942
+ return nested;
8943
+ }
8944
+ }
8945
+ }
8946
+ for (const entry of scope.pages) {
8947
+ const page = absolute(entry);
8948
+ const pending = [page];
8949
+ const visited = new Set;
8950
+ const imported = new Map;
8951
+ while (pending.length) {
8952
+ const file = pending.pop();
8953
+ if (visited.has(file))
8954
+ continue;
8955
+ visited.add(file);
8956
+ let reading = sources.get(file);
8957
+ if (!reading) {
8958
+ reading = readFile(file, "utf8").catch(() => "");
8959
+ sources.set(file, reading);
8960
+ }
8961
+ const source = await reading;
8962
+ if (IMAGE_BUILTIN_PATTERN.test(source))
8963
+ return true;
8964
+ const references = [];
8965
+ const directives = /@(import|component|extends|layout|include|partial|includeIf|includeWhen|includeUnless|includeFirst)\s*\(/g;
8966
+ let match;
8967
+ while ((match = directives.exec(source)) !== null) {
8968
+ const parsed = extractParenthesizedExpression(source, directives.lastIndex - 1);
8969
+ if (!parsed)
8970
+ continue;
8971
+ references.push({ kind: match[1], args: splitTopLevelArgs(parsed.expression) });
8972
+ directives.lastIndex = parsed.endPos;
8973
+ }
8974
+ const literal = (arg = "") => /^(['"])([^'"\r\n]+)\1$/.exec(arg.trim())?.[2];
8975
+ for (const ref of references.filter((ref) => ref.kind === "import")) {
8976
+ for (const arg of ref.args) {
8977
+ const name = literal(arg);
8978
+ if (!name)
8979
+ continue;
8980
+ const resolved = await firstFile(componentImportCandidates(name, file, options));
8981
+ if (resolved) {
8982
+ const base = path.basename(name, ".stx");
8983
+ const pascal = base.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
8984
+ for (const alias of [base, base.toLowerCase(), pascal, pascalToKebab(pascal)])
8985
+ imported.set(alias, resolved);
8986
+ }
8987
+ }
8988
+ }
8989
+ for (const ref of references) {
8990
+ const arg = ref.args[ref.kind === "includeWhen" || ref.kind === "includeUnless" ? 1 : 0];
8991
+ const names = ref.kind === "includeFirst" && arg?.trim().startsWith("[") ? splitTopLevelArgs(arg.trim().slice(1, -1)).map(literal) : [literal(arg)];
8992
+ for (const name of names) {
8993
+ if (!name)
8994
+ continue;
8995
+ let resolved;
8996
+ if (ref.kind === "component")
8997
+ resolved = await component(name, file, page, imported);
8998
+ else if (ref.kind === "extends" || ref.kind === "layout")
8999
+ resolved = await resolveTemplatePath(name, file, options);
9000
+ else if (ref.kind !== "import") {
9001
+ const base = name.startsWith(".") ? path.dirname(file) : options.partialsDir || "partials";
9002
+ resolved = await firstFile([path.resolve(absolute(base), name.endsWith(".stx") ? name : `${name}.stx`)]);
9003
+ }
9004
+ if (resolved)
9005
+ pending.push(absolute(resolved));
9006
+ if (resolved && ref.kind === "includeFirst")
9007
+ break;
9008
+ }
9009
+ }
9010
+ const tags = /<([A-Z][\w.-]*|[a-z][\w.]*-[\w.-]+)(?=[\s/>])/g;
9011
+ const names = new Set;
9012
+ while ((match = tags.exec(source)) !== null)
9013
+ names.add(match[1]);
9014
+ for (const name of names) {
9015
+ const resolved = await component(name, file, page, imported);
9016
+ if (resolved)
9017
+ pending.push(resolved);
9018
+ }
9019
+ }
9020
+ }
9021
+ return false;
9022
+ }
8864
9023
  // src/page-middleware.ts
8865
9024
  var DEFAULT_PRIORITY = 10;
8866
9025
  function middlewarePriority(middleware) {
@@ -10629,7 +10788,22 @@ ${block}`;
10629
10788
  console.warn(`\x1B[2m${error.stack}\x1B[0m`);
10630
10789
  }
10631
10790
  async function runImageWarmup() {
10632
- const imageWarmupEnabled = imageWarmupSetting === "auto" ? await projectUsesImageBuiltins(process20.cwd()) : imageWarmupSetting !== false;
10791
+ let imageWarmupEnabled = imageWarmupSetting !== false;
10792
+ if (imageWarmupSetting === "auto") {
10793
+ const seen = new Set;
10794
+ const pages = (await discoverFiles()).filter((file) => {
10795
+ const relative = pageRootOf(file, patterns).relative;
10796
+ if (seen.has(relative))
10797
+ return false;
10798
+ seen.add(relative);
10799
+ return true;
10800
+ });
10801
+ imageWarmupEnabled = await projectUsesImageBuiltins(process20.cwd(), {
10802
+ pages,
10803
+ options: { componentsDir, layoutsDir, fallbackLayoutsDir, fallbackComponentsDir, partialsDir },
10804
+ pluginComponentDirs: stxConfig._pluginComponentDirs
10805
+ });
10806
+ }
10633
10807
  if (!imageWarmupEnabled) {
10634
10808
  placeholdersAreReady = true;
10635
10809
  markPlaceholdersReady();
@@ -11588,7 +11762,6 @@ async function main() {
11588
11762
  if (import.meta.main) {
11589
11763
  main().catch(console.error);
11590
11764
  }
11591
- var IMAGE_BUILTIN_PATTERN = /<(?:StxImage|Image|stx-image|stx-img)[\s/>]|@image\s*\(/;
11592
11765
  var TEMPLATE_SCAN_SKIP = new Set(["node_modules", "dist", "build", "coverage", "vendor"]);
11593
11766
  function escapeRegExp(value) {
11594
11767
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -11601,7 +11774,9 @@ function usesComponent(source, name) {
11601
11774
  const spellings = (kebab.includes("-") ? [name, kebab] : [name]).map(escapeRegExp).join("|");
11602
11775
  return new RegExp(`<(?:${spellings})[\\s/>]|@component\\(\\s*['"](?:${spellings})['"]`).test(source);
11603
11776
  }
11604
- async function projectUsesImageBuiltins(root) {
11777
+ async function projectUsesImageBuiltins(root, scope) {
11778
+ if (scope)
11779
+ return servedTemplatesUseImageBuiltins(root, scope);
11605
11780
  const { readdir, readFile } = await import("fs/promises");
11606
11781
  const templates = [];
11607
11782
  const pending = [root];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-plugin-stx",
3
3
  "type": "module",
4
- "version": "0.2.674",
4
+ "version": "0.2.675",
5
5
  "description": "A Bun plugin that allows for using Laravel Blade-like syntax.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -74,7 +74,7 @@
74
74
  "typecheck": "bun tsc --noEmit"
75
75
  },
76
76
  "dependencies": {
77
- "@stacksjs/stx": "0.2.674",
77
+ "@stacksjs/stx": "0.2.675",
78
78
  "stx-router": "0.2.674"
79
79
  }
80
80
  }