@mindees/compiler 0.22.0 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ import { Maturity, NotImplementedError, PackageInfo, notImplemented } from "@min
12
12
  /** The npm package name. */
13
13
  declare const name = "@mindees/compiler";
14
14
  /** The package version. All `@mindees/*` packages share one locked version line. */
15
- declare const VERSION = "0.22.0";
15
+ declare const VERSION = "0.22.2";
16
16
  /**
17
17
  * Current maturity. The build-time optimizer — type-check gate, TSX→createElement
18
18
  * transform, tree-flattening, per-route manifest, plugin API — is implemented
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { NotImplementedError, notImplemented } from "@mindees/core";
10
10
  /** The npm package name. */
11
11
  const name = "@mindees/compiler";
12
12
  /** The package version. All `@mindees/*` packages share one locked version line. */
13
- const VERSION = "0.22.0";
13
+ const VERSION = "0.22.2";
14
14
  /**
15
15
  * Current maturity. The build-time optimizer — type-check gate, TSX→createElement
16
16
  * transform, tree-flattening, per-route manifest, plugin API — is implemented
package/dist/index.js.map CHANGED
@@ -1 +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/** Build-time performance budget (opt-in via `compileChecked(src, { budget })`) — fails the build. */\nexport { type BudgetOptions, checkBudget } from './budget'\n/** Tree-flattening optimizer pass. */\nexport { createFlattenTransformer, STATIC_MARKER } from './flatten'\n/** Build-time perf-lint (opt-in via `compileChecked(src, { perf: true })`). */\nexport { type PerfLintOptions, perfLint } from './perf-lint'\n/** Per-route code-splitting manifest + file-based route codegen. */\nexport {\n buildRouteManifest,\n chunkName,\n fileToRoute,\n type GenerateRouteModuleOptions,\n generateRouteModule,\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.22.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":";;;;;;;;;;AAqCA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;AAQvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
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/** Build-time performance budget (opt-in via `compileChecked(src, { budget })`) — fails the build. */\nexport { type BudgetOptions, checkBudget } from './budget'\n/** Tree-flattening optimizer pass. */\nexport { createFlattenTransformer, STATIC_MARKER } from './flatten'\n/** Build-time perf-lint (opt-in via `compileChecked(src, { perf: true })`). */\nexport { type PerfLintOptions, perfLint } from './perf-lint'\n/** Per-route code-splitting manifest + file-based route codegen. */\nexport {\n buildRouteManifest,\n chunkName,\n fileToRoute,\n type GenerateRouteModuleOptions,\n generateRouteModule,\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.22.2'\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":";;;;;;;;;;AAqCA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;AAQvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
package/dist/routes.js CHANGED
@@ -105,7 +105,7 @@ function generateRouteModule(files, options = {}) {
105
105
  const importBase = (options.importBase ?? "./app").replace(/\/+$/, "");
106
106
  const exportName = options.exportName ?? "routes";
107
107
  const routeFiles = [...files].map((f) => f.replace(/\\/g, "/")).filter((f) => ROUTE_FILE.test(f)).sort();
108
- const imports = routeFiles.map((file, i) => `import * as _route${i} from '${importBase}/${stripExt(file)}'`);
108
+ const imports = routeFiles.map((file, i) => `import * as _route${i} from ${JSON.stringify(`${importBase}/${stripExt(file)}`)}`);
109
109
  const entries = routeFiles.map((file, i) => ` ${JSON.stringify(file)}: _route${i},`);
110
110
  const header = "// AUTO-GENERATED by @mindees/compiler generateRouteModule. Do not edit; regenerate when routes change.\n";
111
111
  const body = `export const ${exportName} = {\n${entries.join("\n")}\n}\n`;
@@ -1 +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 // Normalize Windows separators: a backslash path (what `path.join` yields on Windows) would\n // otherwise never split into segments, collapsing the whole route to one literal segment.\n const segments = stripExt(file.replace(/\\\\/g, '/'))\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 // Normalize separators up front so the manifest's `file` (an `import()` specifier — backslashes\n // are invalid there), chunk names, and route paths are all POSIX, regardless of the host OS.\n const normalized = files.map((f) => f.replace(/\\\\/g, '/'))\n for (const file of [...normalized].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\n/** Options for {@link generateRouteModule}. */\nexport interface GenerateRouteModuleOptions {\n /** Import-specifier prefix for the route files. Default `'./app'`. */\n importBase?: string\n /** Name of the exported module-map constant. Default `'routes'`. */\n exportName?: string\n}\n\n/**\n * Generate a TypeScript module that statically imports every route file and exposes\n * them as a module map keyed by relative path — exactly the input\n * `@mindees/router`'s `createFileRouter`/`routesFromModules` consume.\n *\n * This is the codegen behind **file-based routing** for bundlers without\n * `import.meta.glob` (e.g. an embedded-engine native bundle): scan the `app/` dir, run\n * this over the file list, write the result (e.g. `routes.gen.ts`), and import the map.\n * Files are sorted for deterministic output. (`_layout`/`+not-found` are intentionally\n * kept — the router applies them via its conventions.)\n *\n * @example\n * generateRouteModule(['index.tsx', 'about.tsx'], { importBase: './app' })\n * // import * as _route0 from './app/about'\n * // import * as _route1 from './app/index'\n * // export const routes = { 'about.tsx': _route0, 'index.tsx': _route1 }\n */\nexport function generateRouteModule(\n files: readonly string[],\n options: GenerateRouteModuleOptions = {},\n): string {\n const importBase = (options.importBase ?? './app').replace(/\\/+$/, '')\n const exportName = options.exportName ?? 'routes'\n // Normalize separators: backslashes are invalid in import specifiers, and the map keys must be\n // the POSIX paths `routesFromModules` matches against.\n const routeFiles = [...files]\n .map((f) => f.replace(/\\\\/g, '/'))\n .filter((f) => ROUTE_FILE.test(f))\n .sort()\n const imports = routeFiles.map(\n (file, i) => `import * as _route${i} from '${importBase}/${stripExt(file)}'`,\n )\n const entries = routeFiles.map((file, i) => ` ${JSON.stringify(file)}: _route${i},`)\n const header =\n '// AUTO-GENERATED by @mindees/compiler generateRouteModule. Do not edit; regenerate when routes change.\\n'\n const body = `export const ${exportName} = {\\n${entries.join('\\n')}\\n}\\n`\n return imports.length > 0 ? `${header}${imports.join('\\n')}\\n\\n${body}` : `${header}${body}`\n}\n"],"mappings":"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"}
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 // Normalize Windows separators: a backslash path (what `path.join` yields on Windows) would\n // otherwise never split into segments, collapsing the whole route to one literal segment.\n const segments = stripExt(file.replace(/\\\\/g, '/'))\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 // Normalize separators up front so the manifest's `file` (an `import()` specifier — backslashes\n // are invalid there), chunk names, and route paths are all POSIX, regardless of the host OS.\n const normalized = files.map((f) => f.replace(/\\\\/g, '/'))\n for (const file of [...normalized].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\n/** Options for {@link generateRouteModule}. */\nexport interface GenerateRouteModuleOptions {\n /** Import-specifier prefix for the route files. Default `'./app'`. */\n importBase?: string\n /** Name of the exported module-map constant. Default `'routes'`. */\n exportName?: string\n}\n\n/**\n * Generate a TypeScript module that statically imports every route file and exposes\n * them as a module map keyed by relative path — exactly the input\n * `@mindees/router`'s `createFileRouter`/`routesFromModules` consume.\n *\n * This is the codegen behind **file-based routing** for bundlers without\n * `import.meta.glob` (e.g. an embedded-engine native bundle): scan the `app/` dir, run\n * this over the file list, write the result (e.g. `routes.gen.ts`), and import the map.\n * Files are sorted for deterministic output. (`_layout`/`+not-found` are intentionally\n * kept — the router applies them via its conventions.)\n *\n * @example\n * generateRouteModule(['index.tsx', 'about.tsx'], { importBase: './app' })\n * // import * as _route0 from './app/about'\n * // import * as _route1 from './app/index'\n * // export const routes = { 'about.tsx': _route0, 'index.tsx': _route1 }\n */\nexport function generateRouteModule(\n files: readonly string[],\n options: GenerateRouteModuleOptions = {},\n): string {\n const importBase = (options.importBase ?? './app').replace(/\\/+$/, '')\n const exportName = options.exportName ?? 'routes'\n // Normalize separators: backslashes are invalid in import specifiers, and the map keys must be\n // the POSIX paths `routesFromModules` matches against.\n const routeFiles = [...files]\n .map((f) => f.replace(/\\\\/g, '/'))\n .filter((f) => ROUTE_FILE.test(f))\n .sort()\n const imports = routeFiles.map(\n // JSON.stringify the specifier (escaped, double-quoted) — a filename with a quote/special char\n // would otherwise terminate the string literal and emit a non-parsing module (the map key below\n // is already escaped this way).\n (file, i) => `import * as _route${i} from ${JSON.stringify(`${importBase}/${stripExt(file)}`)}`,\n )\n const entries = routeFiles.map((file, i) => ` ${JSON.stringify(file)}: _route${i},`)\n const header =\n '// AUTO-GENERATED by @mindees/compiler generateRouteModule. Do not edit; regenerate when routes change.\\n'\n const body = `export const ${exportName} = {\\n${entries.join('\\n')}\\n}\\n`\n return imports.length > 0 ? `${header}${imports.join('\\n')}\\n\\n${body}` : `${header}${body}`\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","names":[],"sources":["../src/transform.ts"],"mappings":";;;AAgGoF;AAuDpF;;;;;;;AAvDoF,iBAApE,OAAA,CAAQ,MAAA,UAAgB,OAAA,GAAS,cAAA,GAAsB,aAAa;;AAuDO;;;iBAA3E,cAAA,CAAe,MAAA,UAAgB,OAAA,GAAS,cAAA,GAAsB,aAAa"}
1
+ {"version":3,"file":"transform.d.ts","names":[],"sources":["../src/transform.ts"],"mappings":";;;AAwGoF;AAuDpF;;;;;;;AAvDoF,iBAApE,OAAA,CAAQ,MAAA,UAAgB,OAAA,GAAS,cAAA,GAAsB,aAAa;;AAuDO;;;iBAA3E,cAAA,CAAe,MAAA,UAAgB,OAAA,GAAS,cAAA,GAAsB,aAAa"}
package/dist/transform.js CHANGED
@@ -39,9 +39,16 @@ const RUNTIME_NAMES = ["createElement", "Fragment"];
39
39
  function createRuntimeImportTransformer(tsmod) {
40
40
  return (context) => (sourceFile) => {
41
41
  const imported = /* @__PURE__ */ new Set();
42
- for (const stmt of sourceFile.statements) if (tsmod.isImportDeclaration(stmt) && tsmod.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === "@mindees/core") {
43
- const named = stmt.importClause?.namedBindings;
44
- if (named && tsmod.isNamedImports(named)) for (const el of named.elements) imported.add((el.propertyName ?? el.name).text);
42
+ for (const stmt of sourceFile.statements) if (tsmod.isImportDeclaration(stmt) && stmt.importClause) {
43
+ const clause = stmt.importClause;
44
+ if (clause.name) imported.add(clause.name.text);
45
+ const named = clause.namedBindings;
46
+ if (named) if (tsmod.isNamespaceImport(named)) imported.add(named.name.text);
47
+ else for (const el of named.elements) imported.add(el.name.text);
48
+ } else if (tsmod.isVariableStatement(stmt)) {
49
+ for (const d of stmt.declarationList.declarations) if (tsmod.isIdentifier(d.name)) imported.add(d.name.text);
50
+ } else if (tsmod.isFunctionDeclaration(stmt) || tsmod.isClassDeclaration(stmt)) {
51
+ if (stmt.name) imported.add(stmt.name.text);
45
52
  }
46
53
  const referenced = /* @__PURE__ */ new Set();
47
54
  const visit = (node) => {
@@ -1 +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 { checkBudget } from './budget'\nimport { createFlattenTransformer } from './flatten'\nimport { perfLint } from './perf-lint'\nimport { hasErrors, typecheck } from './typecheck'\nimport type { CompileOptions, CompileResult, CompileStats } from './types'\n\n/** Compiler options for emit (JSX → `createElement`/`Fragment`, which the optimizer matches). */\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/** Runtime names the JSX desugar references; injected from `@mindees/core` if unbound. */\nconst RUNTIME_NAMES = ['createElement', 'Fragment'] as const\n\n/**\n * Ensure the JSX runtime is in scope. Idiomatic components use **automatic JSX** and import\n * nothing, but we emit classic `createElement(...)`/`Fragment` (so the tree-flatten optimizer\n * can match them) — which would be unbound at runtime. This transformer prepends\n * `import { createElement, Fragment } from '@mindees/core'` for any runtime name that is\n * referenced but not already imported, so emitted modules run. Runs LAST (after flatten/plugins),\n * so names the optimizer removed don't get a needless import.\n */\nfunction createRuntimeImportTransformer(tsmod: typeof ts): ts.TransformerFactory<ts.SourceFile> {\n return (context) => (sourceFile) => {\n const imported = new Set<string>()\n for (const stmt of sourceFile.statements) {\n if (\n tsmod.isImportDeclaration(stmt) &&\n tsmod.isStringLiteral(stmt.moduleSpecifier) &&\n stmt.moduleSpecifier.text === '@mindees/core'\n ) {\n const named = stmt.importClause?.namedBindings\n if (named && tsmod.isNamedImports(named)) {\n for (const el of named.elements) imported.add((el.propertyName ?? el.name).text)\n }\n }\n }\n const referenced = new Set<string>()\n const visit = (node: ts.Node): void => {\n if (tsmod.isIdentifier(node) && (RUNTIME_NAMES as readonly string[]).includes(node.text)) {\n referenced.add(node.text)\n }\n tsmod.forEachChild(node, visit)\n }\n visit(sourceFile)\n const missing = RUNTIME_NAMES.filter((n) => referenced.has(n) && !imported.has(n))\n if (missing.length === 0) return sourceFile\n const importDecl = tsmod.factory.createImportDeclaration(\n undefined,\n tsmod.factory.createImportClause(\n false,\n undefined,\n tsmod.factory.createNamedImports(\n missing.map((n) =>\n tsmod.factory.createImportSpecifier(\n false,\n undefined,\n tsmod.factory.createIdentifier(n),\n ),\n ),\n ),\n ),\n tsmod.factory.createStringLiteral('@mindees/core'),\n )\n return context.factory.updateSourceFile(sourceFile, [importDecl, ...sourceFile.statements])\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 // LAST: bind the JSX runtime (automatic-JSX components import nothing) so output runs.\n after.push(createRuntimeImportTransformer(ts))\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 // Opt-in build-time perf-lint: warnings only (never blocks — the gate above already returned on\n // errors, and every perf diagnostic is severity 'warning').\n const perfDiagnostics = options.perf\n ? perfLint(source, fileName, typeof options.perf === 'object' ? options.perf : {})\n : []\n // Performance budget (spec §12): violations are ERRORS that refuse to emit — \"100% optimized,\n // enforced.\" Attach the budget errors to a file so editors surface them.\n const budgetDiagnostics = options.budget\n ? checkBudget(compiled, options.budget).map((d) => ({ ...d, file: fileName }))\n : []\n const allDiagnostics = [\n ...diagnostics,\n ...perfDiagnostics,\n ...budgetDiagnostics,\n ...compiled.diagnostics,\n ]\n if (hasErrors(budgetDiagnostics)) {\n // Over budget → refuse to emit (same contract as the type-check gate above).\n return { code: '', diagnostics: allDiagnostics, stats: compiled.stats }\n }\n return { ...compiled, diagnostics: allDiagnostics }\n}\n"],"mappings":"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"}
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 { checkBudget } from './budget'\nimport { createFlattenTransformer } from './flatten'\nimport { perfLint } from './perf-lint'\nimport { hasErrors, typecheck } from './typecheck'\nimport type { CompileOptions, CompileResult, CompileStats } from './types'\n\n/** Compiler options for emit (JSX → `createElement`/`Fragment`, which the optimizer matches). */\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/** Runtime names the JSX desugar references; injected from `@mindees/core` if unbound. */\nconst RUNTIME_NAMES = ['createElement', 'Fragment'] as const\n\n/**\n * Ensure the JSX runtime is in scope. Idiomatic components use **automatic JSX** and import\n * nothing, but we emit classic `createElement(...)`/`Fragment` (so the tree-flatten optimizer\n * can match them) — which would be unbound at runtime. This transformer prepends\n * `import { createElement, Fragment } from '@mindees/core'` for any runtime name that is\n * referenced but not already imported, so emitted modules run. Runs LAST (after flatten/plugins),\n * so names the optimizer removed don't get a needless import.\n */\nfunction createRuntimeImportTransformer(tsmod: typeof ts): ts.TransformerFactory<ts.SourceFile> {\n return (context) => (sourceFile) => {\n // Every name already bound at the top level — from ANY import (named/aliased/default/namespace,\n // any module) OR a local var/function/class. We must never inject a runtime import for a name that\n // already has a binding: a duplicate top-level binding is a hard SyntaxError that fails module load.\n const imported = new Set<string>()\n for (const stmt of sourceFile.statements) {\n if (tsmod.isImportDeclaration(stmt) && stmt.importClause) {\n const clause = stmt.importClause\n if (clause.name) imported.add(clause.name.text) // default import\n const named = clause.namedBindings\n if (named) {\n if (tsmod.isNamespaceImport(named)) imported.add(named.name.text)\n else for (const el of named.elements) imported.add(el.name.text) // local binding name\n }\n } else if (tsmod.isVariableStatement(stmt)) {\n for (const d of stmt.declarationList.declarations) {\n if (tsmod.isIdentifier(d.name)) imported.add(d.name.text)\n }\n } else if (tsmod.isFunctionDeclaration(stmt) || tsmod.isClassDeclaration(stmt)) {\n if (stmt.name) imported.add(stmt.name.text)\n }\n }\n const referenced = new Set<string>()\n const visit = (node: ts.Node): void => {\n if (tsmod.isIdentifier(node) && (RUNTIME_NAMES as readonly string[]).includes(node.text)) {\n referenced.add(node.text)\n }\n tsmod.forEachChild(node, visit)\n }\n visit(sourceFile)\n const missing = RUNTIME_NAMES.filter((n) => referenced.has(n) && !imported.has(n))\n if (missing.length === 0) return sourceFile\n const importDecl = tsmod.factory.createImportDeclaration(\n undefined,\n tsmod.factory.createImportClause(\n false,\n undefined,\n tsmod.factory.createNamedImports(\n missing.map((n) =>\n tsmod.factory.createImportSpecifier(\n false,\n undefined,\n tsmod.factory.createIdentifier(n),\n ),\n ),\n ),\n ),\n tsmod.factory.createStringLiteral('@mindees/core'),\n )\n return context.factory.updateSourceFile(sourceFile, [importDecl, ...sourceFile.statements])\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 // LAST: bind the JSX runtime (automatic-JSX components import nothing) so output runs.\n after.push(createRuntimeImportTransformer(ts))\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 // Opt-in build-time perf-lint: warnings only (never blocks — the gate above already returned on\n // errors, and every perf diagnostic is severity 'warning').\n const perfDiagnostics = options.perf\n ? perfLint(source, fileName, typeof options.perf === 'object' ? options.perf : {})\n : []\n // Performance budget (spec §12): violations are ERRORS that refuse to emit — \"100% optimized,\n // enforced.\" Attach the budget errors to a file so editors surface them.\n const budgetDiagnostics = options.budget\n ? checkBudget(compiled, options.budget).map((d) => ({ ...d, file: fileName }))\n : []\n const allDiagnostics = [\n ...diagnostics,\n ...perfDiagnostics,\n ...budgetDiagnostics,\n ...compiled.diagnostics,\n ]\n if (hasErrors(budgetDiagnostics)) {\n // Over budget → refuse to emit (same contract as the type-check gate above).\n return { code: '', diagnostics: allDiagnostics, stats: compiled.stats }\n }\n return { ...compiled, diagnostics: allDiagnostics }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindees/compiler",
3
- "version": "0.22.0",
3
+ "version": "0.22.2",
4
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
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "typescript": "6.0.3",
27
- "@mindees/core": "0.22.0"
27
+ "@mindees/core": "0.22.2"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsdown",