@stacksjs/stx 0.2.152 → 0.2.154

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 (88) hide show
  1. package/dist/animation.js +6 -6
  2. package/dist/app-shell.js +2 -1
  3. package/dist/browser-composables.d.ts +8 -0
  4. package/dist/build-id.d.ts +49 -0
  5. package/dist/build-id.js +20 -0
  6. package/dist/build-message.d.ts +10 -0
  7. package/dist/build-message.js +26 -0
  8. package/dist/builtins/attrs.js +6 -1
  9. package/dist/cli.js +1743 -1255
  10. package/dist/client-script-bundler.js +2 -4
  11. package/dist/client-script.d.ts +29 -0
  12. package/dist/client-script.js +142 -97
  13. package/dist/color-mode-boot.d.ts +46 -10
  14. package/dist/color-mode-boot.js +53 -13
  15. package/dist/component-client-factories.js +3 -2
  16. package/dist/component-renderer.js +10 -21
  17. package/dist/composables/index.d.ts +8 -1
  18. package/dist/composables/use-clipboard.d.ts +1 -5
  19. package/dist/composables/use-clipboard.js +57 -46
  20. package/dist/composables/use-color-mode.d.ts +19 -4
  21. package/dist/composables/use-color-mode.js +49 -28
  22. package/dist/composables/use-router.d.ts +28 -11
  23. package/dist/composables/use-router.js +63 -9
  24. package/dist/composables/use-storage.d.ts +14 -15
  25. package/dist/composables/use-storage.js +63 -2
  26. package/dist/config.js +22 -7
  27. package/dist/craft.js +1319 -841
  28. package/dist/dev-server/crosswind.js +8 -6
  29. package/dist/dev-server/serve-app.js +13 -7
  30. package/dist/dev-server/serve-markdown.js +4 -1
  31. package/dist/dev-server/serve-multi.js +32 -20
  32. package/dist/dev-server/types.d.ts +8 -1
  33. package/dist/document-shell.d.ts +120 -0
  34. package/dist/document-shell.js +161 -14
  35. package/dist/dynamic-components.js +123 -4
  36. package/dist/events.d.ts +26 -49
  37. package/dist/events.js +23 -9
  38. package/dist/expressions.d.ts +1 -1
  39. package/dist/expressions.js +3 -3
  40. package/dist/framework-composables.d.ts +41 -0
  41. package/dist/framework-composables.js +115 -0
  42. package/dist/head.js +5 -1
  43. package/dist/hot-reload.js +8 -2
  44. package/dist/includes.js +4 -4
  45. package/dist/index.d.ts +17 -0
  46. package/dist/index.js +9 -0
  47. package/dist/isolated-build.d.ts +12 -0
  48. package/dist/isolated-build.js +18 -0
  49. package/dist/plugin-system.d.ts +1 -1
  50. package/dist/process.js +46 -12
  51. package/dist/production-builder.js +3 -1
  52. package/dist/production-server.js +4 -1
  53. package/dist/prop-sentinels.d.ts +31 -0
  54. package/dist/prop-sentinels.js +7 -0
  55. package/dist/pwa.js +1258 -780
  56. package/dist/reactive.js +1 -1
  57. package/dist/reactivity.d.ts +0 -15
  58. package/dist/reactivity.js +15 -6
  59. package/dist/runtime-globals.d.ts +40 -2
  60. package/dist/runtime-globals.js +90 -0
  61. package/dist/runtime-injection.d.ts +51 -0
  62. package/dist/runtime-injection.js +13 -0
  63. package/dist/script-emit.d.ts +20 -0
  64. package/dist/script-emit.js +11 -0
  65. package/dist/script-validation.js +12 -7
  66. package/dist/signal-processing.js +25 -40
  67. package/dist/signals-api.d.ts +30 -0
  68. package/dist/signals-api.js +18 -0
  69. package/dist/signals.js +532 -85
  70. package/dist/site-builder/build.js +21 -7
  71. package/dist/site-builder/seo.js +19 -10
  72. package/dist/site-builder/theme.js +5 -4
  73. package/dist/site-builder/types.d.ts +1 -0
  74. package/dist/ssg.js +1255 -777
  75. package/dist/state-management.js +4 -1
  76. package/dist/store-loader.d.ts +0 -11
  77. package/dist/store-loader.js +27 -3
  78. package/dist/strict-lint.d.ts +9 -6
  79. package/dist/strict-lint.js +2 -21
  80. package/dist/stx.d.ts +125 -22
  81. package/dist/types/config-types.d.ts +1 -0
  82. package/dist/unresolved-identifiers.d.ts +50 -0
  83. package/dist/unresolved-identifiers.js +146 -0
  84. package/dist/utils.js +3 -3
  85. package/dist/variable-extractor.js +17 -1
  86. package/dist/x-element.js +1 -1
  87. package/package.json +5 -5
  88. package/stx.d.ts +125 -22
@@ -19,6 +19,7 @@ import { generateSitemap } from "./sitemap.js";
19
19
  import { generateRobots } from "./robots.js";
20
20
  import { injectRouterScript } from "./router.js";
21
21
  import { injectThemeBootstrap } from "./theme.js";
22
+ import { buildIsolatingFailures } from "../isolated-build.js";
22
23
  import {
23
24
  applyTranslations,
24
25
  buildAlternateLinks,
@@ -31,18 +32,31 @@ export async function buildStaticSite(options) {
31
32
  if (!options.noClean)
32
33
  rmSync(outDir, { recursive: !0, force: !0 });
33
34
  mkdirSync(outDir, { recursive: !0 });
34
- const glob = new Glob(`${pagesDir}/**/*.stx`), entrypoints = await Array.fromAsync(glob.scan("."));
35
+ const glob = new Glob(`${pagesDir}/**/*.stx`), discovered = await Array.fromAsync(glob.scan(".")), excludeDirs = options.excludeDirs ?? ["layouts", "partials", "components"], excluded = [], entrypoints = discovered.filter((file) => {
36
+ const rel = file.slice(pagesDir.length).replace(/^[\\/]+/, "");
37
+ if (rel.split(/[\\/]/).slice(0, -1).some((seg) => excludeDirs.includes(seg))) {
38
+ excluded.push(file);
39
+ return !1;
40
+ }
41
+ return !rel.split(/[\\/]/).some((seg) => seg.startsWith("_"));
42
+ });
43
+ if (excluded.length > 0)
44
+ console.warn(`[stx] ${excluded.length} file(s) under ${pagesDir}/ skipped as non-pages: ${excludeDirs.join(", ")}`);
35
45
  if (entrypoints.length === 0)
36
46
  throw Error(`No .stx files found in ${pagesDir}/`);
37
- const result = await Bun.build({
38
- entrypoints,
47
+ const isolated = await buildIsolatingFailures(entrypoints, (subset) => Bun.build({
48
+ entrypoints: subset,
39
49
  outdir: outDir,
40
50
  plugins: [stxPlugin()],
41
51
  naming: { entry: "[name].[ext]" }
42
- });
43
- if (!result.success) {
44
- console.error(result.logs);
45
- throw Error("Build failed");
52
+ }));
53
+ if (isolated.failed.length > 0) {
54
+ for (const { entrypoint, error } of isolated.failed)
55
+ console.error(`[stx] failed to build ${entrypoint}:
56
+ ${error}`);
57
+ if (isolated.succeeded.length === 0)
58
+ throw Error(`Build failed: none of the ${entrypoints.length} page(s) could be built`);
59
+ console.error(`[stx] ${isolated.failed.length} of ${entrypoints.length} page(s) skipped; the rest were built.`);
46
60
  }
47
61
  pruneEmptyJsChunks(outDir);
48
62
  const htmlFiles = listFiles(outDir).filter((f) => f.endsWith(".html")), i18n = resolveI18n(options, process.cwd()), knownPaths = new Set(htmlFiles.map(pathFromFile));
@@ -18,30 +18,39 @@ export function injectSeo(html, site, page = {}, pagePath = "/") {
18
18
  html = html.replace(stxAutoBlockRe, "");
19
19
  html = html.replace(/<!--\s*SEO\s*-->[\s\S]*?<!--\s*\/SEO\s*-->\s*/g, "");
20
20
  html = html.replace(/<meta\s+name="title"\s+content="stx Project"[^>]*>\s*/g, "").replace(/<meta\s+name="description"\s+content="A website built with stx[^"]*"[^>]*>\s*/g, "").replace(/<meta\s+property="og:title"\s+content="stx Project"[^>]*>\s*/g, "").replace(/<meta\s+property="og:description"\s+content="A website built with stx[^"]*"[^>]*>\s*/g, "").replace(/<meta\s+name="twitter:title"\s+content="stx Project"[^>]*>\s*/g, "").replace(/<meta\s+name="twitter:description"\s+content="A website built with stx[^"]*"[^>]*>\s*/g, "");
21
- const headEnd = html.search(/<\/head>/i), head = headEnd === -1 ? html : html.slice(0, headEnd), declared = (tag) => {
21
+ const headEnd = html.search(/<\/head>/i), head = headEnd === -1 ? html : html.slice(0, headEnd), PLACEHOLDER_TITLES = /^(?:stx App|stx Project|Document|Untitled)$/i, existingTitle = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(head)?.[1]?.trim(), titleIsPlaceholder = existingTitle === void 0 || existingTitle === "" || PLACEHOLDER_TITLES.test(existingTitle), declared = (tag) => {
22
22
  if (/^<title[\s>]/i.test(tag))
23
- return /<title[\s>]/i.test(head);
23
+ return /<title[\s>]/i.test(head) && !titleIsPlaceholder;
24
24
  if (/rel="canonical"/i.test(tag))
25
25
  return /<link[^>]+rel=["']canonical["']/i.test(head);
26
26
  const key = /(?:property|name)="([^"]+)"/i.exec(tag)?.[1];
27
27
  if (!key)
28
28
  return !1;
29
29
  return new RegExp(`<meta[^>]+(?:property|name)=["']${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']`, "i").test(head);
30
- }, missing = tags.filter((tag) => !declared(tag));
30
+ };
31
+ let missing = tags.filter((tag) => !declared(tag));
31
32
  if (missing.length === 0)
32
33
  return html;
34
+ let output = html;
35
+ const titleTag = missing.find((tag) => /^<title[\s>]/i.test(tag));
36
+ if (titleTag && titleIsPlaceholder && /<title[^>]*>[\s\S]*?<\/title>/i.test(output)) {
37
+ output = output.replace(/<title[^>]*>[\s\S]*?<\/title>/i, () => titleTag);
38
+ missing = missing.filter((tag) => tag !== titleTag);
39
+ if (missing.length === 0)
40
+ return output;
41
+ }
33
42
  const block = `<!-- SEO -->
34
43
  ${missing.join(`
35
44
  `)}
36
- <!-- /SEO -->`;
37
- if (headEnd !== -1)
38
- return `${html.slice(0, headEnd)}${block}
39
- ${html.slice(headEnd)}`;
40
- if (/<head[^>]*>/i.test(html))
41
- return html.replace(/<head([^>]*)>/i, `<head$1>
45
+ <!-- /SEO -->`, outEnd = output.search(/<\/head>/i);
46
+ if (outEnd !== -1)
47
+ return `${output.slice(0, outEnd)}${block}
48
+ ${output.slice(outEnd)}`;
49
+ if (/<head[^>]*>/i.test(output))
50
+ return output.replace(/<head([^>]*)>/i, `<head$1>
42
51
  ${block}`);
43
52
  return `${block}
44
- ${html}`;
53
+ ${output}`;
45
54
  }
46
55
  function escapeAttr(s) {
47
56
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -1,9 +1,10 @@
1
+ import { COLOR_MODE_BOOT_MARKER } from "../color-mode-boot.js";
1
2
  const FAVICON_FN = `window.__stxThemeIcon=function(isDark){try{var want=isDark?'dark':'light';var pick=document.querySelector('link[rel~="icon"][media*="prefers-color-scheme: '+want+'"]');if(!pick)return;var live=document.querySelector('link[rel~="icon"]:not([media])');if(!live){live=document.createElement('link');live.setAttribute('rel','icon');document.head.appendChild(live)}if(pick.type)live.setAttribute('type',pick.type);var href=pick.getAttribute('href');if(live.getAttribute('href')!==href)live.setAttribute('href',href)}catch(e){}};`, FOUC_SCRIPT = (defaultTheme, storageKey, lightColor, darkColor) => `(function(){${FAVICON_FN}function read(){try{var k=${JSON.stringify(storageKey)};var t=localStorage.getItem(k);return t===null?(${JSON.stringify(defaultTheme)}==='dark'||(${JSON.stringify(defaultTheme)}==='auto'&&window.matchMedia('(prefers-color-scheme: dark)').matches)):t==='dark'}catch(e){return ${JSON.stringify(defaultTheme)}==='dark'}}function apply(){var isDark=read();var html=document.documentElement;html.classList[isDark?'add':'remove']('dark');var color=isDark?${JSON.stringify(darkColor)}:${JSON.stringify(lightColor)};if(!document.head)return;var tc=document.querySelector('meta[name="theme-color"]:not([media])');if(!tc){tc=document.createElement('meta');tc.setAttribute('name','theme-color');tc.setAttribute('data-stx-theme-meta','1')}tc.setAttribute('content',color);if(tc!==document.head.firstChild){document.head.insertBefore(tc,document.head.firstChild)}if(window.__stxThemeIcon)window.__stxThemeIcon(isDark)}apply();window.addEventListener('stx:navigate',apply);window.addEventListener('popstate',apply)})();`, TOGGLE_HANDLER = (storageKey, lightColor, darkColor) => `document.addEventListener('click',function(e){var t=e.target&&e.target.closest&&e.target.closest('#theme-toggle');if(!t)return;var html=document.documentElement;var isDark=html.classList.toggle('dark');try{localStorage.setItem(${JSON.stringify(storageKey)},isDark?'dark':'light')}catch(e){}var tc=document.querySelector('meta[name="theme-color"]:not([media])');if(tc)tc.setAttribute('content',isDark?${JSON.stringify(darkColor)}:${JSON.stringify(lightColor)});if(window.__stxThemeIcon)window.__stxThemeIcon(isDark)});`;
2
3
  export function injectThemeBootstrap(html, site) {
3
4
  if (site.theme === !1)
4
5
  return html;
5
- const opts = typeof site.theme === "object" && site.theme !== null ? site.theme : {}, defaultTheme = opts.default ?? "dark", storageKey = opts.storageKey ?? "theme", lightColor = opts.colors?.light ?? "#ffffff", darkColor = opts.colors?.dark ?? "#000000";
6
- if (defaultTheme === "dark") {
6
+ const colorModeOwnsTheme = html.includes(COLOR_MODE_BOOT_MARKER), opts = typeof site.theme === "object" && site.theme !== null ? site.theme : {}, defaultTheme = opts.default ?? "dark", storageKey = opts.storageKey ?? "theme", lightColor = opts.colors?.light ?? "#ffffff", darkColor = opts.colors?.dark ?? "#000000";
7
+ if (defaultTheme === "dark" && !colorModeOwnsTheme) {
7
8
  if (!/<html\b[^>]*\bclass=/.test(html))
8
9
  html = html.replace(/<html\b([^>]*)>/i, '<html$1 class="dark">');
9
10
  else if (!/<html\b[^>]*\bclass="[^"]*\bdark\b/.test(html))
@@ -19,12 +20,12 @@ export function injectThemeBootstrap(html, site) {
19
20
  html = html.replace(/<head\b([^>]*)>/i, `<head$1>
20
21
  ${metas}`);
21
22
  }
22
- if (!html.includes("__stxThemeGuard") && /<head\b[^>]*>/i.test(html)) {
23
+ if (!colorModeOwnsTheme && !html.includes("__stxThemeGuard") && /<head\b[^>]*>/i.test(html)) {
23
24
  const guard = `<script data-stx-theme-guard="1">${FOUC_SCRIPT(defaultTheme, storageKey, lightColor, darkColor)};window.__stxThemeGuard=1;</script>`;
24
25
  html = html.replace(/<head\b([^>]*)>/i, `<head$1>
25
26
  ${guard}`);
26
27
  }
27
- if (!html.includes("__stxThemeToggle") && /<\/body>/i.test(html)) {
28
+ if (!colorModeOwnsTheme && !html.includes("__stxThemeToggle") && /<\/body>/i.test(html)) {
28
29
  const handler = `<script data-stx-theme-toggle="1">${TOGGLE_HANDLER(storageKey, lightColor, darkColor)};window.__stxThemeToggle=1;</script>`;
29
30
  html = html.replace(/<\/body>/i, `${handler}
30
31
  </body>`);
@@ -23,6 +23,7 @@ export declare interface SiteConfig {
23
23
  social?: SiteSocial
24
24
  seo?: SiteSeo
25
25
  pagesDir?: string
26
+ excludeDirs?: string[]
26
27
  publicDir?: string
27
28
  outDir?: string
28
29
  sitemap?: boolean