@aprovan/patchwork 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +22 -0
- package/.github/workflows/publish.yml +41 -0
- package/.prettierignore +17 -0
- package/LICENSE +373 -0
- package/README.md +15 -0
- package/apps/chat/.utcp_config.json +14 -0
- package/apps/chat/.working/widgets/27060b91-a2a5-4272-b243-6eb904bd4070/main.tsx +107 -0
- package/apps/chat/index.html +17 -0
- package/apps/chat/node_modules/.bin/autoprefixer +17 -0
- package/apps/chat/node_modules/.bin/browserslist +17 -0
- package/apps/chat/node_modules/.bin/conc +17 -0
- package/apps/chat/node_modules/.bin/concurrently +17 -0
- package/apps/chat/node_modules/.bin/copilot-proxy +17 -0
- package/apps/chat/node_modules/.bin/jiti +17 -0
- package/apps/chat/node_modules/.bin/tailwind +17 -0
- package/apps/chat/node_modules/.bin/tailwindcss +17 -0
- package/apps/chat/node_modules/.bin/tsc +17 -0
- package/apps/chat/node_modules/.bin/tsserver +17 -0
- package/apps/chat/node_modules/.bin/tsx +17 -0
- package/apps/chat/node_modules/.bin/vite +17 -0
- package/apps/chat/package.json +55 -0
- package/apps/chat/postcss.config.js +6 -0
- package/apps/chat/src/App.tsx +7 -0
- package/apps/chat/src/components/ui/avatar.tsx +48 -0
- package/apps/chat/src/components/ui/badge.tsx +36 -0
- package/apps/chat/src/components/ui/button.tsx +56 -0
- package/apps/chat/src/components/ui/card.tsx +86 -0
- package/apps/chat/src/components/ui/collapsible.tsx +9 -0
- package/apps/chat/src/components/ui/dialog.tsx +60 -0
- package/apps/chat/src/components/ui/input.tsx +25 -0
- package/apps/chat/src/components/ui/scroll-area.tsx +46 -0
- package/apps/chat/src/index.css +190 -0
- package/apps/chat/src/lib/utils.ts +6 -0
- package/apps/chat/src/main.tsx +10 -0
- package/apps/chat/src/pages/ChatPage.tsx +460 -0
- package/apps/chat/tailwind.config.js +71 -0
- package/apps/chat/tsconfig.json +25 -0
- package/apps/chat/vite.config.ts +26 -0
- package/package.json +35 -0
- package/packages/bobbin/node_modules/.bin/esbuild +14 -0
- package/packages/bobbin/node_modules/.bin/jiti +17 -0
- package/packages/bobbin/node_modules/.bin/tsc +17 -0
- package/packages/bobbin/node_modules/.bin/tsserver +17 -0
- package/packages/bobbin/node_modules/.bin/tsup +17 -0
- package/packages/bobbin/node_modules/.bin/tsup-node +17 -0
- package/packages/bobbin/node_modules/.bin/tsx +17 -0
- package/packages/bobbin/package.json +30 -0
- package/packages/bobbin/src/Bobbin.tsx +89 -0
- package/packages/bobbin/src/components/EditPanel/EditPanel.tsx +376 -0
- package/packages/bobbin/src/components/EditPanel/controls/ColorPicker.tsx +138 -0
- package/packages/bobbin/src/components/EditPanel/controls/QuickSelectDropdown.tsx +142 -0
- package/packages/bobbin/src/components/EditPanel/controls/SliderInput.tsx +94 -0
- package/packages/bobbin/src/components/EditPanel/controls/SpacingControl.tsx +285 -0
- package/packages/bobbin/src/components/EditPanel/controls/ToggleGroup.tsx +37 -0
- package/packages/bobbin/src/components/EditPanel/controls/TokenDropdown.tsx +33 -0
- package/packages/bobbin/src/components/EditPanel/sections/AnnotationSection.tsx +136 -0
- package/packages/bobbin/src/components/EditPanel/sections/BackgroundSection.tsx +79 -0
- package/packages/bobbin/src/components/EditPanel/sections/EffectsSection.tsx +85 -0
- package/packages/bobbin/src/components/EditPanel/sections/LayoutSection.tsx +224 -0
- package/packages/bobbin/src/components/EditPanel/sections/SectionWrapper.tsx +57 -0
- package/packages/bobbin/src/components/EditPanel/sections/SizeSection.tsx +166 -0
- package/packages/bobbin/src/components/EditPanel/sections/SpacingSection.tsx +69 -0
- package/packages/bobbin/src/components/EditPanel/sections/TypographySection.tsx +148 -0
- package/packages/bobbin/src/components/Inspector/Inspector.tsx +221 -0
- package/packages/bobbin/src/components/Overlay/ControlHandles.tsx +572 -0
- package/packages/bobbin/src/components/Overlay/MarginPaddingOverlay.tsx +229 -0
- package/packages/bobbin/src/components/Overlay/SelectionOverlay.tsx +73 -0
- package/packages/bobbin/src/components/Pill/Pill.tsx +155 -0
- package/packages/bobbin/src/components/ThemeToggle/ThemeToggle.tsx +72 -0
- package/packages/bobbin/src/core/changeSerializer.ts +139 -0
- package/packages/bobbin/src/core/useBobbin.ts +399 -0
- package/packages/bobbin/src/core/useChangeTracker.ts +186 -0
- package/packages/bobbin/src/core/useClipboard.ts +21 -0
- package/packages/bobbin/src/core/useElementSelection.ts +146 -0
- package/packages/bobbin/src/index.ts +46 -0
- package/packages/bobbin/src/tokens/borders.ts +19 -0
- package/packages/bobbin/src/tokens/colors.ts +150 -0
- package/packages/bobbin/src/tokens/index.ts +37 -0
- package/packages/bobbin/src/tokens/shadows.ts +10 -0
- package/packages/bobbin/src/tokens/spacing.ts +37 -0
- package/packages/bobbin/src/tokens/typography.ts +51 -0
- package/packages/bobbin/src/types.ts +157 -0
- package/packages/bobbin/src/utils/animation.ts +40 -0
- package/packages/bobbin/src/utils/dom.ts +36 -0
- package/packages/bobbin/src/utils/selectors.ts +76 -0
- package/packages/bobbin/tsconfig.json +10 -0
- package/packages/bobbin/tsup.config.ts +10 -0
- package/packages/compiler/node_modules/.bin/esbuild +17 -0
- package/packages/compiler/node_modules/.bin/jiti +17 -0
- package/packages/compiler/node_modules/.bin/tsc +17 -0
- package/packages/compiler/node_modules/.bin/tsserver +17 -0
- package/packages/compiler/node_modules/.bin/tsup +17 -0
- package/packages/compiler/node_modules/.bin/tsup-node +17 -0
- package/packages/compiler/node_modules/.bin/tsx +17 -0
- package/packages/compiler/package.json +38 -0
- package/packages/compiler/src/compiler.ts +258 -0
- package/packages/compiler/src/images/index.ts +13 -0
- package/packages/compiler/src/images/loader.ts +234 -0
- package/packages/compiler/src/images/registry.ts +112 -0
- package/packages/compiler/src/index.ts +141 -0
- package/packages/compiler/src/mount/bridge.ts +399 -0
- package/packages/compiler/src/mount/embedded.ts +306 -0
- package/packages/compiler/src/mount/iframe.ts +433 -0
- package/packages/compiler/src/mount/index.ts +18 -0
- package/packages/compiler/src/schemas.ts +169 -0
- package/packages/compiler/src/transforms/cdn.ts +411 -0
- package/packages/compiler/src/transforms/index.ts +4 -0
- package/packages/compiler/src/transforms/vfs.ts +138 -0
- package/packages/compiler/src/types.ts +233 -0
- package/packages/compiler/src/vfs/backends/indexeddb.ts +66 -0
- package/packages/compiler/src/vfs/backends/local-fs.ts +41 -0
- package/packages/compiler/src/vfs/backends/s3.ts +60 -0
- package/packages/compiler/src/vfs/index.ts +11 -0
- package/packages/compiler/src/vfs/project.ts +56 -0
- package/packages/compiler/src/vfs/store.ts +53 -0
- package/packages/compiler/src/vfs/types.ts +20 -0
- package/packages/compiler/tsconfig.json +8 -0
- package/packages/compiler/tsup.config.ts +14 -0
- package/packages/editor/node_modules/.bin/jiti +17 -0
- package/packages/editor/node_modules/.bin/tsc +17 -0
- package/packages/editor/node_modules/.bin/tsserver +17 -0
- package/packages/editor/node_modules/.bin/tsup +17 -0
- package/packages/editor/node_modules/.bin/tsup-node +17 -0
- package/packages/editor/node_modules/.bin/tsx +17 -0
- package/packages/editor/package.json +45 -0
- package/packages/editor/src/components/CodeBlockExtension.tsx +190 -0
- package/packages/editor/src/components/CodePreview.tsx +344 -0
- package/packages/editor/src/components/MarkdownEditor.tsx +270 -0
- package/packages/editor/src/components/ServicesInspector.tsx +118 -0
- package/packages/editor/src/components/edit/EditHistory.tsx +89 -0
- package/packages/editor/src/components/edit/EditModal.tsx +236 -0
- package/packages/editor/src/components/edit/FileTree.tsx +144 -0
- package/packages/editor/src/components/edit/api.ts +100 -0
- package/packages/editor/src/components/edit/index.ts +6 -0
- package/packages/editor/src/components/edit/types.ts +53 -0
- package/packages/editor/src/components/edit/useEditSession.ts +164 -0
- package/packages/editor/src/components/index.ts +5 -0
- package/packages/editor/src/index.ts +72 -0
- package/packages/editor/src/lib/code-extractor.ts +210 -0
- package/packages/editor/src/lib/diff.ts +308 -0
- package/packages/editor/src/lib/index.ts +4 -0
- package/packages/editor/src/lib/utils.ts +6 -0
- package/packages/editor/src/lib/vfs.ts +106 -0
- package/packages/editor/tsconfig.json +10 -0
- package/packages/editor/tsup.config.ts +10 -0
- package/packages/images/ink/node_modules/.bin/jiti +17 -0
- package/packages/images/ink/node_modules/.bin/tsc +17 -0
- package/packages/images/ink/node_modules/.bin/tsserver +17 -0
- package/packages/images/ink/node_modules/.bin/tsup +17 -0
- package/packages/images/ink/node_modules/.bin/tsup-node +17 -0
- package/packages/images/ink/node_modules/.bin/tsx +17 -0
- package/packages/images/ink/package.json +53 -0
- package/packages/images/ink/src/index.ts +48 -0
- package/packages/images/ink/src/runner.ts +331 -0
- package/packages/images/ink/src/setup.ts +123 -0
- package/packages/images/ink/tsconfig.json +10 -0
- package/packages/images/ink/tsup.config.ts +11 -0
- package/packages/images/shadcn/node_modules/.bin/jiti +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsc +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsserver +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsup +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsup-node +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsx +17 -0
- package/packages/images/shadcn/package.json +82 -0
- package/packages/images/shadcn/src/html.ts +341 -0
- package/packages/images/shadcn/src/index.ts +37 -0
- package/packages/images/shadcn/src/setup.ts +287 -0
- package/packages/images/shadcn/tsconfig.json +9 -0
- package/packages/images/shadcn/tsup.config.ts +13 -0
- package/packages/images/vanilla/node_modules/.bin/jiti +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsc +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsserver +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsup +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsup-node +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsx +17 -0
- package/packages/images/vanilla/package.json +35 -0
- package/packages/images/vanilla/src/index.ts +7 -0
- package/packages/images/vanilla/src/setup.ts +6 -0
- package/packages/images/vanilla/tsconfig.json +9 -0
- package/packages/images/vanilla/tsup.config.ts +10 -0
- package/packages/patchwork/node_modules/.bin/jiti +17 -0
- package/packages/patchwork/node_modules/.bin/tsc +17 -0
- package/packages/patchwork/node_modules/.bin/tsserver +17 -0
- package/packages/patchwork/node_modules/.bin/tsup +17 -0
- package/packages/patchwork/node_modules/.bin/tsup-node +17 -0
- package/packages/patchwork/node_modules/.bin/tsx +17 -0
- package/packages/patchwork/package.json +27 -0
- package/packages/patchwork/src/index.ts +15 -0
- package/packages/patchwork/src/services/index.ts +11 -0
- package/packages/patchwork/src/services/proxy.ts +213 -0
- package/packages/patchwork/src/services/types.ts +28 -0
- package/packages/patchwork/src/types.ts +116 -0
- package/packages/patchwork/tsconfig.json +8 -0
- package/packages/patchwork/tsup.config.ts +14 -0
- package/packages/stitchery/node_modules/.bin/jiti +17 -0
- package/packages/stitchery/node_modules/.bin/tsc +17 -0
- package/packages/stitchery/node_modules/.bin/tsserver +17 -0
- package/packages/stitchery/node_modules/.bin/tsup +17 -0
- package/packages/stitchery/node_modules/.bin/tsup-node +17 -0
- package/packages/stitchery/node_modules/.bin/tsx +17 -0
- package/packages/stitchery/package.json +40 -0
- package/packages/stitchery/src/cli.ts +116 -0
- package/packages/stitchery/src/index.ts +16 -0
- package/packages/stitchery/src/prompts.ts +326 -0
- package/packages/stitchery/src/server/index.ts +365 -0
- package/packages/stitchery/src/server/local-packages.ts +91 -0
- package/packages/stitchery/src/server/routes.ts +122 -0
- package/packages/stitchery/src/server/services.ts +382 -0
- package/packages/stitchery/src/server/vfs-routes.ts +142 -0
- package/packages/stitchery/src/types.ts +59 -0
- package/packages/stitchery/tsconfig.json +13 -0
- package/packages/stitchery/tsup.config.ts +15 -0
- package/packages/utcp/node_modules/.bin/jiti +17 -0
- package/packages/utcp/node_modules/.bin/tsc +17 -0
- package/packages/utcp/node_modules/.bin/tsserver +17 -0
- package/packages/utcp/node_modules/.bin/tsup +17 -0
- package/packages/utcp/node_modules/.bin/tsup-node +17 -0
- package/packages/utcp/node_modules/.bin/tsx +17 -0
- package/packages/utcp/package.json +38 -0
- package/packages/utcp/src/index.ts +153 -0
- package/packages/utcp/tsconfig.json +8 -0
- package/packages/utcp/tsup.config.ts +12 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.json +18 -0
- package/turbo.json +23 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN transform - converts bare imports to CDN URLs (esm.sh)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Plugin } from 'esbuild-wasm';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CDN_BASE = 'https://esm.sh';
|
|
8
|
+
let cdnBaseUrl = DEFAULT_CDN_BASE;
|
|
9
|
+
|
|
10
|
+
export function setCdnBaseUrl(url: string): void {
|
|
11
|
+
cdnBaseUrl = url;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getCdnBaseUrl(): string {
|
|
15
|
+
return cdnBaseUrl;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Packages that should be externalized (not bundled from CDN)
|
|
19
|
+
const EXTERNAL_PACKAGES = new Set(['react', 'react-dom', 'ink']);
|
|
20
|
+
|
|
21
|
+
// Built-in Node.js modules that should remain external
|
|
22
|
+
const NODE_BUILTINS = new Set([
|
|
23
|
+
'assert',
|
|
24
|
+
'buffer',
|
|
25
|
+
'child_process',
|
|
26
|
+
'cluster',
|
|
27
|
+
'crypto',
|
|
28
|
+
'dgram',
|
|
29
|
+
'dns',
|
|
30
|
+
'events',
|
|
31
|
+
'fs',
|
|
32
|
+
'http',
|
|
33
|
+
'http2',
|
|
34
|
+
'https',
|
|
35
|
+
'net',
|
|
36
|
+
'os',
|
|
37
|
+
'path',
|
|
38
|
+
'perf_hooks',
|
|
39
|
+
'process',
|
|
40
|
+
'querystring',
|
|
41
|
+
'readline',
|
|
42
|
+
'stream',
|
|
43
|
+
'string_decoder',
|
|
44
|
+
'timers',
|
|
45
|
+
'tls',
|
|
46
|
+
'tty',
|
|
47
|
+
'url',
|
|
48
|
+
'util',
|
|
49
|
+
'v8',
|
|
50
|
+
'vm',
|
|
51
|
+
'worker_threads',
|
|
52
|
+
'zlib',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse a package specifier into name and version
|
|
57
|
+
*/
|
|
58
|
+
export function parsePackageSpec(spec: string): {
|
|
59
|
+
name: string;
|
|
60
|
+
version?: string;
|
|
61
|
+
} {
|
|
62
|
+
// Handle scoped packages (@scope/name)
|
|
63
|
+
if (spec.startsWith('@')) {
|
|
64
|
+
const parts = spec.split('/');
|
|
65
|
+
if (parts.length >= 2) {
|
|
66
|
+
const scope = parts[0];
|
|
67
|
+
const nameAndVersion = parts.slice(1).join('/');
|
|
68
|
+
const atIndex = nameAndVersion.lastIndexOf('@');
|
|
69
|
+
if (atIndex > 0) {
|
|
70
|
+
return {
|
|
71
|
+
name: `${scope}/${nameAndVersion.slice(0, atIndex)}`,
|
|
72
|
+
version: nameAndVersion.slice(atIndex + 1),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return { name: `${scope}/${nameAndVersion}` };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Handle non-scoped packages
|
|
80
|
+
const atIndex = spec.lastIndexOf('@');
|
|
81
|
+
if (atIndex > 0) {
|
|
82
|
+
return {
|
|
83
|
+
name: spec.slice(0, atIndex),
|
|
84
|
+
version: spec.slice(atIndex + 1),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { name: spec };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Convert a package specifier to an esm.sh URL
|
|
92
|
+
*
|
|
93
|
+
* @param packageName - The npm package name
|
|
94
|
+
* @param version - Optional version specifier
|
|
95
|
+
* @param subpath - Optional subpath (e.g., '/client' for 'react-dom/client')
|
|
96
|
+
* @param deps - Optional dependency version overrides (use ?deps=react@18)
|
|
97
|
+
*/
|
|
98
|
+
export function toEsmShUrl(
|
|
99
|
+
packageName: string,
|
|
100
|
+
version?: string,
|
|
101
|
+
subpath?: string,
|
|
102
|
+
deps?: Record<string, string>,
|
|
103
|
+
): string {
|
|
104
|
+
let url = `${cdnBaseUrl}/${packageName}`;
|
|
105
|
+
|
|
106
|
+
if (version) {
|
|
107
|
+
url += `@${version}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (subpath) {
|
|
111
|
+
url += `/${subpath}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Add deps flag to ensure consistent dependency versions across all packages
|
|
115
|
+
// This makes all packages use the same React version, avoiding version mismatches
|
|
116
|
+
if (deps && Object.keys(deps).length > 0) {
|
|
117
|
+
const depsStr = Object.entries(deps)
|
|
118
|
+
.map(([name, ver]) => `${name}@${ver}`)
|
|
119
|
+
.join(',');
|
|
120
|
+
url += `?deps=${depsStr}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return url;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if an import path is a bare module specifier
|
|
128
|
+
*/
|
|
129
|
+
export function isBareImport(path: string): boolean {
|
|
130
|
+
// Not bare if starts with ., /, or is a URL
|
|
131
|
+
if (
|
|
132
|
+
path.startsWith('.') ||
|
|
133
|
+
path.startsWith('/') ||
|
|
134
|
+
path.startsWith('http://') ||
|
|
135
|
+
path.startsWith('https://')
|
|
136
|
+
) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Extract package name and subpath from an import
|
|
144
|
+
*/
|
|
145
|
+
export function parseImportPath(importPath: string): {
|
|
146
|
+
packageName: string;
|
|
147
|
+
subpath?: string;
|
|
148
|
+
} {
|
|
149
|
+
// Handle scoped packages
|
|
150
|
+
if (importPath.startsWith('@')) {
|
|
151
|
+
const parts = importPath.split('/');
|
|
152
|
+
if (parts.length >= 2) {
|
|
153
|
+
const packageName = `${parts[0]}/${parts[1]}`;
|
|
154
|
+
const subpath = parts.slice(2).join('/');
|
|
155
|
+
return { packageName, subpath: subpath || undefined };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Handle non-scoped packages
|
|
160
|
+
const parts = importPath.split('/');
|
|
161
|
+
const packageName = parts[0] as string;
|
|
162
|
+
const subpath = parts.slice(1).join('/');
|
|
163
|
+
return { packageName, subpath: subpath || undefined };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface CdnTransformOptions {
|
|
167
|
+
/** Map of package names to versions */
|
|
168
|
+
packages?: Record<string, string>;
|
|
169
|
+
/** Additional external packages */
|
|
170
|
+
external?: string[];
|
|
171
|
+
/** Use bundled versions from esm.sh (adds ?bundle) */
|
|
172
|
+
bundle?: boolean;
|
|
173
|
+
/** Packages to inject from window globals instead of CDN */
|
|
174
|
+
globals?: Record<string, string>;
|
|
175
|
+
/** Dependency version overrides for CDN URLs (e.g., { react: '18' }) */
|
|
176
|
+
deps?: Record<string, string>;
|
|
177
|
+
/** Import path aliases (e.g., { '@/components/ui/*': '@packagedcn/react' }) */
|
|
178
|
+
aliases?: Record<string, string>;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Match an import path against alias patterns
|
|
183
|
+
* Supports glob patterns like '@/components/ui/*'
|
|
184
|
+
*/
|
|
185
|
+
function matchAlias(
|
|
186
|
+
importPath: string,
|
|
187
|
+
aliases: Record<string, string>,
|
|
188
|
+
): string | null {
|
|
189
|
+
for (const [pattern, target] of Object.entries(aliases)) {
|
|
190
|
+
// Handle glob patterns ending with /*
|
|
191
|
+
if (pattern.endsWith('/*')) {
|
|
192
|
+
const prefix = pattern.slice(0, -2); // Remove /*
|
|
193
|
+
if (importPath === prefix || importPath.startsWith(prefix + '/')) {
|
|
194
|
+
return target;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Exact match
|
|
198
|
+
if (importPath === pattern) {
|
|
199
|
+
return target;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Create an esbuild plugin that transforms bare imports to CDN URLs
|
|
207
|
+
* and injects globals for specified packages (like React)
|
|
208
|
+
*/
|
|
209
|
+
export function cdnTransformPlugin(options: CdnTransformOptions = {}): Plugin {
|
|
210
|
+
const {
|
|
211
|
+
packages = {},
|
|
212
|
+
external = [],
|
|
213
|
+
bundle = false,
|
|
214
|
+
globals = {},
|
|
215
|
+
deps = {},
|
|
216
|
+
aliases = {},
|
|
217
|
+
} = options;
|
|
218
|
+
|
|
219
|
+
const externalSet = new Set([...EXTERNAL_PACKAGES, ...external]);
|
|
220
|
+
const globalsSet = new Set(Object.keys(globals));
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
name: 'cdn-transform',
|
|
224
|
+
setup(build) {
|
|
225
|
+
// Handle import aliases first (e.g., @/components/ui/* -> @packagedcn/react)
|
|
226
|
+
// This must resolve directly to CDN URL or global-inject
|
|
227
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
228
|
+
const aliasTarget = matchAlias(args.path, aliases);
|
|
229
|
+
if (aliasTarget) {
|
|
230
|
+
const { packageName, subpath } = parseImportPath(aliasTarget);
|
|
231
|
+
|
|
232
|
+
// Check if aliased target should use globals
|
|
233
|
+
if (globalsSet.has(packageName)) {
|
|
234
|
+
return {
|
|
235
|
+
path: aliasTarget,
|
|
236
|
+
namespace: 'global-inject',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Convert aliased import directly to CDN URL
|
|
241
|
+
const version = packages[packageName];
|
|
242
|
+
let url = toEsmShUrl(
|
|
243
|
+
packageName,
|
|
244
|
+
version,
|
|
245
|
+
subpath,
|
|
246
|
+
Object.keys(deps).length > 0 ? deps : undefined,
|
|
247
|
+
);
|
|
248
|
+
if (bundle) {
|
|
249
|
+
url += url.includes('?') ? '&bundle' : '?bundle';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
path: url,
|
|
254
|
+
external: true,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Handle packages that should come from window globals
|
|
261
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
262
|
+
if (!isBareImport(args.path)) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const { packageName } = parseImportPath(args.path);
|
|
267
|
+
|
|
268
|
+
// Check if this package should be injected from globals
|
|
269
|
+
if (globalsSet.has(packageName)) {
|
|
270
|
+
return {
|
|
271
|
+
path: args.path,
|
|
272
|
+
namespace: 'global-inject',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return null;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Provide virtual modules that export window globals
|
|
280
|
+
build.onLoad({ filter: /.*/, namespace: 'global-inject' }, (args) => {
|
|
281
|
+
const { packageName, subpath } = parseImportPath(args.path);
|
|
282
|
+
const globalName = globals[packageName];
|
|
283
|
+
|
|
284
|
+
if (!globalName) return null;
|
|
285
|
+
|
|
286
|
+
// Handle subpath imports like 'react-dom/client'
|
|
287
|
+
if (subpath) {
|
|
288
|
+
// For react-dom/client, we need to access window.ReactDOM (which is already the client)
|
|
289
|
+
return {
|
|
290
|
+
contents: `export * from '${packageName}'; export { default } from '${packageName}';`,
|
|
291
|
+
loader: 'js',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Generate a module that exports the global
|
|
296
|
+
// This handles both default and named exports
|
|
297
|
+
const contents = `
|
|
298
|
+
const mod = window.${globalName};
|
|
299
|
+
export default mod;
|
|
300
|
+
// Re-export all properties as named exports
|
|
301
|
+
const { ${getCommonExports(packageName).join(', ')} } = mod;
|
|
302
|
+
export { ${getCommonExports(packageName).join(', ')} };
|
|
303
|
+
`;
|
|
304
|
+
return {
|
|
305
|
+
contents,
|
|
306
|
+
loader: 'js',
|
|
307
|
+
};
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Mark external packages and transform to CDN URLs
|
|
311
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
312
|
+
if (!isBareImport(args.path)) {
|
|
313
|
+
return null; // Let esbuild handle relative/absolute imports
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Check if it's a Node.js builtin
|
|
317
|
+
if (NODE_BUILTINS.has(args.path)) {
|
|
318
|
+
return { external: true };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const { packageName, subpath } = parseImportPath(args.path);
|
|
322
|
+
|
|
323
|
+
// Skip if handled by globals
|
|
324
|
+
if (globalsSet.has(packageName)) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check if it should be external (but not converted to CDN)
|
|
329
|
+
if (externalSet.has(packageName)) {
|
|
330
|
+
return { external: true };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Get version from packages map
|
|
334
|
+
const version = packages[packageName];
|
|
335
|
+
|
|
336
|
+
// Use deps from options for consistent dependency versions across CDN packages
|
|
337
|
+
// This prevents multiple React instances which cause element serialization errors
|
|
338
|
+
let url = toEsmShUrl(
|
|
339
|
+
packageName,
|
|
340
|
+
version,
|
|
341
|
+
subpath,
|
|
342
|
+
Object.keys(deps).length > 0 ? deps : undefined,
|
|
343
|
+
);
|
|
344
|
+
if (bundle) {
|
|
345
|
+
url += url.includes('?') ? '&bundle' : '?bundle';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
path: url,
|
|
350
|
+
external: true,
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get common named exports for known packages
|
|
359
|
+
*/
|
|
360
|
+
function getCommonExports(packageName: string): string[] {
|
|
361
|
+
const exports: Record<string, string[]> = {
|
|
362
|
+
react: [
|
|
363
|
+
'useState',
|
|
364
|
+
'useEffect',
|
|
365
|
+
'useCallback',
|
|
366
|
+
'useMemo',
|
|
367
|
+
'useRef',
|
|
368
|
+
'useContext',
|
|
369
|
+
'useReducer',
|
|
370
|
+
'useLayoutEffect',
|
|
371
|
+
'useId',
|
|
372
|
+
'createContext',
|
|
373
|
+
'createElement',
|
|
374
|
+
'cloneElement',
|
|
375
|
+
'createRef',
|
|
376
|
+
'forwardRef',
|
|
377
|
+
'lazy',
|
|
378
|
+
'memo',
|
|
379
|
+
'Fragment',
|
|
380
|
+
'Suspense',
|
|
381
|
+
'StrictMode',
|
|
382
|
+
'Component',
|
|
383
|
+
'PureComponent',
|
|
384
|
+
'Children',
|
|
385
|
+
'isValidElement',
|
|
386
|
+
],
|
|
387
|
+
'react-dom': [
|
|
388
|
+
'createPortal',
|
|
389
|
+
'flushSync',
|
|
390
|
+
'render',
|
|
391
|
+
'hydrate',
|
|
392
|
+
'unmountComponentAtNode',
|
|
393
|
+
],
|
|
394
|
+
};
|
|
395
|
+
return exports[packageName] || [];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Generate import map for CDN dependencies
|
|
400
|
+
*/
|
|
401
|
+
export function generateImportMap(
|
|
402
|
+
packages: Record<string, string>,
|
|
403
|
+
): Record<string, string> {
|
|
404
|
+
const imports: Record<string, string> = {};
|
|
405
|
+
|
|
406
|
+
for (const [name, version] of Object.entries(packages)) {
|
|
407
|
+
imports[name] = toEsmShUrl(name, version);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return imports;
|
|
411
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { Plugin, Loader } from 'esbuild-wasm';
|
|
2
|
+
import type { VirtualProject } from '../vfs/types.js';
|
|
3
|
+
|
|
4
|
+
function dirname(path: string): string {
|
|
5
|
+
const idx = path.lastIndexOf('/');
|
|
6
|
+
return idx === -1 ? '.' : path.slice(0, idx) || '.';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function normalizePath(path: string): string {
|
|
10
|
+
const parts: string[] = [];
|
|
11
|
+
for (const segment of path.split('/')) {
|
|
12
|
+
if (segment === '..') parts.pop();
|
|
13
|
+
else if (segment && segment !== '.') parts.push(segment);
|
|
14
|
+
}
|
|
15
|
+
return parts.join('/');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function inferLoader(path: string, language?: string): Loader {
|
|
19
|
+
if (language) {
|
|
20
|
+
switch (language) {
|
|
21
|
+
case 'typescript':
|
|
22
|
+
case 'ts':
|
|
23
|
+
return 'ts';
|
|
24
|
+
case 'tsx':
|
|
25
|
+
return 'tsx';
|
|
26
|
+
case 'javascript':
|
|
27
|
+
case 'js':
|
|
28
|
+
return 'js';
|
|
29
|
+
case 'jsx':
|
|
30
|
+
return 'jsx';
|
|
31
|
+
case 'json':
|
|
32
|
+
return 'json';
|
|
33
|
+
case 'css':
|
|
34
|
+
return 'css';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const ext = path.split('.').pop();
|
|
38
|
+
switch (ext) {
|
|
39
|
+
case 'ts':
|
|
40
|
+
return 'ts';
|
|
41
|
+
case 'tsx':
|
|
42
|
+
return 'tsx';
|
|
43
|
+
case 'js':
|
|
44
|
+
return 'js';
|
|
45
|
+
case 'jsx':
|
|
46
|
+
return 'jsx';
|
|
47
|
+
case 'json':
|
|
48
|
+
return 'json';
|
|
49
|
+
case 'css':
|
|
50
|
+
return 'css';
|
|
51
|
+
default:
|
|
52
|
+
return 'tsx';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeVFSPath(path: string): string {
|
|
57
|
+
if (path.startsWith('@/')) {
|
|
58
|
+
return path.slice(2);
|
|
59
|
+
}
|
|
60
|
+
return path;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveRelativePath(importer: string, target: string): string {
|
|
64
|
+
const importerDir = dirname(normalizeVFSPath(importer));
|
|
65
|
+
const combined = importerDir === '.' ? target : `${importerDir}/${target}`;
|
|
66
|
+
return normalizePath(combined);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function matchAlias(
|
|
70
|
+
importPath: string,
|
|
71
|
+
aliases?: Record<string, string>,
|
|
72
|
+
): string | null {
|
|
73
|
+
if (!aliases) return null;
|
|
74
|
+
for (const [pattern, target] of Object.entries(aliases)) {
|
|
75
|
+
if (pattern.endsWith('/*')) {
|
|
76
|
+
const prefix = pattern.slice(0, -2);
|
|
77
|
+
if (importPath === prefix || importPath.startsWith(prefix + '/')) {
|
|
78
|
+
return target;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (importPath === pattern) {
|
|
82
|
+
return target;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function findFile(project: VirtualProject, path: string): string | null {
|
|
89
|
+
if (project.files.has(path)) return path;
|
|
90
|
+
const extensions = ['.tsx', '.ts', '.jsx', '.js', '.json'];
|
|
91
|
+
for (const ext of extensions) {
|
|
92
|
+
if (project.files.has(path + ext)) return path + ext;
|
|
93
|
+
}
|
|
94
|
+
for (const ext of extensions) {
|
|
95
|
+
const indexPath = `${path}/index${ext}`;
|
|
96
|
+
if (project.files.has(indexPath)) return indexPath;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface VFSPluginOptions {
|
|
102
|
+
aliases?: Record<string, string>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function vfsPlugin(
|
|
106
|
+
project: VirtualProject,
|
|
107
|
+
options: VFSPluginOptions = {},
|
|
108
|
+
): Plugin {
|
|
109
|
+
return {
|
|
110
|
+
name: 'patchwork-vfs',
|
|
111
|
+
setup(build) {
|
|
112
|
+
build.onResolve({ filter: /^@\// }, (args) => {
|
|
113
|
+
const aliased = matchAlias(args.path, options.aliases);
|
|
114
|
+
if (aliased) return null;
|
|
115
|
+
return { path: args.path, namespace: 'vfs' };
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
build.onResolve({ filter: /^\./ }, (args) => {
|
|
119
|
+
if (args.namespace !== 'vfs') return null;
|
|
120
|
+
const resolved = resolveRelativePath(args.importer, args.path);
|
|
121
|
+
return { path: resolved, namespace: 'vfs' };
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
build.onLoad({ filter: /.*/, namespace: 'vfs' }, (args) => {
|
|
125
|
+
const normalPath = normalizeVFSPath(args.path);
|
|
126
|
+
const filePath = findFile(project, normalPath);
|
|
127
|
+
if (!filePath) {
|
|
128
|
+
throw new Error(`File not found in VFS: ${args.path}`);
|
|
129
|
+
}
|
|
130
|
+
const file = project.files.get(filePath)!;
|
|
131
|
+
return {
|
|
132
|
+
contents: file.content,
|
|
133
|
+
loader: inferLoader(filePath, file.language),
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|