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.
- package/dist/image-warmup-scope.d.ts +13 -0
- package/dist/serve.d.ts +6 -1
- package/dist/serve.js +178 -3
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
77
|
+
"@stacksjs/stx": "0.2.675",
|
|
78
78
|
"stx-router": "0.2.674"
|
|
79
79
|
}
|
|
80
80
|
}
|