@mindees/compiler 0.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 +31 -0
- package/README.md +77 -0
- package/dist/aot.d.ts +30 -0
- package/dist/aot.d.ts.map +1 -0
- package/dist/aot.js +32 -0
- package/dist/aot.js.map +1 -0
- package/dist/flatten.d.ts +17 -0
- package/dist/flatten.d.ts.map +1 -0
- package/dist/flatten.js +103 -0
- package/dist/flatten.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/routes.d.ts +58 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +89 -0
- package/dist/routes.js.map +1 -0
- package/dist/transform.d.ts +20 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +93 -0
- package/dist/transform.js.map +1 -0
- package/dist/typecheck.d.ts +16 -0
- package/dist/typecheck.d.ts.map +1 -0
- package/dist/typecheck.js +129 -0
- package/dist/typecheck.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
MindeesNative is dual-licensed under either of:
|
|
4
|
+
|
|
5
|
+
- **Apache License, Version 2.0** ([LICENSE-APACHE](./LICENSE-APACHE) or
|
|
6
|
+
<https://www.apache.org/licenses/LICENSE-2.0>)
|
|
7
|
+
- **MIT license** ([LICENSE-MIT](./LICENSE-MIT) or
|
|
8
|
+
<https://opensource.org/licenses/MIT>)
|
|
9
|
+
|
|
10
|
+
at your option.
|
|
11
|
+
|
|
12
|
+
This `MIT OR Apache-2.0` dual-license is the same model used by the Rust
|
|
13
|
+
ecosystem and many modern open-source projects. It gives downstream users
|
|
14
|
+
maximum flexibility: the MIT option is short and permissive, while the Apache
|
|
15
|
+
option adds an explicit patent grant.
|
|
16
|
+
|
|
17
|
+
## SPDX identifier
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
SPDX-License-Identifier: MIT OR Apache-2.0
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Contribution
|
|
24
|
+
|
|
25
|
+
Unless you explicitly state otherwise, any contribution intentionally
|
|
26
|
+
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
|
27
|
+
license, shall be dual-licensed as above, without any additional terms or
|
|
28
|
+
conditions.
|
|
29
|
+
|
|
30
|
+
Contributions are accepted under the **Developer Certificate of Origin (DCO)**.
|
|
31
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for details on signing off your commits.
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @mindees/compiler
|
|
2
|
+
|
|
3
|
+
**MDC** — the Mindees Compiler. A build-time optimizer built on the TypeScript
|
|
4
|
+
Compiler API: a strict **type-check gate**, a **TSX → `createElement`** transform
|
|
5
|
+
(matching `@mindees/core`), **tree-flattening**, a per-route **code-splitting**
|
|
6
|
+
manifest, and a **plugin API**.
|
|
7
|
+
|
|
8
|
+
> **Status: 🧪 Experimental (Phase 4).** Implemented and tested. The working
|
|
9
|
+
> emit path is **TS → optimized JavaScript**. **TS → native machine code (AOT)**
|
|
10
|
+
> is a research track (`compileToNative` throws `NotImplementedError`). APIs may
|
|
11
|
+
> change before `1.0`.
|
|
12
|
+
|
|
13
|
+
## Why the TypeScript Compiler API?
|
|
14
|
+
|
|
15
|
+
Only TypeScript can **type-check** — the compiler's #1 job. It also does JSX
|
|
16
|
+
lowering, transforms, and source maps in one tool, with **zero native binaries**
|
|
17
|
+
(deterministic, reproducible CI). See
|
|
18
|
+
[ADR-0002](../../docs/adr/0002-compiler-foundation.md). An SWC/oxc-accelerated
|
|
19
|
+
*emit* path is a documented future optimization.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { compileChecked, buildRouteManifest } from '@mindees/compiler'
|
|
25
|
+
|
|
26
|
+
// Type-check gate: a build must not ship type errors.
|
|
27
|
+
const bad = compileChecked('export const a: number = "oops"')
|
|
28
|
+
bad.code // '' — refused to emit
|
|
29
|
+
bad.diagnostics // [{ code: 'TS2322', severity: 'error', ... }]
|
|
30
|
+
|
|
31
|
+
// Compile valid TSX → createElement, with tree-flattening + a source map.
|
|
32
|
+
const ok = compileChecked('export const v = <view id="x"><text>hi</text></view>')
|
|
33
|
+
ok.code // '..._static(createElement("view", { id: "x" }, ...))...'
|
|
34
|
+
ok.stats // { flattenedNodes: 1, totalElements: 2 }
|
|
35
|
+
ok.map // JSON source map
|
|
36
|
+
|
|
37
|
+
// Per-route code-splitting manifest (file-based routing).
|
|
38
|
+
buildRouteManifest(['index.tsx', 'blog/[slug].tsx'])
|
|
39
|
+
// → { routes: [{ routePath: '/', ... },
|
|
40
|
+
// { routePath: '/blog/:slug', params: ['slug'], chunk: 'route_blog_slug' }] }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## What the optimizer does
|
|
44
|
+
|
|
45
|
+
- **Type-check gate** (`typecheck` / `compileChecked`) — strict (`strict`,
|
|
46
|
+
`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`); structured
|
|
47
|
+
`Diagnostic`s with `TSxxxx` codes + positions. `compileChecked` refuses to emit
|
|
48
|
+
on any `error`.
|
|
49
|
+
- **TSX → `createElement`** — JSX lowered to the `@mindees/core` factory, with
|
|
50
|
+
source maps.
|
|
51
|
+
- **Tree-flattening** — fully-static element subtrees are wrapped once as
|
|
52
|
+
`_static(createElement(...))`, a create-once / never-diff constant. Purely
|
|
53
|
+
additive (never drops or reorders nodes). `stats.flattenedNodes` /
|
|
54
|
+
`stats.totalElements` quantify it.
|
|
55
|
+
- **Per-route manifest** (`buildRouteManifest`) — maps a file tree to lazily
|
|
56
|
+
loadable chunks (`index` → `/`, `[param]` → `:param`, `[...rest]` catch-all,
|
|
57
|
+
`(group)` layout groups dropped from the URL).
|
|
58
|
+
- **Plugin API** (`MdcPlugin`) — plugins receive the `typescript` module and
|
|
59
|
+
return a transformer that runs **after** JSX desugaring (so they see
|
|
60
|
+
`createElement(...)` calls, not raw JSX).
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
| Export | Kind | Description |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| `typecheck(source, fileName?)` | fn | Type-check → `Diagnostic[]`. |
|
|
67
|
+
| `hasErrors(diagnostics)` | fn | Any `error`-severity diagnostic? |
|
|
68
|
+
| `compile(source, options?)` | fn | TSX → JS (+ flatten, plugins, source map). |
|
|
69
|
+
| `compileChecked(source, options?)` | fn | Gate, then compile; no emit on errors. |
|
|
70
|
+
| `buildRouteManifest(files)` / `fileToRoute` / `chunkName` | fn | Per-route manifest. |
|
|
71
|
+
| `createFlattenTransformer` / `STATIC_MARKER` | fn | The flatten pass. |
|
|
72
|
+
| `compileToNative` | fn | 🔬 research track — throws `NotImplementedError`. |
|
|
73
|
+
| `CompileOptions`, `CompileResult`, `Diagnostic`, `MdcPlugin`, … | type | Public types. |
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
`MIT OR Apache-2.0`
|
package/dist/aot.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/aot.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript → native machine code (AOT) — 🔬 **research track**.
|
|
4
|
+
*
|
|
5
|
+
* The framework spec's north star is compiling typed TS to native (ARM64/x86-64)
|
|
6
|
+
* for native-grade throughput (à la Static Hermes / Valdi's TS→native pipeline).
|
|
7
|
+
* That is **not implemented**: it's at the frontier and unproven across the full
|
|
8
|
+
* dynamic-TS surface.
|
|
9
|
+
*
|
|
10
|
+
* Per the Working-Code Doctrine, this module is honest about that. The **working
|
|
11
|
+
* fallback is the standard {@link compile} path** (TS → optimized JavaScript),
|
|
12
|
+
* which runs everywhere today. {@link compileToNative} throws
|
|
13
|
+
* {@link NotImplementedError} so nothing silently pretends to emit native code.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
/** Target architecture for a future native AOT backend. */
|
|
18
|
+
type NativeTarget = 'arm64' | 'x86-64';
|
|
19
|
+
/**
|
|
20
|
+
* 🔬 Research track — not implemented. Throws {@link NotImplementedError}.
|
|
21
|
+
*
|
|
22
|
+
* Use {@link compile} (TS → optimized JS) today; that is the working path and
|
|
23
|
+
* the documented fallback for any code that can't (yet) be AOT-compiled.
|
|
24
|
+
*
|
|
25
|
+
* @experimental
|
|
26
|
+
*/
|
|
27
|
+
declare function compileToNative(_source: string, _target: NativeTarget): never;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { NativeTarget, compileToNative };
|
|
30
|
+
//# sourceMappingURL=aot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aot.d.ts","names":[],"sources":["../src/aot.ts"],"mappings":";;AAmBA;;;;AAAwB;AAUxB;;;;;;;;AAAsE;;KAV1D,YAAA;;;;;;;;;iBAUI,eAAA,CAAgB,OAAA,UAAiB,OAAA,EAAS,YAAY"}
|
package/dist/aot.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NotImplementedError } from "@mindees/core";
|
|
2
|
+
//#region src/aot.ts
|
|
3
|
+
/**
|
|
4
|
+
* TypeScript → native machine code (AOT) — 🔬 **research track**.
|
|
5
|
+
*
|
|
6
|
+
* The framework spec's north star is compiling typed TS to native (ARM64/x86-64)
|
|
7
|
+
* for native-grade throughput (à la Static Hermes / Valdi's TS→native pipeline).
|
|
8
|
+
* That is **not implemented**: it's at the frontier and unproven across the full
|
|
9
|
+
* dynamic-TS surface.
|
|
10
|
+
*
|
|
11
|
+
* Per the Working-Code Doctrine, this module is honest about that. The **working
|
|
12
|
+
* fallback is the standard {@link compile} path** (TS → optimized JavaScript),
|
|
13
|
+
* which runs everywhere today. {@link compileToNative} throws
|
|
14
|
+
* {@link NotImplementedError} so nothing silently pretends to emit native code.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* 🔬 Research track — not implemented. Throws {@link NotImplementedError}.
|
|
20
|
+
*
|
|
21
|
+
* Use {@link compile} (TS → optimized JS) today; that is the working path and
|
|
22
|
+
* the documented fallback for any code that can't (yet) be AOT-compiled.
|
|
23
|
+
*
|
|
24
|
+
* @experimental
|
|
25
|
+
*/
|
|
26
|
+
function compileToNative(_source, _target) {
|
|
27
|
+
throw new NotImplementedError("TypeScript → native machine code (AOT)");
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { compileToNative };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=aot.js.map
|
package/dist/aot.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aot.js","names":[],"sources":["../src/aot.ts"],"sourcesContent":["/**\n * TypeScript → native machine code (AOT) — 🔬 **research track**.\n *\n * The framework spec's north star is compiling typed TS to native (ARM64/x86-64)\n * for native-grade throughput (à la Static Hermes / Valdi's TS→native pipeline).\n * That is **not implemented**: it's at the frontier and unproven across the full\n * dynamic-TS surface.\n *\n * Per the Working-Code Doctrine, this module is honest about that. The **working\n * fallback is the standard {@link compile} path** (TS → optimized JavaScript),\n * which runs everywhere today. {@link compileToNative} throws\n * {@link NotImplementedError} so nothing silently pretends to emit native code.\n *\n * @module\n */\n\nimport { NotImplementedError } from '@mindees/core'\n\n/** Target architecture for a future native AOT backend. */\nexport type NativeTarget = 'arm64' | 'x86-64'\n\n/**\n * 🔬 Research track — not implemented. Throws {@link NotImplementedError}.\n *\n * Use {@link compile} (TS → optimized JS) today; that is the working path and\n * the documented fallback for any code that can't (yet) be AOT-compiled.\n *\n * @experimental\n */\nexport function compileToNative(_source: string, _target: NativeTarget): never {\n throw new NotImplementedError('TypeScript → native machine code (AOT)')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,gBAAgB,SAAiB,SAA8B;CAC7E,MAAM,IAAI,oBAAoB,wCAAwC;AACxE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CompileStats } from "./types.js";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
|
|
4
|
+
//#region src/flatten.d.ts
|
|
5
|
+
/** Name the runtime exposes for the static-subtree marker. */
|
|
6
|
+
declare const STATIC_MARKER = "_static";
|
|
7
|
+
/**
|
|
8
|
+
* Build the flatten transformer. Returns a TS `TransformerFactory` plus a live
|
|
9
|
+
* `stats` object updated as the transform runs.
|
|
10
|
+
*/
|
|
11
|
+
declare function createFlattenTransformer(tsmod: typeof ts): {
|
|
12
|
+
factory: ts.TransformerFactory<ts.SourceFile>;
|
|
13
|
+
stats: CompileStats;
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { STATIC_MARKER, createFlattenTransformer };
|
|
17
|
+
//# sourceMappingURL=flatten.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flatten.d.ts","names":[],"sources":["../src/flatten.ts"],"mappings":";;;;AAuCqB;AAAA,cARR,aAAA;;;;;iBAMG,wBAAA,CAAyB,KAAA,SAAc,EAAA;EACrD,OAAA,EAAS,EAAA,CAAG,kBAAA,CAAmB,EAAA,CAAG,UAAA;EAClC,KAAA,EAAO,YAAA;AAAA"}
|
package/dist/flatten.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//#region src/flatten.ts
|
|
2
|
+
/** Name the runtime exposes for the static-subtree marker. */
|
|
3
|
+
const STATIC_MARKER = "_static";
|
|
4
|
+
/**
|
|
5
|
+
* Build the flatten transformer. Returns a TS `TransformerFactory` plus a live
|
|
6
|
+
* `stats` object updated as the transform runs.
|
|
7
|
+
*/
|
|
8
|
+
function createFlattenTransformer(tsmod) {
|
|
9
|
+
const stats = {
|
|
10
|
+
flattenedNodes: 0,
|
|
11
|
+
totalElements: 0
|
|
12
|
+
};
|
|
13
|
+
const isCreateElementCall = (node) => tsmod.isCallExpression(node) && tsmod.isIdentifier(node.expression) && node.expression.text === "createElement";
|
|
14
|
+
const isStaticExpression = (node) => {
|
|
15
|
+
switch (node.kind) {
|
|
16
|
+
case tsmod.SyntaxKind.StringLiteral:
|
|
17
|
+
case tsmod.SyntaxKind.NumericLiteral:
|
|
18
|
+
case tsmod.SyntaxKind.TrueKeyword:
|
|
19
|
+
case tsmod.SyntaxKind.FalseKeyword:
|
|
20
|
+
case tsmod.SyntaxKind.NullKeyword: return true;
|
|
21
|
+
default: return isCreateElementCall(node) && isStaticElement(node);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const isStaticProps = (node) => {
|
|
25
|
+
if (!node) return true;
|
|
26
|
+
if (node.kind === tsmod.SyntaxKind.NullKeyword) return true;
|
|
27
|
+
if (tsmod.isIdentifier(node) && node.text === "undefined") return true;
|
|
28
|
+
if (tsmod.isObjectLiteralExpression(node)) return node.properties.every((p) => tsmod.isPropertyAssignment(p) && !tsmod.isComputedPropertyName(p.name) && isStaticExpression(p.initializer));
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
const isStaticElement = (call) => {
|
|
32
|
+
const [type, props, ...children] = call.arguments;
|
|
33
|
+
if (!type || !tsmod.isStringLiteral(type)) return false;
|
|
34
|
+
if (!isStaticProps(props)) return false;
|
|
35
|
+
return children.every((c) => isStaticExpression(c));
|
|
36
|
+
};
|
|
37
|
+
/** Count every createElement call in a subtree (for accurate totals). */
|
|
38
|
+
const countElements = (node) => {
|
|
39
|
+
if (isCreateElementCall(node)) stats.totalElements++;
|
|
40
|
+
tsmod.forEachChild(node, countElements);
|
|
41
|
+
};
|
|
42
|
+
/** Whether a binding name (incl. destructuring patterns) binds `name`. */
|
|
43
|
+
const bindingHasName = (binding, name) => {
|
|
44
|
+
if (tsmod.isIdentifier(binding)) return binding.text === name;
|
|
45
|
+
return binding.elements.some((el) => tsmod.isBindingElement(el) && bindingHasName(el.name, name));
|
|
46
|
+
};
|
|
47
|
+
/** Whether the module already binds `name` at the top level (var/fn/class/import). */
|
|
48
|
+
const bindsTopLevel = (sf, name) => sf.statements.some((s) => {
|
|
49
|
+
if (tsmod.isVariableStatement(s)) return s.declarationList.declarations.some((d) => bindingHasName(d.name, name));
|
|
50
|
+
if (tsmod.isFunctionDeclaration(s) || tsmod.isClassDeclaration(s)) return s.name?.text === name;
|
|
51
|
+
if (tsmod.isImportDeclaration(s) && s.importClause) {
|
|
52
|
+
const clause = s.importClause;
|
|
53
|
+
if (clause.name?.text === name) return true;
|
|
54
|
+
const named = clause.namedBindings;
|
|
55
|
+
if (named) {
|
|
56
|
+
if (tsmod.isNamespaceImport(named)) return named.name.text === name;
|
|
57
|
+
return named.elements.some((e) => e.name.text === name);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
});
|
|
62
|
+
/** `const _static = (node) => node` — the hoisted create-once marker. */
|
|
63
|
+
const makeMarkerDecl = (f) => f.createVariableStatement(void 0, f.createVariableDeclarationList([f.createVariableDeclaration(f.createIdentifier(STATIC_MARKER), void 0, void 0, f.createArrowFunction(void 0, void 0, [f.createParameterDeclaration(void 0, void 0, f.createIdentifier("node"))], void 0, f.createToken(tsmod.SyntaxKind.EqualsGreaterThanToken), f.createIdentifier("node")))], tsmod.NodeFlags.Const));
|
|
64
|
+
const factory = (context) => {
|
|
65
|
+
const { factory: f } = context;
|
|
66
|
+
return (sourceFile) => {
|
|
67
|
+
const visit = (node) => {
|
|
68
|
+
if (isCreateElementCall(node) && isStaticElement(node)) {
|
|
69
|
+
stats.flattenedNodes++;
|
|
70
|
+
return f.createCallExpression(f.createIdentifier(STATIC_MARKER), void 0, [node]);
|
|
71
|
+
}
|
|
72
|
+
return tsmod.visitEachChild(node, visit, context);
|
|
73
|
+
};
|
|
74
|
+
countElements(sourceFile);
|
|
75
|
+
if (bindsTopLevel(sourceFile, "_static")) return sourceFile;
|
|
76
|
+
const visited = tsmod.visitNode(sourceFile, visit);
|
|
77
|
+
if (stats.flattenedNodes === 0) return visited;
|
|
78
|
+
const statements = visited.statements;
|
|
79
|
+
let insertAt = 0;
|
|
80
|
+
while (insertAt < statements.length) {
|
|
81
|
+
const stmt = statements[insertAt];
|
|
82
|
+
if (!stmt) break;
|
|
83
|
+
const isImport = tsmod.isImportDeclaration(stmt);
|
|
84
|
+
const isDirective = tsmod.isExpressionStatement(stmt) && tsmod.isStringLiteral(stmt.expression);
|
|
85
|
+
if (!isImport && !isDirective) break;
|
|
86
|
+
insertAt++;
|
|
87
|
+
}
|
|
88
|
+
return f.updateSourceFile(visited, [
|
|
89
|
+
...statements.slice(0, insertAt),
|
|
90
|
+
makeMarkerDecl(f),
|
|
91
|
+
...statements.slice(insertAt)
|
|
92
|
+
]);
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
factory,
|
|
97
|
+
stats
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { STATIC_MARKER, createFlattenTransformer };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=flatten.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flatten.js","names":[],"sources":["../src/flatten.ts"],"sourcesContent":["/**\n * Tree-flattening optimizer pass.\n *\n * Operates on the **desugared** `createElement(type, props, ...children)` call\n * form. TS lowers JSX during the `after` phase, so this runs as an **`after`**\n * transformer (a `before` transformer would see raw JSX and match no\n * `createElement` calls) — see transform.ts and ADR-0002.\n *\n * What it does (v0, conservative + correct): a `createElement` call is\n * **static** when its `type` is a string literal, its props are a static object\n * literal (or null/undefined), and all its children are themselves static. The\n * **outermost** static call is wrapped once as `_static(createElement(...))`.\n * When any wrapping happens, the pass also **injects a self-contained\n * `const _static = (node) => node`** at the top of the module (after imports), so\n * the emitted code is runnable standalone — `_static` is a create-once marker\n * that is an identity passthrough today (the element is created once at the call\n * site regardless); a future reconciler fast-path may special-case it for\n * never-diff. Nested static children are left untouched (created once as part of\n * the wrapped root). The transform is purely additive (wraps a root + hoists the\n * marker; never drops or reorders nodes), so it can't change behavior.\n *\n * `stats.flattenedNodes` counts static roots wrapped; `stats.totalElements`\n * counts every `createElement` call. These feed tests and the perf budget.\n *\n * @module\n */\n\nimport type ts from 'typescript'\nimport type { CompileStats } from './types'\n\n/** Name the runtime exposes for the static-subtree marker. */\nexport const STATIC_MARKER = '_static'\n\n/**\n * Build the flatten transformer. Returns a TS `TransformerFactory` plus a live\n * `stats` object updated as the transform runs.\n */\nexport function createFlattenTransformer(tsmod: typeof ts): {\n factory: ts.TransformerFactory<ts.SourceFile>\n stats: CompileStats\n} {\n const stats: CompileStats = { flattenedNodes: 0, totalElements: 0 }\n\n const isCreateElementCall = (node: ts.Node): node is ts.CallExpression =>\n tsmod.isCallExpression(node) &&\n tsmod.isIdentifier(node.expression) &&\n node.expression.text === 'createElement'\n\n const isStaticExpression = (node: ts.Expression): boolean => {\n switch (node.kind) {\n case tsmod.SyntaxKind.StringLiteral:\n case tsmod.SyntaxKind.NumericLiteral:\n case tsmod.SyntaxKind.TrueKeyword:\n case tsmod.SyntaxKind.FalseKeyword:\n case tsmod.SyntaxKind.NullKeyword:\n return true\n default:\n return isCreateElementCall(node) && isStaticElement(node)\n }\n }\n\n const isStaticProps = (node: ts.Expression | undefined): boolean => {\n if (!node) return true\n if (node.kind === tsmod.SyntaxKind.NullKeyword) return true\n if (tsmod.isIdentifier(node) && node.text === 'undefined') return true\n if (tsmod.isObjectLiteralExpression(node)) {\n return node.properties.every(\n (p) =>\n // Only plain `key: value` props with a NON-computed key count as\n // static. A computed key (`{[k]: 1}`) can be dynamic/side-effectful,\n // and shorthand/spread/method/accessor members are not statically\n // analyzable here — any of these makes the element non-static.\n tsmod.isPropertyAssignment(p) &&\n !tsmod.isComputedPropertyName(p.name) &&\n isStaticExpression(p.initializer),\n )\n }\n return false\n }\n\n const isStaticElement = (call: ts.CallExpression): boolean => {\n const [type, props, ...children] = call.arguments\n if (!type || !tsmod.isStringLiteral(type)) return false\n if (!isStaticProps(props)) return false\n return children.every((c) => isStaticExpression(c))\n }\n\n /** Count every createElement call in a subtree (for accurate totals). */\n const countElements = (node: ts.Node): void => {\n if (isCreateElementCall(node)) stats.totalElements++\n tsmod.forEachChild(node, countElements)\n }\n\n /** Whether a binding name (incl. destructuring patterns) binds `name`. */\n const bindingHasName = (binding: ts.BindingName, name: string): boolean => {\n if (tsmod.isIdentifier(binding)) return binding.text === name\n // Object/Array binding pattern: recurse into each element's binding name.\n return binding.elements.some(\n (el) => tsmod.isBindingElement(el) && bindingHasName(el.name, name),\n )\n }\n\n /** Whether the module already binds `name` at the top level (var/fn/class/import). */\n const bindsTopLevel = (sf: ts.SourceFile, name: string): boolean =>\n sf.statements.some((s) => {\n if (tsmod.isVariableStatement(s)) {\n return s.declarationList.declarations.some((d) => bindingHasName(d.name, name))\n }\n if (tsmod.isFunctionDeclaration(s) || tsmod.isClassDeclaration(s)) {\n return s.name?.text === name\n }\n if (tsmod.isImportDeclaration(s) && s.importClause) {\n const clause = s.importClause\n if (clause.name?.text === name) return true\n const named = clause.namedBindings\n if (named) {\n if (tsmod.isNamespaceImport(named)) return named.name.text === name\n return named.elements.some((e) => e.name.text === name)\n }\n }\n return false\n })\n\n /** `const _static = (node) => node` — the hoisted create-once marker. */\n const makeMarkerDecl = (f: ts.NodeFactory): ts.Statement =>\n f.createVariableStatement(\n undefined,\n f.createVariableDeclarationList(\n [\n f.createVariableDeclaration(\n f.createIdentifier(STATIC_MARKER),\n undefined,\n undefined,\n f.createArrowFunction(\n undefined,\n undefined,\n [f.createParameterDeclaration(undefined, undefined, f.createIdentifier('node'))],\n undefined,\n f.createToken(tsmod.SyntaxKind.EqualsGreaterThanToken),\n f.createIdentifier('node'),\n ),\n ),\n ],\n tsmod.NodeFlags.Const,\n ),\n )\n\n const factory: ts.TransformerFactory<ts.SourceFile> = (context) => {\n const { factory: f } = context\n return (sourceFile) => {\n const visit = (node: ts.Node): ts.Node => {\n if (isCreateElementCall(node) && isStaticElement(node)) {\n // Outermost static root: wrap once and DON'T recurse (the whole\n // subtree is static and created as part of this wrapped constant).\n stats.flattenedNodes++\n return f.createCallExpression(f.createIdentifier(STATIC_MARKER), undefined, [node])\n }\n return tsmod.visitEachChild(node, visit, context)\n }\n // Count all elements up front (separately from the wrapping logic), so the\n // total is accurate even though we stop recursion at static roots.\n countElements(sourceFile)\n // If the module already binds `_static` at the top level, injecting our marker\n // would redeclare it (a SyntaxError) and our wrap calls would rebind the user's\n // binding. Skip flattening this module rather than emit broken code.\n if (bindsTopLevel(sourceFile, STATIC_MARKER)) return sourceFile\n const visited = tsmod.visitNode(sourceFile, visit) as ts.SourceFile\n if (stats.flattenedNodes === 0) return visited\n\n // Inject the `_static` marker once, after any leading **directive prologue**\n // (`\"use client\"`/`\"use server\"`/`\"use strict\"`) AND leading imports, so the\n // emitted module is self-contained and runnable AND the marker never lands\n // before a directive (which would demote it to a no-op string expression).\n const statements = visited.statements\n let insertAt = 0\n while (insertAt < statements.length) {\n const stmt = statements[insertAt]\n if (!stmt) break\n const isImport = tsmod.isImportDeclaration(stmt)\n const isDirective =\n tsmod.isExpressionStatement(stmt) && tsmod.isStringLiteral(stmt.expression)\n if (!isImport && !isDirective) break\n insertAt++\n }\n return f.updateSourceFile(visited, [\n ...statements.slice(0, insertAt),\n makeMarkerDecl(f),\n ...statements.slice(insertAt),\n ])\n }\n }\n\n return { factory, stats }\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NativeTarget, compileToNative } from "./aot.js";
|
|
2
|
+
import { CompileOptions, CompileResult, CompileStats, Diagnostic, DiagnosticSeverity, MdcPlugin, SourcePosition } from "./types.js";
|
|
3
|
+
import { STATIC_MARKER, createFlattenTransformer } from "./flatten.js";
|
|
4
|
+
import { RouteEntry, RouteManifest, buildRouteManifest, chunkName, fileToRoute } from "./routes.js";
|
|
5
|
+
import { compile, compileChecked } from "./transform.js";
|
|
6
|
+
import { hasErrors, typecheck } from "./typecheck.js";
|
|
7
|
+
import { Maturity, NotImplementedError, PackageInfo, notImplemented } from "@mindees/core";
|
|
8
|
+
|
|
9
|
+
//#region src/index.d.ts
|
|
10
|
+
/** The npm package name. */
|
|
11
|
+
declare const name = "@mindees/compiler";
|
|
12
|
+
/** The package version. All `@mindees/*` packages share one locked version line. */
|
|
13
|
+
declare const VERSION = "0.1.0";
|
|
14
|
+
/**
|
|
15
|
+
* Current maturity. The build-time optimizer — type-check gate, TSX→createElement
|
|
16
|
+
* transform, tree-flattening, per-route manifest, plugin API — is implemented
|
|
17
|
+
* and tested on the TypeScript Compiler API. TS→native AOT is a research track
|
|
18
|
+
* (throws `NotImplementedError`); the working path is TS → optimized JS.
|
|
19
|
+
*/
|
|
20
|
+
declare const maturity: Maturity;
|
|
21
|
+
/**
|
|
22
|
+
* Static identity + maturity metadata for this package. Frozen so the
|
|
23
|
+
* self-reported identity tooling introspects cannot be mutated at runtime,
|
|
24
|
+
* matching the `readonly` fields of {@link PackageInfo}.
|
|
25
|
+
*/
|
|
26
|
+
declare const info: PackageInfo;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { type CompileOptions, type CompileResult, type CompileStats, type Diagnostic, type DiagnosticSeverity, type Maturity, type MdcPlugin, type NativeTarget, NotImplementedError, type PackageInfo, type RouteEntry, type RouteManifest, STATIC_MARKER, type SourcePosition, VERSION, buildRouteManifest, chunkName, compile, compileChecked, compileToNative, createFlattenTransformer, fileToRoute, hasErrors, info, maturity, name, notImplemented, typecheck };
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;AAkCA;AAAA,cAHa,IAAA;;cAGA,OAAA;AAAO;AAQpB;;;;AAAgD;AAR5B,cAQP,QAAA,EAAU,QAAyB;;;;AAOoC;;cAAvE,IAAA,EAAM,WAAiE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { compileToNative } from "./aot.js";
|
|
2
|
+
import { STATIC_MARKER, createFlattenTransformer } from "./flatten.js";
|
|
3
|
+
import { buildRouteManifest, chunkName, fileToRoute } from "./routes.js";
|
|
4
|
+
import { hasErrors, typecheck } from "./typecheck.js";
|
|
5
|
+
import { compile, compileChecked } from "./transform.js";
|
|
6
|
+
import { NotImplementedError, notImplemented } from "@mindees/core";
|
|
7
|
+
//#region src/index.ts
|
|
8
|
+
/** The npm package name. */
|
|
9
|
+
const name = "@mindees/compiler";
|
|
10
|
+
/** The package version. All `@mindees/*` packages share one locked version line. */
|
|
11
|
+
const VERSION = "0.1.0";
|
|
12
|
+
/**
|
|
13
|
+
* Current maturity. The build-time optimizer — type-check gate, TSX→createElement
|
|
14
|
+
* transform, tree-flattening, per-route manifest, plugin API — is implemented
|
|
15
|
+
* and tested on the TypeScript Compiler API. TS→native AOT is a research track
|
|
16
|
+
* (throws `NotImplementedError`); the working path is TS → optimized JS.
|
|
17
|
+
*/
|
|
18
|
+
const maturity = "experimental";
|
|
19
|
+
/**
|
|
20
|
+
* Static identity + maturity metadata for this package. Frozen so the
|
|
21
|
+
* self-reported identity tooling introspects cannot be mutated at runtime,
|
|
22
|
+
* matching the `readonly` fields of {@link PackageInfo}.
|
|
23
|
+
*/
|
|
24
|
+
const info = Object.freeze({
|
|
25
|
+
name,
|
|
26
|
+
version: VERSION,
|
|
27
|
+
maturity
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { NotImplementedError, STATIC_MARKER, VERSION, buildRouteManifest, chunkName, compile, compileChecked, compileToNative, createFlattenTransformer, fileToRoute, hasErrors, info, maturity, name, notImplemented, typecheck };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from '@mindees/core'\nimport { NotImplementedError, notImplemented } from '@mindees/core'\n\n/** TS → native AOT (research track). */\nexport { compileToNative, type NativeTarget } from './aot'\n/** Tree-flattening optimizer pass. */\nexport { createFlattenTransformer, STATIC_MARKER } from './flatten'\n/** Per-route code-splitting manifest. */\nexport {\n buildRouteManifest,\n chunkName,\n fileToRoute,\n type RouteEntry,\n type RouteManifest,\n} from './routes'\n/** Compile pipeline (TSX → optimized JS). */\nexport { compile, compileChecked } from './transform'\n/** The type-check gate. */\nexport { hasErrors, typecheck } from './typecheck'\n/** Shared types. */\nexport type {\n CompileOptions,\n CompileResult,\n CompileStats,\n Diagnostic,\n DiagnosticSeverity,\n MdcPlugin,\n SourcePosition,\n} from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/compiler'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.1.0'\n\n/**\n * Current maturity. The build-time optimizer — type-check gate, TSX→createElement\n * transform, tree-flattening, per-route manifest, plugin API — is implemented\n * and tested on the TypeScript Compiler API. TS→native AOT is a research track\n * (throws `NotImplementedError`); the working path is TS → optimized JS.\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n\nexport type { Maturity, PackageInfo }\nexport { NotImplementedError, notImplemented }\n"],"mappings":";;;;;;;;AA+BA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;AAQvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region src/routes.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Per-route code-splitting: build a route manifest from a file tree.
|
|
4
|
+
*
|
|
5
|
+
* MDC maps a routes directory (file-based routing, à la the Quantum Router) to a
|
|
6
|
+
* **manifest** of chunks — one lazily-loadable entry per route — so the bundler
|
|
7
|
+
* can split per route and the runtime can load a route's code on demand. This
|
|
8
|
+
* pass is platform-agnostic: it produces the manifest; wiring it to a specific
|
|
9
|
+
* bundler/runtime is the CLI's job (Phase 5) and the router's job (Phase 6).
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/** A single route in the manifest. */
|
|
14
|
+
interface RouteEntry {
|
|
15
|
+
/** URL path, e.g. `/`, `/about`, `/blog/[slug]`. */
|
|
16
|
+
routePath: string;
|
|
17
|
+
/** Source file implementing the route, relative to the routes dir. */
|
|
18
|
+
file: string;
|
|
19
|
+
/** Stable chunk name for the split bundle, e.g. `route_blog_slug`. */
|
|
20
|
+
chunk: string;
|
|
21
|
+
/** Dynamic param names parsed from the path, e.g. `["slug"]`. */
|
|
22
|
+
params: string[];
|
|
23
|
+
/** True for a catch-all segment (`[...rest]`). */
|
|
24
|
+
catchAll: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** The route manifest: every route plus a not-found fallback if present. */
|
|
27
|
+
interface RouteManifest {
|
|
28
|
+
routes: RouteEntry[];
|
|
29
|
+
/** The `+not-found` route file, if any. */
|
|
30
|
+
notFound?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Convert a file path (relative, POSIX separators) to a route path + params.
|
|
34
|
+
*
|
|
35
|
+
* Conventions (subset of the Quantum Router, see ROADMAP Phase 6):
|
|
36
|
+
* - `index` → the directory's path (`index.tsx` → `/`).
|
|
37
|
+
* - `[param]` → a dynamic segment (`:param`); collected into `params`.
|
|
38
|
+
* - `[...rest]` → a catch-all segment; sets `catchAll`.
|
|
39
|
+
* - `(group)` → a layout group that does NOT affect the URL (removed).
|
|
40
|
+
*/
|
|
41
|
+
declare function fileToRoute(file: string): {
|
|
42
|
+
routePath: string;
|
|
43
|
+
params: string[];
|
|
44
|
+
catchAll: boolean;
|
|
45
|
+
};
|
|
46
|
+
/** Build a stable chunk name from a route file (filesystem-safe identifier). */
|
|
47
|
+
declare function chunkName(file: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Build a {@link RouteManifest} from a list of route files (relative paths,
|
|
50
|
+
* POSIX `/` separators). Routes are sorted for deterministic output.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* buildRouteManifest(['index.tsx', 'about.tsx', 'blog/[slug].tsx', '+not-found.tsx'])
|
|
54
|
+
*/
|
|
55
|
+
declare function buildRouteManifest(files: readonly string[]): RouteManifest;
|
|
56
|
+
//#endregion
|
|
57
|
+
export { RouteEntry, RouteManifest, buildRouteManifest, chunkName, fileToRoute };
|
|
58
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","names":[],"sources":["../src/routes.ts"],"mappings":";;AAaA;;;;;;;;;;AAUU;AAAA,UAVO,UAAA;EAca;EAZ5B,SAAA;EAakB;EAXlB,IAAA;EAWQ;EATR,KAAA;EAWQ;EATR,MAAA;EA4Bc;EA1Bd,QAAA;AAAA;;UAIe,aAAA;EACf,MAAA,EAAQ,UAAU;EAuBlB;EArBA,QAAA;AAAA;AAsBQ;AA4CV;;;;AAAsC;AAmBtC;;;AA/DU,iBAHM,WAAA,CAAY,IAAA;EAC1B,SAAA;EACA,MAAA;EACA,QAAA;AAAA;;iBA4Cc,SAAA,CAAU,IAAY;;;;;;;;iBAmBtB,kBAAA,CAAmB,KAAA,sBAA2B,aAAa"}
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//#region src/routes.ts
|
|
2
|
+
const ROUTE_FILE = /\.(tsx|ts|jsx|js)$/;
|
|
3
|
+
/** Strip the file extension. */
|
|
4
|
+
function stripExt(file) {
|
|
5
|
+
return file.replace(ROUTE_FILE, "");
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Convert a file path (relative, POSIX separators) to a route path + params.
|
|
9
|
+
*
|
|
10
|
+
* Conventions (subset of the Quantum Router, see ROADMAP Phase 6):
|
|
11
|
+
* - `index` → the directory's path (`index.tsx` → `/`).
|
|
12
|
+
* - `[param]` → a dynamic segment (`:param`); collected into `params`.
|
|
13
|
+
* - `[...rest]` → a catch-all segment; sets `catchAll`.
|
|
14
|
+
* - `(group)` → a layout group that does NOT affect the URL (removed).
|
|
15
|
+
*/
|
|
16
|
+
function fileToRoute(file) {
|
|
17
|
+
const params = [];
|
|
18
|
+
let catchAll = false;
|
|
19
|
+
const segments = stripExt(file).split("/").filter((s) => s.length > 0).filter((s) => !(s.startsWith("(") && s.endsWith(")"))).map((s) => {
|
|
20
|
+
if (s === "index") return "";
|
|
21
|
+
const restMatch = s.match(/^\[\.\.\.(.+)\]$/);
|
|
22
|
+
if (restMatch?.[1]) {
|
|
23
|
+
catchAll = true;
|
|
24
|
+
params.push(restMatch[1]);
|
|
25
|
+
return `:${restMatch[1]}*`;
|
|
26
|
+
}
|
|
27
|
+
const paramMatch = s.match(/^\[(.+)\]$/);
|
|
28
|
+
if (paramMatch?.[1]) {
|
|
29
|
+
params.push(paramMatch[1]);
|
|
30
|
+
return `:${paramMatch[1]}`;
|
|
31
|
+
}
|
|
32
|
+
return s;
|
|
33
|
+
}).filter((s) => s.length > 0);
|
|
34
|
+
const catchAllIndex = segments.findIndex((s) => s.endsWith("*"));
|
|
35
|
+
if (catchAllIndex !== -1 && catchAllIndex !== segments.length - 1) throw new Error(`Invalid route file "${file}": a catch-all segment ([...x]) must be the last segment.`);
|
|
36
|
+
return {
|
|
37
|
+
routePath: segments.length === 0 ? "/" : `/${segments.join("/")}`,
|
|
38
|
+
params,
|
|
39
|
+
catchAll
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Build a stable chunk name from a route file (filesystem-safe identifier). */
|
|
43
|
+
function chunkName(file) {
|
|
44
|
+
return `route_${stripExt(file).replace(/[/\\]/g, "_").replace(/\[\.\.\.(.+?)\]/g, "rest_$1").replace(/\[(.+?)\]/g, "$1").replace(/[()]/g, "").replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "index"}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build a {@link RouteManifest} from a list of route files (relative paths,
|
|
48
|
+
* POSIX `/` separators). Routes are sorted for deterministic output.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* buildRouteManifest(['index.tsx', 'about.tsx', 'blog/[slug].tsx', '+not-found.tsx'])
|
|
52
|
+
*/
|
|
53
|
+
function buildRouteManifest(files) {
|
|
54
|
+
const routes = [];
|
|
55
|
+
let notFound;
|
|
56
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
57
|
+
const byChunk = /* @__PURE__ */ new Map();
|
|
58
|
+
for (const file of [...files].sort()) {
|
|
59
|
+
if (!ROUTE_FILE.test(file)) continue;
|
|
60
|
+
const baseName = stripExt(file).split("/").pop() ?? "";
|
|
61
|
+
if (baseName === "+not-found") {
|
|
62
|
+
notFound = file;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (baseName.startsWith("_") || baseName.startsWith("+")) continue;
|
|
66
|
+
const { routePath, params, catchAll } = fileToRoute(file);
|
|
67
|
+
const prior = byPath.get(routePath);
|
|
68
|
+
if (prior !== void 0) throw new Error(`Duplicate route path "${routePath}": both "${prior}" and "${file}" map to it.`);
|
|
69
|
+
byPath.set(routePath, file);
|
|
70
|
+
const chunk = chunkName(file);
|
|
71
|
+
const priorChunk = byChunk.get(chunk);
|
|
72
|
+
if (priorChunk !== void 0) throw new Error(`Duplicate chunk name "${chunk}": both "${priorChunk}" and "${file}" produce it; rename one route file so their split bundles do not collide.`);
|
|
73
|
+
byChunk.set(chunk, file);
|
|
74
|
+
routes.push({
|
|
75
|
+
routePath,
|
|
76
|
+
file,
|
|
77
|
+
chunk,
|
|
78
|
+
params,
|
|
79
|
+
catchAll
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const manifest = { routes };
|
|
83
|
+
if (notFound !== void 0) manifest.notFound = notFound;
|
|
84
|
+
return manifest;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { buildRouteManifest, chunkName, fileToRoute };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","names":[],"sources":["../src/routes.ts"],"sourcesContent":["/**\n * Per-route code-splitting: build a route manifest from a file tree.\n *\n * MDC maps a routes directory (file-based routing, à la the Quantum Router) to a\n * **manifest** of chunks — one lazily-loadable entry per route — so the bundler\n * can split per route and the runtime can load a route's code on demand. This\n * pass is platform-agnostic: it produces the manifest; wiring it to a specific\n * bundler/runtime is the CLI's job (Phase 5) and the router's job (Phase 6).\n *\n * @module\n */\n\n/** A single route in the manifest. */\nexport interface RouteEntry {\n /** URL path, e.g. `/`, `/about`, `/blog/[slug]`. */\n routePath: string\n /** Source file implementing the route, relative to the routes dir. */\n file: string\n /** Stable chunk name for the split bundle, e.g. `route_blog_slug`. */\n chunk: string\n /** Dynamic param names parsed from the path, e.g. `[\"slug\"]`. */\n params: string[]\n /** True for a catch-all segment (`[...rest]`). */\n catchAll: boolean\n}\n\n/** The route manifest: every route plus a not-found fallback if present. */\nexport interface RouteManifest {\n routes: RouteEntry[]\n /** The `+not-found` route file, if any. */\n notFound?: string\n}\n\nconst ROUTE_FILE = /\\.(tsx|ts|jsx|js)$/\n\n/** Strip the file extension. */\nfunction stripExt(file: string): string {\n return file.replace(ROUTE_FILE, '')\n}\n\n/**\n * Convert a file path (relative, POSIX separators) to a route path + params.\n *\n * Conventions (subset of the Quantum Router, see ROADMAP Phase 6):\n * - `index` → the directory's path (`index.tsx` → `/`).\n * - `[param]` → a dynamic segment (`:param`); collected into `params`.\n * - `[...rest]` → a catch-all segment; sets `catchAll`.\n * - `(group)` → a layout group that does NOT affect the URL (removed).\n */\nexport function fileToRoute(file: string): {\n routePath: string\n params: string[]\n catchAll: boolean\n} {\n const params: string[] = []\n let catchAll = false\n\n const segments = stripExt(file)\n .split('/')\n .filter((s) => s.length > 0)\n // Drop layout groups like `(marketing)`.\n .filter((s) => !(s.startsWith('(') && s.endsWith(')')))\n .map((s) => {\n // index → empty (resolves to parent path)\n if (s === 'index') return ''\n // [...rest] → catch-all\n const restMatch = s.match(/^\\[\\.\\.\\.(.+)\\]$/)\n if (restMatch?.[1]) {\n catchAll = true\n params.push(restMatch[1])\n return `:${restMatch[1]}*`\n }\n // [param] → dynamic\n const paramMatch = s.match(/^\\[(.+)\\]$/)\n if (paramMatch?.[1]) {\n params.push(paramMatch[1])\n return `:${paramMatch[1]}`\n }\n return s\n })\n .filter((s) => s.length > 0)\n\n // A catch-all must terminate the path; `docs/[...rest]/edit` is structurally\n // invalid and would otherwise emit a nonsensical `/docs/:rest*/edit`.\n const catchAllIndex = segments.findIndex((s) => s.endsWith('*'))\n if (catchAllIndex !== -1 && catchAllIndex !== segments.length - 1) {\n throw new Error(\n `Invalid route file \"${file}\": a catch-all segment ([...x]) must be the last segment.`,\n )\n }\n\n const routePath = segments.length === 0 ? '/' : `/${segments.join('/')}`\n return { routePath, params, catchAll }\n}\n\n/** Build a stable chunk name from a route file (filesystem-safe identifier). */\nexport function chunkName(file: string): string {\n const base = stripExt(file)\n .replace(/[/\\\\]/g, '_')\n .replace(/\\[\\.\\.\\.(.+?)\\]/g, 'rest_$1')\n .replace(/\\[(.+?)\\]/g, '$1')\n .replace(/[()]/g, '')\n .replace(/[^a-zA-Z0-9_]/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '')\n return `route_${base || 'index'}`\n}\n\n/**\n * Build a {@link RouteManifest} from a list of route files (relative paths,\n * POSIX `/` separators). Routes are sorted for deterministic output.\n *\n * @example\n * buildRouteManifest(['index.tsx', 'about.tsx', 'blog/[slug].tsx', '+not-found.tsx'])\n */\nexport function buildRouteManifest(files: readonly string[]): RouteManifest {\n const routes: RouteEntry[] = []\n let notFound: string | undefined\n // Detect collisions (e.g. `index.tsx` and `(app)/index.tsx` both map to `/`).\n const byPath = new Map<string, string>()\n // Distinct routes must also get distinct chunk names — `chunkName` is lossy\n // (e.g. `blog/[slug]` and `blog/slug` both strip to `route_blog_slug`), so a\n // collision here would silently make two routes share one split bundle.\n const byChunk = new Map<string, string>()\n\n for (const file of [...files].sort()) {\n if (!ROUTE_FILE.test(file)) continue\n const baseName = stripExt(file).split('/').pop() ?? ''\n if (baseName === '+not-found') {\n notFound = file\n continue\n }\n // Skip layout files (`_layout`) and other reserved `_`/`+` prefixed files\n // from the navigable route table (they're handled separately by the router).\n if (baseName.startsWith('_') || baseName.startsWith('+')) continue\n\n const { routePath, params, catchAll } = fileToRoute(file)\n const prior = byPath.get(routePath)\n if (prior !== undefined) {\n throw new Error(\n `Duplicate route path \"${routePath}\": both \"${prior}\" and \"${file}\" map to it.`,\n )\n }\n byPath.set(routePath, file)\n const chunk = chunkName(file)\n const priorChunk = byChunk.get(chunk)\n if (priorChunk !== undefined) {\n throw new Error(\n `Duplicate chunk name \"${chunk}\": both \"${priorChunk}\" and \"${file}\" produce it; ` +\n 'rename one route file so their split bundles do not collide.',\n )\n }\n byChunk.set(chunk, file)\n routes.push({ routePath, file, chunk, params, catchAll })\n }\n\n const manifest: RouteManifest = { routes }\n if (notFound !== undefined) manifest.notFound = notFound\n return manifest\n}\n"],"mappings":";AAiCA,MAAM,aAAa;;AAGnB,SAAS,SAAS,MAAsB;CACtC,OAAO,KAAK,QAAQ,YAAY,EAAE;AACpC;;;;;;;;;;AAWA,SAAgB,YAAY,MAI1B;CACA,MAAM,SAAmB,CAAC;CAC1B,IAAI,WAAW;CAEf,MAAM,WAAW,SAAS,IAAI,EAC3B,MAAM,GAAG,EACT,QAAQ,MAAM,EAAE,SAAS,CAAC,EAE1B,QAAQ,MAAM,EAAE,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,EAAE,EACrD,KAAK,MAAM;EAEV,IAAI,MAAM,SAAS,OAAO;EAE1B,MAAM,YAAY,EAAE,MAAM,kBAAkB;EAC5C,IAAI,YAAY,IAAI;GAClB,WAAW;GACX,OAAO,KAAK,UAAU,EAAE;GACxB,OAAO,IAAI,UAAU,GAAG;EAC1B;EAEA,MAAM,aAAa,EAAE,MAAM,YAAY;EACvC,IAAI,aAAa,IAAI;GACnB,OAAO,KAAK,WAAW,EAAE;GACzB,OAAO,IAAI,WAAW;EACxB;EACA,OAAO;CACT,CAAC,EACA,QAAQ,MAAM,EAAE,SAAS,CAAC;CAI7B,MAAM,gBAAgB,SAAS,WAAW,MAAM,EAAE,SAAS,GAAG,CAAC;CAC/D,IAAI,kBAAkB,MAAM,kBAAkB,SAAS,SAAS,GAC9D,MAAM,IAAI,MACR,uBAAuB,KAAK,0DAC9B;CAIF,OAAO;EAAE,WADS,SAAS,WAAW,IAAI,MAAM,IAAI,SAAS,KAAK,GAAG;EACjD;EAAQ;CAAS;AACvC;;AAGA,SAAgB,UAAU,MAAsB;CAS9C,OAAO,SARM,SAAS,IAAI,EACvB,QAAQ,UAAU,GAAG,EACrB,QAAQ,oBAAoB,SAAS,EACrC,QAAQ,cAAc,IAAI,EAC1B,QAAQ,SAAS,EAAE,EACnB,QAAQ,kBAAkB,GAAG,EAC7B,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EACF,KAAK;AAC1B;;;;;;;;AASA,SAAgB,mBAAmB,OAAyC;CAC1E,MAAM,SAAuB,CAAC;CAC9B,IAAI;CAEJ,MAAM,yBAAS,IAAI,IAAoB;CAIvC,MAAM,0BAAU,IAAI,IAAoB;CAExC,KAAK,MAAM,QAAQ,CAAC,GAAG,KAAK,EAAE,KAAK,GAAG;EACpC,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG;EAC5B,MAAM,WAAW,SAAS,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI,KAAK;EACpD,IAAI,aAAa,cAAc;GAC7B,WAAW;GACX;EACF;EAGA,IAAI,SAAS,WAAW,GAAG,KAAK,SAAS,WAAW,GAAG,GAAG;EAE1D,MAAM,EAAE,WAAW,QAAQ,aAAa,YAAY,IAAI;EACxD,MAAM,QAAQ,OAAO,IAAI,SAAS;EAClC,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MACR,yBAAyB,UAAU,WAAW,MAAM,SAAS,KAAK,aACpE;EAEF,OAAO,IAAI,WAAW,IAAI;EAC1B,MAAM,QAAQ,UAAU,IAAI;EAC5B,MAAM,aAAa,QAAQ,IAAI,KAAK;EACpC,IAAI,eAAe,KAAA,GACjB,MAAM,IAAI,MACR,yBAAyB,MAAM,WAAW,WAAW,SAAS,KAAK,2EAErE;EAEF,QAAQ,IAAI,OAAO,IAAI;EACvB,OAAO,KAAK;GAAE;GAAW;GAAM;GAAO;GAAQ;EAAS,CAAC;CAC1D;CAEA,MAAM,WAA0B,EAAE,OAAO;CACzC,IAAI,aAAa,KAAA,GAAW,SAAS,WAAW;CAChD,OAAO;AACT"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CompileOptions, CompileResult } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/transform.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Compile a single TSX/TS module to JavaScript.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline: JSX desugar → tree-flatten (optional) → user plugins → emit.
|
|
8
|
+
* Returns emitted code, an optional source map, any (transpile-level)
|
|
9
|
+
* diagnostics, and optimizer stats. Use {@link compileChecked} to gate on the
|
|
10
|
+
* full type checker.
|
|
11
|
+
*/
|
|
12
|
+
declare function compile(source: string, options?: CompileOptions): CompileResult;
|
|
13
|
+
/**
|
|
14
|
+
* Type-check then compile. If the gate finds any `error` diagnostic, returns it
|
|
15
|
+
* WITHOUT emitting code (`code: ''`) — the build must not ship type errors.
|
|
16
|
+
*/
|
|
17
|
+
declare function compileChecked(source: string, options?: CompileOptions): CompileResult;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { compile, compileChecked };
|
|
20
|
+
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","names":[],"sources":["../src/transform.ts"],"mappings":";;;AAqCoF;AAoDpF;;;;;;;AApDoF,iBAApE,OAAA,CAAQ,MAAA,UAAgB,OAAA,GAAS,cAAA,GAAsB,aAAa;;AAoDO;;;iBAA3E,cAAA,CAAe,MAAA,UAAgB,OAAA,GAAS,cAAA,GAAsB,aAAa"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createFlattenTransformer } from "./flatten.js";
|
|
2
|
+
import { hasErrors, typecheck } from "./typecheck.js";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
//#region src/transform.ts
|
|
5
|
+
/**
|
|
6
|
+
* The MDC transform/compile pipeline.
|
|
7
|
+
*
|
|
8
|
+
* `compile()` lowers TSX → `createElement(...)` (matching `@mindees/core`'s
|
|
9
|
+
* factory), runs the built-in optimizer passes (tree-flattening) plus any user
|
|
10
|
+
* plugins, and emits JavaScript + a source map. It does **not** type-check
|
|
11
|
+
* (that's {@link typecheck}); `compileChecked()` runs the gate first and refuses
|
|
12
|
+
* to emit on `error` diagnostics.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/** Compiler options for emit (JSX classic → createElement/Fragment). */
|
|
17
|
+
function emitOptions(sourceMap) {
|
|
18
|
+
return {
|
|
19
|
+
jsx: ts.JsxEmit.React,
|
|
20
|
+
jsxFactory: "createElement",
|
|
21
|
+
jsxFragmentFactory: "Fragment",
|
|
22
|
+
target: ts.ScriptTarget.ES2023,
|
|
23
|
+
module: ts.ModuleKind.ESNext,
|
|
24
|
+
sourceMap
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Compile a single TSX/TS module to JavaScript.
|
|
29
|
+
*
|
|
30
|
+
* Pipeline: JSX desugar → tree-flatten (optional) → user plugins → emit.
|
|
31
|
+
* Returns emitted code, an optional source map, any (transpile-level)
|
|
32
|
+
* diagnostics, and optimizer stats. Use {@link compileChecked} to gate on the
|
|
33
|
+
* full type checker.
|
|
34
|
+
*/
|
|
35
|
+
function compile(source, options = {}) {
|
|
36
|
+
const { fileName = "module.tsx", sourceMap = true, flatten = true, plugins = [] } = options;
|
|
37
|
+
const after = [];
|
|
38
|
+
let stats = {
|
|
39
|
+
flattenedNodes: 0,
|
|
40
|
+
totalElements: 0
|
|
41
|
+
};
|
|
42
|
+
if (flatten) {
|
|
43
|
+
const flattener = createFlattenTransformer(ts);
|
|
44
|
+
after.push(flattener.factory);
|
|
45
|
+
stats = flattener.stats;
|
|
46
|
+
}
|
|
47
|
+
for (const plugin of plugins) after.push(plugin.transformer(ts));
|
|
48
|
+
const output = ts.transpileModule(source, {
|
|
49
|
+
compilerOptions: emitOptions(sourceMap),
|
|
50
|
+
fileName,
|
|
51
|
+
reportDiagnostics: true,
|
|
52
|
+
transformers: { after }
|
|
53
|
+
});
|
|
54
|
+
const diagnostics = (output.diagnostics ?? []).map((d) => {
|
|
55
|
+
const message = ts.flattenDiagnosticMessageText(d.messageText, "\n");
|
|
56
|
+
return {
|
|
57
|
+
severity: d.category === ts.DiagnosticCategory.Error ? "error" : "warning",
|
|
58
|
+
code: `TS${d.code}`,
|
|
59
|
+
message
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
const result = {
|
|
63
|
+
code: output.outputText,
|
|
64
|
+
diagnostics,
|
|
65
|
+
stats
|
|
66
|
+
};
|
|
67
|
+
if (sourceMap && output.sourceMapText) result.map = output.sourceMapText;
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Type-check then compile. If the gate finds any `error` diagnostic, returns it
|
|
72
|
+
* WITHOUT emitting code (`code: ''`) — the build must not ship type errors.
|
|
73
|
+
*/
|
|
74
|
+
function compileChecked(source, options = {}) {
|
|
75
|
+
const diagnostics = typecheck(source, options.fileName ?? "module.tsx");
|
|
76
|
+
if (hasErrors(diagnostics)) return {
|
|
77
|
+
code: "",
|
|
78
|
+
diagnostics,
|
|
79
|
+
stats: {
|
|
80
|
+
flattenedNodes: 0,
|
|
81
|
+
totalElements: 0
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const compiled = compile(source, options);
|
|
85
|
+
return {
|
|
86
|
+
...compiled,
|
|
87
|
+
diagnostics: [...diagnostics, ...compiled.diagnostics]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { compile, compileChecked };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.js","names":[],"sources":["../src/transform.ts"],"sourcesContent":["/**\n * The MDC transform/compile pipeline.\n *\n * `compile()` lowers TSX → `createElement(...)` (matching `@mindees/core`'s\n * factory), runs the built-in optimizer passes (tree-flattening) plus any user\n * plugins, and emits JavaScript + a source map. It does **not** type-check\n * (that's {@link typecheck}); `compileChecked()` runs the gate first and refuses\n * to emit on `error` diagnostics.\n *\n * @module\n */\n\nimport ts from 'typescript'\nimport { createFlattenTransformer } from './flatten'\nimport { hasErrors, typecheck } from './typecheck'\nimport type { CompileOptions, CompileResult, CompileStats } from './types'\n\n/** Compiler options for emit (JSX classic → createElement/Fragment). */\nfunction emitOptions(sourceMap: boolean): ts.CompilerOptions {\n return {\n jsx: ts.JsxEmit.React,\n jsxFactory: 'createElement',\n jsxFragmentFactory: 'Fragment',\n target: ts.ScriptTarget.ES2023,\n module: ts.ModuleKind.ESNext,\n sourceMap,\n }\n}\n\n/**\n * Compile a single TSX/TS module to JavaScript.\n *\n * Pipeline: JSX desugar → tree-flatten (optional) → user plugins → emit.\n * Returns emitted code, an optional source map, any (transpile-level)\n * diagnostics, and optimizer stats. Use {@link compileChecked} to gate on the\n * full type checker.\n */\nexport function compile(source: string, options: CompileOptions = {}): CompileResult {\n const { fileName = 'module.tsx', sourceMap = true, flatten = true, plugins = [] } = options\n\n // IMPORTANT: our optimizer + plugins operate on the desugared\n // `createElement(...)` call form, but `transpileModule` runs `before`\n // transformers on the *pre-desugar* JSX AST. JSX is lowered during the\n // `after` phase, so flatten/plugins must run there to see the calls.\n const after: ts.TransformerFactory<ts.SourceFile>[] = []\n let stats: CompileStats = { flattenedNodes: 0, totalElements: 0 }\n\n if (flatten) {\n const flattener = createFlattenTransformer(ts)\n after.push(flattener.factory)\n stats = flattener.stats // live object, updated during emit\n }\n\n for (const plugin of plugins) {\n after.push(plugin.transformer(ts) as ts.TransformerFactory<ts.SourceFile>)\n }\n\n const output = ts.transpileModule(source, {\n compilerOptions: emitOptions(sourceMap),\n fileName,\n reportDiagnostics: true,\n transformers: { after },\n })\n\n // transpileModule only surfaces a few syntactic diagnostics; semantic ones\n // come from the type-check gate. Map each to our structured form.\n const diagnostics = (output.diagnostics ?? []).map((d) => {\n const message = ts.flattenDiagnosticMessageText(d.messageText, '\\n')\n return {\n severity:\n d.category === ts.DiagnosticCategory.Error ? ('error' as const) : ('warning' as const),\n code: `TS${d.code}`,\n message,\n }\n })\n\n const result: CompileResult = {\n code: output.outputText,\n diagnostics,\n stats,\n }\n if (sourceMap && output.sourceMapText) result.map = output.sourceMapText\n return result\n}\n\n/**\n * Type-check then compile. If the gate finds any `error` diagnostic, returns it\n * WITHOUT emitting code (`code: ''`) — the build must not ship type errors.\n */\nexport function compileChecked(source: string, options: CompileOptions = {}): CompileResult {\n const fileName = options.fileName ?? 'module.tsx'\n const diagnostics = typecheck(source, fileName)\n if (hasErrors(diagnostics)) {\n return { code: '', diagnostics, stats: { flattenedNodes: 0, totalElements: 0 } }\n }\n const compiled = compile(source, options)\n // Surface any type-check warnings alongside the compile result.\n return { ...compiled, diagnostics: [...diagnostics, ...compiled.diagnostics] }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,SAAS,YAAY,WAAwC;CAC3D,OAAO;EACL,KAAK,GAAG,QAAQ;EAChB,YAAY;EACZ,oBAAoB;EACpB,QAAQ,GAAG,aAAa;EACxB,QAAQ,GAAG,WAAW;EACtB;CACF;AACF;;;;;;;;;AAUA,SAAgB,QAAQ,QAAgB,UAA0B,CAAC,GAAkB;CACnF,MAAM,EAAE,WAAW,cAAc,YAAY,MAAM,UAAU,MAAM,UAAU,CAAC,MAAM;CAMpF,MAAM,QAAgD,CAAC;CACvD,IAAI,QAAsB;EAAE,gBAAgB;EAAG,eAAe;CAAE;CAEhE,IAAI,SAAS;EACX,MAAM,YAAY,yBAAyB,EAAE;EAC7C,MAAM,KAAK,UAAU,OAAO;EAC5B,QAAQ,UAAU;CACpB;CAEA,KAAK,MAAM,UAAU,SACnB,MAAM,KAAK,OAAO,YAAY,EAAE,CAAyC;CAG3E,MAAM,SAAS,GAAG,gBAAgB,QAAQ;EACxC,iBAAiB,YAAY,SAAS;EACtC;EACA,mBAAmB;EACnB,cAAc,EAAE,MAAM;CACxB,CAAC;CAID,MAAM,eAAe,OAAO,eAAe,CAAC,GAAG,KAAK,MAAM;EACxD,MAAM,UAAU,GAAG,6BAA6B,EAAE,aAAa,IAAI;EACnE,OAAO;GACL,UACE,EAAE,aAAa,GAAG,mBAAmB,QAAS,UAAqB;GACrE,MAAM,KAAK,EAAE;GACb;EACF;CACF,CAAC;CAED,MAAM,SAAwB;EAC5B,MAAM,OAAO;EACb;EACA;CACF;CACA,IAAI,aAAa,OAAO,eAAe,OAAO,MAAM,OAAO;CAC3D,OAAO;AACT;;;;;AAMA,SAAgB,eAAe,QAAgB,UAA0B,CAAC,GAAkB;CAE1F,MAAM,cAAc,UAAU,QADb,QAAQ,YAAY,YACS;CAC9C,IAAI,UAAU,WAAW,GACvB,OAAO;EAAE,MAAM;EAAI;EAAa,OAAO;GAAE,gBAAgB;GAAG,eAAe;EAAE;CAAE;CAEjF,MAAM,WAAW,QAAQ,QAAQ,OAAO;CAExC,OAAO;EAAE,GAAG;EAAU,aAAa,CAAC,GAAG,aAAa,GAAG,SAAS,WAAW;CAAE;AAC/E"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Diagnostic } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/typecheck.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Type-check a single module's source and return its diagnostics.
|
|
6
|
+
*
|
|
7
|
+
* @param source - The module source (`.tsx` is assumed unless `fileName` says otherwise).
|
|
8
|
+
* @param fileName - Logical file name. Default `"module.tsx"`.
|
|
9
|
+
* @returns All `error`/`warning` diagnostics (semantic + syntactic). Empty = clean.
|
|
10
|
+
*/
|
|
11
|
+
declare function typecheck(source: string, fileName?: string): Diagnostic[];
|
|
12
|
+
/** True if any diagnostic is an `error` (i.e. the build must fail). */
|
|
13
|
+
declare function hasErrors(diagnostics: readonly Diagnostic[]): boolean;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { hasErrors, typecheck };
|
|
16
|
+
//# sourceMappingURL=typecheck.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typecheck.d.ts","names":[],"sources":["../src/typecheck.ts"],"mappings":";;;;;;AAiK4D;;;;iBAvD5C,SAAA,CAAU,MAAA,UAAgB,QAAA,YAA0B,UAAU;;iBAuD9D,SAAA,CAAU,WAAkC,WAAZ,UAAU"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
//#region src/typecheck.ts
|
|
3
|
+
/**
|
|
4
|
+
* The MDC type-check gate.
|
|
5
|
+
*
|
|
6
|
+
* Runs the TypeScript checker over a single in-memory module and reports
|
|
7
|
+
* structured {@link Diagnostic}s. This is the compiler's correctness gate: a
|
|
8
|
+
* build with any `error`-severity diagnostic must not ship.
|
|
9
|
+
*
|
|
10
|
+
* It uses an in-memory `CompilerHost` so no files touch disk; lib `.d.ts` files
|
|
11
|
+
* are read from the real `typescript` install so global types resolve.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
/** Synthetic ambient module that gives the single-module gate a JSX environment. */
|
|
16
|
+
const JSX_LIB_FILE = "__mindees_jsx__.d.ts";
|
|
17
|
+
const JSX_LIB_SOURCE = `declare namespace JSX {
|
|
18
|
+
interface IntrinsicElements { [name: string]: Record<string, unknown> }
|
|
19
|
+
type Element = unknown
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
/**
|
|
23
|
+
* Module-resolution diagnostic codes the single-module gate cannot meaningfully
|
|
24
|
+
* judge (it type-checks ONE module with `noResolve`, so every import is
|
|
25
|
+
* "unresolved"). Cross-module resolution is the project-graph type-check's job.
|
|
26
|
+
* - TS2307: Cannot find module '…'.
|
|
27
|
+
* - TS2792: Cannot find module … (did you mean to set 'moduleResolution'?).
|
|
28
|
+
*/
|
|
29
|
+
const UNRESOLVED_IMPORT_CODES = new Set([2307, 2792]);
|
|
30
|
+
/** Default compiler options for the gate: strict, modern, JSX-aware. */
|
|
31
|
+
function defaultOptions() {
|
|
32
|
+
return {
|
|
33
|
+
strict: true,
|
|
34
|
+
noUncheckedIndexedAccess: true,
|
|
35
|
+
exactOptionalPropertyTypes: true,
|
|
36
|
+
target: ts.ScriptTarget.ES2023,
|
|
37
|
+
module: ts.ModuleKind.ESNext,
|
|
38
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
39
|
+
jsx: ts.JsxEmit.React,
|
|
40
|
+
jsxFactory: "createElement",
|
|
41
|
+
jsxFragmentFactory: "Fragment",
|
|
42
|
+
noEmit: true,
|
|
43
|
+
skipLibCheck: true,
|
|
44
|
+
noResolve: true,
|
|
45
|
+
types: []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function toSeverity(category) {
|
|
49
|
+
if (category === ts.DiagnosticCategory.Error) return "error";
|
|
50
|
+
if (category === ts.DiagnosticCategory.Warning) return "warning";
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Map a file extension to its `ScriptKind`. `ts.createSourceFile` does NOT infer
|
|
55
|
+
* this — omitting it would mis-parse files — so we map it explicitly (not via
|
|
56
|
+
* any TypeScript-internal helper). A `.ts` file thus rejects JSX, while `.tsx`
|
|
57
|
+
* accepts it.
|
|
58
|
+
*/
|
|
59
|
+
function scriptKindForFile(fileName) {
|
|
60
|
+
const lower = fileName.toLowerCase();
|
|
61
|
+
if (lower.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
62
|
+
if (lower.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
63
|
+
if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs")) return ts.ScriptKind.JS;
|
|
64
|
+
if (lower.endsWith(".json")) return ts.ScriptKind.JSON;
|
|
65
|
+
return ts.ScriptKind.TS;
|
|
66
|
+
}
|
|
67
|
+
/** Convert a TypeScript diagnostic to our structured form. */
|
|
68
|
+
function convert(diag) {
|
|
69
|
+
if (UNRESOLVED_IMPORT_CODES.has(diag.code)) return null;
|
|
70
|
+
const severity = toSeverity(diag.category);
|
|
71
|
+
if (!severity) return null;
|
|
72
|
+
const message = ts.flattenDiagnosticMessageText(diag.messageText, "\n");
|
|
73
|
+
const out = {
|
|
74
|
+
severity,
|
|
75
|
+
code: `TS${diag.code}`,
|
|
76
|
+
message
|
|
77
|
+
};
|
|
78
|
+
if (diag.file) {
|
|
79
|
+
out.file = diag.file.fileName;
|
|
80
|
+
if (diag.start !== void 0) {
|
|
81
|
+
const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start);
|
|
82
|
+
out.position = {
|
|
83
|
+
line: line + 1,
|
|
84
|
+
column: character + 1
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Type-check a single module's source and return its diagnostics.
|
|
92
|
+
*
|
|
93
|
+
* @param source - The module source (`.tsx` is assumed unless `fileName` says otherwise).
|
|
94
|
+
* @param fileName - Logical file name. Default `"module.tsx"`.
|
|
95
|
+
* @returns All `error`/`warning` diagnostics (semantic + syntactic). Empty = clean.
|
|
96
|
+
*/
|
|
97
|
+
function typecheck(source, fileName = "module.tsx") {
|
|
98
|
+
const options = defaultOptions();
|
|
99
|
+
const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.ES2023, true, scriptKindForFile(fileName));
|
|
100
|
+
const jsxLib = ts.createSourceFile(JSX_LIB_FILE, JSX_LIB_SOURCE, ts.ScriptTarget.ES2023, true, ts.ScriptKind.TS);
|
|
101
|
+
const defaultHost = ts.createCompilerHost(options);
|
|
102
|
+
const host = {
|
|
103
|
+
...defaultHost,
|
|
104
|
+
getSourceFile: (name, languageVersion, onError, shouldCreate) => {
|
|
105
|
+
if (name === fileName) return sourceFile;
|
|
106
|
+
if (name === JSX_LIB_FILE) return jsxLib;
|
|
107
|
+
return defaultHost.getSourceFile(name, languageVersion, onError, shouldCreate);
|
|
108
|
+
},
|
|
109
|
+
writeFile: () => {},
|
|
110
|
+
fileExists: (name) => name === fileName || name === JSX_LIB_FILE || defaultHost.fileExists(name),
|
|
111
|
+
readFile: (name) => name === fileName ? source : name === JSX_LIB_FILE ? JSX_LIB_SOURCE : defaultHost.readFile(name)
|
|
112
|
+
};
|
|
113
|
+
const program = ts.createProgram([JSX_LIB_FILE, fileName], options, host);
|
|
114
|
+
const diagnostics = [...program.getSyntacticDiagnostics(sourceFile), ...program.getSemanticDiagnostics(sourceFile)];
|
|
115
|
+
const out = [];
|
|
116
|
+
for (const d of diagnostics) {
|
|
117
|
+
const converted = convert(d);
|
|
118
|
+
if (converted) out.push(converted);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** True if any diagnostic is an `error` (i.e. the build must fail). */
|
|
123
|
+
function hasErrors(diagnostics) {
|
|
124
|
+
return diagnostics.some((d) => d.severity === "error");
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { hasErrors, typecheck };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=typecheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typecheck.js","names":[],"sources":["../src/typecheck.ts"],"sourcesContent":["/**\n * The MDC type-check gate.\n *\n * Runs the TypeScript checker over a single in-memory module and reports\n * structured {@link Diagnostic}s. This is the compiler's correctness gate: a\n * build with any `error`-severity diagnostic must not ship.\n *\n * It uses an in-memory `CompilerHost` so no files touch disk; lib `.d.ts` files\n * are read from the real `typescript` install so global types resolve.\n *\n * @module\n */\n\nimport ts from 'typescript'\nimport type { Diagnostic } from './types'\n\n/** Synthetic ambient module that gives the single-module gate a JSX environment. */\nconst JSX_LIB_FILE = '__mindees_jsx__.d.ts'\nconst JSX_LIB_SOURCE = `declare namespace JSX {\n interface IntrinsicElements { [name: string]: Record<string, unknown> }\n type Element = unknown\n}\n`\n\n/**\n * Module-resolution diagnostic codes the single-module gate cannot meaningfully\n * judge (it type-checks ONE module with `noResolve`, so every import is\n * \"unresolved\"). Cross-module resolution is the project-graph type-check's job.\n * - TS2307: Cannot find module '…'.\n * - TS2792: Cannot find module … (did you mean to set 'moduleResolution'?).\n */\nconst UNRESOLVED_IMPORT_CODES = new Set([2307, 2792])\n\n/** Default compiler options for the gate: strict, modern, JSX-aware. */\nfunction defaultOptions(): ts.CompilerOptions {\n return {\n strict: true,\n noUncheckedIndexedAccess: true,\n exactOptionalPropertyTypes: true,\n target: ts.ScriptTarget.ES2023,\n module: ts.ModuleKind.ESNext,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n jsx: ts.JsxEmit.React,\n jsxFactory: 'createElement',\n jsxFragmentFactory: 'Fragment',\n noEmit: true,\n skipLibCheck: true,\n // Type-check a single module in isolation; imports are not resolved here\n // (unresolved-import diagnostics are filtered — see UNRESOLVED_IMPORT_CODES).\n noResolve: true,\n types: [],\n }\n}\n\nfunction toSeverity(category: ts.DiagnosticCategory): 'error' | 'warning' | null {\n if (category === ts.DiagnosticCategory.Error) return 'error'\n if (category === ts.DiagnosticCategory.Warning) return 'warning'\n return null\n}\n\n/**\n * Map a file extension to its `ScriptKind`. `ts.createSourceFile` does NOT infer\n * this — omitting it would mis-parse files — so we map it explicitly (not via\n * any TypeScript-internal helper). A `.ts` file thus rejects JSX, while `.tsx`\n * accepts it.\n */\nfunction scriptKindForFile(fileName: string): ts.ScriptKind {\n const lower = fileName.toLowerCase()\n if (lower.endsWith('.tsx')) return ts.ScriptKind.TSX\n if (lower.endsWith('.jsx')) return ts.ScriptKind.JSX\n if (lower.endsWith('.js') || lower.endsWith('.mjs') || lower.endsWith('.cjs')) {\n return ts.ScriptKind.JS\n }\n if (lower.endsWith('.json')) return ts.ScriptKind.JSON\n return ts.ScriptKind.TS\n}\n\n/** Convert a TypeScript diagnostic to our structured form. */\nfunction convert(diag: ts.Diagnostic): Diagnostic | null {\n // The single-module gate doesn't resolve imports; drop unresolved-import noise.\n if (UNRESOLVED_IMPORT_CODES.has(diag.code)) return null\n const severity = toSeverity(diag.category)\n if (!severity) return null\n const message = ts.flattenDiagnosticMessageText(diag.messageText, '\\n')\n const out: Diagnostic = {\n severity,\n code: `TS${diag.code}`,\n message,\n }\n if (diag.file) {\n out.file = diag.file.fileName\n if (diag.start !== undefined) {\n const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start)\n out.position = { line: line + 1, column: character + 1 }\n }\n }\n return out\n}\n\n/**\n * Type-check a single module's source and return its diagnostics.\n *\n * @param source - The module source (`.tsx` is assumed unless `fileName` says otherwise).\n * @param fileName - Logical file name. Default `\"module.tsx\"`.\n * @returns All `error`/`warning` diagnostics (semantic + syntactic). Empty = clean.\n */\nexport function typecheck(source: string, fileName = 'module.tsx'): Diagnostic[] {\n const options = defaultOptions()\n const sourceFile = ts.createSourceFile(\n fileName,\n source,\n ts.ScriptTarget.ES2023,\n true,\n scriptKindForFile(fileName),\n )\n // A synthetic ambient JSX lib so intrinsic elements (`<view>`, `<text>`, …)\n // type-check without each module declaring JSX.IntrinsicElements.\n const jsxLib = ts.createSourceFile(\n JSX_LIB_FILE,\n JSX_LIB_SOURCE,\n ts.ScriptTarget.ES2023,\n true,\n ts.ScriptKind.TS,\n )\n\n const defaultHost = ts.createCompilerHost(options)\n const host: ts.CompilerHost = {\n ...defaultHost,\n getSourceFile: (name, languageVersion, onError, shouldCreate) => {\n if (name === fileName) return sourceFile\n if (name === JSX_LIB_FILE) return jsxLib\n return defaultHost.getSourceFile(name, languageVersion, onError, shouldCreate)\n },\n writeFile: () => {\n /* noEmit */\n },\n fileExists: (name) =>\n name === fileName || name === JSX_LIB_FILE || defaultHost.fileExists(name),\n readFile: (name) =>\n name === fileName\n ? source\n : name === JSX_LIB_FILE\n ? JSX_LIB_SOURCE\n : defaultHost.readFile(name),\n }\n\n const program = ts.createProgram([JSX_LIB_FILE, fileName], options, host)\n const diagnostics = [\n ...program.getSyntacticDiagnostics(sourceFile),\n ...program.getSemanticDiagnostics(sourceFile),\n ]\n\n const out: Diagnostic[] = []\n for (const d of diagnostics) {\n const converted = convert(d)\n if (converted) out.push(converted)\n }\n return out\n}\n\n/** True if any diagnostic is an `error` (i.e. the build must fail). */\nexport function hasErrors(diagnostics: readonly Diagnostic[]): boolean {\n return diagnostics.some((d) => d.severity === 'error')\n}\n"],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared types for the Mindees Compiler (MDC).
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
/** Severity of a {@link Diagnostic}. */
|
|
8
|
+
type DiagnosticSeverity = 'error' | 'warning';
|
|
9
|
+
/** A 1-based source position. */
|
|
10
|
+
interface SourcePosition {
|
|
11
|
+
/** 1-based line. */
|
|
12
|
+
line: number;
|
|
13
|
+
/** 1-based column. */
|
|
14
|
+
column: number;
|
|
15
|
+
}
|
|
16
|
+
/** A compiler diagnostic (a type error, lint-ish issue, or transform note). */
|
|
17
|
+
interface Diagnostic {
|
|
18
|
+
severity: DiagnosticSeverity;
|
|
19
|
+
/** Stable code, e.g. `"TS2345"` for a TypeScript diagnostic. */
|
|
20
|
+
code: string;
|
|
21
|
+
/** Human-readable message. */
|
|
22
|
+
message: string;
|
|
23
|
+
/** Source file the diagnostic refers to, if known. */
|
|
24
|
+
file?: string;
|
|
25
|
+
/** Start position in the file, if known. */
|
|
26
|
+
position?: SourcePosition;
|
|
27
|
+
}
|
|
28
|
+
/** The result of compiling a single module. */
|
|
29
|
+
interface CompileResult {
|
|
30
|
+
/** Emitted JavaScript. */
|
|
31
|
+
code: string;
|
|
32
|
+
/** Source map (JSON string), when source maps are enabled. */
|
|
33
|
+
map?: string;
|
|
34
|
+
/** Diagnostics produced (type errors etc.). Empty on a clean compile. */
|
|
35
|
+
diagnostics: Diagnostic[];
|
|
36
|
+
/** Optimizer statistics for this module. */
|
|
37
|
+
stats: CompileStats;
|
|
38
|
+
}
|
|
39
|
+
/** Per-compile optimizer statistics, used by tests and the perf budget. */
|
|
40
|
+
interface CompileStats {
|
|
41
|
+
/** `createElement` calls collapsed by tree-flattening. */
|
|
42
|
+
flattenedNodes: number;
|
|
43
|
+
/** Total `createElement` calls seen before flattening. */
|
|
44
|
+
totalElements: number;
|
|
45
|
+
}
|
|
46
|
+
/** Options controlling a compile. */
|
|
47
|
+
interface CompileOptions {
|
|
48
|
+
/** Logical file name (drives JSX/TS parsing + diagnostics + maps). Default `"module.tsx"`. */
|
|
49
|
+
fileName?: string;
|
|
50
|
+
/** Emit a source map. Default `true`. */
|
|
51
|
+
sourceMap?: boolean;
|
|
52
|
+
/** Run the tree-flattening optimizer pass. Default `true`. */
|
|
53
|
+
flatten?: boolean;
|
|
54
|
+
/** Additional transform plugins to run (after the built-in passes). */
|
|
55
|
+
plugins?: MdcPlugin[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A transform plugin. Plugins operate on the desugared `createElement(...)` call
|
|
59
|
+
* form, so they see a stable, framework-level shape. Because `transpileModule`
|
|
60
|
+
* lowers JSX during the **`after`** phase, the returned factory is run as an
|
|
61
|
+
* **`after`** transformer (a `before` transformer would see raw JSX, not calls).
|
|
62
|
+
*
|
|
63
|
+
* Typed structurally against `unknown` here to avoid leaking the `typescript`
|
|
64
|
+
* types across the package boundary; see `transform.ts` for the concrete usage.
|
|
65
|
+
*/
|
|
66
|
+
interface MdcPlugin {
|
|
67
|
+
/** Unique plugin name (for diagnostics + ordering). */
|
|
68
|
+
name: string;
|
|
69
|
+
/**
|
|
70
|
+
* Build a TypeScript transformer factory:
|
|
71
|
+
* `(ts, program?) => TransformerFactory<SourceFile>`.
|
|
72
|
+
* Receives the `typescript` module so plugins don't import it themselves.
|
|
73
|
+
*/
|
|
74
|
+
transformer: (ts: typeof import('typescript')) => unknown;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { CompileOptions, CompileResult, CompileStats, Diagnostic, DiagnosticSeverity, MdcPlugin, SourcePosition };
|
|
78
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;AAOA;;;;AAA8B;AAAA,KAAlB,kBAAA;;UAGK,cAAA;EAEf;EAAA,IAAA;EAMe;EAJf,MAAM;AAAA;;UAIS,UAAA;EACf,QAAA,EAAU,kBAAA;EAEV;EAAA,IAAA;EAIA;EAFA,OAAA;EAIW;EAFX,IAAA;EAEyB;EAAzB,QAAA,GAAW,cAAc;AAAA;;UAIV,aAAA;EAEf;EAAA,IAAA;EAIA;EAFA,GAAA;EAIA;EAFA,WAAA,EAAa,UAAA;EAEM;EAAnB,KAAA,EAAO,YAAY;AAAA;;UAIJ,YAAA;EAEf;EAAA,cAAA;EAMe;EAJf,aAAa;AAAA;;UAIE,cAAA;EAIf;EAFA,QAAA;EAMA;EAJA,SAAA;EAImB;EAFnB,OAAA;EAce;EAZf,OAAA,GAAU,SAAS;AAAA;;;;;;AAoB0B;;;;UAR9B,SAAA;;EAEf,IAAA;;;;;;EAMA,WAAA,GAAc,EAAA;AAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mindees/compiler",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MindeesNative Compiler (MDC) — build-time optimizer: type-check gate, TSX→createElement transform, tree-flattening, per-route code-splitting, and a plugin API. TS→native AOT is a research track.",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/mindees/mindees.git",
|
|
23
|
+
"directory": "packages/compiler"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"typescript": "6.0.3",
|
|
27
|
+
"@mindees/core": "0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsdown",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
}
|
|
33
|
+
}
|