rari 0.1.3

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.
@@ -0,0 +1,951 @@
1
+ import type { Plugin } from 'rolldown-vite'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import process from 'node:process'
5
+ import { build } from 'esbuild'
6
+
7
+ export interface ServerComponentManifest {
8
+ components: Record<
9
+ string,
10
+ {
11
+ id: string
12
+ filePath: string
13
+ relativePath: string
14
+ bundlePath: string
15
+ dependencies: string[]
16
+ hasNodeImports: boolean
17
+ }
18
+ >
19
+ version: string
20
+ buildTime: string
21
+ }
22
+
23
+ export interface ServerBuildOptions {
24
+ outDir?: string
25
+ serverDir?: string
26
+ manifestPath?: string
27
+ minify?: boolean
28
+ }
29
+
30
+ export class ServerComponentBuilder {
31
+ private serverComponents = new Map<
32
+ string,
33
+ {
34
+ filePath: string
35
+ originalCode: string
36
+ dependencies: string[]
37
+ hasNodeImports: boolean
38
+ }
39
+ >()
40
+
41
+ private options: Required<ServerBuildOptions>
42
+ private projectRoot: string
43
+
44
+ getComponentCount(): number {
45
+ return this.serverComponents.size
46
+ }
47
+
48
+ constructor(projectRoot: string, options: ServerBuildOptions = {}) {
49
+ this.projectRoot = projectRoot
50
+ this.options = {
51
+ outDir: options.outDir || path.join(projectRoot, 'dist'),
52
+ serverDir: options.serverDir || 'server',
53
+ manifestPath: options.manifestPath || 'server-manifest.json',
54
+ minify: options.minify ?? process.env.NODE_ENV === 'production',
55
+ }
56
+ }
57
+
58
+ isServerComponent(filePath: string): boolean {
59
+ try {
60
+ if (!fs.existsSync(filePath)) {
61
+ return false
62
+ }
63
+ const code = fs.readFileSync(filePath, 'utf-8')
64
+
65
+ const serverDirectives = [
66
+ '\'use server\'',
67
+ '"use server"',
68
+ '/* @server */',
69
+ '// @server',
70
+ ]
71
+
72
+ const trimmedCode = code.trim()
73
+
74
+ const hasServerDirective = serverDirectives.some(
75
+ directive =>
76
+ trimmedCode.startsWith(directive) || code.includes(directive),
77
+ )
78
+
79
+ const isInFunctionsDir
80
+ = filePath.includes('/functions/') || filePath.includes('\\functions\\')
81
+ const hasServerFunctionSignature
82
+ = (code.includes('export async function')
83
+ || code.includes('export function'))
84
+ && code.includes('\'use server\'')
85
+
86
+ const hasNodeImports
87
+ = code.includes('from \'node:')
88
+ || code.includes('from "node:')
89
+ || code.includes('from \'fs\'')
90
+ || code.includes('from "fs"')
91
+ || code.includes('from \'path\'')
92
+ || code.includes('from "path"')
93
+
94
+ const hasAsyncDefaultExport = /export\s+default\s+async\s+function/.test(
95
+ code,
96
+ )
97
+
98
+ const isServer
99
+ = hasServerDirective
100
+ || (isInFunctionsDir && hasServerFunctionSignature)
101
+ || (hasNodeImports && hasAsyncDefaultExport)
102
+
103
+ return isServer
104
+ }
105
+ catch {
106
+ return false
107
+ }
108
+ }
109
+
110
+ private isClientComponent(filePath: string): boolean {
111
+ try {
112
+ if (!fs.existsSync(filePath)) {
113
+ return false
114
+ }
115
+ const code = fs.readFileSync(filePath, 'utf-8')
116
+
117
+ const clientDirectives = [
118
+ '\'use client\'',
119
+ '"use client"',
120
+ '/* @client */',
121
+ '// @client',
122
+ ]
123
+
124
+ const trimmedCode = code.trim()
125
+
126
+ const hasClientDirective = clientDirectives.some(
127
+ directive =>
128
+ trimmedCode.startsWith(directive) || code.includes(directive),
129
+ )
130
+
131
+ return hasClientDirective
132
+ }
133
+ catch {
134
+ return false
135
+ }
136
+ }
137
+
138
+ addServerComponent(filePath: string) {
139
+ if (!this.isServerComponent(filePath)) {
140
+ return
141
+ }
142
+
143
+ const code = fs.readFileSync(filePath, 'utf-8')
144
+ const dependencies = this.extractDependencies(code)
145
+ const hasNodeImports = this.hasNodeImports(code)
146
+
147
+ this.serverComponents.set(filePath, {
148
+ filePath,
149
+ originalCode: code,
150
+ dependencies,
151
+ hasNodeImports,
152
+ })
153
+ }
154
+
155
+ private extractDependencies(code: string): string[] {
156
+ const dependencies: string[] = []
157
+ const importRegex
158
+ = /import(?:\s+(?:\w+|\{[^}]*\}|\*\s+as\s+\w+)(?:\s*,\s*(?:\w+|\{[^}]*\}|\*\s+as\s+\w+))*\s+from\s+)?['"]([^'"]+)['"]/g
159
+ let match
160
+
161
+ while (true) {
162
+ match = importRegex.exec(code)
163
+ if (match === null)
164
+ break
165
+
166
+ const importPath = match[1]
167
+ if (
168
+ !importPath.startsWith('.')
169
+ && !importPath.startsWith('/')
170
+ && !importPath.startsWith('node:')
171
+ && !this.isNodeBuiltin(importPath)
172
+ ) {
173
+ dependencies.push(importPath)
174
+ }
175
+ }
176
+
177
+ return Array.from(new Set(dependencies))
178
+ }
179
+
180
+ private isNodeBuiltin(moduleName: string): boolean {
181
+ const nodeBuiltins = [
182
+ 'fs',
183
+ 'path',
184
+ 'os',
185
+ 'crypto',
186
+ 'util',
187
+ 'stream',
188
+ 'events',
189
+ 'process',
190
+ 'buffer',
191
+ 'url',
192
+ 'querystring',
193
+ 'zlib',
194
+ 'http',
195
+ 'https',
196
+ 'net',
197
+ 'tls',
198
+ 'child_process',
199
+ 'cluster',
200
+ 'worker_threads',
201
+ ]
202
+ return nodeBuiltins.includes(moduleName)
203
+ }
204
+
205
+ private hasNodeImports(code: string): boolean {
206
+ return (
207
+ code.includes('from \'node:')
208
+ || code.includes('from "node:')
209
+ || code.includes('from \'fs\'')
210
+ || code.includes('from "fs"')
211
+ || code.includes('from \'path\'')
212
+ || code.includes('from "path"')
213
+ || code.includes('from \'os\'')
214
+ || code.includes('from "os"')
215
+ || code.includes('from \'crypto\'')
216
+ || code.includes('from "crypto"')
217
+ || code.includes('from \'util\'')
218
+ || code.includes('from "util"')
219
+ || code.includes('from \'stream\'')
220
+ || code.includes('from "stream"')
221
+ || code.includes('from \'events\'')
222
+ || code.includes('from "events"')
223
+ )
224
+ }
225
+
226
+ async getTransformedComponentsForDevelopment(): Promise<Array<{ id: string, code: string }>> {
227
+ const components: Array<{ id: string, code: string }> = []
228
+
229
+ for (const [filePath, component] of this.serverComponents) {
230
+ const relativePath = path.relative(this.projectRoot, filePath)
231
+ const componentId = this.getComponentId(relativePath)
232
+
233
+ const transformedCode = await this.buildComponentCodeOnly(filePath, componentId, component)
234
+
235
+ components.push({
236
+ id: componentId,
237
+ code: transformedCode,
238
+ })
239
+ }
240
+
241
+ return components
242
+ }
243
+
244
+ private async buildComponentCodeOnly(
245
+ inputPath: string,
246
+ componentId: string,
247
+ _component: { dependencies: string[], hasNodeImports: boolean },
248
+ ): Promise<string> {
249
+ const originalCode = await fs.promises.readFile(inputPath, 'utf-8')
250
+ const clientTransformedCode = this.transformClientImports(
251
+ originalCode,
252
+ inputPath,
253
+ )
254
+ const transformedCode = this.transformNodeImports(clientTransformedCode)
255
+
256
+ const ext = path.extname(inputPath)
257
+ let loader: string
258
+ if (ext === '.tsx') {
259
+ loader = 'tsx'
260
+ }
261
+ else if (ext === '.ts') {
262
+ loader = 'ts'
263
+ }
264
+ else if (ext === '.jsx') {
265
+ loader = 'jsx'
266
+ }
267
+ else {
268
+ loader = 'js'
269
+ }
270
+
271
+ try {
272
+ const result = await build({
273
+ stdin: {
274
+ contents: transformedCode,
275
+ resolveDir: path.dirname(inputPath),
276
+ sourcefile: inputPath,
277
+ loader: loader as any,
278
+ },
279
+ bundle: true,
280
+ platform: 'neutral',
281
+ target: 'es2022',
282
+ format: 'esm',
283
+ external: [],
284
+ mainFields: ['module', 'main'],
285
+ conditions: ['import', 'module', 'default'],
286
+ define: {
287
+ 'global': 'globalThis',
288
+ 'process.env.NODE_ENV': '"production"',
289
+ },
290
+ loader: {
291
+ '.ts': 'ts',
292
+ '.tsx': 'tsx',
293
+ '.js': 'js',
294
+ '.jsx': 'jsx',
295
+ },
296
+ resolveExtensions: ['.ts', '.tsx', '.js', '.jsx'],
297
+ minify: false,
298
+ minifyIdentifiers: false,
299
+ sourcemap: false,
300
+ metafile: false,
301
+ write: false,
302
+ plugins: [
303
+ {
304
+ name: 'hmr-auto-external',
305
+ setup(build) {
306
+ build.onResolve({ filter: /^[^./]/ }, async (args) => {
307
+ return { path: args.path, external: true }
308
+ })
309
+ },
310
+ },
311
+ {
312
+ name: 'resolve-server-functions',
313
+ setup(build) {
314
+ build.onResolve(
315
+ { filter: /^\.\.?\/.*functions/ },
316
+ async (args) => {
317
+ const resolvedPath = path.resolve(
318
+ path.dirname(args.importer),
319
+ args.path,
320
+ )
321
+
322
+ const possibleExtensions = [
323
+ '.ts',
324
+ '.js',
325
+ '.tsx',
326
+ '.jsx',
327
+ '/index.ts',
328
+ '/index.js',
329
+ ]
330
+ for (const ext of possibleExtensions) {
331
+ const fullPath = resolvedPath + ext
332
+ if (fs.existsSync(fullPath)) {
333
+ return { path: fullPath }
334
+ }
335
+ }
336
+
337
+ return null
338
+ },
339
+ )
340
+ },
341
+ },
342
+ ],
343
+ banner: {
344
+ js: `// Rari Server Component Bundle
345
+ // Generated at: ${new Date().toISOString()}
346
+ // Original file: ${path.relative(this.projectRoot, inputPath)}
347
+ `,
348
+ },
349
+ })
350
+
351
+ if (result.outputFiles && result.outputFiles.length > 0) {
352
+ const outputFile = result.outputFiles[0]
353
+
354
+ const finalTransformedCode = this.createSelfRegisteringModule(
355
+ outputFile.text,
356
+ componentId,
357
+ )
358
+
359
+ return finalTransformedCode
360
+ }
361
+
362
+ if (result.errors.length > 0) {
363
+ console.error('ESBuild errors:', result.errors)
364
+ throw new Error(
365
+ `ESBuild compilation failed with ${result.errors.length} errors`,
366
+ )
367
+ }
368
+
369
+ throw new Error('No output generated from ESBuild')
370
+ }
371
+ catch (error) {
372
+ console.error(`ESBuild failed for ${inputPath}:`, error)
373
+ throw error
374
+ }
375
+ }
376
+
377
+ async buildServerComponents(): Promise<ServerComponentManifest> {
378
+ const serverOutDir = path.join(this.options.outDir, this.options.serverDir)
379
+
380
+ await fs.promises.mkdir(serverOutDir, { recursive: true })
381
+
382
+ const manifest: ServerComponentManifest = {
383
+ components: {},
384
+ version: '1.0.0',
385
+ buildTime: new Date().toISOString(),
386
+ }
387
+
388
+ for (const [filePath, component] of this.serverComponents) {
389
+ const relativePath = path.relative(this.projectRoot, filePath)
390
+ const componentId = this.getComponentId(relativePath)
391
+ const bundlePath = path.join(this.options.serverDir, `${componentId}.js`)
392
+ const fullBundlePath = path.join(this.options.outDir, bundlePath)
393
+
394
+ const bundleDir = path.dirname(fullBundlePath)
395
+ await fs.promises.mkdir(bundleDir, { recursive: true })
396
+
397
+ await this.buildSingleComponent(filePath, fullBundlePath, component)
398
+
399
+ manifest.components[componentId] = {
400
+ id: componentId,
401
+ filePath,
402
+ relativePath,
403
+ bundlePath,
404
+ dependencies: component.dependencies,
405
+ hasNodeImports: component.hasNodeImports,
406
+ }
407
+ }
408
+
409
+ const manifestPath = path.join(
410
+ this.options.outDir,
411
+ this.options.manifestPath,
412
+ )
413
+ await fs.promises.writeFile(
414
+ manifestPath,
415
+ JSON.stringify(manifest, null, 2),
416
+ 'utf-8',
417
+ )
418
+
419
+ return manifest
420
+ }
421
+
422
+ private async buildSingleComponent(
423
+ inputPath: string,
424
+ outputPath: string,
425
+ _component: { dependencies: string[], hasNodeImports: boolean },
426
+ ): Promise<void> {
427
+ const componentId = this.getComponentId(
428
+ path.relative(this.projectRoot, inputPath),
429
+ )
430
+
431
+ const originalCode = await fs.promises.readFile(inputPath, 'utf-8')
432
+ const clientTransformedCode = this.transformClientImports(
433
+ originalCode,
434
+ inputPath,
435
+ )
436
+ const transformedCode = this.transformNodeImports(clientTransformedCode)
437
+
438
+ const ext = path.extname(inputPath)
439
+ let loader: string
440
+ if (ext === '.tsx') {
441
+ loader = 'tsx'
442
+ }
443
+ else if (ext === '.ts') {
444
+ loader = 'ts'
445
+ }
446
+ else if (ext === '.jsx') {
447
+ loader = 'jsx'
448
+ }
449
+ else {
450
+ loader = 'js'
451
+ }
452
+
453
+ try {
454
+ const result = await build({
455
+ stdin: {
456
+ contents: transformedCode,
457
+ resolveDir: path.dirname(inputPath),
458
+ sourcefile: inputPath,
459
+ loader: loader as any,
460
+ },
461
+ bundle: true,
462
+ platform: 'neutral',
463
+ target: 'es2022',
464
+ format: 'esm',
465
+ outfile: outputPath,
466
+ external: [],
467
+ mainFields: ['module', 'main'],
468
+ conditions: ['import', 'module', 'default'],
469
+ define: {
470
+ 'global': 'globalThis',
471
+ 'process.env.NODE_ENV': '"production"',
472
+ },
473
+ loader: {
474
+ '.ts': 'ts',
475
+ '.tsx': 'tsx',
476
+ '.js': 'js',
477
+ '.jsx': 'jsx',
478
+ },
479
+ resolveExtensions: ['.ts', '.tsx', '.js', '.jsx'],
480
+ minify: false,
481
+ minifyIdentifiers: false,
482
+ sourcemap: false,
483
+ metafile: false,
484
+ write: false,
485
+ plugins: [
486
+ {
487
+ name: 'auto-external',
488
+ setup(build) {
489
+ build.onResolve({ filter: /^[^./]/ }, async (args) => {
490
+ return { path: args.path, external: true }
491
+ })
492
+ },
493
+ },
494
+ {
495
+ name: 'resolve-server-functions',
496
+ setup(build) {
497
+ build.onResolve(
498
+ { filter: /^\.\.?\/.*functions/ },
499
+ async (args) => {
500
+ const resolvedPath = path.resolve(
501
+ path.dirname(args.importer),
502
+ args.path,
503
+ )
504
+
505
+ const possibleExtensions = [
506
+ '.ts',
507
+ '.js',
508
+ '.tsx',
509
+ '.jsx',
510
+ '/index.ts',
511
+ '/index.js',
512
+ ]
513
+ for (const ext of possibleExtensions) {
514
+ const fullPath = resolvedPath + ext
515
+ if (fs.existsSync(fullPath)) {
516
+ return { path: fullPath }
517
+ }
518
+ }
519
+
520
+ return null
521
+ },
522
+ )
523
+ },
524
+ },
525
+ ],
526
+ banner: {
527
+ js: `// Rari Server Component Bundle
528
+ // Generated at: ${new Date().toISOString()}
529
+ // Original file: ${path.relative(this.projectRoot, inputPath)}
530
+ `,
531
+ },
532
+ })
533
+
534
+ if (result.outputFiles && result.outputFiles.length > 0) {
535
+ const outputFile = result.outputFiles[0]
536
+
537
+ const finalTransformedCode = this.createSelfRegisteringModule(
538
+ outputFile.text,
539
+ componentId,
540
+ )
541
+
542
+ await fs.promises.writeFile(outputPath, finalTransformedCode, 'utf-8')
543
+ }
544
+
545
+ if (result.errors.length > 0) {
546
+ console.error('ESBuild errors:', result.errors)
547
+ throw new Error(
548
+ `ESBuild compilation failed with ${result.errors.length} errors`,
549
+ )
550
+ }
551
+
552
+ if (result.warnings.length > 0) {
553
+ console.warn('ESBuild warnings:', result.warnings)
554
+ }
555
+ }
556
+ catch (error) {
557
+ console.error(`ESBuild failed for ${inputPath}:`, error)
558
+ throw error
559
+ }
560
+ }
561
+
562
+ private createSelfRegisteringModule(
563
+ code: string,
564
+ componentId: string,
565
+ ): string {
566
+ if (code.includes('Self-registering Production Component')) {
567
+ return code
568
+ }
569
+
570
+ let transformedCode = code
571
+
572
+ let defaultExportName: string | null = null
573
+ const namedExports: string[] = []
574
+
575
+ transformedCode = transformedCode.replace(
576
+ /^export\s+default\s+function\s+(\w+)/gm,
577
+ (match, name) => {
578
+ defaultExportName = name
579
+ return `function ${name}`
580
+ },
581
+ )
582
+
583
+ transformedCode = transformedCode.replace(
584
+ /^export\s+default\s+async\s+function\s+(\w+)/gm,
585
+ (match, name) => {
586
+ defaultExportName = name
587
+ return `async function ${name}`
588
+ },
589
+ )
590
+
591
+ transformedCode = transformedCode.replace(
592
+ /^export\s+default\s+(\w+);?\s*$/gm,
593
+ (match, name) => {
594
+ defaultExportName = name
595
+ return `// Default export: ${name}`
596
+ },
597
+ )
598
+
599
+ transformedCode = transformedCode.replace(
600
+ /^export\s*\{\s*(\w+)\s+as\s+default\s*\};?\s*$/gm,
601
+ (match, name) => {
602
+ defaultExportName = name
603
+ return `// Default export: ${name}`
604
+ },
605
+ )
606
+
607
+ transformedCode = transformedCode.replace(
608
+ /^export\s*\{([^}]+)\};?\s*$/gm,
609
+ (match, exports) => {
610
+ const exportList = exports.split(',').map((exp: string) => exp.trim())
611
+ exportList.forEach((exp: string) => {
612
+ if (exp.includes('as default')) {
613
+ const actualName = exp.replace('as default', '').trim()
614
+ defaultExportName = actualName
615
+ }
616
+ else if (exp === 'default') {
617
+ const possibleDefault = `${componentId}_default`
618
+ if (transformedCode.includes(`var ${possibleDefault}`)) {
619
+ defaultExportName = possibleDefault
620
+ }
621
+ }
622
+ else {
623
+ namedExports.push(exp)
624
+ }
625
+ })
626
+ return `// Exports: ${exports}`
627
+ },
628
+ )
629
+
630
+ transformedCode = transformedCode.replace(
631
+ /^export\s+(?:async\s+)?function\s+(\w+)/gm,
632
+ (match, name) => {
633
+ namedExports.push(name)
634
+ return match.replace('export ', '')
635
+ },
636
+ )
637
+
638
+ transformedCode = transformedCode.replace(
639
+ /^export\s+(const|let|var)\s+(\w+)/gm,
640
+ (match, keyword, name) => {
641
+ namedExports.push(name)
642
+ return `${keyword} ${name}`
643
+ },
644
+ )
645
+
646
+ if (!defaultExportName) {
647
+ const possibleDefault = `${componentId}_default`
648
+ if (transformedCode.includes(`var ${possibleDefault}`)) {
649
+ defaultExportName = possibleDefault
650
+ }
651
+ }
652
+
653
+ const mainExport = defaultExportName || componentId
654
+
655
+ const selfRegisteringCode = `// Self-registering Production Component: ${componentId}
656
+ "use module";
657
+
658
+ // Original component code with exports removed for self-registration
659
+ ${transformedCode}
660
+
661
+ // Self-registration logic
662
+ (function() {
663
+ try {
664
+ const moduleKey = "${componentId}";
665
+ const registrationKey = "Component_${Math.random().toString(36).substr(2, 8)}";
666
+ let mainExport = null;
667
+ let exportedFunctions = {};
668
+
669
+ globalThis.__rsc_functions = globalThis.__rsc_functions || {};
670
+
671
+ // Register named exports
672
+ ${namedExports
673
+ .map(
674
+ name => `
675
+ if (typeof ${name} !== 'undefined') {
676
+ globalThis.${name} = ${name};
677
+ globalThis.__rsc_functions['${name}'] = ${name};
678
+ exportedFunctions['${name}'] = ${name};
679
+ }`,
680
+ )
681
+ .join('')}
682
+
683
+ // Set main export
684
+ if (typeof ${mainExport} !== 'undefined') {
685
+ mainExport = ${mainExport};
686
+ } else {
687
+ const potentialExports = {};
688
+ ${namedExports.map(name => `if (typeof ${name} !== 'undefined') potentialExports.${name} = ${name};`).join('\n ')}
689
+
690
+ if (Object.keys(potentialExports).length > 0) {
691
+ if (Object.keys(potentialExports).length === 1) {
692
+ mainExport = potentialExports[Object.keys(potentialExports)[0]];
693
+ } else {
694
+ mainExport = potentialExports;
695
+ }
696
+ }
697
+ }
698
+
699
+ if (mainExport !== null) {
700
+ if (!globalThis[moduleKey]) {
701
+ globalThis[moduleKey] = mainExport;
702
+ }
703
+
704
+ if (!globalThis[registrationKey]) {
705
+ globalThis[registrationKey] = mainExport;
706
+ }
707
+
708
+ if (typeof globalThis.RscModuleManager !== 'undefined' && globalThis.RscModuleManager.register) {
709
+ globalThis.RscModuleManager.register(moduleKey, mainExport, exportedFunctions);
710
+ }
711
+ }
712
+ } catch (error) {
713
+ console.error('Error in self-registration for ${componentId}:', error);
714
+ }
715
+ })();`
716
+
717
+ return selfRegisteringCode
718
+ }
719
+
720
+ private transformClientImports(code: string, inputPath: string): string {
721
+ let transformedCode = code
722
+
723
+ const importRegex
724
+ = /import\s+(\w+)(?:\s*,\s*\{[^}]*\})?\s+from\s+['"]([./][^'"]+)['"];?\s*$/gm
725
+ let match
726
+
727
+ const replacements: Array<{ original: string, replacement: string }> = []
728
+ let hasClientComponents = false
729
+
730
+ while (true) {
731
+ match = importRegex.exec(code)
732
+ if (match === null)
733
+ break
734
+
735
+ const [fullMatch, defaultImport, importPath] = match
736
+
737
+ const resolvedPath = this.resolveImportPath(importPath, inputPath)
738
+
739
+ if (this.isClientComponent(resolvedPath)) {
740
+ hasClientComponents = true
741
+ const componentName = defaultImport || 'default'
742
+
743
+ const replacement = `const ${componentName} = registerClientReference(
744
+ null,
745
+ ${JSON.stringify(path.relative(this.projectRoot, resolvedPath))},
746
+ "default"
747
+ );`
748
+
749
+ replacements.push({ original: fullMatch, replacement })
750
+ }
751
+ }
752
+
753
+ if (hasClientComponents) {
754
+ const functionDefinition = `
755
+ // registerClientReference function for client components
756
+ function registerClientReference(clientReference, id, exportName) {
757
+ const key = id + '#' + exportName;
758
+
759
+ const clientProxy = {};
760
+
761
+ Object.defineProperty(clientProxy, '$$typeof', {
762
+ value: Symbol.for('react.client.reference'),
763
+ enumerable: false
764
+ });
765
+
766
+ Object.defineProperty(clientProxy, '$$id', {
767
+ value: key,
768
+ enumerable: false
769
+ });
770
+
771
+ Object.defineProperty(clientProxy, '$$async', {
772
+ value: false,
773
+ enumerable: false
774
+ });
775
+
776
+ try {
777
+ if (typeof globalThis.__rari_bridge !== 'undefined' &&
778
+ typeof globalThis.__rari_bridge.registerClientReference === 'function') {
779
+ globalThis.__rari_bridge.registerClientReference(key, id, exportName);
780
+ }
781
+ } catch (error) {
782
+ console.error('Failed to register client reference with Rust bridge:', error);
783
+ }
784
+
785
+ return clientProxy;
786
+ }
787
+
788
+ `
789
+ transformedCode = functionDefinition + transformedCode
790
+ }
791
+
792
+ for (const { original, replacement } of replacements) {
793
+ transformedCode = transformedCode.replace(original, replacement)
794
+ }
795
+
796
+ return transformedCode
797
+ }
798
+
799
+ private resolveImportPath(importPath: string, importerPath: string): string {
800
+ const resolvedPath = path.resolve(path.dirname(importerPath), importPath)
801
+
802
+ const extensions = ['.tsx', '.jsx', '.ts', '.js']
803
+ for (const ext of extensions) {
804
+ const pathWithExt = `${resolvedPath}${ext}`
805
+ if (fs.existsSync(pathWithExt)) {
806
+ return pathWithExt
807
+ }
808
+ }
809
+
810
+ if (fs.existsSync(resolvedPath)) {
811
+ for (const ext of extensions) {
812
+ const indexPath = path.join(resolvedPath, `index${ext}`)
813
+ if (fs.existsSync(indexPath)) {
814
+ return indexPath
815
+ }
816
+ }
817
+ }
818
+
819
+ return `${resolvedPath}.tsx`
820
+ }
821
+
822
+ private transformNodeImports(code: string): string {
823
+ let transformedCode = code
824
+
825
+ transformedCode = transformedCode.replace(
826
+ /import\s+(\w+)\s+from\s+['"]node:process['"];?\s*$/gm,
827
+ (match, importName) => {
828
+ return `const ${importName} = globalThis.process;`
829
+ },
830
+ )
831
+
832
+ transformedCode = transformedCode.replace(
833
+ /import\s+\{([^}]+)\}\s+from\s+['"]node:fs['"];?\s*$/gm,
834
+ (match, imports) => {
835
+ const importList = imports.split(',').map((imp: string) => imp.trim())
836
+ return importList
837
+ .map((imp: string) => {
838
+ const cleanImp = imp.replace(/\s+as\s+\w+/, '')
839
+ if (cleanImp === 'existsSync') {
840
+ return `const ${cleanImp} = (path) => { try { if (globalThis.Deno?.statSync) { globalThis.Deno.statSync(path); return true; } return false; } catch (error) { return false; } };`
841
+ }
842
+ if (cleanImp === 'readFileSync') {
843
+ return `const ${cleanImp} = (path, encoding = 'utf8') => globalThis.Deno.readTextFileSync(path);`
844
+ }
845
+ return `const ${cleanImp} = globalThis.Deno?.${cleanImp} || (() => { throw new Error('${cleanImp} not available'); });`
846
+ })
847
+ .join('\n')
848
+ },
849
+ )
850
+
851
+ transformedCode = transformedCode.replace(
852
+ /import\s+\{([^}]+)\}\s+from\s+['"]node:path['"];?\s*$/gm,
853
+ (match, imports) => {
854
+ const importList = imports.split(',').map((imp: string) => imp.trim())
855
+ return importList
856
+ .map((imp: string) => {
857
+ const cleanImp = imp.replace(/\s+as\s+\w+/, '')
858
+ if (cleanImp === 'join') {
859
+ return `const ${cleanImp} = (...paths) => paths.filter(Boolean).join('/').replace(/\\/+/g, '/');`
860
+ }
861
+ return `const ${cleanImp} = globalThis.path?.${cleanImp} || (() => { throw new Error('${cleanImp} not available'); });`
862
+ })
863
+ .join('\n')
864
+ },
865
+ )
866
+
867
+ transformedCode = transformedCode.replace(
868
+ /import\s+\{([^}]+)\}\s+from\s+['"]node:process['"];?\s*$/gm,
869
+ (match, imports) => {
870
+ const importList = imports.split(',').map((imp: string) => imp.trim())
871
+ return importList
872
+ .map((imp: string) => {
873
+ const cleanImp = imp.replace(/\s+as\s+\w+/, '')
874
+ if (cleanImp === 'cwd') {
875
+ return `const ${cleanImp} = () => globalThis.Deno?.cwd?.() || '.';`
876
+ }
877
+ return `const ${cleanImp} = globalThis.process?.${cleanImp} || (() => { throw new Error('${cleanImp} not available'); });`
878
+ })
879
+ .join('\n')
880
+ },
881
+ )
882
+
883
+ return transformedCode
884
+ }
885
+
886
+ private getComponentId(relativePath: string): string {
887
+ return relativePath
888
+ .replace(/\\/g, '/')
889
+ .replace(/\.(tsx?|jsx?)$/, '')
890
+ .replace(/[^\w/-]/g, '_')
891
+ .replace(/^src\//, '')
892
+ .replace(/^components\//, '')
893
+ }
894
+ }
895
+
896
+ export function scanDirectory(dir: string, builder: ServerComponentBuilder) {
897
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
898
+
899
+ for (const entry of entries) {
900
+ const fullPath = path.join(dir, entry.name)
901
+
902
+ if (entry.isDirectory()) {
903
+ scanDirectory(fullPath, builder)
904
+ }
905
+ else if (entry.isFile() && /\.(?:tsx?|jsx?)$/.test(entry.name)) {
906
+ try {
907
+ if (builder.isServerComponent(fullPath)) {
908
+ builder.addServerComponent(fullPath)
909
+ }
910
+ }
911
+ catch (error) {
912
+ console.warn(
913
+ `[server-build] Error checking ${fullPath}:`,
914
+ error instanceof Error ? error.message : error,
915
+ )
916
+ }
917
+ }
918
+ }
919
+ }
920
+
921
+ export function createServerBuildPlugin(
922
+ options: ServerBuildOptions = {},
923
+ ): Plugin {
924
+ let builder: ServerComponentBuilder | null = null
925
+ let projectRoot: string
926
+
927
+ return {
928
+ name: 'rari-server-build',
929
+
930
+ configResolved(config) {
931
+ projectRoot = config.root
932
+ builder = new ServerComponentBuilder(projectRoot, options)
933
+ },
934
+
935
+ buildStart() {
936
+ if (!builder)
937
+ return
938
+
939
+ const srcDir = path.join(projectRoot, 'src')
940
+ if (fs.existsSync(srcDir)) {
941
+ scanDirectory(srcDir, builder)
942
+ }
943
+ },
944
+
945
+ async closeBundle() {
946
+ if (builder) {
947
+ await builder.buildServerComponents()
948
+ }
949
+ },
950
+ }
951
+ }