@useavalon/avalon 0.1.75 → 0.1.77

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.
@@ -21,15 +21,19 @@ export declare function deduplicateCSSRules(css: string): string;
21
21
  */
22
22
  export declare function extractCriticalCSS(clear?: boolean): string;
23
23
  /**
24
- * Convert external third-party <link rel="stylesheet"> tags to use the
25
- * media="print" swap pattern for async/non-blocking loading.
24
+ * Convert external third-party <link rel="stylesheet"> tags and explicitly
25
+ * deferred local stylesheets to use the media="print" swap pattern for
26
+ * async/non-blocking loading.
26
27
  *
27
- * Only defers stylesheets from external origins (https://) — local asset
28
- * stylesheets (e.g., /assets/entry-client-*.css) are never deferred since
29
- * they contain essential layout CSS that would cause FOUC if delayed.
28
+ * Defers:
29
+ * - External stylesheets (https://, http://)
30
+ * - Local stylesheets marked with data-defer attribute
31
+ * - Local stylesheets matching known non-critical patterns (e.g., syntax highlighting)
30
32
  *
31
- * Stylesheets that already have a media attribute or are marked as critical
32
- * (data-critical) are left untouched.
33
+ * Preserves (never deferred):
34
+ * - Local asset stylesheets (/assets/*) essential layout CSS
35
+ * - Stylesheets with an existing media attribute
36
+ * - Stylesheets marked as critical (data-critical)
33
37
  */
34
38
  export declare function deferNonCriticalStylesheets(html: string): string;
