@tamagui/compiler-core 0.0.0-bootstrap.0 → 3.0.0-beta.1093.1

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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -1
  3. package/dist/cjs/ast.cjs +108 -0
  4. package/dist/cjs/contracts.cjs +61 -0
  5. package/dist/cjs/diagnostics.cjs +68 -0
  6. package/dist/cjs/evaluate.cjs +602 -0
  7. package/dist/cjs/graph.cjs +335 -0
  8. package/dist/cjs/hash.cjs +48 -0
  9. package/dist/cjs/index.cjs +43 -0
  10. package/dist/cjs/ir.cjs +32 -0
  11. package/dist/cjs/lower.cjs +232 -0
  12. package/dist/cjs/materialize.cjs +222 -0
  13. package/dist/cjs/normalize.cjs +628 -0
  14. package/dist/cjs/output.cjs +148 -0
  15. package/dist/cjs/planCache.cjs +262 -0
  16. package/dist/cjs/session.cjs +127 -0
  17. package/dist/cjs/yuku.cjs +166 -0
  18. package/dist/cjs/zero.cjs +389 -0
  19. package/dist/esm/ast.mjs +79 -0
  20. package/dist/esm/ast.mjs.map +1 -0
  21. package/dist/esm/contracts.mjs +34 -0
  22. package/dist/esm/contracts.mjs.map +1 -0
  23. package/dist/esm/diagnostics.mjs +44 -0
  24. package/dist/esm/diagnostics.mjs.map +1 -0
  25. package/dist/esm/evaluate.mjs +576 -0
  26. package/dist/esm/evaluate.mjs.map +1 -0
  27. package/dist/esm/graph.mjs +313 -0
  28. package/dist/esm/graph.mjs.map +1 -0
  29. package/dist/esm/hash.mjs +26 -0
  30. package/dist/esm/hash.mjs.map +1 -0
  31. package/dist/esm/index.mjs +30 -0
  32. package/dist/esm/ir.mjs +12 -0
  33. package/dist/esm/ir.mjs.map +1 -0
  34. package/dist/esm/lower.mjs +210 -0
  35. package/dist/esm/lower.mjs.map +1 -0
  36. package/dist/esm/materialize.mjs +203 -0
  37. package/dist/esm/materialize.mjs.map +1 -0
  38. package/dist/esm/normalize.mjs +604 -0
  39. package/dist/esm/normalize.mjs.map +1 -0
  40. package/dist/esm/output.mjs +119 -0
  41. package/dist/esm/output.mjs.map +1 -0
  42. package/dist/esm/planCache.mjs +232 -0
  43. package/dist/esm/planCache.mjs.map +1 -0
  44. package/dist/esm/session.mjs +108 -0
  45. package/dist/esm/session.mjs.map +1 -0
  46. package/dist/esm/yuku.mjs +144 -0
  47. package/dist/esm/yuku.mjs.map +1 -0
  48. package/dist/esm/zero.mjs +354 -0
  49. package/dist/esm/zero.mjs.map +1 -0
  50. package/package.json +42 -4
  51. package/src/ast.ts +106 -0
  52. package/src/contracts.ts +150 -0
  53. package/src/diagnostics.ts +96 -0
  54. package/src/evaluate.ts +875 -0
  55. package/src/graph.ts +425 -0
  56. package/src/hash.ts +36 -0
  57. package/src/index.ts +15 -0
  58. package/src/ir.ts +156 -0
  59. package/src/lower.ts +435 -0
  60. package/src/materialize.ts +383 -0
  61. package/src/normalize.ts +834 -0
  62. package/src/output.ts +175 -0
  63. package/src/planCache.ts +362 -0
  64. package/src/session.ts +192 -0
  65. package/src/yuku.ts +197 -0
  66. package/src/zero.ts +609 -0
  67. package/types/ast.d.ts +11 -0
  68. package/types/ast.d.ts.map +1 -0
  69. package/types/contracts.d.ts +99 -0
  70. package/types/contracts.d.ts.map +1 -0
  71. package/types/diagnostics.d.ts +31 -0
  72. package/types/diagnostics.d.ts.map +1 -0
  73. package/types/evaluate.d.ts +48 -0
  74. package/types/evaluate.d.ts.map +1 -0
  75. package/types/graph.d.ts +51 -0
  76. package/types/graph.d.ts.map +1 -0
  77. package/types/hash.d.ts +8 -0
  78. package/types/hash.d.ts.map +1 -0
  79. package/types/index.d.ts +16 -0
  80. package/types/index.d.ts.map +1 -0
  81. package/types/ir.d.ts +120 -0
  82. package/types/ir.d.ts.map +1 -0
  83. package/types/lower.d.ts +99 -0
  84. package/types/lower.d.ts.map +1 -0
  85. package/types/materialize.d.ts +121 -0
  86. package/types/materialize.d.ts.map +1 -0
  87. package/types/normalize.d.ts +7 -0
  88. package/types/normalize.d.ts.map +1 -0
  89. package/types/output.d.ts +32 -0
  90. package/types/output.d.ts.map +1 -0
  91. package/types/planCache.d.ts +123 -0
  92. package/types/planCache.d.ts.map +1 -0
  93. package/types/session.d.ts +52 -0
  94. package/types/session.d.ts.map +1 -0
  95. package/types/yuku.d.ts +4 -0
  96. package/types/yuku.d.ts.map +1 -0
  97. package/types/zero.d.ts +99 -0
  98. package/types/zero.d.ts.map +1 -0
