@hanzogui/static 2.0.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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/check-dep-versions.cjs +267 -0
- package/dist/checkDeps.cjs +200 -0
- package/dist/constants.cjs +49 -0
- package/dist/exports.cjs +44 -0
- package/dist/extractor/accessSafe.cjs +46 -0
- package/dist/extractor/babelParse.cjs +54 -0
- package/dist/extractor/buildClassName.cjs +62 -0
- package/dist/extractor/bundle.cjs +185 -0
- package/dist/extractor/bundleConfig.cjs +513 -0
- package/dist/extractor/concatClassName.cjs +80 -0
- package/dist/extractor/createEvaluator.cjs +75 -0
- package/dist/extractor/createExtractor.cjs +1150 -0
- package/dist/extractor/createLogger.cjs +45 -0
- package/dist/extractor/detectModuleFormat.cjs +49 -0
- package/dist/extractor/ensureImportingConcat.cjs +48 -0
- package/dist/extractor/errors.cjs +26 -0
- package/dist/extractor/esbuildAliasPlugin.cjs +44 -0
- package/dist/extractor/esbuildTsconfigPaths.cjs +86 -0
- package/dist/extractor/evaluateAstNode.cjs +83 -0
- package/dist/extractor/extractHelpers.cjs +119 -0
- package/dist/extractor/extractMediaStyle.cjs +128 -0
- package/dist/extractor/extractToClassNames.cjs +321 -0
- package/dist/extractor/extractToNative.cjs +266 -0
- package/dist/extractor/findTopmostFunction.cjs +34 -0
- package/dist/extractor/generatedUid.cjs +47 -0
- package/dist/extractor/getGuiConfigPathFromOptionsConfig.cjs +35 -0
- package/dist/extractor/getPrefixLogs.cjs +29 -0
- package/dist/extractor/getPropValueFromAttributes.cjs +67 -0
- package/dist/extractor/getSourceModule.cjs +64 -0
- package/dist/extractor/getStaticBindingsForScope.cjs +150 -0
- package/dist/extractor/hoistClassNames.cjs +62 -0
- package/dist/extractor/literalToAst.cjs +84 -0
- package/dist/extractor/loadFile.cjs +11 -0
- package/dist/extractor/loadGui.cjs +322 -0
- package/dist/extractor/logLines.cjs +35 -0
- package/dist/extractor/normalizeTernaries.cjs +73 -0
- package/dist/extractor/propsToFontFamilyCache.cjs +38 -0
- package/dist/extractor/regenerateConfig.cjs +150 -0
- package/dist/extractor/removeUnusedHooks.cjs +76 -0
- package/dist/extractor/timer.cjs +43 -0
- package/dist/extractor/validHTMLAttributes.cjs +77 -0
- package/dist/extractor/watchGuiConfig.cjs +58 -0
- package/dist/getPragmaOptions.cjs +58 -0
- package/dist/helpers/memoize.cjs +37 -0
- package/dist/helpers/requireGuiCore.cjs +33 -0
- package/dist/index.cjs +40 -0
- package/dist/registerRequire.cjs +115 -0
- package/dist/server.cjs +75 -0
- package/dist/setup.cjs +0 -0
- package/dist/types.cjs +16 -0
- package/dist/worker.cjs +89 -0
- package/package.json +101 -0
- package/src/check-dep-versions.ts +738 -0
- package/src/checkDeps.ts +371 -0
- package/src/constants.ts +14 -0
- package/src/exports.ts +15 -0
- package/src/extractor/accessSafe.ts +22 -0
- package/src/extractor/babelParse.ts +37 -0
- package/src/extractor/buildClassName.ts +76 -0
- package/src/extractor/bundle.ts +277 -0
- package/src/extractor/bundleConfig.ts +958 -0
- package/src/extractor/concatClassName.ts +105 -0
- package/src/extractor/createEvaluator.ts +82 -0
- package/src/extractor/createExtractor.ts +2665 -0
- package/src/extractor/createLogger.ts +41 -0
- package/src/extractor/detectModuleFormat.ts +42 -0
- package/src/extractor/ensureImportingConcat.ts +36 -0
- package/src/extractor/errors.ts +1 -0
- package/src/extractor/esbuildAliasPlugin.ts +40 -0
- package/src/extractor/esbuildTsconfigPaths.ts +103 -0
- package/src/extractor/evaluateAstNode.ts +136 -0
- package/src/extractor/extractHelpers.ts +211 -0
- package/src/extractor/extractMediaStyle.ts +194 -0
- package/src/extractor/extractToClassNames.ts +631 -0
- package/src/extractor/extractToNative.ts +463 -0
- package/src/extractor/findTopmostFunction.ts +24 -0
- package/src/extractor/generatedUid.ts +41 -0
- package/src/extractor/getGuiConfigPathFromOptionsConfig.ts +24 -0
- package/src/extractor/getPrefixLogs.ts +9 -0
- package/src/extractor/getPropValueFromAttributes.ts +95 -0
- package/src/extractor/getSourceModule.ts +96 -0
- package/src/extractor/getStaticBindingsForScope.ts +237 -0
- package/src/extractor/hoistClassNames.ts +52 -0
- package/src/extractor/literalToAst.ts +85 -0
- package/src/extractor/loadFile.ts +17 -0
- package/src/extractor/loadGui.ts +487 -0
- package/src/extractor/logLines.ts +16 -0
- package/src/extractor/normalizeTernaries.ts +75 -0
- package/src/extractor/propsToFontFamilyCache.ts +18 -0
- package/src/extractor/regenerateConfig.ts +186 -0
- package/src/extractor/removeUnusedHooks.ts +83 -0
- package/src/extractor/timer.ts +25 -0
- package/src/extractor/validHTMLAttributes.ts +52 -0
- package/src/extractor/watchGuiConfig.ts +57 -0
- package/src/getPragmaOptions.ts +57 -0
- package/src/helpers/memoize.ts +24 -0
- package/src/helpers/requireGuiCore.ts +28 -0
- package/src/index.ts +4 -0
- package/src/registerRequire.ts +236 -0
- package/src/server.ts +43 -0
- package/src/setup.ts +0 -0
- package/src/types.ts +109 -0
- package/src/worker.ts +142 -0
- package/types/check-dep-versions.d.ts +37 -0
- package/types/check-dep-versions.d.ts.map +1 -0
- package/types/checkDeps.d.ts +19 -0
- package/types/checkDeps.d.ts.map +1 -0
- package/types/constants.d.ts +6 -0
- package/types/constants.d.ts.map +1 -0
- package/types/exports.d.ts +16 -0
- package/types/exports.d.ts.map +1 -0
- package/types/extractor/accessSafe.d.ts +3 -0
- package/types/extractor/accessSafe.d.ts.map +1 -0
- package/types/extractor/babelParse.d.ts +5 -0
- package/types/extractor/babelParse.d.ts.map +1 -0
- package/types/extractor/buildClassName.d.ts +7 -0
- package/types/extractor/buildClassName.d.ts.map +1 -0
- package/types/extractor/bundle.d.ts +121 -0
- package/types/extractor/bundle.d.ts.map +1 -0
- package/types/extractor/bundleConfig.d.ts +49 -0
- package/types/extractor/bundleConfig.d.ts.map +1 -0
- package/types/extractor/concatClassName.d.ts +8 -0
- package/types/extractor/concatClassName.d.ts.map +1 -0
- package/types/extractor/createEvaluator.d.ts +12 -0
- package/types/extractor/createEvaluator.d.ts.map +1 -0
- package/types/extractor/createExtractor.d.ts +32 -0
- package/types/extractor/createExtractor.d.ts.map +1 -0
- package/types/extractor/createLogger.d.ts +3 -0
- package/types/extractor/createLogger.d.ts.map +1 -0
- package/types/extractor/detectModuleFormat.d.ts +5 -0
- package/types/extractor/detectModuleFormat.d.ts.map +1 -0
- package/types/extractor/ensureImportingConcat.d.ts +4 -0
- package/types/extractor/ensureImportingConcat.d.ts.map +1 -0
- package/types/extractor/errors.d.ts +3 -0
- package/types/extractor/errors.d.ts.map +1 -0
- package/types/extractor/esbuildAliasPlugin.d.ts +18 -0
- package/types/extractor/esbuildAliasPlugin.d.ts.map +1 -0
- package/types/extractor/esbuildTsconfigPaths.d.ts +11 -0
- package/types/extractor/esbuildTsconfigPaths.d.ts.map +1 -0
- package/types/extractor/evaluateAstNode.d.ts +3 -0
- package/types/extractor/evaluateAstNode.d.ts.map +1 -0
- package/types/extractor/extractHelpers.d.ts +28 -0
- package/types/extractor/extractHelpers.d.ts.map +1 -0
- package/types/extractor/extractMediaStyle.d.ts +11 -0
- package/types/extractor/extractMediaStyle.d.ts.map +1 -0
- package/types/extractor/extractToClassNames.d.ts +25 -0
- package/types/extractor/extractToClassNames.d.ts.map +1 -0
- package/types/extractor/extractToNative.d.ts +13 -0
- package/types/extractor/extractToNative.d.ts.map +1 -0
- package/types/extractor/findTopmostFunction.d.ts +4 -0
- package/types/extractor/findTopmostFunction.d.ts.map +1 -0
- package/types/extractor/generatedUid.d.ts +5 -0
- package/types/extractor/generatedUid.d.ts.map +1 -0
- package/types/extractor/getGuiConfigPathFromOptionsConfig.d.ts +3 -0
- package/types/extractor/getGuiConfigPathFromOptionsConfig.d.ts.map +1 -0
- package/types/extractor/getPrefixLogs.d.ts +3 -0
- package/types/extractor/getPrefixLogs.d.ts.map +1 -0
- package/types/extractor/getPropValueFromAttributes.d.ts +19 -0
- package/types/extractor/getPropValueFromAttributes.d.ts.map +1 -0
- package/types/extractor/getSourceModule.d.ts +16 -0
- package/types/extractor/getSourceModule.d.ts.map +1 -0
- package/types/extractor/getStaticBindingsForScope.d.ts +5 -0
- package/types/extractor/getStaticBindingsForScope.d.ts.map +1 -0
- package/types/extractor/hoistClassNames.d.ts +6 -0
- package/types/extractor/hoistClassNames.d.ts.map +1 -0
- package/types/extractor/literalToAst.d.ts +4 -0
- package/types/extractor/literalToAst.d.ts.map +1 -0
- package/types/extractor/loadFile.d.ts +1 -0
- package/types/extractor/loadFile.d.ts.map +1 -0
- package/types/extractor/loadGui.d.ts +22 -0
- package/types/extractor/loadGui.d.ts.map +1 -0
- package/types/extractor/logLines.d.ts +2 -0
- package/types/extractor/logLines.d.ts.map +1 -0
- package/types/extractor/normalizeTernaries.d.ts +3 -0
- package/types/extractor/normalizeTernaries.d.ts.map +1 -0
- package/types/extractor/propsToFontFamilyCache.d.ts +4 -0
- package/types/extractor/propsToFontFamilyCache.d.ts.map +1 -0
- package/types/extractor/regenerateConfig.d.ts +9 -0
- package/types/extractor/regenerateConfig.d.ts.map +1 -0
- package/types/extractor/removeUnusedHooks.d.ts +3 -0
- package/types/extractor/removeUnusedHooks.d.ts.map +1 -0
- package/types/extractor/timer.d.ts +5 -0
- package/types/extractor/timer.d.ts.map +1 -0
- package/types/extractor/validHTMLAttributes.d.ts +52 -0
- package/types/extractor/validHTMLAttributes.d.ts.map +1 -0
- package/types/extractor/watchGuiConfig.d.ts +5 -0
- package/types/extractor/watchGuiConfig.d.ts.map +1 -0
- package/types/getPragmaOptions.d.ts +8 -0
- package/types/getPragmaOptions.d.ts.map +1 -0
- package/types/helpers/memoize.d.ts +8 -0
- package/types/helpers/memoize.d.ts.map +1 -0
- package/types/helpers/requireGuiCore.d.ts +3 -0
- package/types/helpers/requireGuiCore.d.ts.map +1 -0
- package/types/index.d.ts +4 -0
- package/types/index.d.ts.map +1 -0
- package/types/registerRequire.d.ts +10 -0
- package/types/registerRequire.d.ts.map +1 -0
- package/types/server.d.ts +3 -0
- package/types/server.d.ts.map +1 -0
- package/types/setup.d.ts +1 -0
- package/types/setup.d.ts.map +1 -0
- package/types/types.d.ts +92 -0
- package/types/types.d.ts.map +1 -0
- package/types/worker.d.ts +46 -0
- package/types/worker.d.ts.map +1 -0
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
import generate from '@babel/generator'
|
|
2
|
+
import traverse from '@babel/traverse'
|
|
3
|
+
import * as t from '@babel/types'
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
import { existsSync, readFileSync, unlinkSync } from 'node:fs'
|
|
6
|
+
import { basename, dirname, extname, join, relative, sep } from 'node:path'
|
|
7
|
+
import { pathToFileURL } from 'node:url'
|
|
8
|
+
// @ts-ignore why
|
|
9
|
+
import { Color, colorLog } from '@hanzogui/cli-color'
|
|
10
|
+
import { type StaticConfig, type GuiInternalConfig } from '@hanzogui/web'
|
|
11
|
+
import esbuild from 'esbuild'
|
|
12
|
+
import * as FS from 'fs-extra'
|
|
13
|
+
import { readFile } from 'node:fs/promises'
|
|
14
|
+
import { registerRequire, setRequireResult } from '../registerRequire'
|
|
15
|
+
import type { GuiOptions } from '../types'
|
|
16
|
+
import { babelParse } from './babelParse'
|
|
17
|
+
import { esbuildLoaderConfig, esbundleGuiConfig } from './bundle'
|
|
18
|
+
import { getGuiConfigPathFromOptionsConfig } from './getGuiConfigPathFromOptionsConfig'
|
|
19
|
+
import { requireGuiCore } from '../helpers/requireGuiCore'
|
|
20
|
+
import { detectModuleFormat } from './detectModuleFormat'
|
|
21
|
+
|
|
22
|
+
// track temp files for cleanup on exit
|
|
23
|
+
const activeTempFiles = new Set<string>()
|
|
24
|
+
|
|
25
|
+
function getDynamicEvalOutfile(name: string, format: 'esm' | 'cjs', contents: string) {
|
|
26
|
+
const ext = format === 'esm' ? 'mjs' : 'cjs'
|
|
27
|
+
const hash = createHash('sha1')
|
|
28
|
+
.update(name)
|
|
29
|
+
.update('\0')
|
|
30
|
+
.update(format)
|
|
31
|
+
.update('\0')
|
|
32
|
+
.update(contents)
|
|
33
|
+
.digest('hex')
|
|
34
|
+
.slice(0, 10)
|
|
35
|
+
return join(process.cwd(), '.gui', `dynamic-eval-${hash}-${basename(name)}.${ext}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getEsbuildStdinLoader(filePath: string): esbuild.Loader {
|
|
39
|
+
if (filePath.endsWith('.tsx')) return 'tsx'
|
|
40
|
+
if (filePath.endsWith('.ts')) return 'ts'
|
|
41
|
+
if (filePath.endsWith('.jsx')) return 'jsx'
|
|
42
|
+
return 'js'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolvePackageEntry(packageName: string, format: 'esm' | 'cjs') {
|
|
46
|
+
if (format === 'cjs') {
|
|
47
|
+
return require.resolve(packageName)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
|
51
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
|
|
52
|
+
const packageRoot = dirname(packageJsonPath)
|
|
53
|
+
const exportEntry = packageJson.exports?.['.']
|
|
54
|
+
|
|
55
|
+
const esmEntry =
|
|
56
|
+
exportEntry?.import ||
|
|
57
|
+
exportEntry?.module ||
|
|
58
|
+
exportEntry?.browser ||
|
|
59
|
+
packageJson.module
|
|
60
|
+
|
|
61
|
+
if (typeof esmEntry === 'string') {
|
|
62
|
+
return join(packageRoot, esmEntry)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return require.resolve(packageName)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function cleanupTempFiles() {
|
|
69
|
+
for (const f of activeTempFiles) {
|
|
70
|
+
try {
|
|
71
|
+
unlinkSync(f)
|
|
72
|
+
} catch {}
|
|
73
|
+
}
|
|
74
|
+
activeTempFiles.clear()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
process.on('exit', cleanupTempFiles)
|
|
78
|
+
process.on('SIGINT', () => {
|
|
79
|
+
cleanupTempFiles()
|
|
80
|
+
process.exit()
|
|
81
|
+
})
|
|
82
|
+
process.on('SIGTERM', () => {
|
|
83
|
+
cleanupTempFiles()
|
|
84
|
+
process.exit()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
type NameToPaths = {
|
|
88
|
+
[key: string]: Set<string>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type LoadedComponents = {
|
|
92
|
+
moduleName: string
|
|
93
|
+
nameToInfo: Record<
|
|
94
|
+
string,
|
|
95
|
+
{
|
|
96
|
+
staticConfig: StaticConfig
|
|
97
|
+
}
|
|
98
|
+
>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type GuiProjectInfo = {
|
|
102
|
+
components?: LoadedComponents[]
|
|
103
|
+
guiConfig?: GuiInternalConfig | null
|
|
104
|
+
nameToPaths?: NameToPaths
|
|
105
|
+
cached?: boolean
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const external = [
|
|
109
|
+
'@hanzogui/core',
|
|
110
|
+
'@hanzogui/web',
|
|
111
|
+
'react',
|
|
112
|
+
'react-dom',
|
|
113
|
+
'react-native-svg',
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
const esbuildExtraOptions = {
|
|
117
|
+
define: {
|
|
118
|
+
__DEV__: `${process.env.NODE_ENV === 'development'}`,
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// plugin to handle ESM-only features when bundling to CJS
|
|
123
|
+
const handleEsmFeaturesPlugin: esbuild.Plugin = {
|
|
124
|
+
name: 'handle-esm-features',
|
|
125
|
+
setup(build) {
|
|
126
|
+
// only apply transforms for CJS output - ESM supports these natively
|
|
127
|
+
const isCjs = build.initialOptions.format === 'cjs' || !build.initialOptions.format
|
|
128
|
+
|
|
129
|
+
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs)$/ }, (args) => {
|
|
130
|
+
// skip if ESM output - import.meta and top-level await work natively
|
|
131
|
+
if (!isCjs) {
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// skip most node_modules
|
|
136
|
+
if (args.path.includes('node_modules') && !args.path.includes('@gui')) {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let contents = readFileSync(args.path, 'utf8')
|
|
141
|
+
let modified = false
|
|
142
|
+
|
|
143
|
+
// transform import.meta.env -> process.env (Vite-style env vars)
|
|
144
|
+
if (contents.includes('import.meta.env')) {
|
|
145
|
+
contents = contents.replace(/import\.meta\.env/g, 'process.env')
|
|
146
|
+
modified = true
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// transform import.meta.url -> "" (not needed for static extraction)
|
|
150
|
+
if (contents.includes('import.meta.url')) {
|
|
151
|
+
contents = contents.replace(/import\.meta\.url/g, '""')
|
|
152
|
+
modified = true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// transform import.meta.main -> false
|
|
156
|
+
if (contents.includes('import.meta.main')) {
|
|
157
|
+
contents = contents.replace(/import\.meta\.main/g, 'false')
|
|
158
|
+
modified = true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// stub files with top-level await - they're typically runtime-only
|
|
162
|
+
if (
|
|
163
|
+
/^\s*(?:const|let|var|export)\s+[^=]*=\s*await\b/m.test(contents) ||
|
|
164
|
+
/^await\s/m.test(contents)
|
|
165
|
+
) {
|
|
166
|
+
if (process.env.DEBUG?.startsWith('@hanzo/gui')) {
|
|
167
|
+
console.info(`[hanzo-gui] stubbing file with top-level await: ${args.path}`)
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
contents: `// stubbed - contains top-level await\nmodule.exports = {}`,
|
|
171
|
+
loader: 'js',
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (modified) {
|
|
176
|
+
return {
|
|
177
|
+
contents,
|
|
178
|
+
loader: args.path.endsWith('.tsx')
|
|
179
|
+
? 'tsx'
|
|
180
|
+
: args.path.endsWith('.ts')
|
|
181
|
+
? 'ts'
|
|
182
|
+
: args.path.endsWith('.jsx')
|
|
183
|
+
? 'jsx'
|
|
184
|
+
: 'js',
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null
|
|
189
|
+
})
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// base options for transformSync (no plugins)
|
|
194
|
+
const esbuildTransformOptions = {
|
|
195
|
+
target: 'es2022',
|
|
196
|
+
format: 'cjs',
|
|
197
|
+
jsx: 'automatic',
|
|
198
|
+
platform: 'node',
|
|
199
|
+
...esbuildExtraOptions,
|
|
200
|
+
} satisfies esbuild.TransformOptions
|
|
201
|
+
|
|
202
|
+
// options for buildSync - NO plugins (buildSync doesn't support plugins)
|
|
203
|
+
export const esbuildOptions = {
|
|
204
|
+
...esbuildTransformOptions,
|
|
205
|
+
} satisfies esbuild.BuildOptions
|
|
206
|
+
|
|
207
|
+
// options for async build (with plugins)
|
|
208
|
+
export const esbuildOptionsWithPlugins = {
|
|
209
|
+
...esbuildTransformOptions,
|
|
210
|
+
plugins: [handleEsmFeaturesPlugin],
|
|
211
|
+
} satisfies esbuild.BuildOptions
|
|
212
|
+
|
|
213
|
+
export type BundledConfig = Exclude<Awaited<ReturnType<typeof bundleConfig>>, undefined>
|
|
214
|
+
|
|
215
|
+
// will use cached one if watching
|
|
216
|
+
let currentBundle: BundledConfig | null = null
|
|
217
|
+
let isBundling = false
|
|
218
|
+
let lastBundle: BundledConfig | null = null
|
|
219
|
+
const waitForBundle = new Set<Function>()
|
|
220
|
+
|
|
221
|
+
export function hasBundledConfigChanged() {
|
|
222
|
+
if (lastBundle === currentBundle) {
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
lastBundle = currentBundle
|
|
226
|
+
return true
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let loadedConfig: GuiInternalConfig | null = null
|
|
230
|
+
|
|
231
|
+
export const getLoadedConfig = () => loadedConfig
|
|
232
|
+
|
|
233
|
+
export async function getBundledConfig(props: GuiOptions, rebuild = false) {
|
|
234
|
+
if (isBundling) {
|
|
235
|
+
await new Promise((res) => {
|
|
236
|
+
waitForBundle.add(res)
|
|
237
|
+
})
|
|
238
|
+
} else if (!currentBundle || rebuild) {
|
|
239
|
+
return await bundleConfig(props)
|
|
240
|
+
}
|
|
241
|
+
return currentBundle
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
global.guiLastLoaded ||= 0
|
|
245
|
+
|
|
246
|
+
function updateLastLoaded(config: any) {
|
|
247
|
+
global.guiLastLoaded = Date.now()
|
|
248
|
+
global.guiLastBundledConfig = config
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let hasBundledOnce = false
|
|
252
|
+
|
|
253
|
+
// use global to dedupe logging - this works within a single process
|
|
254
|
+
// but may log multiple times if worker threads are recreated
|
|
255
|
+
// that's acceptable - better than nothing
|
|
256
|
+
let hasLoggedBuild = false
|
|
257
|
+
|
|
258
|
+
export async function bundleConfig(props: GuiOptions) {
|
|
259
|
+
// webpack is calling this a ton for no reason
|
|
260
|
+
if (global.guiLastBundledConfig && Date.now() - global.guiLastLoaded < 3000) {
|
|
261
|
+
// just loaded recently
|
|
262
|
+
return global.guiLastBundledConfig
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
isBundling = true
|
|
267
|
+
|
|
268
|
+
const configEntry = props.config
|
|
269
|
+
? getGuiConfigPathFromOptionsConfig(props.config)
|
|
270
|
+
: ''
|
|
271
|
+
const tmpDir = join(process.cwd(), '.gui')
|
|
272
|
+
// detect module format from config entry point
|
|
273
|
+
const configFormat = configEntry ? detectModuleFormat(configEntry) : 'cjs'
|
|
274
|
+
const configExt = configFormat === 'esm' ? '.mjs' : '.cjs'
|
|
275
|
+
const configOutPath = join(tmpDir, `gui.config${configExt}`)
|
|
276
|
+
const baseComponents = (props.components || []).filter((x) => x !== '@hanzogui/core')
|
|
277
|
+
// detect format per component module
|
|
278
|
+
const componentFormats: Array<'esm' | 'cjs'> = baseComponents.map((mod) => {
|
|
279
|
+
try {
|
|
280
|
+
const pkgJson = require.resolve(mod + '/package.json')
|
|
281
|
+
const pkg = JSON.parse(readFileSync(pkgJson, 'utf-8'))
|
|
282
|
+
return pkg.type === 'module' ? 'esm' : 'cjs'
|
|
283
|
+
} catch {
|
|
284
|
+
return 'cjs'
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
const componentOutPaths = baseComponents.map((componentModule, i) => {
|
|
288
|
+
const ext = componentFormats[i] === 'esm' ? '.mjs' : '.cjs'
|
|
289
|
+
return join(
|
|
290
|
+
tmpDir,
|
|
291
|
+
`${componentModule
|
|
292
|
+
.split(sep)
|
|
293
|
+
.join('-')
|
|
294
|
+
.replace(/[^a-z0-9]+/gi, '')}-components.config${ext}`
|
|
295
|
+
)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
if (
|
|
299
|
+
process.env.NODE_ENV === 'development' &&
|
|
300
|
+
process.env.DEBUG?.startsWith('@hanzo/gui')
|
|
301
|
+
) {
|
|
302
|
+
console.info(`Building config entry`, configEntry)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// check if ALL output files (config + components) already exist and are recent
|
|
306
|
+
// (built by another worker) - this prevents duplicate builds across worker threads
|
|
307
|
+
// we must check ALL files, not just the config, to avoid a race where another
|
|
308
|
+
// worker has written the config but not yet finished writing component files
|
|
309
|
+
let shouldBuild = !props.disableInitialBuild
|
|
310
|
+
if (shouldBuild && props.config) {
|
|
311
|
+
const allOutFiles = [configOutPath, ...componentOutPaths]
|
|
312
|
+
try {
|
|
313
|
+
const stats = await Promise.all(
|
|
314
|
+
allOutFiles.map((f) => FS.stat(f).catch(() => null))
|
|
315
|
+
)
|
|
316
|
+
const allExistAndRecent = stats.every(
|
|
317
|
+
(s) => s !== null && Date.now() - s.mtimeMs < 3000
|
|
318
|
+
)
|
|
319
|
+
if (allExistAndRecent) {
|
|
320
|
+
shouldBuild = false
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
// something went wrong checking files, just build
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (shouldBuild) {
|
|
328
|
+
// build them to node-compat versions
|
|
329
|
+
try {
|
|
330
|
+
await FS.ensureDir(tmpDir)
|
|
331
|
+
} catch {
|
|
332
|
+
//
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const start = Date.now()
|
|
336
|
+
|
|
337
|
+
await Promise.all([
|
|
338
|
+
props.config
|
|
339
|
+
? esbundleGuiConfig(
|
|
340
|
+
{
|
|
341
|
+
entryPoints: [configEntry],
|
|
342
|
+
external,
|
|
343
|
+
outfile: configOutPath,
|
|
344
|
+
target: 'node24',
|
|
345
|
+
format: configFormat,
|
|
346
|
+
...esbuildExtraOptions,
|
|
347
|
+
},
|
|
348
|
+
props.platform || 'web'
|
|
349
|
+
)
|
|
350
|
+
: null,
|
|
351
|
+
...baseComponents.map((componentModule, i) => {
|
|
352
|
+
return esbundleGuiConfig(
|
|
353
|
+
{
|
|
354
|
+
entryPoints: [componentModule],
|
|
355
|
+
resolvePlatformSpecificEntries: true,
|
|
356
|
+
external,
|
|
357
|
+
outfile: componentOutPaths[i],
|
|
358
|
+
target: 'node24',
|
|
359
|
+
format: componentFormats[i],
|
|
360
|
+
...esbuildExtraOptions,
|
|
361
|
+
},
|
|
362
|
+
props.platform || 'web'
|
|
363
|
+
)
|
|
364
|
+
}),
|
|
365
|
+
])
|
|
366
|
+
|
|
367
|
+
// only log once per process to avoid duplicate messages
|
|
368
|
+
// also skip if _skipBuildLog is set (used during worker recycle warmup)
|
|
369
|
+
if (!hasLoggedBuild && !props['_skipBuildLog']) {
|
|
370
|
+
hasLoggedBuild = true
|
|
371
|
+
colorLog(
|
|
372
|
+
Color.FgYellow,
|
|
373
|
+
`
|
|
374
|
+
➡ [hanzo-gui] built config, components, prompt (${Date.now() - start}ms)`
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if (process.env.DEBUG?.startsWith('@hanzo/gui')) {
|
|
378
|
+
colorLog(
|
|
379
|
+
Color.Dim,
|
|
380
|
+
`
|
|
381
|
+
Config .${sep}${relative(process.cwd(), configOutPath)}
|
|
382
|
+
Components ${componentOutPaths.map((p) => `.${sep}${relative(process.cwd(), p)}`).join('\n ')}
|
|
383
|
+
`
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// clear specific output file caches so we pick up the fresh (or newly discovered) build
|
|
390
|
+
// only clear the built output files - not all require.cache entries, since that breaks
|
|
391
|
+
// external requires like @hanzogui/config/v3 that are externalized in the bundled CJS
|
|
392
|
+
if (hasBundledOnce) {
|
|
393
|
+
try {
|
|
394
|
+
delete require.cache[require.resolve(configOutPath)]
|
|
395
|
+
} catch {
|
|
396
|
+
// file may not exist yet
|
|
397
|
+
}
|
|
398
|
+
for (const p of componentOutPaths) {
|
|
399
|
+
try {
|
|
400
|
+
delete require.cache[require.resolve(p)]
|
|
401
|
+
} catch {
|
|
402
|
+
// file may not exist yet
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
hasBundledOnce = true
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
let out: any
|
|
410
|
+
if (configFormat === 'esm') {
|
|
411
|
+
// use file:// URL for proper ESM resolution
|
|
412
|
+
out = await import(pathToFileURL(configOutPath).href)
|
|
413
|
+
} else {
|
|
414
|
+
out = require(configOutPath)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// try and find .config, even if on .default
|
|
418
|
+
let config = out.default || out || out.config
|
|
419
|
+
if (config && config.config && !config.tokens) {
|
|
420
|
+
config = config.config
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!config) {
|
|
424
|
+
throw new Error(`No config: ${config}`)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// check for ProxyWorm - indicates a module loading error
|
|
428
|
+
if (config._isProxyWorm) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
`Got a proxied config - likely a module loading error. Set DEBUG=hanzo-gui for details.`
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
loadedConfig = config
|
|
435
|
+
|
|
436
|
+
if (!config.parsed) {
|
|
437
|
+
const { createGui } = requireGuiCore(props.platform || 'web')
|
|
438
|
+
// need to create it
|
|
439
|
+
config = createGui(config)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (props.outputCSS) {
|
|
443
|
+
await writeGuiCSS(props.outputCSS, config)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
let components = await loadComponents({
|
|
447
|
+
...props,
|
|
448
|
+
components: componentOutPaths,
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
if (!components) {
|
|
452
|
+
throw new Error(`No components found: ${componentOutPaths.join(', ')}`)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// map from built back to original module names
|
|
456
|
+
for (const component of components) {
|
|
457
|
+
component.moduleName =
|
|
458
|
+
baseComponents[componentOutPaths.indexOf(component.moduleName)] ||
|
|
459
|
+
component.moduleName
|
|
460
|
+
|
|
461
|
+
if (!component.moduleName) {
|
|
462
|
+
if (process.env.DEBUG?.includes('@hanzo/gui') || process.env.IS_HANZO_GUI_DEV) {
|
|
463
|
+
console.warn(
|
|
464
|
+
`⚠️ no module name found: ${component.moduleName} ${JSON.stringify(
|
|
465
|
+
baseComponents
|
|
466
|
+
)} in ${JSON.stringify(componentOutPaths)}`
|
|
467
|
+
)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (
|
|
473
|
+
process.env.NODE_ENV === 'development' &&
|
|
474
|
+
process.env.DEBUG?.startsWith('@hanzo/gui')
|
|
475
|
+
) {
|
|
476
|
+
console.info('Loaded components', components)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const res = {
|
|
480
|
+
components,
|
|
481
|
+
nameToPaths: {},
|
|
482
|
+
guiConfig: config,
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
currentBundle = res
|
|
486
|
+
updateLastLoaded(res)
|
|
487
|
+
|
|
488
|
+
return res
|
|
489
|
+
} catch (err: any) {
|
|
490
|
+
console.error(
|
|
491
|
+
`Error bundling gui config: ${err?.message} (run with DEBUG=hanzo-gui to see stack)`
|
|
492
|
+
)
|
|
493
|
+
if (process.env.DEBUG?.includes('@hanzo/gui')) {
|
|
494
|
+
console.error(err.stack)
|
|
495
|
+
}
|
|
496
|
+
} finally {
|
|
497
|
+
isBundling = false
|
|
498
|
+
waitForBundle.forEach((cb) => cb())
|
|
499
|
+
waitForBundle.clear()
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export async function writeGuiCSS(outputCSS: string, config: GuiInternalConfig) {
|
|
504
|
+
const flush = async () => {
|
|
505
|
+
colorLog(Color.FgYellow, ` ➡ [hanzo-gui] output css: ${outputCSS}`)
|
|
506
|
+
await FS.writeFile(outputCSS, css)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const css = config.getCSS()
|
|
510
|
+
if (typeof css !== 'string') {
|
|
511
|
+
throw new Error(`Invalid CSS: ${typeof css} ${css}`)
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
if (existsSync(outputCSS) && (await readFile(outputCSS, 'utf8')) === css) {
|
|
515
|
+
// no change
|
|
516
|
+
} else {
|
|
517
|
+
await flush()
|
|
518
|
+
}
|
|
519
|
+
} catch (err) {
|
|
520
|
+
console.info('Error writing themes', err)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export async function loadComponents(props: GuiOptions, forceExports = false) {
|
|
525
|
+
const coreComponents = getCoreComponentsSync(props)
|
|
526
|
+
const otherComponents = await loadComponentsInner(props, forceExports)
|
|
527
|
+
return [...coreComponents, ...(otherComponents || [])]
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export function loadComponentsSync(props: GuiOptions, forceExports = false) {
|
|
531
|
+
const coreComponents = getCoreComponentsSync(props)
|
|
532
|
+
const otherComponents = loadComponentsInnerSync(props, forceExports)
|
|
533
|
+
return [...coreComponents, ...(otherComponents || [])]
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function getCoreComponentsSync(props: GuiOptions) {
|
|
537
|
+
const loaded = loadComponentsInnerSync({
|
|
538
|
+
...props,
|
|
539
|
+
components: ['@hanzogui/core'],
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
if (!loaded) {
|
|
543
|
+
throw new Error(`Core should always load`)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// always load core so we can optimize if directly importing
|
|
547
|
+
return [
|
|
548
|
+
{
|
|
549
|
+
...loaded[0],
|
|
550
|
+
moduleName: '@hanzogui/core',
|
|
551
|
+
},
|
|
552
|
+
]
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export async function loadComponentsInner(
|
|
556
|
+
props: GuiOptions,
|
|
557
|
+
forceExports = false
|
|
558
|
+
): Promise<null | LoadedComponents[]> {
|
|
559
|
+
const componentsModules = props.components || []
|
|
560
|
+
|
|
561
|
+
const key = componentsModules.join('\0')
|
|
562
|
+
|
|
563
|
+
if (!forceExports && cacheComponents[key]) {
|
|
564
|
+
return cacheComponents[key]
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const { unregister } = registerRequire(props.platform || 'web', {
|
|
568
|
+
proxyWormImports: forceExports,
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
const results: LoadedComponents[] = []
|
|
573
|
+
|
|
574
|
+
for (const name of componentsModules) {
|
|
575
|
+
const extension = extname(name)
|
|
576
|
+
const isLocal = Boolean(extension)
|
|
577
|
+
const isDynamic = isLocal && forceExports
|
|
578
|
+
const format = isLocal ? detectModuleFormat(name) : ('cjs' as const)
|
|
579
|
+
|
|
580
|
+
const fileContents = isDynamic ? readFileSync(name, 'utf-8') : ''
|
|
581
|
+
let loadModule = name
|
|
582
|
+
let writtenContents = fileContents
|
|
583
|
+
let didBabel = false
|
|
584
|
+
|
|
585
|
+
const attemptLoad = async ({ forceExports = false } = {}) => {
|
|
586
|
+
if (isDynamic) {
|
|
587
|
+
writtenContents = forceExports
|
|
588
|
+
? transformAddExports(babelParse(esbuildit(fileContents, 'modern'), name))
|
|
589
|
+
: fileContents
|
|
590
|
+
loadModule = getDynamicEvalOutfile(name, format, writtenContents)
|
|
591
|
+
|
|
592
|
+
FS.ensureDirSync(dirname(loadModule))
|
|
593
|
+
activeTempFiles.add(loadModule)
|
|
594
|
+
|
|
595
|
+
await esbuild.build({
|
|
596
|
+
...esbuildOptionsWithPlugins,
|
|
597
|
+
format,
|
|
598
|
+
outfile: loadModule,
|
|
599
|
+
stdin: {
|
|
600
|
+
contents: writtenContents,
|
|
601
|
+
resolveDir: dirname(name),
|
|
602
|
+
sourcefile: name,
|
|
603
|
+
loader: getEsbuildStdinLoader(name),
|
|
604
|
+
},
|
|
605
|
+
alias: {
|
|
606
|
+
'react-native': resolvePackageEntry(
|
|
607
|
+
'@hanzogui/react-native-web-lite',
|
|
608
|
+
format
|
|
609
|
+
),
|
|
610
|
+
'@hanzogui/react-native-web-lite': resolvePackageEntry(
|
|
611
|
+
'@hanzogui/react-native-web-lite',
|
|
612
|
+
format
|
|
613
|
+
),
|
|
614
|
+
'@hanzogui/react-native-web-internals': resolvePackageEntry(
|
|
615
|
+
'@hanzogui/react-native-web-internals',
|
|
616
|
+
format
|
|
617
|
+
),
|
|
618
|
+
},
|
|
619
|
+
bundle: true,
|
|
620
|
+
packages: 'external',
|
|
621
|
+
allowOverwrite: true,
|
|
622
|
+
sourcemap: false,
|
|
623
|
+
loader: esbuildLoaderConfig,
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (process.env.DEBUG === '@hanzo/gui') {
|
|
628
|
+
console.info(`loadModule`, loadModule, format)
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
let moduleResult: any
|
|
632
|
+
if (format === 'esm') {
|
|
633
|
+
// use file:// URL for proper ESM resolution
|
|
634
|
+
moduleResult = await import(pathToFileURL(loadModule).href)
|
|
635
|
+
} else {
|
|
636
|
+
moduleResult = require(loadModule)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (!forceExports) {
|
|
640
|
+
setRequireResult(name, moduleResult)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const nameToInfo = getComponentStaticConfigByName(
|
|
644
|
+
name,
|
|
645
|
+
interopDefaultExport(moduleResult)
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
return {
|
|
649
|
+
moduleName: name,
|
|
650
|
+
nameToInfo,
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const dispose = () => {
|
|
655
|
+
if (isDynamic) {
|
|
656
|
+
FS.removeSync(loadModule)
|
|
657
|
+
activeTempFiles.delete(loadModule)
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
let loaded: LoadedComponents | LoadedComponents[] | undefined
|
|
662
|
+
|
|
663
|
+
try {
|
|
664
|
+
loaded = await attemptLoad({ forceExports: true })
|
|
665
|
+
didBabel = true
|
|
666
|
+
} catch (err) {
|
|
667
|
+
console.info('babel err', err, writtenContents)
|
|
668
|
+
writtenContents = fileContents
|
|
669
|
+
if (process.env.DEBUG?.startsWith('@hanzo/gui')) {
|
|
670
|
+
console.info(`Error parsing babel likely`, err)
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
try {
|
|
674
|
+
loaded = await attemptLoad({ forceExports: false })
|
|
675
|
+
} catch (err2) {
|
|
676
|
+
if (process.env.HANZO_GUI_ENABLE_WARN_DYNAMIC_LOAD) {
|
|
677
|
+
console.info(
|
|
678
|
+
`\nGui attempted but failed to dynamically optimize components in:\n ${name}\n`
|
|
679
|
+
)
|
|
680
|
+
console.info(err2)
|
|
681
|
+
console.info(
|
|
682
|
+
`At: ${loadModule}`,
|
|
683
|
+
`\ndidBabel: ${didBabel}`,
|
|
684
|
+
`\nIn:`,
|
|
685
|
+
writtenContents,
|
|
686
|
+
`\nisDynamic: `,
|
|
687
|
+
isDynamic
|
|
688
|
+
)
|
|
689
|
+
}
|
|
690
|
+
loaded = []
|
|
691
|
+
}
|
|
692
|
+
} finally {
|
|
693
|
+
dispose()
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (Array.isArray(loaded)) {
|
|
697
|
+
results.push(...loaded)
|
|
698
|
+
} else if (loaded) {
|
|
699
|
+
results.push(loaded)
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
cacheComponents[key] = results
|
|
704
|
+
return results
|
|
705
|
+
} catch (err: any) {
|
|
706
|
+
console.info(`Gui error bundling components`, err.message, err.stack)
|
|
707
|
+
return null
|
|
708
|
+
} finally {
|
|
709
|
+
unregister()
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// sync version - uses cjs format for buildSync (no plugin support)
|
|
714
|
+
export function loadComponentsInnerSync(
|
|
715
|
+
props: GuiOptions,
|
|
716
|
+
forceExports = false
|
|
717
|
+
): null | LoadedComponents[] {
|
|
718
|
+
const componentsModules = props.components || []
|
|
719
|
+
|
|
720
|
+
const key = componentsModules.join('\0')
|
|
721
|
+
|
|
722
|
+
if (!forceExports && cacheComponents[key]) {
|
|
723
|
+
return cacheComponents[key]
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const { unregister } = registerRequire(props.platform || 'web', {
|
|
727
|
+
proxyWormImports: forceExports,
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
try {
|
|
731
|
+
const info: LoadedComponents[] = componentsModules.flatMap((name) => {
|
|
732
|
+
const extension = extname(name)
|
|
733
|
+
const isLocal = Boolean(extension)
|
|
734
|
+
const isDynamic = isLocal && forceExports
|
|
735
|
+
|
|
736
|
+
const fileContents = isDynamic ? readFileSync(name, 'utf-8') : ''
|
|
737
|
+
let loadModule = name
|
|
738
|
+
let writtenContents = fileContents
|
|
739
|
+
let didBabel = false
|
|
740
|
+
|
|
741
|
+
function attemptLoad({ forceExports = false } = {}) {
|
|
742
|
+
if (isDynamic) {
|
|
743
|
+
writtenContents = forceExports
|
|
744
|
+
? transformAddExports(babelParse(esbuildit(fileContents, 'modern'), name))
|
|
745
|
+
: fileContents
|
|
746
|
+
loadModule = getDynamicEvalOutfile(name, 'cjs', writtenContents)
|
|
747
|
+
|
|
748
|
+
FS.ensureDirSync(dirname(loadModule))
|
|
749
|
+
activeTempFiles.add(loadModule)
|
|
750
|
+
|
|
751
|
+
esbuild.buildSync({
|
|
752
|
+
...esbuildOptions,
|
|
753
|
+
outfile: loadModule,
|
|
754
|
+
stdin: {
|
|
755
|
+
contents: writtenContents,
|
|
756
|
+
resolveDir: dirname(name),
|
|
757
|
+
sourcefile: name,
|
|
758
|
+
loader: getEsbuildStdinLoader(name),
|
|
759
|
+
},
|
|
760
|
+
alias: {
|
|
761
|
+
'react-native': resolvePackageEntry(
|
|
762
|
+
'@hanzogui/react-native-web-lite',
|
|
763
|
+
'esm'
|
|
764
|
+
),
|
|
765
|
+
'@hanzogui/react-native-web-lite': resolvePackageEntry(
|
|
766
|
+
'@hanzogui/react-native-web-lite',
|
|
767
|
+
'esm'
|
|
768
|
+
),
|
|
769
|
+
'@hanzogui/react-native-web-internals': resolvePackageEntry(
|
|
770
|
+
'@hanzogui/react-native-web-internals',
|
|
771
|
+
'esm'
|
|
772
|
+
),
|
|
773
|
+
},
|
|
774
|
+
bundle: true,
|
|
775
|
+
packages: 'external',
|
|
776
|
+
allowOverwrite: true,
|
|
777
|
+
sourcemap: false,
|
|
778
|
+
loader: esbuildLoaderConfig,
|
|
779
|
+
})
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (process.env.DEBUG === '@hanzo/gui') {
|
|
783
|
+
console.info(`loadModule`, loadModule, require.resolve(loadModule))
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const moduleResult = require(loadModule)
|
|
787
|
+
|
|
788
|
+
if (!forceExports) {
|
|
789
|
+
setRequireResult(name, moduleResult)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const nameToInfo = getComponentStaticConfigByName(
|
|
793
|
+
name,
|
|
794
|
+
interopDefaultExport(moduleResult)
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
moduleName: name,
|
|
799
|
+
nameToInfo,
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const dispose = () => {
|
|
804
|
+
if (isDynamic) {
|
|
805
|
+
FS.removeSync(loadModule)
|
|
806
|
+
activeTempFiles.delete(loadModule)
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
try {
|
|
811
|
+
const res = attemptLoad({ forceExports: true })
|
|
812
|
+
didBabel = true
|
|
813
|
+
return res
|
|
814
|
+
} catch (err) {
|
|
815
|
+
console.info('babel err', err, writtenContents)
|
|
816
|
+
writtenContents = fileContents
|
|
817
|
+
if (process.env.DEBUG?.startsWith('@hanzo/gui')) {
|
|
818
|
+
console.info(`Error parsing babel likely`, err)
|
|
819
|
+
}
|
|
820
|
+
} finally {
|
|
821
|
+
dispose()
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
try {
|
|
825
|
+
return attemptLoad({ forceExports: false })
|
|
826
|
+
} catch (err) {
|
|
827
|
+
if (process.env.HANZO_GUI_ENABLE_WARN_DYNAMIC_LOAD) {
|
|
828
|
+
console.info(
|
|
829
|
+
`\nGui attempted but failed to dynamically optimize components in:\n ${name}\n`
|
|
830
|
+
)
|
|
831
|
+
console.info(err)
|
|
832
|
+
console.info(
|
|
833
|
+
`At: ${loadModule}`,
|
|
834
|
+
`\ndidBabel: ${didBabel}`,
|
|
835
|
+
`\nIn:`,
|
|
836
|
+
writtenContents,
|
|
837
|
+
`\nisDynamic: `,
|
|
838
|
+
isDynamic
|
|
839
|
+
)
|
|
840
|
+
}
|
|
841
|
+
return []
|
|
842
|
+
} finally {
|
|
843
|
+
dispose()
|
|
844
|
+
}
|
|
845
|
+
})
|
|
846
|
+
cacheComponents[key] = info
|
|
847
|
+
return info
|
|
848
|
+
} catch (err: any) {
|
|
849
|
+
console.info(`Gui error bundling components`, err.message, err.stack)
|
|
850
|
+
return null
|
|
851
|
+
} finally {
|
|
852
|
+
unregister()
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const esbuildit = (src: string, target?: 'modern') => {
|
|
857
|
+
return esbuild.transformSync(src, {
|
|
858
|
+
...esbuildTransformOptions,
|
|
859
|
+
...(target === 'modern' && {
|
|
860
|
+
target: 'es2022',
|
|
861
|
+
jsx: 'automatic',
|
|
862
|
+
loader: 'tsx',
|
|
863
|
+
platform: 'neutral',
|
|
864
|
+
format: 'esm',
|
|
865
|
+
}),
|
|
866
|
+
}).code
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function getComponentStaticConfigByName(name: string, exported: any) {
|
|
870
|
+
const components: Record<string, { staticConfig: StaticConfig }> = {}
|
|
871
|
+
try {
|
|
872
|
+
if (!exported || typeof exported !== 'object' || Array.isArray(exported)) {
|
|
873
|
+
throw new Error(`Invalid export from package ${name}: ${typeof exported}`)
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
for (const key in exported) {
|
|
877
|
+
const found = getGuiComponent(key, exported[key])
|
|
878
|
+
if (found) {
|
|
879
|
+
// remove non-stringifyable
|
|
880
|
+
const { Component, ...sc } = found.staticConfig
|
|
881
|
+
components[key] = { staticConfig: sc }
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
} catch (err) {
|
|
885
|
+
if (process.env.HANZO_GUI_ENABLE_WARN_DYNAMIC_LOAD) {
|
|
886
|
+
console.error(
|
|
887
|
+
`Gui failed getting components from ${name} (Disable error by setting environment variable HANZO_GUI_ENABLE_WARN_DYNAMIC_LOAD=1)`
|
|
888
|
+
)
|
|
889
|
+
console.error(err)
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return components
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function getGuiComponent(
|
|
896
|
+
name: string,
|
|
897
|
+
Component: any
|
|
898
|
+
): undefined | { staticConfig: StaticConfig } {
|
|
899
|
+
if (name[0].toUpperCase() !== name[0]) {
|
|
900
|
+
return
|
|
901
|
+
}
|
|
902
|
+
const staticConfig = Component?.staticConfig as StaticConfig | undefined
|
|
903
|
+
if (staticConfig) {
|
|
904
|
+
return Component
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function interopDefaultExport(mod: any) {
|
|
909
|
+
return mod?.default ?? mod
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const cacheComponents: Record<string, LoadedComponents[]> = {}
|
|
913
|
+
|
|
914
|
+
function transformAddExports(ast: t.File) {
|
|
915
|
+
const usedNames = new Set<string>()
|
|
916
|
+
|
|
917
|
+
// avoid clobbering
|
|
918
|
+
// @ts-ignore
|
|
919
|
+
traverse(ast, {
|
|
920
|
+
ExportNamedDeclaration(nodePath) {
|
|
921
|
+
if (nodePath.node.specifiers) {
|
|
922
|
+
for (const spec of nodePath.node.specifiers) {
|
|
923
|
+
usedNames.add(
|
|
924
|
+
t.isIdentifier(spec.exported) ? spec.exported.name : spec.exported.value
|
|
925
|
+
)
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
})
|
|
930
|
+
|
|
931
|
+
// @ts-ignore
|
|
932
|
+
traverse(ast, {
|
|
933
|
+
VariableDeclaration(nodePath) {
|
|
934
|
+
// top level only
|
|
935
|
+
if (!t.isProgram(nodePath.parent)) return
|
|
936
|
+
const decs = nodePath.node.declarations
|
|
937
|
+
if (decs.length > 1) return
|
|
938
|
+
const [dec] = decs
|
|
939
|
+
if (!t.isIdentifier(dec.id)) return
|
|
940
|
+
if (!dec.init) return
|
|
941
|
+
if (usedNames.has(dec.id.name)) return
|
|
942
|
+
usedNames.add(dec.id.name)
|
|
943
|
+
nodePath.replaceWith(
|
|
944
|
+
t.exportNamedDeclaration(t.variableDeclaration('let', [dec]), [
|
|
945
|
+
t.exportSpecifier(t.identifier(dec.id.name), t.identifier(dec.id.name)),
|
|
946
|
+
])
|
|
947
|
+
)
|
|
948
|
+
},
|
|
949
|
+
})
|
|
950
|
+
|
|
951
|
+
// @ts-ignore
|
|
952
|
+
return generate(ast as any, {
|
|
953
|
+
concise: false,
|
|
954
|
+
filename: 'test.tsx',
|
|
955
|
+
retainLines: false,
|
|
956
|
+
sourceMaps: false,
|
|
957
|
+
}).code
|
|
958
|
+
}
|