prev-cli 0.24.11 → 0.24.13
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/cli.js +495 -150
- package/dist/preview-runtime/build-optimized.d.ts +11 -0
- package/dist/preview-runtime/tailwind.d.ts +11 -0
- package/dist/preview-runtime/vendors.d.ts +6 -0
- package/dist/vite/config-parser.d.ts +13 -0
- package/dist/vite/preview-types.d.ts +70 -0
- package/dist/vite/previews.d.ts +6 -0
- package/package.json +3 -2
- package/src/preview-runtime/build-optimized.test.ts +47 -0
- package/src/preview-runtime/build-optimized.ts +136 -0
- package/src/preview-runtime/tailwind.test.ts +30 -0
- package/src/preview-runtime/tailwind.ts +64 -0
- package/src/preview-runtime/vendors.test.ts +15 -0
- package/src/preview-runtime/vendors.ts +52 -0
- package/src/theme/previews/AtlasPreview.tsx +528 -0
- package/src/theme/previews/ComponentPreview.tsx +180 -0
- package/src/theme/previews/FlowPreview.tsx +270 -0
- package/src/theme/previews/PreviewRouter.tsx +189 -0
- package/src/theme/previews/ScreenPreview.tsx +297 -0
- package/src/theme/previews/index.ts +5 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PreviewConfig } from './types';
|
|
2
|
+
export interface OptimizedBuildOptions {
|
|
3
|
+
vendorPath: string;
|
|
4
|
+
}
|
|
5
|
+
export interface OptimizedBuildResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
html: string;
|
|
8
|
+
css: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildOptimizedPreview(config: PreviewConfig, options: OptimizedBuildOptions): Promise<OptimizedBuildResult>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface TailwindResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
css: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
interface ContentFile {
|
|
7
|
+
path: string;
|
|
8
|
+
content: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function compileTailwind(files: ContentFile[]): Promise<TailwindResult>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type PreviewConfig, type FlowDefinition, type AtlasDefinition } from './preview-types';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a config.yaml or config.yml file and validate against schema
|
|
4
|
+
*/
|
|
5
|
+
export declare function parsePreviewConfig(filePath: string): Promise<PreviewConfig | null>;
|
|
6
|
+
/**
|
|
7
|
+
* Parse a flow index.yaml file
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseFlowDefinition(filePath: string): Promise<FlowDefinition | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Parse an atlas index.yaml file
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseAtlasDefinition(filePath: string): Promise<AtlasDefinition | null>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export type PreviewType = 'component' | 'screen' | 'flow' | 'atlas';
|
|
3
|
+
export declare const configSchema: z.ZodObject<{
|
|
4
|
+
tags: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>]>>;
|
|
5
|
+
category: z.ZodOptional<z.ZodString>;
|
|
6
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
7
|
+
draft: "draft";
|
|
8
|
+
stable: "stable";
|
|
9
|
+
deprecated: "deprecated";
|
|
10
|
+
}>>;
|
|
11
|
+
title: z.ZodOptional<z.ZodString>;
|
|
12
|
+
description: z.ZodOptional<z.ZodString>;
|
|
13
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export type PreviewConfig = z.infer<typeof configSchema>;
|
|
16
|
+
export interface PreviewUnit {
|
|
17
|
+
type: PreviewType;
|
|
18
|
+
name: string;
|
|
19
|
+
path: string;
|
|
20
|
+
route: string;
|
|
21
|
+
config: PreviewConfig | null;
|
|
22
|
+
files: {
|
|
23
|
+
index: string;
|
|
24
|
+
states?: string[];
|
|
25
|
+
schema?: string;
|
|
26
|
+
docs?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface FlowStep {
|
|
30
|
+
screen: string;
|
|
31
|
+
state?: string;
|
|
32
|
+
note?: string;
|
|
33
|
+
trigger?: string;
|
|
34
|
+
highlight?: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface FlowDefinition {
|
|
37
|
+
name: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
steps: FlowStep[];
|
|
40
|
+
}
|
|
41
|
+
export interface AtlasArea {
|
|
42
|
+
title: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
parent?: string;
|
|
45
|
+
children?: string[];
|
|
46
|
+
access?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface AtlasDefinition {
|
|
49
|
+
name: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
hierarchy: {
|
|
52
|
+
root: string;
|
|
53
|
+
areas: Record<string, AtlasArea>;
|
|
54
|
+
};
|
|
55
|
+
routes?: Record<string, {
|
|
56
|
+
area: string;
|
|
57
|
+
screen: string;
|
|
58
|
+
guard?: string;
|
|
59
|
+
}>;
|
|
60
|
+
navigation?: Record<string, Array<{
|
|
61
|
+
area?: string;
|
|
62
|
+
icon?: string;
|
|
63
|
+
action?: string;
|
|
64
|
+
}>>;
|
|
65
|
+
relationships?: Array<{
|
|
66
|
+
from: string;
|
|
67
|
+
to: string;
|
|
68
|
+
type: string;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
package/dist/vite/previews.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PreviewFile, PreviewConfig } from '../preview-runtime/types';
|
|
2
|
+
import type { PreviewUnit } from './preview-types';
|
|
2
3
|
export interface Preview {
|
|
3
4
|
name: string;
|
|
4
5
|
route: string;
|
|
@@ -21,3 +22,8 @@ export declare function detectEntry(files: PreviewFile[]): string;
|
|
|
21
22
|
* Build a PreviewConfig for WASM runtime
|
|
22
23
|
*/
|
|
23
24
|
export declare function buildPreviewConfig(previewDir: string): Promise<PreviewConfig>;
|
|
25
|
+
/**
|
|
26
|
+
* Scan previews with multi-type folder structure support
|
|
27
|
+
* Supports: components/, screens/, flows/, atlas/
|
|
28
|
+
*/
|
|
29
|
+
export declare function scanPreviewUnits(rootDir: string): Promise<PreviewUnit[]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prev-cli",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.13",
|
|
4
4
|
"description": "Transform MDX directories into beautiful documentation websites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
"remark-gfm": "^4.0.0",
|
|
66
66
|
"tailwind-merge": "^2.5.0",
|
|
67
67
|
"tailwindcss": "^4.0.0",
|
|
68
|
-
"vite": "npm:rolldown-vite@^7.3.1"
|
|
68
|
+
"vite": "npm:rolldown-vite@^7.3.1",
|
|
69
|
+
"zod": "^4.3.5"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
72
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import { buildOptimizedPreview } from './build-optimized'
|
|
3
|
+
import type { PreviewConfig } from './types'
|
|
4
|
+
|
|
5
|
+
// Tailwind CLI can be slow on first run
|
|
6
|
+
const TAILWIND_TIMEOUT = 30000
|
|
7
|
+
|
|
8
|
+
test('buildOptimizedPreview generates HTML with local vendor imports', async () => {
|
|
9
|
+
const config: PreviewConfig = {
|
|
10
|
+
files: [
|
|
11
|
+
{
|
|
12
|
+
path: 'index.tsx',
|
|
13
|
+
content: `export default function App() { return <div className="p-4">Hello</div> }`,
|
|
14
|
+
type: 'tsx',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
entry: 'index.tsx',
|
|
18
|
+
tailwind: true,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = await buildOptimizedPreview(config, { vendorPath: '../_vendors/runtime.js' })
|
|
22
|
+
|
|
23
|
+
expect(result.success).toBe(true)
|
|
24
|
+
expect(result.html).toContain('../_vendors/runtime.js')
|
|
25
|
+
expect(result.html).not.toContain('esm.sh')
|
|
26
|
+
expect(result.html).not.toContain('tailwindcss/browser')
|
|
27
|
+
}, TAILWIND_TIMEOUT)
|
|
28
|
+
|
|
29
|
+
test('buildOptimizedPreview includes compiled CSS', async () => {
|
|
30
|
+
const config: PreviewConfig = {
|
|
31
|
+
files: [
|
|
32
|
+
{
|
|
33
|
+
path: 'index.tsx',
|
|
34
|
+
content: `export default function App() { return <div className="flex items-center bg-red-500">Hello</div> }`,
|
|
35
|
+
type: 'tsx',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
entry: 'index.tsx',
|
|
39
|
+
tailwind: true,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await buildOptimizedPreview(config, { vendorPath: '../_vendors/runtime.js' })
|
|
43
|
+
|
|
44
|
+
expect(result.success).toBe(true)
|
|
45
|
+
expect(result.css).toContain('flex')
|
|
46
|
+
expect(result.css).toContain('bg-red-500')
|
|
47
|
+
}, TAILWIND_TIMEOUT)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { build } from 'esbuild'
|
|
2
|
+
import type { PreviewConfig } from './types'
|
|
3
|
+
import { compileTailwind } from './tailwind'
|
|
4
|
+
|
|
5
|
+
export interface OptimizedBuildOptions {
|
|
6
|
+
vendorPath: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface OptimizedBuildResult {
|
|
10
|
+
success: boolean
|
|
11
|
+
html: string
|
|
12
|
+
css: string
|
|
13
|
+
error?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function buildOptimizedPreview(
|
|
17
|
+
config: PreviewConfig,
|
|
18
|
+
options: OptimizedBuildOptions
|
|
19
|
+
): Promise<OptimizedBuildResult> {
|
|
20
|
+
try {
|
|
21
|
+
const virtualFs: Record<string, { contents: string; loader: string }> = {}
|
|
22
|
+
for (const file of config.files) {
|
|
23
|
+
const ext = file.path.split('.').pop()?.toLowerCase()
|
|
24
|
+
const loader = ext === 'css' ? 'css' : ext === 'json' ? 'json' : ext || 'tsx'
|
|
25
|
+
virtualFs[file.path] = { contents: file.content, loader }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const entryFile = config.files.find(f => f.path === config.entry)
|
|
29
|
+
if (!entryFile) {
|
|
30
|
+
return { success: false, html: '', css: '', error: `Entry file not found: ${config.entry}` }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const hasDefaultExport = /export\s+default/.test(entryFile.content)
|
|
34
|
+
const userCssCollected: string[] = []
|
|
35
|
+
|
|
36
|
+
const entryCode = hasDefaultExport
|
|
37
|
+
? `
|
|
38
|
+
import React, { createRoot } from '${options.vendorPath}'
|
|
39
|
+
import App from './${config.entry}'
|
|
40
|
+
const root = createRoot(document.getElementById('root'))
|
|
41
|
+
root.render(React.createElement(App))
|
|
42
|
+
`
|
|
43
|
+
: `import './${config.entry}'`
|
|
44
|
+
|
|
45
|
+
const result = await build({
|
|
46
|
+
stdin: { contents: entryCode, loader: 'tsx', resolveDir: '/' },
|
|
47
|
+
bundle: true,
|
|
48
|
+
write: false,
|
|
49
|
+
format: 'esm',
|
|
50
|
+
jsx: 'automatic',
|
|
51
|
+
jsxImportSource: 'react',
|
|
52
|
+
target: 'es2020',
|
|
53
|
+
minify: true,
|
|
54
|
+
plugins: [
|
|
55
|
+
{
|
|
56
|
+
name: 'optimized-preview',
|
|
57
|
+
setup(build) {
|
|
58
|
+
// External: vendor runtime
|
|
59
|
+
build.onResolve(
|
|
60
|
+
{ filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) },
|
|
61
|
+
args => {
|
|
62
|
+
return { path: args.path, external: true }
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// External: React (map to vendor bundle)
|
|
67
|
+
build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
|
|
68
|
+
return { path: options.vendorPath, external: true }
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Resolve relative imports
|
|
72
|
+
build.onResolve({ filter: /^\./ }, args => {
|
|
73
|
+
let resolved = args.path.replace(/^\.\//, '')
|
|
74
|
+
if (!resolved.includes('.')) {
|
|
75
|
+
for (const ext of ['.tsx', '.ts', '.jsx', '.js', '.css']) {
|
|
76
|
+
if (virtualFs[resolved + ext]) {
|
|
77
|
+
resolved = resolved + ext
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { path: resolved, namespace: 'virtual' }
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// Load from virtual FS
|
|
86
|
+
build.onLoad({ filter: /.*/, namespace: 'virtual' }, args => {
|
|
87
|
+
const file = virtualFs[args.path]
|
|
88
|
+
if (file) {
|
|
89
|
+
if (file.loader === 'css') {
|
|
90
|
+
userCssCollected.push(file.contents)
|
|
91
|
+
return { contents: '', loader: 'js' }
|
|
92
|
+
}
|
|
93
|
+
return { contents: file.contents, loader: file.loader as any }
|
|
94
|
+
}
|
|
95
|
+
return { contents: '', loader: 'empty' }
|
|
96
|
+
})
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const jsFile = result.outputFiles?.find(f => f.path.endsWith('.js')) || result.outputFiles?.[0]
|
|
103
|
+
const jsCode = jsFile?.text || ''
|
|
104
|
+
|
|
105
|
+
let css = ''
|
|
106
|
+
if (config.tailwind) {
|
|
107
|
+
const tailwindResult = await compileTailwind(
|
|
108
|
+
config.files.map(f => ({ path: f.path, content: f.content }))
|
|
109
|
+
)
|
|
110
|
+
if (tailwindResult.success) css = tailwindResult.css
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const userCss = userCssCollected.join('\n')
|
|
114
|
+
const allCss = css + '\n' + userCss
|
|
115
|
+
|
|
116
|
+
const html = `<!DOCTYPE html>
|
|
117
|
+
<html lang="en">
|
|
118
|
+
<head>
|
|
119
|
+
<meta charset="UTF-8">
|
|
120
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
121
|
+
<title>Preview</title>
|
|
122
|
+
<style>${allCss}</style>
|
|
123
|
+
<style>body { margin: 0; } #root { min-height: 100vh; }</style>
|
|
124
|
+
</head>
|
|
125
|
+
<body>
|
|
126
|
+
<div id="root"></div>
|
|
127
|
+
<script type="module" src="${options.vendorPath}"></script>
|
|
128
|
+
<script type="module">${jsCode}</script>
|
|
129
|
+
</body>
|
|
130
|
+
</html>`
|
|
131
|
+
|
|
132
|
+
return { success: true, html, css: allCss }
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return { success: false, html: '', css: '', error: err instanceof Error ? err.message : String(err) }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import { compileTailwind } from './tailwind'
|
|
3
|
+
|
|
4
|
+
test('compileTailwind extracts used classes from content', async () => {
|
|
5
|
+
const content = `
|
|
6
|
+
export default function App() {
|
|
7
|
+
return <div className="flex items-center p-4 bg-blue-500">Hello</div>
|
|
8
|
+
}
|
|
9
|
+
`
|
|
10
|
+
|
|
11
|
+
const result = await compileTailwind([{ path: 'App.tsx', content }])
|
|
12
|
+
|
|
13
|
+
expect(result.success).toBe(true)
|
|
14
|
+
expect(result.css).toBeDefined()
|
|
15
|
+
expect(result.css).toContain('flex')
|
|
16
|
+
expect(result.css).toContain('items-center')
|
|
17
|
+
expect(result.css).toContain('bg-blue-500')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('compileTailwind returns empty CSS for no Tailwind classes', async () => {
|
|
21
|
+
const content = `
|
|
22
|
+
export default function App() {
|
|
23
|
+
return <div style={{ color: 'red' }}>Hello</div>
|
|
24
|
+
}
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
const result = await compileTailwind([{ path: 'App.tsx', content }])
|
|
28
|
+
|
|
29
|
+
expect(result.success).toBe(true)
|
|
30
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { $ } from 'bun'
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'fs'
|
|
3
|
+
import { join, dirname } from 'path'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
|
|
6
|
+
export interface TailwindResult {
|
|
7
|
+
success: boolean
|
|
8
|
+
css: string
|
|
9
|
+
error?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ContentFile {
|
|
13
|
+
path: string
|
|
14
|
+
content: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function compileTailwind(files: ContentFile[]): Promise<TailwindResult> {
|
|
18
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'prev-tailwind-'))
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Write content files (create parent dirs for nested paths)
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const filePath = join(tempDir, file.path)
|
|
24
|
+
const parentDir = dirname(filePath)
|
|
25
|
+
mkdirSync(parentDir, { recursive: true })
|
|
26
|
+
writeFileSync(filePath, file.content)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create Tailwind config - use .cjs for compatibility
|
|
30
|
+
const configContent = `
|
|
31
|
+
module.exports = {
|
|
32
|
+
content: [${JSON.stringify(tempDir + '/**/*.{tsx,jsx,ts,js,html}')}],
|
|
33
|
+
}
|
|
34
|
+
`
|
|
35
|
+
const configPath = join(tempDir, 'tailwind.config.cjs')
|
|
36
|
+
writeFileSync(configPath, configContent)
|
|
37
|
+
|
|
38
|
+
// Create input CSS
|
|
39
|
+
const inputCss = `
|
|
40
|
+
@tailwind base;
|
|
41
|
+
@tailwind components;
|
|
42
|
+
@tailwind utilities;
|
|
43
|
+
`
|
|
44
|
+
const inputPath = join(tempDir, 'input.css')
|
|
45
|
+
writeFileSync(inputPath, inputCss)
|
|
46
|
+
|
|
47
|
+
const outputPath = join(tempDir, 'output.css')
|
|
48
|
+
|
|
49
|
+
// Run Tailwind CLI
|
|
50
|
+
await $`bunx tailwindcss -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet()
|
|
51
|
+
|
|
52
|
+
const css = readFileSync(outputPath, 'utf-8')
|
|
53
|
+
|
|
54
|
+
return { success: true, css }
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
css: '',
|
|
59
|
+
error: err instanceof Error ? err.message : String(err),
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import { buildVendorBundle } from './vendors'
|
|
3
|
+
|
|
4
|
+
test('buildVendorBundle creates runtime.js with React', async () => {
|
|
5
|
+
const result = await buildVendorBundle()
|
|
6
|
+
expect(result.success).toBe(true)
|
|
7
|
+
expect(result.code).toBeDefined()
|
|
8
|
+
expect(result.code).toContain('createElement')
|
|
9
|
+
expect(result.code).toContain('createRoot')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('buildVendorBundle output is valid ESM', async () => {
|
|
13
|
+
const result = await buildVendorBundle()
|
|
14
|
+
expect(result.code).toContain('export')
|
|
15
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { build } from 'esbuild'
|
|
2
|
+
import { dirname } from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
|
|
5
|
+
// Resolve from CLI's location, not user's project (React is our dependency)
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
|
|
8
|
+
export interface VendorBundleResult {
|
|
9
|
+
success: boolean
|
|
10
|
+
code: string
|
|
11
|
+
error?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function buildVendorBundle(): Promise<VendorBundleResult> {
|
|
15
|
+
try {
|
|
16
|
+
const entryCode = `
|
|
17
|
+
import * as React from 'react'
|
|
18
|
+
import * as ReactDOM from 'react-dom'
|
|
19
|
+
import { createRoot } from 'react-dom/client'
|
|
20
|
+
export { jsx, jsxs, Fragment } from 'react/jsx-runtime'
|
|
21
|
+
export { React, ReactDOM, createRoot }
|
|
22
|
+
export default React
|
|
23
|
+
`
|
|
24
|
+
|
|
25
|
+
const result = await build({
|
|
26
|
+
stdin: {
|
|
27
|
+
contents: entryCode,
|
|
28
|
+
loader: 'ts',
|
|
29
|
+
resolveDir: __dirname, // Resolve React from CLI's node_modules
|
|
30
|
+
},
|
|
31
|
+
bundle: true,
|
|
32
|
+
write: false,
|
|
33
|
+
format: 'esm',
|
|
34
|
+
target: 'es2020',
|
|
35
|
+
minify: true,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Select JS output file explicitly (in case sourcemaps are added later)
|
|
39
|
+
const jsFile = result.outputFiles?.find(f => f.path.endsWith('.js')) || result.outputFiles?.[0]
|
|
40
|
+
if (!jsFile) {
|
|
41
|
+
return { success: false, code: '', error: 'No output generated' }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { success: true, code: jsFile.text }
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
code: '',
|
|
49
|
+
error: err instanceof Error ? err.message : String(err),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|