@fluixi/compiler 1.0.0-alpha.70 → 1.0.0-alpha.72
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/babel-E7EQLX36.mjs +2 -0
- package/dist/chunk-EF6V4GTD.mjs +1 -0
- package/dist/frontend/babel/build-ir-lit.cjs +1 -1
- package/dist/frontend/babel/build-ir-lit.mjs +1 -1
- package/dist/frontend/babel/build-ir.cjs +1 -1
- package/dist/frontend/babel/build-ir.d.ts.map +1 -1
- package/dist/frontend/babel/build-ir.js +5 -2
- package/dist/frontend/babel/build-ir.mjs +1 -1
- package/dist/frontend/babel/index.cjs +2 -2
- package/dist/frontend/babel/index.mjs +2 -2
- package/dist/frontend/babel/lower-template.cjs +1 -1
- package/dist/frontend/babel/lower-template.mjs +1 -1
- package/dist/frontend/babel/plugin.cjs +2 -2
- package/dist/frontend/babel/plugin.js +19 -1
- package/dist/frontend/babel/plugin.mjs +2 -2
- package/dist/index.cjs +22 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.mjs +22 -1
- package/dist/integrations.cjs +27 -6
- package/dist/integrations.d.ts +16 -0
- package/dist/integrations.d.ts.map +1 -1
- package/dist/integrations.js +8 -0
- package/dist/integrations.mjs +27 -6
- package/dist/resolve/component-globals.cjs +22 -0
- package/dist/resolve/component-globals.d.ts +73 -0
- package/dist/resolve/component-globals.d.ts.map +1 -0
- package/dist/resolve/component-globals.js +137 -0
- package/dist/resolve/component-globals.mjs +22 -0
- package/dist/resolve/write-globals.cjs +22 -0
- package/dist/resolve/write-globals.d.ts +31 -0
- package/dist/resolve/write-globals.d.ts.map +1 -0
- package/dist/resolve/write-globals.js +136 -0
- package/dist/resolve/write-globals.mjs +22 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/dist/babel-NY6FXXLW.mjs +0 -2
- package/dist/chunk-3SAEGOMQ.mjs +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient declarations for the components `resolve` supplies without an import.
|
|
3
|
+
*
|
|
4
|
+
* The compiler injects the import, so nothing in the source imports these — which leaves
|
|
5
|
+
* TypeScript reporting `TS2304: Cannot find name`. The code compiles and runs; the editor
|
|
6
|
+
* is unusable and `tsc` fails. These declarations describe what the compiler already
|
|
7
|
+
* does. They do not enable it.
|
|
8
|
+
*
|
|
9
|
+
* This lives next to the resolver, and takes rules rather than a flat map, because the
|
|
10
|
+
* resolver is the only thing that knows the whole picture: a UI library's components, the
|
|
11
|
+
* project's own, and the framework builtins all arrive through the same config. A
|
|
12
|
+
* component library cannot generate this on its own — it does not know what else the
|
|
13
|
+
* project resolves — which is why the generator belongs here and the library-specific CLI
|
|
14
|
+
* that predates it does not.
|
|
15
|
+
*
|
|
16
|
+
* Pure, like the resolver: rules in, source out, no filesystem. Writing the file is the
|
|
17
|
+
* caller's job (the Vite plugin does it on config resolve; a CLI can do it in CI).
|
|
18
|
+
*/
|
|
19
|
+
import { type ComponentResolverRule } from './component-resolver.js';
|
|
20
|
+
export interface ComponentGlobalsOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Packages the project can actually resolve — normally its declared dependencies.
|
|
23
|
+
*
|
|
24
|
+
* Declaring a component from a package the project does not depend on produces an
|
|
25
|
+
* unresolvable `import(...)`, and under the usual `skipLibCheck: true` that failure is
|
|
26
|
+
* silent: the component degrades to `any` and every prop typo type-checks, which is
|
|
27
|
+
* worse than the error it replaced. Omit to declare everything, which is only correct
|
|
28
|
+
* when every package is present.
|
|
29
|
+
*
|
|
30
|
+
* Relative and alias specifiers are never filtered — there is no package to check.
|
|
31
|
+
*/
|
|
32
|
+
available?: Iterable<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Declare the framework builtins (`Show`, `For`, `Portal`, `Router`, …) too.
|
|
35
|
+
*
|
|
36
|
+
* On by default because the compiler resolves them whether or not a rule mentions
|
|
37
|
+
* them, so declaring them is what matches reality. A real import shadows the ambient
|
|
38
|
+
* declaration, so this stays safe for code that imports them explicitly.
|
|
39
|
+
*/
|
|
40
|
+
builtins?: boolean;
|
|
41
|
+
/** Attribution written into the header. */
|
|
42
|
+
generator?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Rewrite a module specifier for the file being generated.
|
|
45
|
+
*
|
|
46
|
+
* A bundler and TypeScript do not agree on every specifier: Vite resolves a
|
|
47
|
+
* root-absolute `/src/x.tsx` that TypeScript cannot see at all. The caller knows where
|
|
48
|
+
* the file will land and so is the only one able to fix that up; the generator stays
|
|
49
|
+
* pure and just asks.
|
|
50
|
+
*/
|
|
51
|
+
specifier?: (module: string) => string;
|
|
52
|
+
}
|
|
53
|
+
export interface ComponentGlobalsResult {
|
|
54
|
+
/** The `.d.ts` source to write. */
|
|
55
|
+
source: string;
|
|
56
|
+
/** Components declared. */
|
|
57
|
+
declared: string[];
|
|
58
|
+
/** Left out because `lib.dom` already claims the name. */
|
|
59
|
+
shadowedByDom: string[];
|
|
60
|
+
/** Left out because the project does not depend on their package. */
|
|
61
|
+
unavailable: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Pattern rules, which have no finite name list and so cannot be declared.
|
|
64
|
+
*
|
|
65
|
+
* `Icon(.+) -> @/icons/$1` matches unboundedly many names; the resolver answers them on
|
|
66
|
+
* demand but cannot enumerate them. Those tags stay manual, and the caller is told how
|
|
67
|
+
* many rules are in that position rather than left to wonder why nothing appeared.
|
|
68
|
+
*/
|
|
69
|
+
patternRules: number;
|
|
70
|
+
}
|
|
71
|
+
/** Build the ambient `.d.ts` for the components a project's `resolve` config supplies. */
|
|
72
|
+
export declare function componentGlobals(rules: readonly ComponentResolverRule[], options?: ComponentGlobalsOptions): ComponentGlobalsResult;
|
|
73
|
+
//# sourceMappingURL=component-globals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-globals.d.ts","sourceRoot":"","sources":["../../src/resolve/component-globals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,yBAAyB,CAAC;AAkCjC,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,sBAAsB;IACrC,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,qEAAqE;IACrE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB;AAgBD,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,qBAAqB,EAAE,EACvC,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAyFxB"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient declarations for the components `resolve` supplies without an import.
|
|
3
|
+
*
|
|
4
|
+
* The compiler injects the import, so nothing in the source imports these — which leaves
|
|
5
|
+
* TypeScript reporting `TS2304: Cannot find name`. The code compiles and runs; the editor
|
|
6
|
+
* is unusable and `tsc` fails. These declarations describe what the compiler already
|
|
7
|
+
* does. They do not enable it.
|
|
8
|
+
*
|
|
9
|
+
* This lives next to the resolver, and takes rules rather than a flat map, because the
|
|
10
|
+
* resolver is the only thing that knows the whole picture: a UI library's components, the
|
|
11
|
+
* project's own, and the framework builtins all arrive through the same config. A
|
|
12
|
+
* component library cannot generate this on its own — it does not know what else the
|
|
13
|
+
* project resolves — which is why the generator belongs here and the library-specific CLI
|
|
14
|
+
* that predates it does not.
|
|
15
|
+
*
|
|
16
|
+
* Pure, like the resolver: rules in, source out, no filesystem. Writing the file is the
|
|
17
|
+
* caller's job (the Vite plugin does it on config resolve; a CLI can do it in CI).
|
|
18
|
+
*/
|
|
19
|
+
import { createComponentResolver, } from './component-resolver.js';
|
|
20
|
+
import { builtinComponentMap } from './builtins.js';
|
|
21
|
+
/**
|
|
22
|
+
* Names `lib.dom` already declares as globals.
|
|
23
|
+
*
|
|
24
|
+
* An ambient `const Text` cannot beat `lib.dom`'s `var Text`, so for these the editor
|
|
25
|
+
* would disagree with the compiler — resolution still works at runtime, but the type
|
|
26
|
+
* shown would be the DOM node's. They are left out and reported instead, since importing
|
|
27
|
+
* them explicitly makes both agree.
|
|
28
|
+
*/
|
|
29
|
+
const DOM_GLOBALS = new Set([
|
|
30
|
+
'Text',
|
|
31
|
+
'Image',
|
|
32
|
+
'Option',
|
|
33
|
+
'Audio',
|
|
34
|
+
'Comment',
|
|
35
|
+
'Range',
|
|
36
|
+
'Selection',
|
|
37
|
+
'Notification',
|
|
38
|
+
'Event',
|
|
39
|
+
'Document',
|
|
40
|
+
'Element',
|
|
41
|
+
'Node',
|
|
42
|
+
'Screen',
|
|
43
|
+
'Window',
|
|
44
|
+
'Location',
|
|
45
|
+
'History',
|
|
46
|
+
'Storage',
|
|
47
|
+
'Response',
|
|
48
|
+
'Request',
|
|
49
|
+
'Headers',
|
|
50
|
+
]);
|
|
51
|
+
/** The package a specifier belongs to, or undefined when it is not a package import. */
|
|
52
|
+
function packageOf(specifier) {
|
|
53
|
+
if (specifier.startsWith('.') || specifier.startsWith('/'))
|
|
54
|
+
return undefined;
|
|
55
|
+
// Alias specifiers (`@/x`, `~/x`) are project-local and resolve through tsconfig paths.
|
|
56
|
+
if (/^[~@]\//.test(specifier))
|
|
57
|
+
return undefined;
|
|
58
|
+
const parts = specifier.split('/');
|
|
59
|
+
return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
60
|
+
}
|
|
61
|
+
/** A valid identifier for the namespace import of `module`. */
|
|
62
|
+
function namespaceAlias(module) {
|
|
63
|
+
return `_${module.replace(/[^A-Za-z0-9]/g, '_')}`;
|
|
64
|
+
}
|
|
65
|
+
/** Build the ambient `.d.ts` for the components a project's `resolve` config supplies. */
|
|
66
|
+
export function componentGlobals(rules, options = {}) {
|
|
67
|
+
const { available: availableList, builtins = true, generator = '@fluixi/compiler', specifier = (module) => module, } = options;
|
|
68
|
+
const all = [...rules];
|
|
69
|
+
if (builtins) {
|
|
70
|
+
// Appended, so an explicit rule for the same name still wins — the resolver is
|
|
71
|
+
// first-match, and a project retargeting `Router` should keep its own answer.
|
|
72
|
+
all.push({ components: builtinComponentMap() });
|
|
73
|
+
}
|
|
74
|
+
const resolver = createComponentResolver(all);
|
|
75
|
+
const available = availableList ? new Set(availableList) : undefined;
|
|
76
|
+
const declared = [];
|
|
77
|
+
const shadowedByDom = [];
|
|
78
|
+
const unavailable = [];
|
|
79
|
+
const resolved = new Map();
|
|
80
|
+
for (const name of [...resolver.names()].sort()) {
|
|
81
|
+
const target = resolver.resolve(name);
|
|
82
|
+
/* c8 ignore next */
|
|
83
|
+
if (!target)
|
|
84
|
+
continue;
|
|
85
|
+
const pkg = packageOf(target.module);
|
|
86
|
+
if (DOM_GLOBALS.has(name))
|
|
87
|
+
shadowedByDom.push(name);
|
|
88
|
+
else if (available && pkg && !available.has(pkg))
|
|
89
|
+
unavailable.push(name);
|
|
90
|
+
else {
|
|
91
|
+
declared.push(name);
|
|
92
|
+
resolved.set(name, target);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const modules = [...new Set(declared.map((name) => resolved.get(name).module))].sort();
|
|
96
|
+
const imports = modules.map((m) => `import * as ${namespaceAlias(m)} from '${specifier(m)}';`);
|
|
97
|
+
// `export import X = ns.X` rather than `const X: typeof import('…').X`: an alias
|
|
98
|
+
// resolves to the original symbol, so hover shows the real declaration
|
|
99
|
+
// (`Button(props: ButtonProps): ComponentChild`) and go-to-definition lands on the
|
|
100
|
+
// source. Both forms keep the JSDoc; the alias also keeps the shape.
|
|
101
|
+
//
|
|
102
|
+
// A default export cannot use it — `import X = ns.default` is a syntax error, since
|
|
103
|
+
// `default` is a reserved word in that position — so those fall back to the `const`
|
|
104
|
+
// form, which types and documents correctly but renders as an anonymous arrow.
|
|
105
|
+
const lines = declared.map((name) => {
|
|
106
|
+
const target = resolved.get(name);
|
|
107
|
+
const ns = namespaceAlias(target.module);
|
|
108
|
+
return target.export === 'default'
|
|
109
|
+
? ` const ${name}: typeof ${ns}.default;`
|
|
110
|
+
: ` export import ${name} = ${ns}.${target.export};`;
|
|
111
|
+
});
|
|
112
|
+
const note = shadowedByDom.length > 0
|
|
113
|
+
? `//\n// Import these explicitly — lib.dom already declares the name, so an ambient\n` +
|
|
114
|
+
`// declaration cannot win:\n` +
|
|
115
|
+
shadowedByDom
|
|
116
|
+
.map((n) => {
|
|
117
|
+
const module = resolver.resolve(n)?.module;
|
|
118
|
+
return `// import { ${n} } from '${module ? specifier(module) : ''}';\n`;
|
|
119
|
+
})
|
|
120
|
+
.join('')
|
|
121
|
+
: '';
|
|
122
|
+
const patternRules = rules.filter((rule) => 'match' in rule).length;
|
|
123
|
+
const patternNote = patternRules > 0
|
|
124
|
+
? `//\n// ${patternRules} pattern rule${patternRules === 1 ? '' : 's'} cannot be declared:\n` +
|
|
125
|
+
`// a pattern matches unboundedly many names, so there is no list to emit. Those\n` +
|
|
126
|
+
`// tags still resolve at build time; declare them by hand if the editor needs them.\n`
|
|
127
|
+
: '';
|
|
128
|
+
const source = `// GENERATED by ${generator} — do not edit.\n` +
|
|
129
|
+
`//\n` +
|
|
130
|
+
`// The components \`resolve\` supplies without an import. This describes what the\n` +
|
|
131
|
+
`// compiler already does; it does not enable it.\n` +
|
|
132
|
+
note +
|
|
133
|
+
patternNote +
|
|
134
|
+
(imports.length > 0 ? `\n${imports.join('\n')}\n` : '\n') +
|
|
135
|
+
`\ndeclare global {\n${lines.join('\n')}\n}\n`;
|
|
136
|
+
return { source, declared, shadowedByDom, unavailable, patternRules };
|
|
137
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var $=t=>"components"in t;function v(t,o){return t.replace(/\$(\d+)/g,(l,p)=>o[Number(p)]??l)}function N(t,o){return typeof o=="string"?{module:o,export:t}:o}function b(t=[]){let o=new Map,l=[],p=[];for(let r of t){if(!$(r)){p.push(r);continue}for(let[n,a]of Object.entries(r.components)){let s=N(n,a),m=o.get(n);if(m&&m.module!==s.module){let d=l.find(f=>f.name===n);d?d.modules.push(s.module):l.push({name:n,modules:[m.module,s.module]});continue}o.set(n,s)}}let i=new Map;return{resolve:r=>{if(i.has(r))return i.get(r);let n=o.get(r);if(!n)for(let a of p){let s=r.match(new RegExp(a.match.source,a.match.flags.replace("g","")));if(s){n={module:v(a.module,s),export:a.export?v(a.export,s):r};break}}return i.set(r,n),n},names:()=>[...o.keys()],conflicts:()=>l}}var C=["Show","For","Index","Switch","Match","Portal","Dynamic","ErrorBoundary"],h=["Suspense","SuspenseList","Await"],M=["Router","Outlet","Redirect","Link"],j=[...C,...h,...M],A=new Set([...C,...h]);function O(t={}){let{controlFlowModule:o="@fluixi/dom",coreModule:l="@fluixi/core",routerModule:p="@fluixi/core/router-next"}=t,i={};for(let u of C)i[u]=o;for(let u of h)i[u]=l;for(let u of M)i[u]=p;return i}var T=new Set(["Text","Image","Option","Audio","Comment","Range","Selection","Notification","Event","Document","Element","Node","Screen","Window","Location","History","Storage","Response","Request","Headers"]);function L(t){if(t.startsWith(".")||t.startsWith("/")||/^[~@]\//.test(t))return;let o=t.split("/");return t.startsWith("@")?o.slice(0,2).join("/"):o[0]}function y(t){return`_${t.replace(/[^A-Za-z0-9]/g,"_")}`}function W(t,o={}){let{available:l,builtins:p=!0,generator:i="@fluixi/compiler",specifier:u=e=>e}=o,r=[...t];p&&r.push({components:O()});let n=b(r),a=l?new Set(l):void 0,s=[],m=[],d=[],f=new Map;for(let e of[...n.names()].sort()){let c=n.resolve(e);if(!c)continue;let g=L(c.module);T.has(e)?m.push(e):a&&g&&!a.has(g)?d.push(e):(s.push(e),f.set(e,c))}let x=[...new Set(s.map(e=>f.get(e).module))].sort().map(e=>`import * as ${y(e)} from '${u(e)}';`),w=s.map(e=>{let c=f.get(e),g=y(c.module);return c.export==="default"?` const ${e}: typeof ${g}.default;`:` export import ${e} = ${g}.${c.export};`}),E=m.length>0?`//
|
|
2
|
+
// Import these explicitly — lib.dom already declares the name, so an ambient
|
|
3
|
+
// declaration cannot win:
|
|
4
|
+
`+m.map(e=>{let c=n.resolve(e)?.module;return`// import { ${e} } from '${c?u(c):""}';
|
|
5
|
+
`}).join(""):"",R=t.filter(e=>"match"in e).length,S=R>0?`//
|
|
6
|
+
// ${R} pattern rule${R===1?"":"s"} cannot be declared:
|
|
7
|
+
// a pattern matches unboundedly many names, so there is no list to emit. Those
|
|
8
|
+
// tags still resolve at build time; declare them by hand if the editor needs them.
|
|
9
|
+
`:"";return{source:`// GENERATED by ${i} — do not edit.
|
|
10
|
+
//
|
|
11
|
+
// The components \`resolve\` supplies without an import. This describes what the
|
|
12
|
+
// compiler already does; it does not enable it.
|
|
13
|
+
`+E+S+(x.length>0?`
|
|
14
|
+
${x.join(`
|
|
15
|
+
`)}
|
|
16
|
+
`:`
|
|
17
|
+
`)+`
|
|
18
|
+
declare global {
|
|
19
|
+
${w.join(`
|
|
20
|
+
`)}
|
|
21
|
+
}
|
|
22
|
+
`,declared:s,shadowedByDom:m,unavailable:d,patternRules:R}}export{W as componentGlobals};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";var x=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var W=(e,n)=>{for(var o in n)x(e,o,{get:n[o],enumerable:!0})},B=(e,n,o,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of L(n))!_.call(e,t)&&t!==o&&x(e,t,{get:()=>n[t],enumerable:!(r=D(n,t))||r.enumerable});return e};var P=e=>B(x({},"__esModule",{value:!0}),e);var Z={};W(Z,{DEFAULT_GLOBALS_PATH:()=>N,writeComponentGlobals:()=>V});module.exports=P(Z);var f=require("node:fs"),c=require("node:path");var F=e=>"components"in e;function O(e,n){return e.replace(/\$(\d+)/g,(o,r)=>n[Number(r)]??o)}function I(e,n){return typeof n=="string"?{module:n,export:e}:n}function w(e=[]){let n=new Map,o=[],r=[];for(let i of e){if(!F(i)){r.push(i);continue}for(let[l,p]of Object.entries(i.components)){let u=I(l,p),m=n.get(l);if(m&&m.module!==u.module){let g=o.find(R=>R.name===l);g?g.modules.push(u.module):o.push({name:l,modules:[m.module,u.module]});continue}n.set(l,u)}}let t=new Map;return{resolve:i=>{if(t.has(i))return t.get(i);let l=n.get(i);if(!l)for(let p of r){let u=i.match(new RegExp(p.match.source,p.match.flags.replace("g","")));if(u){l={module:O(p.module,u),export:p.export?O(p.export,u):i};break}}return t.set(i,l),l},names:()=>[...n.keys()],conflicts:()=>o}}var b=["Show","For","Index","Switch","Match","Portal","Dynamic","ErrorBoundary"],C=["Suspense","SuspenseList","Await"],M=["Router","Outlet","Redirect","Link"],ne=[...b,...C,...M],te=new Set([...b,...C]);function E(e={}){let{controlFlowModule:n="@fluixi/dom",coreModule:o="@fluixi/core",routerModule:r="@fluixi/core/router-next"}=e,t={};for(let a of b)t[a]=n;for(let a of C)t[a]=o;for(let a of M)t[a]=r;return t}var U=new Set(["Text","Image","Option","Audio","Comment","Range","Selection","Notification","Event","Document","Element","Node","Screen","Window","Location","History","Storage","Response","Request","Headers"]);function H(e){if(e.startsWith(".")||e.startsWith("/")||/^[~@]\//.test(e))return;let n=e.split("/");return e.startsWith("@")?n.slice(0,2).join("/"):n[0]}function S(e){return`_${e.replace(/[^A-Za-z0-9]/g,"_")}`}function $(e,n={}){let{available:o,builtins:r=!0,generator:t="@fluixi/compiler",specifier:a=s=>s}=n,i=[...e];r&&i.push({components:E()});let l=w(i),p=o?new Set(o):void 0,u=[],m=[],g=[],R=new Map;for(let s of[...l.names()].sort()){let d=l.resolve(s);if(!d)continue;let h=H(d.module);U.has(s)?m.push(s):p&&h&&!p.has(h)?g.push(s):(u.push(s),R.set(s,d))}let y=[...new Set(u.map(s=>R.get(s).module))].sort().map(s=>`import * as ${S(s)} from '${a(s)}';`),A=u.map(s=>{let d=R.get(s),h=S(d.module);return d.export==="default"?` const ${s}: typeof ${h}.default;`:` export import ${s} = ${h}.${d.export};`}),G=m.length>0?`//
|
|
2
|
+
// Import these explicitly — lib.dom already declares the name, so an ambient
|
|
3
|
+
// declaration cannot win:
|
|
4
|
+
`+m.map(s=>{let d=l.resolve(s)?.module;return`// import { ${s} } from '${d?a(d):""}';
|
|
5
|
+
`}).join(""):"",v=e.filter(s=>"match"in s).length,k=v>0?`//
|
|
6
|
+
// ${v} pattern rule${v===1?"":"s"} cannot be declared:
|
|
7
|
+
// a pattern matches unboundedly many names, so there is no list to emit. Those
|
|
8
|
+
// tags still resolve at build time; declare them by hand if the editor needs them.
|
|
9
|
+
`:"";return{source:`// GENERATED by ${t} — do not edit.
|
|
10
|
+
//
|
|
11
|
+
// The components \`resolve\` supplies without an import. This describes what the
|
|
12
|
+
// compiler already does; it does not enable it.
|
|
13
|
+
`+G+k+(y.length>0?`
|
|
14
|
+
${y.join(`
|
|
15
|
+
`)}
|
|
16
|
+
`:`
|
|
17
|
+
`)+`
|
|
18
|
+
declare global {
|
|
19
|
+
${A.join(`
|
|
20
|
+
`)}
|
|
21
|
+
}
|
|
22
|
+
`,declared:u,shadowedByDom:m,unavailable:g,patternRules:v}}var J="GENERATED by @fluixi/compiler",N="fluixi.d.ts";function q(e,n){let o;try{let t=(0,f.readFileSync)((0,c.resolve)(e,"tsconfig.json"),"utf8");o=JSON.parse(t.replace(/^\s*\/\/.*$/gm,""))}catch{return}if(o.extends!==void 0)return;if(!Array.isArray(o.include))return!0;let r=T(e,n);return o.include.some(t=>{if(typeof t!="string")return!1;let a=t.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*\//g,"(?:.*/)?").replace(/\*/g,"[^/]*");return new RegExp(`^${a}(?:/.*)?$`).test(r)})}function T(e,n){return(0,c.relative)(e,n).split(c.sep).join("/")}function z(e,n,o){if(!e.startsWith("/"))return e;let r=(0,c.resolve)(o,e.slice(1)),t=(0,c.relative)(n,r).split(c.sep).join("/");return t.startsWith(".")||(t=`./${t}`),t.replace(/\.(tsx|ts|jsx|mts|cts)$/,".js")}function K(e){try{let n=JSON.parse((0,f.readFileSync)((0,c.resolve)(e,"package.json"),"utf8"));return[...Object.keys(n.dependencies??{}),...Object.keys(n.devDependencies??{}),...Object.keys(n.peerDependencies??{})]}catch{return}}function V(e,n){if(e.globals===!1)return{status:"skipped",reason:"disabled"};let o=e.resolve??[],r=(0,c.resolve)(n,e.globals??N),t;try{t=(0,f.readFileSync)(r,"utf8")}catch{t=void 0}if(t!==void 0&&!t.includes(J))return{status:"skipped",reason:"foreign-file",path:r};let{source:a,declared:i}=$(o,{available:K(n),specifier:l=>z(l,(0,c.dirname)(r),n)});return t===a?{status:"unchanged",path:r,declared:i.length,warning:j(n,r)}:((0,f.mkdirSync)((0,c.dirname)(r),{recursive:!0}),(0,f.writeFileSync)(r,a),{status:"written",path:r,declared:i.length,warning:j(n,r)})}function j(e,n){if(q(e,n)!==!1)return;let o=T(e,n);return`[fluixi] ${o} is not covered by "include" in tsconfig.json, so TypeScript will ignore it and every resolved component stays "Cannot find name". Add "${o}" to "include".`}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ComponentResolverRule } from './component-resolver.js';
|
|
2
|
+
export declare const DEFAULT_GLOBALS_PATH = "fluixi.d.ts";
|
|
3
|
+
export interface WriteGlobalsOptions {
|
|
4
|
+
resolve?: readonly ComponentResolverRule[];
|
|
5
|
+
globals?: string | false;
|
|
6
|
+
}
|
|
7
|
+
export type WriteGlobalsOutcome = ({
|
|
8
|
+
status: 'written';
|
|
9
|
+
path: string;
|
|
10
|
+
declared: number;
|
|
11
|
+
} | {
|
|
12
|
+
status: 'unchanged';
|
|
13
|
+
path: string;
|
|
14
|
+
declared: number;
|
|
15
|
+
} | {
|
|
16
|
+
status: 'skipped';
|
|
17
|
+
reason: 'disabled' | 'foreign-file';
|
|
18
|
+
path?: string;
|
|
19
|
+
}) & {
|
|
20
|
+
/**
|
|
21
|
+
* Set when the file was written somewhere TypeScript will not read it.
|
|
22
|
+
*
|
|
23
|
+
* Worth surfacing because the symptom is indistinguishable from the generator never
|
|
24
|
+
* having run: every resolved tag is still `Cannot find name`, and the file sitting
|
|
25
|
+
* right there makes it look like it should have worked.
|
|
26
|
+
*/
|
|
27
|
+
warning?: string;
|
|
28
|
+
};
|
|
29
|
+
/** Generate and write the declarations for `root`, returning what was done. */
|
|
30
|
+
export declare function writeComponentGlobals(options: WriteGlobalsOptions, root: string): WriteGlobalsOutcome;
|
|
31
|
+
//# sourceMappingURL=write-globals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-globals.d.ts","sourceRoot":"","sources":["../../src/resolve/write-globals.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAKrE,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAElD,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC3C,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAAG,CAC9B;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,UAAU,GAAG,cAAc,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAC5E,GAAG;IACF;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AA2EF,+EAA+E;AAC/E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,mBAAmB,EAC5B,IAAI,EAAE,MAAM,GACX,mBAAmB,CAwCrB"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write the generated component declarations into a project.
|
|
3
|
+
*
|
|
4
|
+
* Split from the generator so the generator stays pure and testable; this half owns the
|
|
5
|
+
* three things that only matter once a real file is involved:
|
|
6
|
+
*
|
|
7
|
+
* - **Never clobber someone's file.** A path like `src/fluixi.d.ts` is plausible enough
|
|
8
|
+
* that a project may already have one. Anything without our header is left alone and
|
|
9
|
+
* reported, because silently overwriting hand-written declarations is unrecoverable.
|
|
10
|
+
* - **Do not rewrite unchanged content.** The dev server watches this directory; an
|
|
11
|
+
* identical write still bumps mtime and can cost a reload every restart.
|
|
12
|
+
* - **Only declare what the project can resolve.** Dependencies come from its manifest,
|
|
13
|
+
* so a component from an uninstalled package is skipped rather than degraded to `any`.
|
|
14
|
+
*/
|
|
15
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { dirname, relative, sep, resolve as resolvePath } from 'node:path';
|
|
17
|
+
import { componentGlobals } from './component-globals.js';
|
|
18
|
+
/** The marker every generated file carries, used to tell ours from a hand-written one. */
|
|
19
|
+
const GENERATED_MARKER = 'GENERATED by @fluixi/compiler';
|
|
20
|
+
export const DEFAULT_GLOBALS_PATH = 'fluixi.d.ts';
|
|
21
|
+
/**
|
|
22
|
+
* Whether `target` looks covered by the project's `include`, as best we can tell.
|
|
23
|
+
*
|
|
24
|
+
* `undefined` when there is nothing to judge from — no tsconfig, unreadable, or an
|
|
25
|
+
* `extends` we would have to resolve to answer honestly. Best-effort by design: this
|
|
26
|
+
* drives a warning, never a failure, so a wrong guess must not be able to break a build.
|
|
27
|
+
*/
|
|
28
|
+
function coveredByInclude(root, target) {
|
|
29
|
+
let config;
|
|
30
|
+
try {
|
|
31
|
+
const raw = readFileSync(resolvePath(root, 'tsconfig.json'), 'utf8');
|
|
32
|
+
// Comments are legal in tsconfig and would break JSON.parse.
|
|
33
|
+
config = JSON.parse(raw.replace(/^\s*\/\/.*$/gm, ''));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if (config.extends !== undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
// No `include` means the whole directory tree, which covers anything we write.
|
|
41
|
+
if (!Array.isArray(config.include))
|
|
42
|
+
return true;
|
|
43
|
+
const relative = relativePosix(root, target);
|
|
44
|
+
return config.include.some((entry) => {
|
|
45
|
+
if (typeof entry !== 'string')
|
|
46
|
+
return false;
|
|
47
|
+
const pattern = entry
|
|
48
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
49
|
+
.replace(/\*\*\//g, '(?:.*/)?')
|
|
50
|
+
.replace(/\*/g, '[^/]*');
|
|
51
|
+
// A bare directory entry (`src`) covers everything beneath it.
|
|
52
|
+
return new RegExp(`^${pattern}(?:/.*)?$`).test(relative);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** `from` -> `to` as a posix-style relative path. */
|
|
56
|
+
function relativePosix(from, to) {
|
|
57
|
+
return relative(from, to).split(sep).join('/');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Make a specifier resolvable from the generated file.
|
|
61
|
+
*
|
|
62
|
+
* A bundler-root-absolute `/src/components/Card.tsx` is what Vite wants and what
|
|
63
|
+
* TypeScript cannot resolve at all, so it becomes a path relative to the generated file.
|
|
64
|
+
* The extension goes to `.js` because that is what TypeScript expects to be written for
|
|
65
|
+
* a `.ts`/`.tsx` source under every module resolution mode that accepts an extension.
|
|
66
|
+
*/
|
|
67
|
+
function forTypeScript(module, outDir, root) {
|
|
68
|
+
if (!module.startsWith('/'))
|
|
69
|
+
return module;
|
|
70
|
+
const absolute = resolvePath(root, module.slice(1));
|
|
71
|
+
let rel = relative(outDir, absolute).split(sep).join('/');
|
|
72
|
+
if (!rel.startsWith('.'))
|
|
73
|
+
rel = `./${rel}`;
|
|
74
|
+
return rel.replace(/\.(tsx|ts|jsx|mts|cts)$/, '.js');
|
|
75
|
+
}
|
|
76
|
+
/** The project's declared dependencies, or undefined when there is no readable manifest. */
|
|
77
|
+
function projectDependencies(root) {
|
|
78
|
+
try {
|
|
79
|
+
const manifest = JSON.parse(readFileSync(resolvePath(root, 'package.json'), 'utf8'));
|
|
80
|
+
return [
|
|
81
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
82
|
+
...Object.keys(manifest.devDependencies ?? {}),
|
|
83
|
+
...Object.keys(manifest.peerDependencies ?? {}),
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// No manifest to read — declare everything and let TypeScript report what is missing.
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Generate and write the declarations for `root`, returning what was done. */
|
|
92
|
+
export function writeComponentGlobals(options, root) {
|
|
93
|
+
if (options.globals === false)
|
|
94
|
+
return { status: 'skipped', reason: 'disabled' };
|
|
95
|
+
const rules = options.resolve ?? [];
|
|
96
|
+
const target = resolvePath(root, options.globals ?? DEFAULT_GLOBALS_PATH);
|
|
97
|
+
let existing;
|
|
98
|
+
try {
|
|
99
|
+
existing = readFileSync(target, 'utf8');
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
existing = undefined;
|
|
103
|
+
}
|
|
104
|
+
if (existing !== undefined && !existing.includes(GENERATED_MARKER)) {
|
|
105
|
+
return { status: 'skipped', reason: 'foreign-file', path: target };
|
|
106
|
+
}
|
|
107
|
+
const { source, declared } = componentGlobals(rules, {
|
|
108
|
+
available: projectDependencies(root),
|
|
109
|
+
specifier: (module) => forTypeScript(module, dirname(target), root),
|
|
110
|
+
});
|
|
111
|
+
if (existing === source) {
|
|
112
|
+
return {
|
|
113
|
+
status: 'unchanged',
|
|
114
|
+
path: target,
|
|
115
|
+
declared: declared.length,
|
|
116
|
+
warning: warnIfUnreachable(root, target),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
120
|
+
writeFileSync(target, source);
|
|
121
|
+
return {
|
|
122
|
+
status: 'written',
|
|
123
|
+
path: target,
|
|
124
|
+
declared: declared.length,
|
|
125
|
+
warning: warnIfUnreachable(root, target),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** The message to show when the generated file is outside the project's `include`. */
|
|
129
|
+
function warnIfUnreachable(root, target) {
|
|
130
|
+
if (coveredByInclude(root, target) !== false)
|
|
131
|
+
return undefined;
|
|
132
|
+
const relative = relativePosix(root, target);
|
|
133
|
+
return (`[fluixi] ${relative} is not covered by "include" in tsconfig.json, so TypeScript ` +
|
|
134
|
+
`will ignore it and every resolved component stays "Cannot find name". Add ` +
|
|
135
|
+
`"${relative}" to "include".`);
|
|
136
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import{mkdirSync as P,readFileSync as b,writeFileSync as F}from"node:fs";import{dirname as $,relative as N,sep as T,resolve as h}from"node:path";var L=e=>"components"in e;function y(e,n){return e.replace(/\$(\d+)/g,(o,r)=>n[Number(r)]??o)}function _(e,n){return typeof n=="string"?{module:n,export:e}:n}function O(e=[]){let n=new Map,o=[],r=[];for(let i of e){if(!L(i)){r.push(i);continue}for(let[l,u]of Object.entries(i.components)){let c=_(l,u),d=n.get(l);if(d&&d.module!==c.module){let m=o.find(f=>f.name===l);m?m.modules.push(c.module):o.push({name:l,modules:[d.module,c.module]});continue}n.set(l,c)}}let t=new Map;return{resolve:i=>{if(t.has(i))return t.get(i);let l=n.get(i);if(!l)for(let u of r){let c=i.match(new RegExp(u.match.source,u.match.flags.replace("g","")));if(c){l={module:y(u.module,c),export:u.export?y(u.export,c):i};break}}return t.set(i,l),l},names:()=>[...n.keys()],conflicts:()=>o}}var v=["Show","For","Index","Switch","Match","Portal","Dynamic","ErrorBoundary"],x=["Suspense","SuspenseList","Await"],w=["Router","Outlet","Redirect","Link"],Z=[...v,...x,...w],Q=new Set([...v,...x]);function M(e={}){let{controlFlowModule:n="@fluixi/dom",coreModule:o="@fluixi/core",routerModule:r="@fluixi/core/router-next"}=e,t={};for(let a of v)t[a]=n;for(let a of x)t[a]=o;for(let a of w)t[a]=r;return t}var W=new Set(["Text","Image","Option","Audio","Comment","Range","Selection","Notification","Event","Document","Element","Node","Screen","Window","Location","History","Storage","Response","Request","Headers"]);function B(e){if(e.startsWith(".")||e.startsWith("/")||/^[~@]\//.test(e))return;let n=e.split("/");return e.startsWith("@")?n.slice(0,2).join("/"):n[0]}function E(e){return`_${e.replace(/[^A-Za-z0-9]/g,"_")}`}function S(e,n={}){let{available:o,builtins:r=!0,generator:t="@fluixi/compiler",specifier:a=s=>s}=n,i=[...e];r&&i.push({components:M()});let l=O(i),u=o?new Set(o):void 0,c=[],d=[],m=[],f=new Map;for(let s of[...l.names()].sort()){let p=l.resolve(s);if(!p)continue;let g=B(p.module);W.has(s)?d.push(s):u&&g&&!u.has(g)?m.push(s):(c.push(s),f.set(s,p))}let C=[...new Set(c.map(s=>f.get(s).module))].sort().map(s=>`import * as ${E(s)} from '${a(s)}';`),G=c.map(s=>{let p=f.get(s),g=E(p.module);return p.export==="default"?` const ${s}: typeof ${g}.default;`:` export import ${s} = ${g}.${p.export};`}),k=d.length>0?`//
|
|
2
|
+
// Import these explicitly — lib.dom already declares the name, so an ambient
|
|
3
|
+
// declaration cannot win:
|
|
4
|
+
`+d.map(s=>{let p=l.resolve(s)?.module;return`// import { ${s} } from '${p?a(p):""}';
|
|
5
|
+
`}).join(""):"",R=e.filter(s=>"match"in s).length,D=R>0?`//
|
|
6
|
+
// ${R} pattern rule${R===1?"":"s"} cannot be declared:
|
|
7
|
+
// a pattern matches unboundedly many names, so there is no list to emit. Those
|
|
8
|
+
// tags still resolve at build time; declare them by hand if the editor needs them.
|
|
9
|
+
`:"";return{source:`// GENERATED by ${t} — do not edit.
|
|
10
|
+
//
|
|
11
|
+
// The components \`resolve\` supplies without an import. This describes what the
|
|
12
|
+
// compiler already does; it does not enable it.
|
|
13
|
+
`+k+D+(C.length>0?`
|
|
14
|
+
${C.join(`
|
|
15
|
+
`)}
|
|
16
|
+
`:`
|
|
17
|
+
`)+`
|
|
18
|
+
declare global {
|
|
19
|
+
${G.join(`
|
|
20
|
+
`)}
|
|
21
|
+
}
|
|
22
|
+
`,declared:c,shadowedByDom:d,unavailable:m,patternRules:R}}var I="GENERATED by @fluixi/compiler",U="fluixi.d.ts";function H(e,n){let o;try{let t=b(h(e,"tsconfig.json"),"utf8");o=JSON.parse(t.replace(/^\s*\/\/.*$/gm,""))}catch{return}if(o.extends!==void 0)return;if(!Array.isArray(o.include))return!0;let r=A(e,n);return o.include.some(t=>{if(typeof t!="string")return!1;let a=t.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*\//g,"(?:.*/)?").replace(/\*/g,"[^/]*");return new RegExp(`^${a}(?:/.*)?$`).test(r)})}function A(e,n){return N(e,n).split(T).join("/")}function J(e,n,o){if(!e.startsWith("/"))return e;let r=h(o,e.slice(1)),t=N(n,r).split(T).join("/");return t.startsWith(".")||(t=`./${t}`),t.replace(/\.(tsx|ts|jsx|mts|cts)$/,".js")}function q(e){try{let n=JSON.parse(b(h(e,"package.json"),"utf8"));return[...Object.keys(n.dependencies??{}),...Object.keys(n.devDependencies??{}),...Object.keys(n.peerDependencies??{})]}catch{return}}function se(e,n){if(e.globals===!1)return{status:"skipped",reason:"disabled"};let o=e.resolve??[],r=h(n,e.globals??U),t;try{t=b(r,"utf8")}catch{t=void 0}if(t!==void 0&&!t.includes(I))return{status:"skipped",reason:"foreign-file",path:r};let{source:a,declared:i}=S(o,{available:q(n),specifier:l=>J(l,$(r),n)});return t===a?{status:"unchanged",path:r,declared:i.length,warning:j(n,r)}:(P($(r),{recursive:!0}),F(r,a),{status:"written",path:r,declared:i.length,warning:j(n,r)})}function j(e,n){if(H(e,n)!==!1)return;let o=A(e,n);return`[fluixi] ${o} is not covered by "include" in tsconfig.json, so TypeScript will ignore it and every resolved component stays "Cannot find name". Add "${o}" to "include".`}export{U as DEFAULT_GLOBALS_PATH,se as writeComponentGlobals};
|