praxis-kit 1.1.0
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/LICENSE +21 -0
- package/dist/chunk-KDUNVQK2.js +2059 -0
- package/dist/codemod/index.d.ts +21 -0
- package/dist/codemod/index.js +241955 -0
- package/dist/eslint/index.d.ts +84 -0
- package/dist/eslint/index.js +1068 -0
- package/dist/lit/index.d.ts +304 -0
- package/dist/lit/index.js +1887 -0
- package/dist/merge-refs-CfBqh1UO.d.ts +374 -0
- package/dist/preact/index.d.ts +292 -0
- package/dist/preact/index.js +2062 -0
- package/dist/react/index.d.ts +27 -0
- package/dist/react/index.js +197 -0
- package/dist/react/legacy.d.ts +21 -0
- package/dist/react/legacy.js +209 -0
- package/dist/solid/index.d.ts +287 -0
- package/dist/solid/index.js +1837 -0
- package/dist/svelte/index.d.ts +343 -0
- package/dist/svelte/index.js +1723 -0
- package/dist/tailwind/index.d.ts +138 -0
- package/dist/tailwind/index.js +508 -0
- package/dist/ts-plugin/index.cjs +191 -0
- package/dist/ts-plugin/index.d.cts +10 -0
- package/dist/vite-plugin/index.d.ts +395 -0
- package/dist/vite-plugin/index.js +1329 -0
- package/dist/vue/index.d.ts +309 -0
- package/dist/vue/index.js +1922 -0
- package/dist/web/index.d.ts +302 -0
- package/dist/web/index.js +1858 -0
- package/package.json +176 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// ../ts-plugin/src/ast.ts
|
|
4
|
+
function getObjectProperty(ts, obj, key) {
|
|
5
|
+
for (const prop of obj.properties) {
|
|
6
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
7
|
+
const name = prop.name;
|
|
8
|
+
if (ts.isIdentifier(name) && name.text === key) return prop;
|
|
9
|
+
if (ts.isStringLiteral(name) && name.text === key) return prop;
|
|
10
|
+
}
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
function asObjectLiteralExpression(ts, node) {
|
|
14
|
+
if (!node) return void 0;
|
|
15
|
+
return ts.isObjectLiteralExpression(node) ? node : void 0;
|
|
16
|
+
}
|
|
17
|
+
function asArrayLiteralExpression(ts, node) {
|
|
18
|
+
if (!node) return void 0;
|
|
19
|
+
return ts.isArrayLiteralExpression(node) ? node : void 0;
|
|
20
|
+
}
|
|
21
|
+
function asNumericValue(ts, node) {
|
|
22
|
+
if (!node) return void 0;
|
|
23
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
24
|
+
if (ts.isPrefixUnaryExpression(node) && (node.operator === ts.SyntaxKind.MinusToken || node.operator === ts.SyntaxKind.PlusToken) && ts.isNumericLiteral(node.operand)) {
|
|
25
|
+
const val = Number(node.operand.text);
|
|
26
|
+
return node.operator === ts.SyntaxKind.MinusToken ? -val : val;
|
|
27
|
+
}
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
function isFactoryCall(ts, node, names) {
|
|
31
|
+
const { expression } = node;
|
|
32
|
+
if (ts.isIdentifier(expression)) return names.has(expression.text);
|
|
33
|
+
if (ts.isPropertyAccessExpression(expression)) return names.has(expression.name.text);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function getFirstObjectArg(ts, node) {
|
|
37
|
+
const [first] = node.arguments;
|
|
38
|
+
if (!first) return void 0;
|
|
39
|
+
return ts.isObjectLiteralExpression(first) ? first : void 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ../ts-plugin/src/diagnostics/walk-enforcement.ts
|
|
43
|
+
function walkEnforcement(ts, sourceFile, calleeNames, cb) {
|
|
44
|
+
function visit(node) {
|
|
45
|
+
if (ts.isCallExpression(node) && isFactoryCall(ts, node, calleeNames)) {
|
|
46
|
+
const arg = getFirstObjectArg(ts, node);
|
|
47
|
+
if (arg) {
|
|
48
|
+
const enfProp = getObjectProperty(ts, arg, "enforcement");
|
|
49
|
+
if (enfProp) {
|
|
50
|
+
const enf = asObjectLiteralExpression(ts, enfProp.initializer);
|
|
51
|
+
if (enf) cb(node, enf);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
ts.forEachChild(node, visit);
|
|
56
|
+
}
|
|
57
|
+
visit(sourceFile);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ../ts-plugin/src/diagnostics/no-enforcement-without-strict.ts
|
|
61
|
+
var MISSING_STRICT_CODE = 90001;
|
|
62
|
+
function checkNoEnforcementWithoutStrict(ts, sourceFile, calleeNames) {
|
|
63
|
+
const diagnostics = [];
|
|
64
|
+
walkEnforcement(ts, sourceFile, calleeNames, (node, enf) => {
|
|
65
|
+
const hasStrict = getObjectProperty(ts, enf, "strict") !== void 0;
|
|
66
|
+
if (!hasStrict) {
|
|
67
|
+
for (const field of ["children", "aria"]) {
|
|
68
|
+
const fieldProp = getObjectProperty(ts, enf, field);
|
|
69
|
+
if (!fieldProp) continue;
|
|
70
|
+
if (field === "children") {
|
|
71
|
+
const arr = asArrayLiteralExpression(ts, fieldProp.initializer);
|
|
72
|
+
if (!arr || arr.elements.length === 0) continue;
|
|
73
|
+
}
|
|
74
|
+
diagnostics.push({
|
|
75
|
+
file: sourceFile,
|
|
76
|
+
start: node.getStart(sourceFile),
|
|
77
|
+
length: node.getWidth(sourceFile),
|
|
78
|
+
category: ts.DiagnosticCategory.Warning,
|
|
79
|
+
code: MISSING_STRICT_CODE,
|
|
80
|
+
messageText: `enforcement.${field} is defined but enforcement.strict is not explicitly set. Adapter defaults vary \u2014 declare strict explicitly so the behavior is clear at the call site.`,
|
|
81
|
+
source: "@praxis-kit/ts-plugin"
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return diagnostics;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ../ts-plugin/src/diagnostics/valid-cardinality.ts
|
|
91
|
+
var NEGATIVE_MIN_CODE = 90002;
|
|
92
|
+
var NEGATIVE_MAX_CODE = 90003;
|
|
93
|
+
var MAX_LESS_THAN_MIN_CODE = 90004;
|
|
94
|
+
var ZERO_MAX_CODE = 90005;
|
|
95
|
+
function checkValidCardinality(ts, sourceFile, calleeNames) {
|
|
96
|
+
const diagnostics = [];
|
|
97
|
+
walkEnforcement(ts, sourceFile, calleeNames, (_, enf) => {
|
|
98
|
+
const childrenProp = getObjectProperty(ts, enf, "children");
|
|
99
|
+
if (!childrenProp) return;
|
|
100
|
+
const arr = asArrayLiteralExpression(ts, childrenProp.initializer);
|
|
101
|
+
if (!arr) return;
|
|
102
|
+
for (const element of arr.elements) {
|
|
103
|
+
if (!ts.isObjectLiteralExpression(element)) continue;
|
|
104
|
+
const cardProp = getObjectProperty(ts, element, "cardinality");
|
|
105
|
+
if (!cardProp) continue;
|
|
106
|
+
const card = asObjectLiteralExpression(ts, cardProp.initializer);
|
|
107
|
+
if (!card) continue;
|
|
108
|
+
const minProp = getObjectProperty(ts, card, "min");
|
|
109
|
+
const maxProp = getObjectProperty(ts, card, "max");
|
|
110
|
+
const min = minProp ? asNumericValue(ts, minProp.initializer) : void 0;
|
|
111
|
+
const max = maxProp ? asNumericValue(ts, maxProp.initializer) : void 0;
|
|
112
|
+
if (min !== void 0 && min < 0) {
|
|
113
|
+
diagnostics.push({
|
|
114
|
+
file: sourceFile,
|
|
115
|
+
start: minProp.getStart(sourceFile),
|
|
116
|
+
length: minProp.getWidth(sourceFile),
|
|
117
|
+
category: ts.DiagnosticCategory.Error,
|
|
118
|
+
code: NEGATIVE_MIN_CODE,
|
|
119
|
+
messageText: `cardinality.min must be >= 0 (got ${min}).`,
|
|
120
|
+
source: "@praxis-kit/ts-plugin"
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (max !== void 0 && max < 0) {
|
|
124
|
+
diagnostics.push({
|
|
125
|
+
file: sourceFile,
|
|
126
|
+
start: maxProp.getStart(sourceFile),
|
|
127
|
+
length: maxProp.getWidth(sourceFile),
|
|
128
|
+
category: ts.DiagnosticCategory.Error,
|
|
129
|
+
code: NEGATIVE_MAX_CODE,
|
|
130
|
+
messageText: `cardinality.max must be >= 0 (got ${max}).`,
|
|
131
|
+
source: "@praxis-kit/ts-plugin"
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (max !== void 0 && max === 0) {
|
|
135
|
+
diagnostics.push({
|
|
136
|
+
file: sourceFile,
|
|
137
|
+
start: maxProp.getStart(sourceFile),
|
|
138
|
+
length: maxProp.getWidth(sourceFile),
|
|
139
|
+
category: ts.DiagnosticCategory.Warning,
|
|
140
|
+
code: ZERO_MAX_CODE,
|
|
141
|
+
messageText: `cardinality.max of 0 means no children of this type are allowed. Use 0 intentionally or remove the rule.`,
|
|
142
|
+
source: "@praxis-kit/ts-plugin"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (min !== void 0 && max !== void 0 && min >= 0 && max > 0 && max < min) {
|
|
146
|
+
diagnostics.push({
|
|
147
|
+
file: sourceFile,
|
|
148
|
+
start: cardProp.getStart(sourceFile),
|
|
149
|
+
length: cardProp.getWidth(sourceFile),
|
|
150
|
+
category: ts.DiagnosticCategory.Error,
|
|
151
|
+
code: MAX_LESS_THAN_MIN_CODE,
|
|
152
|
+
messageText: `cardinality.max (${max}) must be >= cardinality.min (${min}). This rule can never be satisfied.`,
|
|
153
|
+
source: "@praxis-kit/ts-plugin"
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return diagnostics;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ../ts-plugin/src/index.ts
|
|
162
|
+
var DEFAULT_CALLEE_NAMES = ["createContractComponent"];
|
|
163
|
+
function init(modules) {
|
|
164
|
+
const ts = modules.typescript;
|
|
165
|
+
function create(info) {
|
|
166
|
+
const calleeNames = new Set(
|
|
167
|
+
info.config.calleeNames ?? DEFAULT_CALLEE_NAMES
|
|
168
|
+
);
|
|
169
|
+
const proxy = /* @__PURE__ */ Object.create(null);
|
|
170
|
+
const proxyRecord = proxy;
|
|
171
|
+
for (const k of Object.keys(info.languageService)) {
|
|
172
|
+
const x = info.languageService[k];
|
|
173
|
+
proxyRecord[k] = typeof x === "function" ? x.bind(info.languageService) : x;
|
|
174
|
+
}
|
|
175
|
+
proxy.getSemanticDiagnostics = (fileName) => {
|
|
176
|
+
const existing = info.languageService.getSemanticDiagnostics(fileName);
|
|
177
|
+
const program = info.languageService.getProgram();
|
|
178
|
+
if (!program) return existing;
|
|
179
|
+
const sourceFile = program.getSourceFile(fileName);
|
|
180
|
+
if (!sourceFile) return existing;
|
|
181
|
+
const extra = [
|
|
182
|
+
...checkNoEnforcementWithoutStrict(ts, sourceFile, calleeNames),
|
|
183
|
+
...checkValidCardinality(ts, sourceFile, calleeNames)
|
|
184
|
+
];
|
|
185
|
+
return [...existing, ...extra];
|
|
186
|
+
};
|
|
187
|
+
return proxy;
|
|
188
|
+
}
|
|
189
|
+
return { create };
|
|
190
|
+
}
|
|
191
|
+
module.exports = init;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import tsserverlibrary from 'typescript/lib/tsserverlibrary';
|
|
2
|
+
|
|
3
|
+
type TS = typeof tsserverlibrary;
|
|
4
|
+
declare function init(modules: {
|
|
5
|
+
typescript: TS;
|
|
6
|
+
}): {
|
|
7
|
+
create: (info: tsserverlibrary.server.PluginCreateInfo) => tsserverlibrary.LanguageService;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export { init as default };
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { Cardinality, ChildRulePosition, Severity } from '@praxis-kit/core';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One enforcement.children rule whose cardinality is statically extractable
|
|
7
|
+
* from the factory call AST. The `Cardinality` discriminated union matches the
|
|
8
|
+
* shape used by the runtime `ChildrenEvaluator` — unbounded rules contribute
|
|
9
|
+
* Infinity to `totalMax`.
|
|
10
|
+
*/
|
|
11
|
+
type StaticBound = {
|
|
12
|
+
cardinality: Cardinality;
|
|
13
|
+
position: ChildRulePosition;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A component definition collected from a createPolymorphicComponent /
|
|
17
|
+
* createContractComponent call whose enforcement.children array contains at
|
|
18
|
+
* least one rule with a statically-extractable cardinality.
|
|
19
|
+
*/
|
|
20
|
+
type ComponentConstraint = {
|
|
21
|
+
/** Variable name the component is bound to (e.g. "Button"). */
|
|
22
|
+
name: string;
|
|
23
|
+
/** One entry per ChildRuleInput whose cardinality is a literal object. */
|
|
24
|
+
rules: StaticBound[];
|
|
25
|
+
/** Sum of all rule.cardinality.min values. */
|
|
26
|
+
totalMin: number;
|
|
27
|
+
/** Sum of all rule.cardinality.max values; Infinity if any rule is unbounded. */
|
|
28
|
+
totalMax: number;
|
|
29
|
+
/** The default HTML tag declared in the factory call (`tag: 'button'`), if statically present. */
|
|
30
|
+
defaultTag?: string;
|
|
31
|
+
/** True when `enforcement.aria` is a non-empty array literal in the factory call. */
|
|
32
|
+
hasAriaRules: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** A diagnostic produced by the static analysis pass. */
|
|
36
|
+
type Diagnostic = {
|
|
37
|
+
message: string;
|
|
38
|
+
/** 1-based line number in the source file. */
|
|
39
|
+
line: number;
|
|
40
|
+
/** 1-based column number. */
|
|
41
|
+
col: number;
|
|
42
|
+
/**
|
|
43
|
+
* Uses the same Severity vocabulary as ValidationViolation in
|
|
44
|
+
* @praxis-kit/contract — 'error' | 'warning'. The plugin wrapper maps
|
|
45
|
+
* 'warning' → this.warn() and 'error' → this.error() for Rollup/Vite.
|
|
46
|
+
*/
|
|
47
|
+
severity: Severity;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Options accepted by contractPlugin() and analyze(). */
|
|
51
|
+
type PluginOptions = {
|
|
52
|
+
/**
|
|
53
|
+
* Factory function names to look for.
|
|
54
|
+
* @default ['createPolymorphicComponent', 'createContractComponent']
|
|
55
|
+
*/
|
|
56
|
+
calleeNames?: string[];
|
|
57
|
+
/**
|
|
58
|
+
* Severity of cardinality violations in Vite build output.
|
|
59
|
+
* Matches the Severity vocabulary used by ValidationViolation.
|
|
60
|
+
* @default 'warning'
|
|
61
|
+
*/
|
|
62
|
+
severity?: Severity;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type ComponentTokens = {
|
|
66
|
+
base: string[];
|
|
67
|
+
variantClasses: string[];
|
|
68
|
+
compoundClasses: string[];
|
|
69
|
+
tagClasses: string[];
|
|
70
|
+
};
|
|
71
|
+
type DesignTokenManifest = {
|
|
72
|
+
components: Record<string, ComponentTokens>;
|
|
73
|
+
allClasses: string[];
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Collects design tokens from all factory calls in a single source file.
|
|
77
|
+
* Each entry in the returned map is keyed by the component variable name.
|
|
78
|
+
*
|
|
79
|
+
* Only `const X = factory(...)` declarations are handled; exported or
|
|
80
|
+
* destructured patterns fall through (same scope as `collectConstraints`).
|
|
81
|
+
*/
|
|
82
|
+
declare function collectFileTokens(source: ts.SourceFile, calleeNames: ReadonlySet<string>): Map<string, ComponentTokens>;
|
|
83
|
+
declare function buildManifest(allTokens: Map<string, ComponentTokens>): DesignTokenManifest;
|
|
84
|
+
type DesignTokensOptions = {
|
|
85
|
+
/**
|
|
86
|
+
* Path where the manifest JSON is written, relative to the Vite project root.
|
|
87
|
+
* @default 'praxis-tokens.json'
|
|
88
|
+
*/
|
|
89
|
+
outFile?: string;
|
|
90
|
+
} & Pick<PluginOptions, 'calleeNames'>;
|
|
91
|
+
/**
|
|
92
|
+
* Vite plugin that collects every statically-declared class string from
|
|
93
|
+
* `createContractComponent` factory calls and writes a design token manifest
|
|
94
|
+
* to a JSON file on each build.
|
|
95
|
+
*
|
|
96
|
+
* The manifest contains per-component class lists and a flat `allClasses`
|
|
97
|
+
* union that can be used as a Tailwind content source to prevent purging of
|
|
98
|
+
* variant classes.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* // vite.config.ts
|
|
102
|
+
* import { designTokensPlugin } from '@praxis-kit/vite-plugin'
|
|
103
|
+
* export default { plugins: [designTokensPlugin({ outFile: 'praxis-tokens.json' })] }
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* // tailwind.config.js
|
|
107
|
+
* export default { content: ['./src/**', './praxis-tokens.json'] }
|
|
108
|
+
*/
|
|
109
|
+
declare function designTokensPlugin(options?: DesignTokensOptions): Plugin;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Pure analysis entry point. Parses `code` as TypeScript/TSX, extracts
|
|
113
|
+
* enforcement.children constraints from factory calls, and validates JSX usage
|
|
114
|
+
* sites in the same file.
|
|
115
|
+
*
|
|
116
|
+
* Statically-analyzable scope:
|
|
117
|
+
* - Single file: component must be defined and used in the same source file.
|
|
118
|
+
* - Literal-only children: JSX sites that include any JSX expression ({...})
|
|
119
|
+
* are skipped — their child count is unknowable at build time.
|
|
120
|
+
* - Named const declarations: `export const X = factory(...)` and destructured
|
|
121
|
+
* patterns are not collected; cross-file analysis is future work.
|
|
122
|
+
*/
|
|
123
|
+
declare function analyze(code: string, filename: string, options?: PluginOptions): Diagnostic[];
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Compile-time asChild → render-prop transform.
|
|
127
|
+
*
|
|
128
|
+
* Rewrites JSX usage sites of the form:
|
|
129
|
+
* <Component asChild ...props>
|
|
130
|
+
* <childTag ...childProps>children</childTag>
|
|
131
|
+
* </Component>
|
|
132
|
+
*
|
|
133
|
+
* to the render-prop form:
|
|
134
|
+
* <Component render={(_p) => <childTag ...childProps {..._p} />} ...props />
|
|
135
|
+
*
|
|
136
|
+
* The render-prop form eliminates the Slot/cloneElement/mergeProps path at
|
|
137
|
+
* runtime — resolved props are passed directly to the render callback with no
|
|
138
|
+
* element cloning.
|
|
139
|
+
*
|
|
140
|
+
* **Safety conditions** — the transform is skipped if any of these are true:
|
|
141
|
+
* 1. The child has a dynamic `className` expression (cannot merge safely).
|
|
142
|
+
* A string-literal `className` IS handled: the transform generates
|
|
143
|
+
* `{..._p, className: _p.className + ' childCls'}`.
|
|
144
|
+
* 2. The child has a bare `style` or `on*` attribute without an initializer.
|
|
145
|
+
* Static object-literal and expression-valued `style` props are merged:
|
|
146
|
+
* `style={{..._p.style, ...childStyle}}`. Event handlers are composed:
|
|
147
|
+
* `onClick={(_e) => { (childHandler)(_e); _p.onClick?.(_e); }}`.
|
|
148
|
+
* 3. The component name starts with a lowercase letter (HTML intrinsic — not
|
|
149
|
+
* a polymorphic component).
|
|
150
|
+
* 4. There are zero or more than one meaningful child elements.
|
|
151
|
+
*
|
|
152
|
+
* The transform is conservative: any condition that is not statically clear
|
|
153
|
+
* causes the node to be left unchanged.
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Applies the asChild → render-prop transform to the given TypeScript source
|
|
158
|
+
* file and returns the printed output.
|
|
159
|
+
*
|
|
160
|
+
* Returns null if no `asChild` attribute is found in the source (fast path —
|
|
161
|
+
* avoids parsing overhead for files that don't use the pattern).
|
|
162
|
+
*/
|
|
163
|
+
declare function transformAsChild(source: ts.SourceFile): string | null;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Compile-time dead compound variant pruning.
|
|
167
|
+
*
|
|
168
|
+
* Removes entries from `styling.compounds` whose conditions can never fire:
|
|
169
|
+
* - condition key is not in `styling.variants`
|
|
170
|
+
* - condition value (string) is not a valid value for that variant key
|
|
171
|
+
* - condition value (array) has no element that is a valid value
|
|
172
|
+
*
|
|
173
|
+
* Only entries whose conditions are all statically evaluable (string/array
|
|
174
|
+
* literals throughout) are candidates for pruning — dynamic conditions (variable
|
|
175
|
+
* references, computed values) are left unchanged.
|
|
176
|
+
*
|
|
177
|
+
* Returns null if no factory calls with `styling.compounds` are found, or if
|
|
178
|
+
* every compound in every factory call passes the validity check.
|
|
179
|
+
*/
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Applies dead-compound pruning to the given TypeScript source file.
|
|
183
|
+
*
|
|
184
|
+
* Returns null when the file has no factory calls with `styling.compounds`, or
|
|
185
|
+
* when every compound entry passes validity — i.e., when no pruning is needed.
|
|
186
|
+
*/
|
|
187
|
+
declare function pruneDeadCompounds(source: ts.SourceFile, calleeNames: ReadonlySet<string>): string | null;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Compile-time variant class precomputation.
|
|
191
|
+
*
|
|
192
|
+
* Extracts `styling.variants`, `styling.defaults`, and `styling.compounds` from
|
|
193
|
+
* factory call ASTs, enumerates all statically-known prop combinations, computes
|
|
194
|
+
* the variant class string for each, and injects the resulting map as
|
|
195
|
+
* `styling.precomputedClasses` directly into the source.
|
|
196
|
+
*
|
|
197
|
+
* At runtime, `VariantClassResolver` checks `precomputedClasses` before
|
|
198
|
+
* calling CVA — a plain object lookup replaces a CVA invocation + LRU cache
|
|
199
|
+
* write for every covered combination.
|
|
200
|
+
*
|
|
201
|
+
* **Skipped when any of the following are true:**
|
|
202
|
+
* - `styling.variants` is absent or contains non-literal values
|
|
203
|
+
* - `styling.compounds` contains non-literal conditions or class values
|
|
204
|
+
* - The total number of combinations exceeds MAX_COMBINATIONS
|
|
205
|
+
* - The styling object already has a `precomputedClasses` property
|
|
206
|
+
*/
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Builds a precomputed class map for the given styling object literal.
|
|
210
|
+
*
|
|
211
|
+
* Returns null when static extraction is not possible (non-literal values,
|
|
212
|
+
* no variants, or combination count exceeds MAX_COMBINATIONS).
|
|
213
|
+
*/
|
|
214
|
+
declare function buildPrecomputedClasses(stylingObj: ts.ObjectLiteralExpression): Record<string, string> | null;
|
|
215
|
+
/**
|
|
216
|
+
* Injects precomputed variant class maps into all factory calls in the given
|
|
217
|
+
* source file that have fully-static `styling.variants` configurations.
|
|
218
|
+
*
|
|
219
|
+
* Returns null when no factory calls with injectable variants are found.
|
|
220
|
+
*/
|
|
221
|
+
declare function injectPrecomputedClasses(source: ts.SourceFile, calleeNames: ReadonlySet<string>): string | null;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Compile-time static composition transform.
|
|
225
|
+
*
|
|
226
|
+
* For same-file factory calls that have `precomputedClasses` injected (by
|
|
227
|
+
* classExtractPlugin), replaces static JSX usage sites with direct element
|
|
228
|
+
* creation — bypassing the runtime render pipeline entirely.
|
|
229
|
+
*
|
|
230
|
+
* Example:
|
|
231
|
+
* // Source (same file defines Button and uses it)
|
|
232
|
+
* const Button = createContractComponent({ tag: 'button', styling: { precomputedClasses: {...} } })
|
|
233
|
+
* <Button size="lg">Click</Button>
|
|
234
|
+
*
|
|
235
|
+
* // Output
|
|
236
|
+
* const Button = createContractComponent({ ... }) // unchanged — still exported
|
|
237
|
+
* <button className="btn btn-lg">Click</button> // inlined!
|
|
238
|
+
*
|
|
239
|
+
* **Eligibility conditions** — a usage site is inlined only when:
|
|
240
|
+
* 1. The component is defined in the same file via a factory call with
|
|
241
|
+
* `precomputedClasses` (classExtractPlugin must run first in the chain)
|
|
242
|
+
* 2. No `as`, `asChild`, `render`, or spread attributes at the usage site
|
|
243
|
+
* 3. All variant props are static string literals
|
|
244
|
+
* 4. `className` is absent or a static string literal
|
|
245
|
+
* 5. The factory config has no top-level `defaults` and no `enforcement`
|
|
246
|
+
* (either would require runtime prop normalization that inlining skips)
|
|
247
|
+
*
|
|
248
|
+
* The factory call itself is intentionally left in the output so the component
|
|
249
|
+
* remains exportable for cross-file consumption that falls back to the runtime
|
|
250
|
+
* path. Dead-code elimination at the bundler level can remove it when no
|
|
251
|
+
* runtime path remains.
|
|
252
|
+
*
|
|
253
|
+
* **Deferred:** cross-file inlining (component defined in one module, used in
|
|
254
|
+
* another) requires Vite module-graph traversal and is not yet implemented.
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Applies the static composition transform to the given source file.
|
|
259
|
+
*
|
|
260
|
+
* Returns null when:
|
|
261
|
+
* - No eligible factory calls are found in the file
|
|
262
|
+
* - No eligible usage sites are found after analysis
|
|
263
|
+
*/
|
|
264
|
+
declare function composeStatically(source: ts.SourceFile, calleeNames: ReadonlySet<string>): string | null;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Vite plugin that performs static enforcement.children cardinality checks at
|
|
268
|
+
* build time for components created with createContractComponent.
|
|
269
|
+
*
|
|
270
|
+
* **Single-file scope:** Components defined and used in the same `.tsx` / `.jsx`
|
|
271
|
+
* file are validated during `transform`. JSX children containing expressions
|
|
272
|
+
* (`{...}`) are skipped — their count is unknowable at compile time.
|
|
273
|
+
*
|
|
274
|
+
* **Cross-file scope:** Components imported from other files are validated in
|
|
275
|
+
* `buildEnd` once the full constraint registry is populated. Only named imports
|
|
276
|
+
* whose source file was also transformed by this plugin are checked.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* // vite.config.ts
|
|
280
|
+
* import { contractPlugin } from '@praxis-kit/vite-plugin'
|
|
281
|
+
* export default { plugins: [contractPlugin()] }
|
|
282
|
+
*/
|
|
283
|
+
declare function contractPlugin(options?: PluginOptions): Plugin;
|
|
284
|
+
/**
|
|
285
|
+
* Vite plugin that removes dead `styling.compounds` entries from factory calls
|
|
286
|
+
* at build time, reducing bundle size and eliminating unreachable CVA compound
|
|
287
|
+
* checks at runtime.
|
|
288
|
+
*
|
|
289
|
+
* A compound entry is dead when any of its conditions reference a variant key
|
|
290
|
+
* that does not exist in `styling.variants`, or a value that is not valid for
|
|
291
|
+
* that key. Only entries whose conditions are fully static (string/array
|
|
292
|
+
* literals) are pruned — dynamic conditions are left unchanged.
|
|
293
|
+
*
|
|
294
|
+
* Place before `contractPlugin` so the pruned source is what gets analyzed.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* // vite.config.ts
|
|
298
|
+
* import { compoundPrunePlugin, contractPlugin } from '@praxis-kit/vite-plugin'
|
|
299
|
+
* export default { plugins: [compoundPrunePlugin(), contractPlugin()] }
|
|
300
|
+
*/
|
|
301
|
+
declare function compoundPrunePlugin(options?: Pick<PluginOptions, 'calleeNames'>): Plugin;
|
|
302
|
+
/**
|
|
303
|
+
* Vite plugin that pre-computes variant class strings at build time and injects
|
|
304
|
+
* them as a static `precomputedClasses` map into each factory call's `styling`
|
|
305
|
+
* object.
|
|
306
|
+
*
|
|
307
|
+
* At runtime, `VariantClassResolver` checks this map before calling CVA — a
|
|
308
|
+
* plain object lookup replaces a CVA invocation + LRU cache write for every
|
|
309
|
+
* statically-known combination. Only combinations that appear in the map are
|
|
310
|
+
* accelerated; invalid or dynamic variant values fall through to the existing
|
|
311
|
+
* compute path unchanged.
|
|
312
|
+
*
|
|
313
|
+
* Injection is skipped when:
|
|
314
|
+
* - `styling.variants` is absent or contains non-literal values
|
|
315
|
+
* - `styling.compounds` contains non-literal conditions or classes
|
|
316
|
+
* - The number of valid combinations exceeds 512
|
|
317
|
+
*
|
|
318
|
+
* Place after `compoundPrunePlugin` so the injected map reflects the live
|
|
319
|
+
* compound set.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* // vite.config.ts
|
|
323
|
+
* import { compoundPrunePlugin, classExtractPlugin, contractPlugin } from '@praxis-kit/vite-plugin'
|
|
324
|
+
* export default { plugins: [compoundPrunePlugin(), classExtractPlugin(), contractPlugin()] }
|
|
325
|
+
*/
|
|
326
|
+
declare function classExtractPlugin(options?: Pick<PluginOptions, 'calleeNames'>): Plugin;
|
|
327
|
+
/**
|
|
328
|
+
* Vite plugin that transforms `asChild` JSX usage sites to the render-prop form
|
|
329
|
+
* at build time, eliminating the Slot/cloneElement/mergeProps runtime path.
|
|
330
|
+
*
|
|
331
|
+
* Only transforms sites where the transform is semantically safe:
|
|
332
|
+
* - Exactly one static child element
|
|
333
|
+
* - Child has no `className`, `style`, or event handler props
|
|
334
|
+
*
|
|
335
|
+
* Complex asChild patterns (conflicting props, dynamic children, Slottable
|
|
336
|
+
* siblings) are left unchanged and handled by the runtime Slot path.
|
|
337
|
+
*
|
|
338
|
+
* Place before `contractPlugin` so cardinality analysis sees the transformed
|
|
339
|
+
* source.
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* // vite.config.ts
|
|
343
|
+
* import { slotTransformPlugin, contractPlugin } from '@praxis-kit/vite-plugin'
|
|
344
|
+
* export default { plugins: [slotTransformPlugin(), contractPlugin()] }
|
|
345
|
+
*/
|
|
346
|
+
declare function slotTransformPlugin(): Plugin;
|
|
347
|
+
/**
|
|
348
|
+
* Vite plugin that replaces same-file polymorphic component usage sites with
|
|
349
|
+
* direct element creation at build time, eliminating the runtime render
|
|
350
|
+
* pipeline for statically-analyzable usages.
|
|
351
|
+
*
|
|
352
|
+
* **Requires classExtractPlugin to run first** so that `precomputedClasses` is
|
|
353
|
+
* present in the factory call before this plugin reads it. Place after
|
|
354
|
+
* `classExtractPlugin` in the plugins array.
|
|
355
|
+
*
|
|
356
|
+
* A usage site is inlined when:
|
|
357
|
+
* - The component is defined in the same file with `precomputedClasses` injected
|
|
358
|
+
* - No `as`, `asChild`, `render`, or spread attributes at the site
|
|
359
|
+
* - All variant props are static string literals
|
|
360
|
+
* - `className` is absent or a static string literal
|
|
361
|
+
* - The factory config has no `defaults` or `enforcement`
|
|
362
|
+
*
|
|
363
|
+
* Sites that do not meet all conditions fall through to the normal runtime path
|
|
364
|
+
* unchanged.
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* // vite.config.ts
|
|
368
|
+
* import { classExtractPlugin, staticCompositionPlugin } from '@praxis-kit/vite-plugin'
|
|
369
|
+
* export default { plugins: [classExtractPlugin(), staticCompositionPlugin()] }
|
|
370
|
+
*/
|
|
371
|
+
declare function staticCompositionPlugin(options?: Pick<PluginOptions, 'calleeNames'>): Plugin;
|
|
372
|
+
/**
|
|
373
|
+
* Convenience plugin bundle that applies all three build-time rendering
|
|
374
|
+
* optimisations in the correct dependency order:
|
|
375
|
+
*
|
|
376
|
+
* 1. `slotTransformPlugin` — rewrites `asChild` → render-prop form,
|
|
377
|
+
* eliminating `cloneElement` for static sites
|
|
378
|
+
* 2. `classExtractPlugin` — injects `precomputedClasses` into factory
|
|
379
|
+
* calls for O(1) variant class resolution
|
|
380
|
+
* 3. `staticCompositionPlugin` — inlines same-file static usages into direct
|
|
381
|
+
* element creation, bypassing the runtime
|
|
382
|
+
* pipeline entirely
|
|
383
|
+
*
|
|
384
|
+
* Place before `contractPlugin` so cardinality analysis sees the transformed
|
|
385
|
+
* source. Especially effective for SSR builds where each component renders
|
|
386
|
+
* exactly once per request and eliminates per-render pipeline overhead.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* // vite.config.ts
|
|
390
|
+
* import { ssrOptimizePlugin, contractPlugin } from '@praxis-kit/vite-plugin'
|
|
391
|
+
* export default { plugins: [ssrOptimizePlugin(), contractPlugin()] }
|
|
392
|
+
*/
|
|
393
|
+
declare function ssrOptimizePlugin(options?: Pick<PluginOptions, 'calleeNames'>): Plugin[];
|
|
394
|
+
|
|
395
|
+
export { type ComponentConstraint, type ComponentTokens, type DesignTokenManifest, type DesignTokensOptions, type Diagnostic, type PluginOptions, type StaticBound, analyze, buildManifest, buildPrecomputedClasses, classExtractPlugin, collectFileTokens, composeStatically, compoundPrunePlugin, contractPlugin, designTokensPlugin, injectPrecomputedClasses, pruneDeadCompounds, slotTransformPlugin, ssrOptimizePlugin, staticCompositionPlugin, transformAsChild };
|