package/src/session.ts ADDED
@@ -0,0 +1,192 @@
1
+ import type { HostModuleInput, ResolvedModuleId } from './contracts'
2
+ import type { GraphInvalidation } from './graph'
3
+ import { ProjectGraph } from './graph'
4
+ import type {
5
+ CompilerLoweringHost,
6
+ CompilerTarget,
7
+ LoweredModulePlan,
8
+ StructuralModulePass,
9
+ } from './lower'
10
+ import { lowerModule } from './lower'
11
+ import { materializeModule, type MaterializedModule } from './materialize'
12
+ import type { AppliedLoweredModule } from './output'
13
+ import { applyLoweredModule } from './output'
14
+ import type { ModulePlanCache } from './planCache'
15
+ import {
16
+ PLAN_CACHE_SCHEMA_VERSION,
17
+ createExternalClosureLookup,
18
+ moduleClosureDigest,
19
+ planCacheKey,
20
+ } from './planCache'
21
+ import { yukuFactory } from './yuku'
22
+
23
+ export interface CompilerAdapter {
24
+ target: CompilerTarget
25
+ projectGeneration: string
26
+ host: CompilerLoweringHost
27
+ load(id: ResolvedModuleId): Promise<HostModuleInput | null>
28
+ /**
29
+ * Runs after a module is materialized and before it lowers, once per uncached
30
+ * compile. The frontend evaluates component modules the host does not know
31
+ * yet here (element and styled() base provenance outside the configured
32
+ * `components`), so lowering never meets an import it could have resolved.
33
+ */
34
+ prepare?(module: MaterializedModule): Promise<void>
35
+ /**
36
+ * Persistent per-module plan reuse across processes. Absent means the host
37
+ * could not produce a content stamp for this project, so nothing is cached
38
+ * rather than cached under a stamp that does not describe the config.
39
+ */
40
+ planCache?: { store: ModulePlanCache; stamp: string }
41
+ }
42
+
43
+ export interface CompileModuleInput {
44
+ module: HostModuleInput
45
+ adapter: CompilerAdapter
46
+ structuralPass?: StructuralModulePass
47
+ }
48
+
49
+ export interface CompilerSessionResult {
50
+ plan: LoweredModulePlan
51
+ output: AppliedLoweredModule
52
+ invalidatedIds: ResolvedModuleId[]
53
+ }
54
+
55
+ function compareIds(left: ResolvedModuleId, right: ResolvedModuleId): number {
56
+ return left < right ? -1 : left > right ? 1 : 0
57
+ }
58
+
59
+ /**
60
+ * Bundler-neutral compiler state. The adapter owns module resolution and loading;
61
+ * the session only accepts canonical host-resolved module records.
62
+ */
63
+ export class CompilerSession {
64
+ readonly #graph = new ProjectGraph(yukuFactory, { modules: [] })
65
+ readonly #externalClosure = createExternalClosureLookup()
66
+ #queue: Promise<unknown> = Promise.resolve()
67
+
68
+ compile(input: CompileModuleInput): Promise<CompilerSessionResult> {
69
+ const operation = this.#queue.then(() => this.#compile(input))
70
+ this.#queue = operation.catch(() => undefined)
71
+ return operation
72
+ }
73
+
74
+ update(module: HostModuleInput): Promise<ResolvedModuleId[]> {
75
+ return this.#enqueue(() => this.#graph.updateModule(module).invalidatedIds)
76
+ }
77
+
78
+ has(id: ResolvedModuleId): boolean {
79
+ return this.#graph.contentHash(id) !== null
80
+ }
81
+
82
+ dependentsOf(id: ResolvedModuleId): ResolvedModuleId[] {
83
+ return this.#graph.dependentsOf(id)
84
+ }
85
+
86
+ remove(id: ResolvedModuleId): Promise<GraphInvalidation> {
87
+ return this.#enqueue(() => this.#graph.removeModule(id))
88
+ }
89
+
90
+ parseCount(id: ResolvedModuleId): number {
91
+ return this.#graph.parseCount(id)
92
+ }
93
+
94
+ #enqueue<T>(operation: () => T | Promise<T>): Promise<T> {
95
+ const queued = this.#queue.then(operation)
96
+ this.#queue = queued.catch(() => undefined)
97
+ return queued
98
+ }
99
+
100
+ async #compile({
101
+ module,
102
+ adapter,
103
+ structuralPass,
104
+ }: CompileModuleInput): Promise<CompilerSessionResult> {
105
+ const invalidated = new Set<ResolvedModuleId>()
106
+ await this.#install(module, adapter, new Set(), invalidated)
107
+ const graph = this.#graph
108
+ // an empty stamp is not an identity: a key without one would be shared by
109
+ // every project and every config, so such a project simply does not cache
110
+ const cache = adapter.planCache?.stamp ? adapter.planCache : null
111
+ const closureDigest = cache
112
+ ? moduleClosureDigest(module.id, (id) => {
113
+ const hash = graph.contentHash(id)
114
+ if (hash) {
115
+ return {
116
+ contentHash: hash,
117
+ dependencies: [...graph.dependenciesOf(id), ...graph.externalImportsOf(id)],
118
+ }
119
+ }
120
+ return this.#externalClosure(id)
121
+ })
122
+ : null
123
+ const entry =
124
+ cache && closureDigest
125
+ ? {
126
+ store: cache.store,
127
+ digest: closureDigest,
128
+ key: planCacheKey(
129
+ {
130
+ stamp: cache.stamp,
131
+ target: adapter.target,
132
+ structuralPassHash:
133
+ structuralPass?.versionHash ?? `${adapter.target}-noop-v1`,
134
+ },
135
+ module.id,
136
+ closureDigest
137
+ ),
138
+ }
139
+ : null
140
+
141
+ const cached = entry
142
+ ? await entry.store.read(entry.key, module.id, entry.digest)
143
+ : null
144
+ let plan = cached?.plan
145
+ if (!plan) {
146
+ const materialized = materializeModule(graph, module.id)
147
+ if (adapter.prepare) await adapter.prepare(materialized)
148
+ plan = lowerModule({
149
+ module: materialized,
150
+ source: module.source,
151
+ target: adapter.target,
152
+ host: adapter.host,
153
+ options: { projectGeneration: adapter.projectGeneration },
154
+ structuralPass,
155
+ })
156
+ }
157
+ if (entry && !cached) {
158
+ await entry.store.write(entry.key, {
159
+ schemaVersion: PLAN_CACHE_SCHEMA_VERSION,
160
+ moduleId: module.id,
161
+ closureDigest: entry.digest,
162
+ plan,
163
+ })
164
+ }
165
+ return {
166
+ plan,
167
+ output: applyLoweredModule(module.source, module.id, plan),
168
+ invalidatedIds: [...invalidated].sort(compareIds),
169
+ }
170
+ }
171
+
172
+ async #install(
173
+ module: HostModuleInput,
174
+ adapter: CompilerAdapter,
175
+ visited: Set<ResolvedModuleId>,
176
+ invalidated: Set<ResolvedModuleId>
177
+ ): Promise<void> {
178
+ if (visited.has(module.id)) return
179
+ visited.add(module.id)
180
+
181
+ for (const dependency of module.imports) {
182
+ if (dependency.external || this.has(dependency.resolvedId)) continue
183
+ const loaded = await adapter.load(dependency.resolvedId)
184
+ if (loaded) {
185
+ await this.#install(loaded, adapter, visited, invalidated)
186
+ }
187
+ }
188
+
189
+ const update = this.#graph.updateModule(module)
190
+ for (const id of update.invalidatedIds) invalidated.add(id)
191
+ }
192
+ }
package/src/yuku.ts ADDED
@@ -0,0 +1,197 @@
1
+ import { Analyzer, type Module, type Symbol as YukuSymbol } from 'yuku-analyzer'
2
+
3
+ import type {
4
+ AnalyzerCandidate,
5
+ AstNode,
6
+ CandidateFactory,
7
+ DefinitionSite,
8
+ ParseDiagnostic,
9
+ ProjectInput,
10
+ ReferenceSite,
11
+ } from './contracts'
12
+ import { resolveFromHost, resolvedModuleId } from './contracts'
13
+ import { childNode, walkAst } from './ast'
14
+ import { asAstNode, definitionFromDeclaration, nodeAtSpan } from './normalize'
15
+
16
+ class YukuCandidate implements AnalyzerCandidate {
17
+ readonly name = 'yuku' as const
18
+ readonly #analyzer: Analyzer
19
+ readonly #parseCounts = new Map<string, number>()
20
+
21
+ constructor(private readonly project: ProjectInput) {
22
+ this.#analyzer = new Analyzer({
23
+ resolve: (specifier, importer) =>
24
+ resolveFromHost(this.project.resolutions, specifier, importer),
25
+ })
26
+ for (const [id, source] of project.files) this.addFile(id, source)
27
+ }
28
+
29
+ addFile(id: string, source: string): void {
30
+ this.#analyzer.addFile(id, source, { preserveParens: true, sourceType: 'module' })
31
+ this.#parseCounts.set(id, (this.#parseCounts.get(id) ?? 0) + 1)
32
+ }
33
+
34
+ removeFile(id: string): boolean {
35
+ return this.#analyzer.removeFile(id)
36
+ }
37
+
38
+ link(): void {
39
+ this.#analyzer.link()
40
+ }
41
+
42
+ #module(id: string): Module {
43
+ const module = this.#analyzer.module(id)
44
+ if (!module) throw new Error(`yuku: unknown module ${id}`)
45
+ return module
46
+ }
47
+
48
+ #canonicalSymbol(id: string, localName: string): YukuSymbol | null {
49
+ const symbol = this.#module(id).resolve(localName)
50
+ return symbol ? (this.#analyzer.definitionOf(symbol)?.symbol ?? null) : null
51
+ }
52
+
53
+ #definitionSite(symbol: YukuSymbol): DefinitionSite | null {
54
+ const declaration = symbol.declarations[0]
55
+ if (!declaration) return null
56
+ return definitionFromDeclaration(
57
+ symbol.module.path,
58
+ symbol.name,
59
+ asAstNode(declaration, `${symbol.name} declaration`),
60
+ (node) => symbol.module.parentOf(node as any) as AstNode | null
61
+ )
62
+ }
63
+
64
+ definitionOf(id: string, localName: string): DefinitionSite | null {
65
+ const symbol = this.#canonicalSymbol(id, localName)
66
+ return symbol ? this.#definitionSite(symbol) : null
67
+ }
68
+
69
+ definitionAt(id: string, start: number, end: number): DefinitionSite | null {
70
+ const module = this.#module(id)
71
+ const node = nodeAtSpan(asAstNode(module.ast, `${module.path} program`), start, end)
72
+ if (!node) return null
73
+ const symbol = module.symbolOf(node as any)
74
+ const definition = symbol && this.#analyzer.definitionOf(symbol)?.symbol
75
+ return definition ? this.#definitionSite(definition) : null
76
+ }
77
+
78
+ referencesOf(definition: DefinitionSite): ReferenceSite[] {
79
+ const symbol = this.#canonicalSymbol(definition.id, definition.name)
80
+ if (!symbol) return []
81
+ return this.#analyzer.referencesOf(symbol).map(({ module, reference }) => ({
82
+ id: resolvedModuleId(module.path),
83
+ name: reference.name,
84
+ start: reference.node.start,
85
+ end: reference.node.end,
86
+ }))
87
+ }
88
+
89
+ dependenciesOf(id: string): string[] {
90
+ return this.#module(id)
91
+ .dependencies.map((module) => module.path)
92
+ .sort()
93
+ }
94
+
95
+ dependentsOf(id: string): string[] {
96
+ return this.#module(id)
97
+ .dependents.map((module) => module.path)
98
+ .sort()
99
+ }
100
+
101
+ affectedBy(id: string): string[] {
102
+ const affected = new Set([id])
103
+ const queue = [id]
104
+ while (queue.length) {
105
+ const current = queue.shift()!
106
+ for (const dependent of this.dependentsOf(current)) {
107
+ if (!affected.has(dependent)) {
108
+ affected.add(dependent)
109
+ queue.push(dependent)
110
+ }
111
+ }
112
+ }
113
+ return [...affected].sort()
114
+ }
115
+
116
+ programOf(id: string) {
117
+ return asAstNode(this.#module(id).ast, `${id} program`)
118
+ }
119
+
120
+ sourceOf(id: string): string {
121
+ return this.#module(id).source
122
+ }
123
+
124
+ parseCount(id: string): number {
125
+ return this.#parseCounts.get(id) ?? 0
126
+ }
127
+
128
+ diagnostics(): ParseDiagnostic[] {
129
+ const parseDiagnostics = [...this.#analyzer.modules.values()].flatMap((module) =>
130
+ module.diagnostics.map((diagnostic) => ({
131
+ kind: 'parse' as const,
132
+ id: resolvedModuleId(module.path),
133
+ message: diagnostic.message,
134
+ }))
135
+ )
136
+ return [
137
+ ...parseDiagnostics,
138
+ ...this.#analyzer.diagnostics.map((diagnostic) => ({
139
+ kind: 'link' as const,
140
+ id: resolvedModuleId(diagnostic.module),
141
+ message: diagnostic.message,
142
+ })),
143
+ ]
144
+ }
145
+ }
146
+
147
+ export const yukuFactory: CandidateFactory = {
148
+ name: 'yuku',
149
+ create: (project) => new YukuCandidate(project),
150
+ scanImports(id, source) {
151
+ const candidate = new YukuCandidate({
152
+ files: new Map([[id, source]]),
153
+ resolutions: new Map(),
154
+ })
155
+ const found: { start: number; specifier: string }[] = []
156
+ walkAst(candidate.programOf(id), (node) => {
157
+ if (
158
+ node.type !== 'ImportDeclaration' &&
159
+ node.type !== 'ExportNamedDeclaration' &&
160
+ node.type !== 'ExportAllDeclaration' &&
161
+ node.type !== 'ImportExpression'
162
+ ) {
163
+ return
164
+ }
165
+ const sourceNode = childNode(node, 'source')
166
+ if (sourceNode && typeof sourceNode.value === 'string') {
167
+ found.push({ start: node.start, specifier: sourceNode.value })
168
+ }
169
+ })
170
+ return [
171
+ ...new Set(
172
+ found
173
+ .sort((left, right) => left.start - right.start)
174
+ .map(({ specifier }) => specifier)
175
+ ),
176
+ ]
177
+ },
178
+ }
179
+
180
+ export function parseModuleAst(
181
+ source: string,
182
+ id = '/tamagui/compiler-module.tsx'
183
+ ): AstNode {
184
+ const candidate = yukuFactory.create({
185
+ files: new Map([[id, source]]),
186
+ resolutions: new Map(),
187
+ })
188
+ const diagnostics = candidate
189
+ .diagnostics()
190
+ .filter((diagnostic) => diagnostic.kind === 'parse')
191
+ if (diagnostics.length) {
192
+ throw new Error(
193
+ `Unable to parse ${id}: ${diagnostics.map(({ message }) => message).join('; ')}`
194
+ )
195
+ }
196
+ return candidate.programOf(id)
197
+ }