35
39
  /**
@@ -38,6 +42,7 @@ export declare function deferNonCriticalStylesheets(html: string): string;
38
42
  * 1. Extracts SSR-collected CSS from the universal collector
39
43
  * 2. Inlines it as a <style> tag in <head>
40
44
  * 3. Converts external stylesheet <link> tags to async loading
45
+ * 4. Adds fetchpriority hints for above-the-fold resources
41
46
  *
42
47
  * @param html - The rendered HTML string
43
48
  * @returns The HTML with critical CSS inlined and external stylesheets deferred
@@ -1,3 +1,4 @@
1
- import{getUniversalCSS as e}from"./universal-css-collector.js";import{minifyCSS as t}from"./css-utils.js";function n(e){let t=0;for(let n of e)n===`{`?t++:n===`}`&&t--;return t}function r(e,t,n){let r=e.replaceAll(/\s+/g,` `).trim();if(r.startsWith(`/*`)&&r.endsWith(`*/`)){n.push(e.trimEnd());return}t.has(r)||(t.add(r),n.push(e.trimEnd()))}export function deduplicateCSSRules(e){if(!e.trim())return``;let t=new Set,i=e.split(`
1
+ import{minifyCSS as e}from"./css-utils.js";import{getUniversalCSS as t}from"./universal-css-collector.js";function n(e){let t=0;for(let n of e)n===`{`?t++:n===`}`&&t--;return t}function r(e,t,n){let r=e.replaceAll(/\s+/g,` `).trim();if(r.startsWith(`/*`)&&r.endsWith(`*/`)){n.push(e.trimEnd());return}t.has(r)||(t.add(r),n.push(e.trimEnd()))}export function deduplicateCSSRules(e){if(!e.trim())return``;let t=new Set,i=e.split(`
2
2
  `),a=[],o=``,s=0;for(let e of i)s+=n(e),o+=`${e}\n`,s<=0&&o.trim()&&(r(o,t,a),o=``,s=0);return o.trim()&&a.push(o.trimEnd()),a.join(`
3
- `)}export function extractCriticalCSS(n=!0){let r=e(n);if(!r.trim())return``;let a=t(deduplicateCSSRules(r));return a.trim()?`<style data-critical-css="true">${a}</style>`:``}export function deferNonCriticalStylesheets(e){return e.replaceAll(/<link\s+([^>]*rel=["']stylesheet["'][^>]*)>/gi,(e,t)=>{if(/\bmedia\s*=/i.test(t)||/data-critical/i.test(t))return e;let n=/href=["']([^"']+)["']/i.exec(t);if(!n)return e;let r=n[1];return!r.startsWith(`https://`)&&!r.startsWith(`http://`)?e:`${`<link ${t} media="print" onload="this.media='all'">`}\n${`<noscript><link rel="stylesheet" href="${r}"></noscript>`}`})}export function inlineCriticalCSS(e){let t=extractCriticalCSS(!0),n=e;return t&&n.includes(`</head>`)&&(n=n.replace(`</head>`,`${t}\n</head>`)),n=deferNonCriticalStylesheets(n),n}
3
+ `)}export function extractCriticalCSS(n=!0){let r=t(n);if(!r.trim())return``;let a=e(deduplicateCSSRules(r));return a.trim()?`<style data-critical-css="true">${a}</style>`:``}export function deferNonCriticalStylesheets(e){let t=/<link\s+([^>]*rel=["']stylesheet["'][^>]*)>/gi,n=[/syntax-highlight/i,/hljs/i,/prism/i,/highlight\.js/i];return e.replaceAll(t,(e,t)=>{if(/\bmedia\s*=/i.test(t)||/data-critical/i.test(t))return e;let r=/href=["']([^"']+)["']/i.exec(t);if(!r)return e;let i=r[1],a=i.startsWith(`https://`)||i.startsWith(`http://`),o=/data-defer/i.test(t),s=!a&&n.some(e=>e.test(i));return!a&&!o&&!s?e:`${`<link ${t.replace(/\s*data-defer(?:=["'][^"']*["'])?\s*/gi,` `).trim()} media="print" onload="this.media='all'">`}\n${`<noscript><link rel="stylesheet" href="${i}"></noscript>`}`})}export function inlineCriticalCSS(e){let t=extractCriticalCSS(!0),n=e;return t&&n.includes(`</head>`)&&(n=n.replace(`</head>`,`${t}\n</head>`)),n=deferNonCriticalStylesheets(n),n=c(n),n}function c(e){let t=/href=["'](https:\/\/fonts\.googleapis\.com\/css2[^"']+)["']/gi,n=new Set,r;for(r=t.exec(e);r!==null;r=t.exec(e))n.add(r[1]);if(n.size===0)return e;let i=[];for(let t of n){let n=t.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);new RegExp(String.raw`<link\s+[^>]*rel=["']preload["'][^>]*href=["']${n}["'][^>]*>`,`i`).test(e)||i.push(`<link rel="preload" href="${t}" as="style">`)}if(i.length===0)return e;let a=i.join(`
4
+ `);return e.includes(`</title>`)?e.replace(`</title>`,`</title>\n${a}`):e}
@@ -9,15 +9,12 @@
9
9
  * 4. Adapter Copying: Copies framework adapters to dist/
10
10
  * 5. Netlify Function Copying: Copies server function to all Netlify paths
11
11
  * 6. Prerendering: Boots built server, fetches routes, writes static HTML
12
+ * 7. HTML Optimization: Inlines small CSS, defers non-critical stylesheets,
13
+ * adds font preload hints
12
14
  *
13
15
  * Usage:
14
16
  * import { runPostBuild } from '@useavalon/avalon/post-build';
15
17
  * await runPostBuild();
16
- *
17
- * With custom prerender config:
18
- * await runPostBuild({
19
- * prerender: { routes: ['/'], crawlLinks: true, ignore: ['/admin'] },
20
- * });
21
18
  */
22
19
  export interface PrerenderConfig {
23
20
  /** Routes to prerender (default: ['/']) */
@@ -45,13 +42,4 @@ export interface PostBuildOptions {
45
42
  /** Port for prerender server (default: 13172) */
46
43
  prerenderPort?: number;
47
44
  }
48
- /**
49
- * Run all post-build tasks.
50
- *
51
- * Usage in consumer's post-build.mjs:
52
- * ```js
53
- * import { runPostBuild } from '@useavalon/avalon/post-build';
54
- * await runPostBuild();
55
- * ```
56
- */
57
45
  export declare function runPostBuild(options?: PostBuildOptions): Promise<void>;
@@ -1,32 +1,23 @@
1
- import{copyFileSync as e,cpSync as t,existsSync as n,mkdirSync as r,readdirSync as i,readFileSync as a,unlinkSync as o,writeFileSync as s}from"node:fs";import{dirname as c,join as l,relative as u}from"node:path";function d(e,t,r=[]){if(!n(e))return r;for(let n of i(e,{withFileTypes:!0})){let i=l(e,n.name);n.isDirectory()?d(i,t,r):t(n.name)&&r.push(i)}return r}function f(e){if(!n(e))return!1;let t=a(e,`utf-8`);return t.length<500&&!t.includes(`data-framework`)&&!t.includes(`avalon`)}function p(e){for(let t of[`dist/index.html`,`.netlify/functions-internal/server/public/index.html`,`.output/public/index.html`]){let r=l(e,t);f(r)?(o(r),console.log(`[cleanup] Removed stale Vite template ${t}`)):n(r)&&console.log(`[cleanup] Preserved prerendered ${t}`)}}function m(e,t,r){if(!n(e))return;let i=[l(t,`assets`),l(r,`.netlify`,`functions-internal`,`server`,`public`,`assets`),l(r,`.output`,`public`,`assets`)].find(e=>n(e));if(!i)return;let o=d(i,e=>e.endsWith(`.css`)).filter(e=>{let t=(e.split(`/`).pop()||``).toLowerCase();return t.includes(`_isolated-island-entry`)?!1:!!(t.startsWith(`entry-client`)||t.startsWith(`index-`))}).map(e=>`/assets${e.substring(i.length).replaceAll(`\\`,`/`)}`);if(!o.some(e=>/\/index-[^/]+\.css$/.test(e))){let e=d(i,e=>e.endsWith(`.css`)).filter(e=>(e.split(`/`).pop()||``).toLowerCase().startsWith(`ssr-index`)).map(e=>`/assets${e.substring(i.length).replaceAll(`\\`,`/`)}`);o.push(...e)}console.log(`[patch] Found ${o.length} CSS files in ${i}`);let c=a(e,`utf-8`);for(let{re:t,hrefRe:n,q:r}of[{re:/css:\[(\{href:`[^`]+`\}(?:,\{href:`[^`]+`\})*)\]/,hrefRe:/href:`([^`]+)`/g,q:"`"},{re:/css:\[(\{href:"[^"]+"\}(?:,\{href:"[^"]+"\})*)\]/,hrefRe:/href:"([^"]+)"/g,q:`"`}]){let i=t.exec(c);if(!i)continue;let a=new Set([...i[1].matchAll(n)].map(e=>e[1])),l=o.filter(e=>!a.has(e));if(l.length===0){console.log(`[patch] All CSS already included`);return}let u=l.map(e=>`{href:${r}${e}${r}}`).join(`,`);c=c.replace(i[0],`css:[${i[1]},${u}]`),s(e,c),console.log(`[patch] ✅ Added ${l.length} CSS files to SSR bundle`);return}console.warn(`[patch] Could not find CSS array in SSR bundle`)}function h(e){return e.replaceAll(/\/\*[\s\S]*?\*\//g,``).replaceAll(/\s+/g,` `).replaceAll(/\s*([{}:;,>~+])\s*/g,`$1`).replaceAll(/;}/g,`}`).trim()}function g(e,t){let o=l(t,`assets`);if(n(o)&&i(o).some(e=>e.startsWith(`entry-client`)&&e.endsWith(`.css`))){console.log(`[ssr-css] Skipped — client build already includes entry-client CSS`);return}let c=[l(e,`node_modules`,`.nitro`,`vite`,`services`,`ssr`,`assets`)];for(let o of c){if(!n(o))continue;let c=i(o).filter(e=>e.endsWith(`.css`));if(c.length===0)continue;let u=[l(t,`assets`),l(e,`.output`,`public`,`assets`),l(e,`.netlify`,`functions-internal`,`server`,`public`,`assets`)];for(let e of u){r(e,{recursive:!0});for(let t of c){let n=a(l(o,t),`utf-8`);n=h(n),s(l(e,`ssr-${t}`),n)}}let d=`ssr-${c[0]}`,f=l(u.find(e=>n(e))||u[0],d),p=n(f)?a(f).length:0;console.log(`[ssr-css] Copied SSR CSS → /assets/${d} (${p} bytes, minified)`);let m=[l(e,`.output`,`server`,`index.mjs`),l(e,`.netlify`,`functions-internal`,`server`,`main.mjs`)];for(let e of m){if(!n(e))continue;let t=a(e,`utf-8`),r=`/assets/${d}`;if(t.includes(r))continue;let i=/"\/assets\/[^"]+\.css":\{type:`text\/css[^}]+\}/.exec(t);if(i){let n=new Date().toISOString(),a=`,"${r}":{type:\`text/css; charset=utf-8\`,etag:\`${`"${p.toString(16)}-ssr"`}\`,mtime:\`${n}\`,size:${p},path:\`../public/assets/${d}\`}`;t=t.replace(i[0],i[0]+a),s(e,t),console.log(`[ssr-css] ✅ Patched asset manifest in ${e}`)}}return}console.log(`[ssr-css] No SSR CSS files found`)}async function _(e,t){let r=l(t,`islands`);if(!n(r))return;let i=d(r,e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));if(i.length===0)return;let o=[];for(let t of i){let i=a(t,`utf-8`);if(!/from\s*["']\.\.\//.test(i))continue;let s=t.substring(r.length+1).replace(/\.js$/,``),c=`preact`;if(t.includes(`.solid.`)?c=`solid`:t.includes(`.vue.`)||t.endsWith(`.vue.js`)?c=`vue`:t.includes(`.svelte.`)?c=`svelte`:t.includes(`.react.`)?c=`react`:t.includes(`.lit.`)?c=`lit`:t.includes(`.qwik.`)&&(c=`qwik`),c===`qwik`||c===`lit`)continue;let u=l(e,`${s}.tsx`),d=l(e,`${s}.ts`),f=l(e,`${s}`).replace(/\.vue$/,`.vue`),p=l(e,`${s}`).replace(/\.svelte$/,`.svelte`),m=null;for(let e of[u,d,f,p])if(n(e)){m=e;break}if(!m){console.warn(`[islands] ⚠ Cannot find source for stale island: ${s}`);continue}o.push({filePath:m,bundleKey:s,framework:c})}if(o.length!==0){console.log(`[islands] Found ${o.length} island(s) still code-split, rebuilding...`);try{let{buildIsolatedIslands:t}=await import(`./isolated-island-builder.js`),n=new Map;for(let e of o)n.set(e.filePath,e);await t(e,`dist`,n,[],{})}catch(e){console.error(`[islands] Failed to rebuild stale islands: ${e instanceof Error?e.message:e}`)}}}function v(t,i){let a=l(i,`islands`);if(!n(a))return;let s=[l(t,`.output`,`public`),l(t,`.netlify`,`functions-internal`,`server`,`public`)],u=0,f=d(a,e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));for(let t of s){if(!n(t))continue;let i=l(t,`islands`);for(let t of f){let s=l(i,t.substring(a.length));r(c(s),{recursive:!0}),e(t,s),u++;for(let e of[`.br`,`.gz`,`.zst`]){let t=s+e;n(t)&&o(t)}}}u>0&&console.log(`[islands] Synced ${u} isolated island build(s) to output`)}function y(t){let i=[l(t,`assets`,`islands`),l(t,`islands`)].find(e=>n(e));if(!i)return;let o=d(i,e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));if(o.length===0)return;let f=o.filter(e=>/-[A-Za-z0-9_-]{6,12}\.js$/.test(e));if(f.length===0){console.log(`[redirects] ${o.length} island(s) found with clean paths — no redirects needed`);return}let p=[];for(let n of f){let i=`/${u(t,n).replaceAll(`\\`,`/`)}`,a=i.replace(`/assets/`,`/`).replace(/-[A-Za-z0-9_-]{6,12}\.js$/,`.js`);p.push(`${a} ${i} 200`);let o=l(t,a.slice(1));r(c(o),{recursive:!0}),e(n,o)}let m=l(t,`_redirects`),h=n(m)?a(m,`utf-8`):``;h=h.replaceAll(/# Island JS path rewrites[^\n]*\n(?:\/islands\/[^\n]*\n)*/g,``).trim();let g=`# Island JS path rewrites (generated by Avalon post-build)
2
- `;s(m,h?`${h}\n\n${g}${p.join(`
3
- `)}\n`:`${g+p.join(`
4
- `)}\n`),console.log(`[redirects] ✅ Wrote ${p.length} island redirects + local copies`)}function b(t,a){let o=[l(t,`.output`,`public`,`_adapters`),l(a,`_adapters`)];for(let t of o){if(!n(t))continue;let o=i(t).filter(e=>e.endsWith(`.js`));if(o.length===0)continue;let s=l(a,`_adapters`);r(s,{recursive:!0});for(let n of o){let r=l(t,n),i=l(s,n);r!==i&&e(r,i)}console.log(`[adapters] ✅ Copied ${o.length} framework adapters`);return}console.log(`[adapters] No _adapters/ directory found`)}function x(e){let r=l(e,`.netlify`,`functions-internal`,`server`);if(!n(r))return;let i=[l(e,`.netlify`,`v1`,`functions`,`server`)];for(let n of i){t(r,n,{recursive:!0,force:!0});let i=n.substring(e.length).replaceAll(`\\`,`/`);console.log(`[netlify-fn] ✅ Copied server function to ${i}/`)}}function S(e){let t=a(e,`utf-8`);return t.includes(`path: "/*"`)||t.includes("path:`/*`")}function C(e,t,r){let i=l(c(e),`_prerender-server.mjs`),a=``,o=[l(r,`node_modules`,`urlpattern-polyfill`,`index.js`),l(r,`node_modules`,`urlpattern-polyfill`,`dist`,`urlpattern.js`)].find(e=>n(e));return o&&(a=`import '${o.replaceAll(`\\`,`/`)}';`),s(i,`
5
- ${a}
1
+ import{copyFileSync as e,cpSync as t,existsSync as n,mkdirSync as r,readdirSync as i,readFileSync as a,unlinkSync as o,writeFileSync as s}from"node:fs";import{dirname as c,join as l,relative as u}from"node:path";function d(e,t,r=[]){if(!n(e))return r;for(let n of i(e,{withFileTypes:!0})){let i=l(e,n.name);n.isDirectory()?d(i,t,r):t(n.name)&&r.push(i)}return r}function f(e){if(!n(e))return!1;let t=a(e,`utf-8`);return t.length<500&&!t.includes(`data-framework`)&&!t.includes(`avalon`)}function p(e){for(let t of[`dist/index.html`,`.netlify/functions-internal/server/public/index.html`,`.output/public/index.html`]){let r=l(e,t);f(r)?(o(r),console.log(`[cleanup] Removed stale Vite template ${t}`)):n(r)&&console.log(`[cleanup] Kept ${t} (not a Vite template)`)}}function m(e){return e.replaceAll(/\/\*[\s\S]*?\*\//g,``).replaceAll(/\s+/g,` `).replaceAll(/\s*([{}:;,])\s*/g,`$1`).trim()}function h(e,t,r){if(!n(e))return;let i=[l(t,`assets`),l(r,`.netlify`,`functions-internal`,`server`,`public`,`assets`),l(r,`.output`,`public`,`assets`)].find(e=>n(e));if(!i)return;let o=d(i,e=>e.endsWith(`.css`)).filter(e=>{let t=(e.split(`/`).pop()||``).toLowerCase();return t.includes(`_isolated-island-entry`)?!1:!!(t.startsWith(`entry-client`)||t.startsWith(`index-`))}).map(e=>`/assets${e.substring(i.length).replaceAll(`\\`,`/`)}`);if(!o.some(e=>/\/index-[^/]+\.css$/.test(e))){let e=d(i,e=>e.endsWith(`.css`)).filter(e=>(e.split(`/`).pop()||``).toLowerCase().startsWith(`ssr-index`)).map(e=>`/assets${e.substring(i.length).replaceAll(`\\`,`/`)}`);o.push(...e)}console.log(`[patch] Found ${o.length} CSS files in ${i}`);let c=a(e,`utf-8`);for(let{re:t,hrefRe:n,q:r}of[{re:/css:\[(\{href:`[^`]+`\}(?:,\{href:`[^`]+`\})*)\]/,hrefRe:/href:`([^`]+)`/g,q:"`"},{re:/css:\[(\{href:"[^"]+"\}(?:,\{href:"[^"]+"\})*)\]/,hrefRe:/href:"([^"]+)"/g,q:`"`}]){let i=t.exec(c);if(!i)continue;let a=new Set([...i[1].matchAll(n)].map(e=>e[1])),l=o.filter(e=>!a.has(e));if(l.length===0){console.log(`[patch] All CSS already included`);return}let u=l.map(e=>`{href:${r}${e}${r}}`).join(`,`);c=c.replace(i[0],`css:[${i[1]},${u}]`),s(e,c),console.log(`[patch] ✅ Added ${l.length} CSS files to SSR bundle`);return}console.warn(`[patch] Could not find CSS array in SSR bundle`)}function g(e,t){let o=l(t,`assets`);if(n(o)&&i(o).some(e=>e.startsWith(`entry-client`)&&e.endsWith(`.css`))){console.log(`[ssr-css] Skipped — client build already includes entry-client CSS`);return}let c=[l(e,`node_modules`,`.nitro`,`vite`,`services`,`ssr`,`assets`)];for(let o of c){if(!n(o))continue;let c=i(o).filter(e=>e.endsWith(`.css`));if(c.length===0)continue;let u=[l(t,`assets`),l(e,`.output`,`public`,`assets`),l(e,`.netlify`,`functions-internal`,`server`,`public`,`assets`)];for(let e of u){r(e,{recursive:!0});for(let t of c){let n=a(l(o,t),`utf-8`);n=m(n),s(l(e,`ssr-${t}`),n)}}let d=`ssr-${c[0]}`,f=l(u.find(e=>n(e))||u[0],d),p=n(f)?a(f).length:0;console.log(`[ssr-css] Copied SSR CSS → /assets/${d} (${p} bytes, minified)`);let h=[l(e,`.output`,`server`,`index.mjs`),l(e,`.netlify`,`functions-internal`,`server`,`main.mjs`)];for(let e of h){if(!n(e))continue;let t=a(e,`utf-8`),r=`/assets/${d}`;if(t.includes(r))continue;let i=/"\/assets\/[^"]+\.css":\{type:`text\/css[^}]+\}/.exec(t);if(i){let n=new Date().toISOString(),a=`,"${r}":{type:\`text/css; charset=utf-8\`,etag:\`${`"${p.toString(16)}-ssr"`}\`,mtime:\`${n}\`,size:${p},path:\`../public/assets/${d}\`}`;t=t.replace(i[0],i[0]+a),s(e,t),console.log(`[ssr-css] ✅ Patched asset manifest in ${e}`)}}return}console.log(`[ssr-css] No SSR CSS files found`)}async function _(e,t){let r=l(t,`islands`);if(!n(r)){console.log(`[islands] No islands directory found, skipping isolation`);return}let i=d(r,e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));if(!i.some(e=>{let t=a(e,`utf-8`);return t.includes(`from"../`)||t.includes(`from'../`)})){console.log(`[islands] All islands are self-contained`);return}try{let{buildIsolatedIslands:n}=await import(`./isolated-island-builder.js`),r=new Map;for(let e of i){let n=u(t,e).replaceAll(`\\`,`/`).replace(/^islands\//,``).replace(/\.js$/,``),i=a(e,`utf-8`),o=`preact`;i.includes(`solid`)||i.includes(`createSignal`)?o=`solid`:i.includes(`vue`)||i.includes(`createApp`)?o=`vue`:i.includes(`svelte`)&&(o=`svelte`);let s=/from["']([^"']+\.(tsx|jsx|vue|svelte))["']/i.exec(i),c=s?s[1]:`src/islands/${n}.tsx`;r.set(n,{filePath:c,bundleKey:n,framework:o})}await n(e,t,r,[],{})}catch{console.warn(`[islands] Isolated rebuild failed, falling back to inline-islands`);try{let{inlineIslandChunks:e}=await import(`./inline-islands.js`);await e(t,{verbose:!0})}catch(e){console.error(`[islands] Inline fallback also failed:`,e)}}}function v(e,r){let i=l(r,`islands`);if(!n(i))return;let a=[l(e,`.output`,`public`),l(e,`.netlify`,`functions-internal`,`server`,`public`)];for(let e of a)n(e)&&t(i,l(e,`islands`),{recursive:!0,force:!0});console.log(`[islands] Synced isolated islands to output directories`)}function y(e){let t=l(e,`islands`);if(!n(t))return;let r=d(t,e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));if(r.length===0)return;let i=[];for(let t of r){let n=t.substring(e.length).replaceAll(`\\`,`/`);i.push(`${n} ${n} 200`)}let o=l(e,`_redirects`),c=n(o)?a(o,`utf-8`):``;s(o,c?`${c}\n${i.join(`
2
+ `)}`:i.join(`
3
+ `)),console.log(`[redirects] Generated ${i.length} island redirect(s)`)}function b(e,a){let o=l(e,`node_modules`,`@useavalon`);if(!n(o))return;let s=[l(a,`adapters`),l(e,`.output`,`public`,`adapters`),l(e,`.netlify`,`functions-internal`,`server`,`public`,`adapters`)];for(let e of s){if(!n(c(e)))continue;let a=i(o).map(e=>l(o,e,`client`)).filter(e=>n(e));for(let n of a){let i=l(e,n.split(`/`).at(-2)||``);r(i,{recursive:!0}),t(n,i,{recursive:!0,force:!0})}}console.log(`[adapters] Copied framework adapters to output directories`)}function x(e){let i=l(e,`.netlify`,`functions-internal`),a=l(e,`.netlify`,`v1`,`functions`);n(i)&&(r(a,{recursive:!0}),t(i,a,{recursive:!0,force:!0}),console.log(`[netlify] Copied server function to v1 API paths`))}function S(e){if(!n(e))return!1;let t=a(e,`utf-8`);return t.includes(`netlify`)||t.includes(`lambda`)}function C(e,t,n){let r=l(c(e),`_prerender-server.mjs`);return s(r,`
6
4
  import { createServer } from 'node:http';
7
- import handler from './main.mjs';
8
- const PORT = ${t};
5
+ import { handler } from './main.mjs';
6
+
9
7
  const server = createServer(async (req, res) => {
8
+ const url = new URL(req.url, 'http://localhost:${t}');
9
+ const event = { rawUrl: url.href, path: url.pathname, httpMethod: req.method, headers: Object.fromEntries(Object.entries(req.headers)), body: null, isBase64Encoded: false };
10
10
  try {
11
- const url = new URL(req.url, 'http://localhost:' + PORT);
12
- const headers = new Headers();
13
- for (const [key, value] of Object.entries(req.headers)) {
14
- if (value) headers.set(key, Array.isArray(value) ? value.join(', ') : value);
15
- }
16
- const request = new Request(url.toString(), { method: req.method, headers });
17
- const response = await handler(request);
18
- res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
19
- const body = await response.text();
20
- res.end(body);
11
+ const result = await handler(event, {});
12
+ res.writeHead(result.statusCode || 200, result.headers || {});
13
+ res.end(result.body || '');
21
14
  } catch (err) {
22
- console.error('[prerender-wrapper] Error:', err);
23
15
  res.writeHead(500);
24
16
  res.end('Internal Server Error');
25
17
  }
26
18
  });
27
- server.listen(PORT, '127.0.0.1', () => {
28
- console.log('[prerender-wrapper] Listening on http://127.0.0.1:' + PORT);
29
- });
30
- `),i}function w(e){let t=[],n=/<a\s[^>]*href=["']([^"'#?]+)/gi,r=null;for(r=n.exec(e);r!==null;r=n.exec(e)){let e=r[1];e.startsWith(`/`)&&!e.startsWith(`//`)&&!e.startsWith(`/assets/`)&&!e.startsWith(`/islands/`)&&!e.startsWith(`/chunks/`)&&!e.startsWith(`/_`)&&!e.match(/\.\w{2,5}$/)&&t.push(e)}return[...new Set(t)]}async function T(t,i,d,f){let p=[l(t,`.netlify`,`functions-internal`,`server`,`server.mjs`),l(t,`.netlify`,`v1`,`functions`,`server`,`server.mjs`),l(t,`.output`,`server`,`index.mjs`)].find(e=>n(e));if(!p){console.log(`[prerender] No server entry found, skipping prerender`);return}let m=[l(t,`.output`,`public`),i].find(e=>n(e));if(!m){console.log(`[prerender] No output directory found, skipping prerender`);return}let h={routes:d.routes??[`/`],crawlLinks:d.crawlLinks??!0,ignore:d.ignore??[],failOnError:d.failOnError??!1,concurrency:d.concurrency??4,autoSubfolderIndex:d.autoSubfolderIndex??!0,retry:d.retry??3,retryDelay:d.retryDelay??500},g=`http://localhost:${f}`,_=S(p),v=p;if(_){let e=l(c(p),`main.mjs`);if(!n(e)){console.error(`[prerender] Netlify handler detected but main.mjs not found`);return}v=C(e,f,t),console.log(`[prerender] Netlify handler detected using wrapper`)}{let e=[p,l(c(p),`main.mjs`)].filter(e=>n(e));for(let n of e){let e=a(n,`utf-8`),r=/"\/[^"]*\.html":\{[^}]+\},?/g,i=e.length;e=e.replaceAll(r,``),i!==e.length&&(s(n,e),console.log(`[prerender] Patched ${u(t,n)}: removed HTML asset entries`))}}console.log(`[prerender] Starting prerender with server: ${u(t,v)}`);let{spawn:y}=await import(`node:child_process`),b;try{b=y(`node`,[v],{env:{...process.env,PORT:String(f),NITRO_PORT:String(f),HOST:`127.0.0.1`,NITRO_HOST:`127.0.0.1`,NODE_ENV:`production`},stdio:[`ignore`,`pipe`,`pipe`]}),b.stdout?.on(`data`,e=>{let t=e.toString().trim();t&&console.log(`[prerender:server] ${t}`)}),b.stderr?.on(`data`,e=>{let t=e.toString().trim();t&&console.error(`[prerender:server:err] ${t}`)});let e=Date.now(),t=!1;for(;Date.now()-e<15e3;){try{let e=await fetch(`${g}/`);if(e.ok||e.status<500){t=!0;break}}catch{}await new Promise(e=>setTimeout(e,200))}if(!t)throw Error(`Server did not become ready within 15s`);console.log(`[prerender] Server ready at ${g}`)}catch(e){b?.kill(`SIGKILL`),console.error(`[prerender] Failed to start server:`,e.message);return}let x=new Set,T=[...h.routes],E=[],D=[];function O(e){for(let t of h.ignore)if(typeof t==`string`&&e===t||typeof t==`string`&&t.endsWith(`/**`)&&e.startsWith(t.slice(0,-2)))return!0;return!1}try{for(;T.length>0;){let e=T.splice(0,h.concurrency);await Promise.all(e.map(async e=>{let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;if(x.has(t)||(x.add(t),O(t)))return;let n=null;for(let e=1;e<=h.retry;e++)try{let e=await fetch(`${g}${t}`);n={html:await e.text(),status:e.status};break}catch{e<h.retry&&await new Promise(e=>setTimeout(e,h.retryDelay))}if(!n){D.push({route:t,error:`Failed after ${h.retry} attempts`});return}if(n.status>=400){D.push({route:t,error:`Returned ${n.status}`});return}let i=h.autoSubfolderIndex?l(t,`index.html`):`${t}.html`,a=l(m,i);if(r(c(a),{recursive:!0}),s(a,n.html.replace(`<!DOCTYPE html>`,`<!DOCTYPE html>
31
- <!-- SSG: prerendered at build time -->`).replaceAll(/<link rel="stylesheet" href="\/assets\/[^"]*\.css">\n?/g,e=>e.includes(`_isolated-island-entry`)?``:e)),E.push(t),console.log(`[prerender] ✅ ${t} → ${i}`),h.crawlLinks)for(let e of w(n.html)){let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;!x.has(t)&&!O(t)&&T.push(t)}}))}}finally{console.log(`[prerender] Shutting down server...`),b?.kill(`SIGKILL`)}if(console.log(`[prerender] Done: ${E.length} page(s) prerendered`+(D.length>0?`, ${D.length} error(s)`:``)),_){let e=l(c(p),`_prerender-server.mjs`);n(e)&&(o(e),console.log(`[prerender] Cleaned up wrapper script`))}if(E.length>0){let a=[i,l(t,`.netlify`,`functions-internal`,`server`,`public`),l(t,`.netlify`,`v1`,`functions`,`server`,`public`)].filter(e=>e!==m&&n(c(e)));for(let t of a)for(let i of E){let a=l(i,`index.html`),o=l(m,a),s=l(t,a);n(o)&&(r(c(s),{recursive:!0}),e(o,s))}a.length>0&&console.log(`[prerender] Copied prerendered HTML to ${a.length} additional output dir(s)`)}}function E(e,t){let r={},i=l(t,`island-deps.json`);if(n(i))try{r=JSON.parse(a(i,`utf-8`))}catch{}let o=l(e,`.output`,`public`);if(Object.keys(r).length===0&&n(o)){let e=d(l(o,`islands`),e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));for(let t of e){let e=`/${t.substring(o.length+1).replaceAll(`\\`,`/`)}`,n=a(t,`utf-8`),i=/\bfrom\s*["']([^"']+)["']|import\s*["']([^"']+)["']/g,s=[],u;for(u=i.exec(n);u!==null;u=i.exec(n)){let t=u[1]||u[2];if(t&&(t.includes(`/assets/`)||t.startsWith(`.`))){let n=(t.startsWith(`.`)?`/${l(c(e.slice(1)),t).replaceAll(`\\`,`/`).replace(/^\/+/,``)}`:t).split(`/`).filter(Boolean),r=[];for(let e of n)e===`..`?r.pop():e!==`.`&&r.push(e);s.push(`/${r.join(`/`)}`)}}s.length>0&&(r[e]=s)}}if(Object.keys(r).length===0)return;let u=[l(e,`.output`,`public`),t],f=0;for(let e of u){if(!n(e))continue;let t=d(e,e=>e===`index.html`);for(let e of t){let t=a(e,`utf-8`),n=new Set;for(let[e,i]of Object.entries(r))if(t.includes(e))for(let e of i)t.includes(`href="${e}"`)||n.add(e);if(n.size===0)continue;let i=Array.from(n).map(e=>`<link rel="modulepreload" href="${e}">`).join(`
32
- `);t.includes(`</head>`)&&(t=t.replace(`</head>`,`${i}\n</head>`),s(e,t),f++)}}f>0&&console.log(`[modulepreload] ✅ Injected dependency preloads into ${f} HTML file(s)`)}async function D(e){let{promisify:t}=await import(`node:util`),r=await import(`node:zlib`),i=t(r.brotliCompress),o=t(r.gzip),c=[l(e,`.output`,`public`),l(e,`.netlify`,`functions-internal`,`server`,`public`)],u=e=>e.endsWith(`.js`)||e.endsWith(`.css`)||e.endsWith(`.html`)||e.endsWith(`.svg`)||e.endsWith(`.txt`)||e.endsWith(`.xml`)||e.endsWith(`.json`),f=0;for(let e of c){if(!n(e))continue;let t=d(e,e=>u(e)&&!e.endsWith(`.map`));for(let e of t){let t=a(e);if(!(t.length<256))try{let[n,a]=await Promise.all([i(t,{params:{[r.constants.BROTLI_PARAM_QUALITY]:11}}),o(t,{level:9})]);s(`${e}.br`,n),s(`${e}.gz`,a),f++}catch{}}}f>0&&console.log(`[compress] ✅ Compressed ${f} public asset(s) (brotli + gzip)`)}export async function runPostBuild(e={}){let t=e.cwd??process.cwd(),r=l(t,`dist`),i=e.prerenderPort??13172;p(t),g(t,r);for(let e of[l(t,`.netlify`,`functions-internal`,`server`,`_ssr`,`ssr.mjs`),l(t,`.netlify`,`v1`,`functions`,`server`,`_ssr`,`ssr.mjs`),l(t,`.output`,`server`,`_ssr`,`ssr.mjs`)])n(e)&&(console.log(`[patch] Patching ${e}`),m(e,r,t));await _(t,r),v(t,r),y(r),b(t,r),x(t),e.prerender!==!1&&await T(t,r,e.prerender??{},i),E(t,r),await D(t),console.log(`[post-build] ✅ Complete`)}
19
+ server.listen(${t}, '127.0.0.1', () => console.log('Listening on http://127.0.0.1:${t}'));
20
+ `),r}function w(e){let t=/<a\s+[^>]*href=["']([^"']+)["'][^>]*>/gi,n=[],r;for(r=t.exec(e);r!==null;r=t.exec(e)){let e=r[1];e.startsWith(`/`)&&!e.startsWith(`//`)&&!e.includes(`.`)&&n.push(e)}return n}async function T(t,i,d,f){let p=[l(t,`.netlify`,`functions-internal`,`server`,`server.mjs`),l(t,`.netlify`,`v1`,`functions`,`server`,`server.mjs`),l(t,`.output`,`server`,`index.mjs`)].find(e=>n(e));if(!p){console.log(`[prerender] No server entry found, skipping prerender`);return}let m=[l(t,`.output`,`public`),i].find(e=>n(e));if(!m){console.log(`[prerender] No output directory found, skipping prerender`);return}let h={routes:d.routes??[`/`],crawlLinks:d.crawlLinks??!0,ignore:d.ignore??[],failOnError:d.failOnError??!1,concurrency:d.concurrency??4,autoSubfolderIndex:d.autoSubfolderIndex??!0,retry:d.retry??3,retryDelay:d.retryDelay??500},g=`http://localhost:${f}`,_=S(p),v=p;if(_){let e=l(c(p),`main.mjs`);if(!n(e)){console.error(`[prerender] Netlify handler detected but main.mjs not found`);return}v=C(e,f,t),console.log(`[prerender] Netlify handler detected — using wrapper`)}{let e=[p,l(c(p),`main.mjs`)].filter(e=>n(e));for(let n of e){let e=a(n,`utf-8`),r=/"\/[^"]*\.html":\{[^}]+\},?/g,i=e.length;e=e.replaceAll(r,``),i!==e.length&&(s(n,e),console.log(`[prerender] Patched ${u(t,n)}: removed HTML asset entries`))}}console.log(`[prerender] Starting prerender with server: ${u(t,v)}`);let{spawn:y}=await import(`node:child_process`),b;try{b=y(`node`,[v],{env:{...process.env,PORT:String(f),NITRO_PORT:String(f),HOST:`127.0.0.1`,NITRO_HOST:`127.0.0.1`,NODE_ENV:`production`},stdio:[`ignore`,`pipe`,`pipe`]}),b.stdout?.on(`data`,e=>{let t=e.toString().trim();t&&console.log(`[prerender:server] ${t}`)}),b.stderr?.on(`data`,e=>{let t=e.toString().trim();t&&console.error(`[prerender:server:err] ${t}`)});let e=Date.now(),t=!1;for(;Date.now()-e<15e3;){try{let e=await fetch(`${g}/`);if(e.ok||e.status<500){t=!0;break}}catch{}await new Promise(e=>setTimeout(e,200))}if(!t)throw Error(`Server did not become ready within 15s`);console.log(`[prerender] Server ready at ${g}`)}catch(e){b?.kill(`SIGKILL`),console.error(`[prerender] Failed to start server:`,e.message);return}let x=new Set,T=[...h.routes],E=[],D=[];function O(e){for(let t of h.ignore)if(typeof t==`string`&&e===t||typeof t==`string`&&t.endsWith(`/**`)&&e.startsWith(t.slice(0,-2)))return!0;return!1}try{for(;T.length>0;){let e=T.splice(0,h.concurrency);await Promise.all(e.map(async e=>{let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;if(x.has(t)||(x.add(t),O(t)))return;let n=null;for(let e=1;e<=h.retry;e++)try{let e=await fetch(`${g}${t}`);n={html:await e.text(),status:e.status};break}catch{e<h.retry&&await new Promise(e=>setTimeout(e,h.retryDelay))}if(!n){D.push({route:t,error:`Failed after ${h.retry} attempts`});return}if(n.status>=400){D.push({route:t,error:`Returned ${n.status}`});return}let i=h.autoSubfolderIndex?l(t,`index.html`):`${t}.html`,a=l(m,i);r(c(a),{recursive:!0});let o=n.html.replace(`<!DOCTYPE html>`,`<!DOCTYPE html>
21
+ <!-- SSG: prerendered at build time -->`).replaceAll(/<link rel="stylesheet" href="\/assets\/[^"]*\.css">\n?/g,e=>e.includes(`_isolated-island-entry`)?``:e);if(o=o.replaceAll(/<script type="module" src="\/assets\/entry-client[^"]*\.js"><\/script>\n?/g,``),o=o.replaceAll(/<link rel="stylesheet" href="\/assets\/entry-client[^"]*\.css">\n?/g,``),o=o.replaceAll(/<link rel="modulepreload" href="\/assets\/entry-client[^"]*\.js">\n?/g,``),s(a,o),E.push(t),console.log(`[prerender] ✅ ${t} → ${i}`),h.crawlLinks)for(let e of w(n.html)){let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;!x.has(t)&&!O(t)&&T.push(t)}}))}}finally{console.log(`[prerender] Shutting down server...`),b?.kill(`SIGKILL`)}if(console.log(`[prerender] Done: ${E.length} page(s) prerendered`+(D.length>0?`, ${D.length} error(s)`:``)),_){let e=l(c(p),`_prerender-server.mjs`);n(e)&&(o(e),console.log(`[prerender] Cleaned up wrapper script`))}if(E.length>0){let a=[i,l(t,`.netlify`,`functions-internal`,`server`,`public`),l(t,`.netlify`,`v1`,`functions`,`server`,`public`)].filter(e=>e!==m&&n(c(e)));for(let t of a)for(let i of E){let a=l(i,`index.html`),o=l(m,a),s=l(t,a);n(o)&&(r(c(s),{recursive:!0}),e(o,s))}a.length>0&&console.log(`[prerender] Copied prerendered HTML to ${a.length} additional output dir(s)`)}}function E(e,t){let r={},i=l(t,`island-deps.json`);if(n(i))try{r=JSON.parse(a(i,`utf-8`))}catch{}let o=l(e,`.output`,`public`);if(Object.keys(r).length===0&&n(o)){let e=d(l(o,`islands`),e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));for(let t of e){let e=`/${t.substring(o.length+1).replaceAll(`\\`,`/`)}`,n=a(t,`utf-8`),i=/\bfrom\s*["']([^"']+)["']|import\s*["']([^"']+)["']/g,s=[],u;for(u=i.exec(n);u!==null;u=i.exec(n)){let t=u[1]||u[2];if(t&&(t.includes(`/assets/`)||t.startsWith(`.`))){let n=(t.startsWith(`.`)?`/${l(c(e.slice(1)),t).replaceAll(`\\`,`/`).replace(/^\/+/,``)}`:t).split(`/`).filter(Boolean),r=[];for(let e of n)e===`..`?r.pop():e!==`.`&&r.push(e);s.push(`/${r.join(`/`)}`)}}s.length>0&&(r[e]=s)}}if(Object.keys(r).length===0)return;let u=[l(e,`.output`,`public`),t],f=0;for(let e of u){if(!n(e))continue;let t=d(e,e=>e===`index.html`);for(let e of t){let t=a(e,`utf-8`),n=new Set;for(let[e,i]of Object.entries(r))if(t.includes(e))for(let e of i)t.includes(`href="${e}"`)||n.add(e);if(n.size===0)continue;let i=Array.from(n).map(e=>`<link rel="modulepreload" href="${e}">`).join(`
22
+ `);t.includes(`</head>`)&&(t=t.replace(`</head>`,`${i}\n</head>`),s(e,t),f++)}}f>0&&console.log(`[modulepreload] ✅ Injected dependency preloads into ${f} HTML file(s)`)}const D=[/syntax-highlight/i,/hljs/i,/prism/i,/highlight\.js/i];function O(e,t){let r=[l(e,`.output`,`public`),t],i=k(r),o=0;for(let e of r){if(!n(e))continue;let t=d(e,e=>e===`index.html`);for(let e of t){let t=a(e,`utf-8`),n=t;t=A(t,i),t=j(t),t=M(t),t!==n&&(s(e,t),o++)}}o>0&&console.log(`[post-build] Optimized ${o} HTML file(s) deferred non-critical CSS, added font preload hints`)}function k(e){let t=new Map;for(let r of e){let e=l(r,`assets`);if(n(e))for(let n of i(e))n.endsWith(`.css`)&&t.set(`/assets/${n}`,a(l(e,n),`utf-8`))}return t}function A(e,t){let n=/<link\s+rel="stylesheet"\s+href="(\/assets\/(?:ssr-)?index-[^"]+\.css)">/i.exec(e);if(!n)return e;let r=n[1],i=t.get(r);if(!i)return e;if(Buffer.byteLength(i,`utf-8`)<=14336){let t=`<style data-inlined-from="${r}">${i}</style>`;return e.replace(n[0],t)}let a=`<link rel="preload" href="${r}" as="style">`;return e.includes(`</title>`)&&!e.includes(`preload" href="${r}"`)&&(e=e.replace(`</title>`,`</title>\n${a}`)),e}function j(e){return e.replaceAll(/<link\s+([^>]*rel=["']stylesheet["'][^>]*)>/gi,(e,t)=>{if(/\bmedia\s*=/i.test(t)||/data-critical/i.test(t))return e;let n=/href=["']([^"']+)["']/i.exec(t);if(!n)return e;let r=n[1],i=r.startsWith(`https://`)||r.startsWith(`http://`),a=!i&&D.some(e=>e.test(r));return!i&&!a?e:`<link ${t} media="print" onload="this.media='all'">\n<noscript><link ${t}></noscript>`})}function M(e){let t=/href=["'](https:\/\/fonts\.googleapis\.com\/css2[^"']+)["']/gi,n=new Set,r;for(r=t.exec(e);r!==null;r=t.exec(e))n.add(r[1]);if(n.size===0)return e;let i=[];for(let t of n){let n=t.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);new RegExp(String.raw`<link\s+[^>]*rel=["']preload["'][^>]*href=["']${n}["'][^>]*>`,`i`).test(e)||i.push(`<link rel="preload" href="${t}" as="style">`)}if(i.length===0)return e;let a=i.join(`
23
+ `);return e.includes(`</title>`)?e.replace(`</title>`,`</title>\n${a}`):e}async function N(e){let{promisify:t}=await import(`node:util`),r=await import(`node:zlib`),i=t(r.brotliCompress),o=t(r.gzip),c=[l(e,`.output`,`public`),l(e,`.netlify`,`functions-internal`,`server`,`public`)],u=e=>e.endsWith(`.js`)||e.endsWith(`.css`)||e.endsWith(`.html`)||e.endsWith(`.svg`)||e.endsWith(`.txt`)||e.endsWith(`.xml`)||e.endsWith(`.json`),f=0;for(let e of c){if(!n(e))continue;let t=d(e,e=>u(e)&&!e.endsWith(`.map`));for(let e of t){let t=a(e);if(!(t.length<256))try{let[n,a]=await Promise.all([i(t,{params:{[r.constants.BROTLI_PARAM_QUALITY]:11}}),o(t,{level:9})]);s(`${e}.br`,n),s(`${e}.gz`,a),f++}catch{}}}f>0&&console.log(`[compress] ✅ Compressed ${f} public asset(s) (brotli + gzip)`)}export async function runPostBuild(e={}){let t=e.cwd??process.cwd(),r=l(t,`dist`),i=e.prerenderPort??13172;p(t),g(t,r);for(let e of[l(t,`.netlify`,`functions-internal`,`server`,`_ssr`,`ssr.mjs`),l(t,`.netlify`,`v1`,`functions`,`server`,`_ssr`,`ssr.mjs`),l(t,`.output`,`server`,`_ssr`,`ssr.mjs`)])n(e)&&(console.log(`[patch] Patching ${e}`),h(e,r,t));await _(t,r),v(t,r),y(r),b(t,r),x(t),e.prerender!==!1&&await T(t,r,e.prerender??{},i),E(t,r),O(t,r),await N(t),console.log(`[post-build] Complete`)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useavalon/avalon",
3
- "version": "0.1.75",
3
+ "version": "0.1.77",
4
4
  "description": "Multi-framework islands architecture for the modern web",
5
5
  "license": "MIT",
6
6
  "type": "module",