uppy/packages/@uppy/components/migrate.mjs
Mikael Finstad 075636a3b3
remove react h (#5938)
it was added in #5935 but no explanation of why.

if you look at the build output of for example
`packages/@uppy/react/lib/headless/generated/Dropzone.js`:

```js
import { jsx as _jsx } from "react/jsx-runtime";
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { Dropzone as PreactDropzone, } from '@uppy/components';
import { h as preactH, render as preactRender } from 'preact';
// biome-ignore lint/correctness/noUnusedImports: it's needed
import { createElement as h, useContext, useEffect, useRef } from 'react';
import { UppyContext } from '../UppyContextProvider.js';
export default function Dropzone(props) {
    const ref = useRef(null);
    const ctx = useContext(UppyContext);
    useEffect(() => {
        if (ref.current) {
            preactRender(preactH(PreactDropzone, {
                ...props,
                ctx,
            }), ref.current);
        }
    }, [ctx, props]);
    return _jsx("div", { ref: ref });
}
```

as can be seen there is no usage of `h`. `import { jsx as _jsx } from
"react/jsx-runtime";` is being injected for React JSX. see also
https://github.com/transloadit/uppy/pull/5896#issuecomment-3169210799
2025-08-29 16:24:16 +02:00

257 lines
7.4 KiB
JavaScript

#!/usr/bin/env node
// When new components are added (not when existing ones are changed),
// run this script to generate React, Vue, and Svelte wrappers
import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
// Get the directory of this script
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const rootDir = path.resolve(scriptDir, '../../..')
// Define paths
// source:
const COMPONENTS_DIR = path.join(rootDir, 'packages/@uppy/components/src')
// destinations:
const REACT_DIR = path.join(
rootDir,
'packages/@uppy/react/src/headless/generated',
)
const VUE_DIR = path.join(rootDir, 'packages/@uppy/vue/src/headless/generated')
const SVELTE_DIR = path.join(
rootDir,
'packages/@uppy/svelte/src/lib/components/headless/generated',
)
// Templates
const REACT_TEMPLATE = `\
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import {
%%ComponentName%% as %%PreactComponentName%%,
type %%PropsTypeName%%,
} from '@uppy/components'
import { h as preactH, render as preactRender } from 'preact'
import { useContext, useEffect, useRef } from 'react'
import { UppyContext } from '../UppyContextProvider.js'
export default function %%ComponentName%%(props: Omit<%%PropsTypeName%%, 'ctx'>) {
const ref = useRef(null)
const ctx = useContext(UppyContext)
useEffect(() => {
if (ref.current) {
preactRender(
preactH(%%PreactComponentName%%, {
...props,
ctx,
} satisfies %%PropsTypeName%%),
ref.current,
)
}
}, [ctx, props])
return <div ref={ref} />
}
`
const VUE_TEMPLATE = `\
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { defineComponent, ref, watch, onMounted, h } from 'vue'
import {
%%ComponentName%% as %%PreactComponentName%%,
type %%PropsTypeName%%,
} from '@uppy/components'
import { h as preactH, render as preactRender } from 'preact'
import { shallowEqualObjects } from 'shallow-equal'
import { useUppyContext } from '../useUppyContext.js'
export default defineComponent<Omit<%%PropsTypeName%%, 'ctx'>>({
name: '%%ComponentName%%',
setup(props, { attrs }) {
const containerRef = ref<HTMLElement | null>(null)
const ctx = useUppyContext()
function render%%ComponentName%%() {
if (containerRef.value) {
preactRender(
preactH(%%PreactComponentName%%, {
...(attrs as %%PropsTypeName%%),
ctx,
} satisfies %%PropsTypeName%%),
containerRef.value,
)
}
}
onMounted(() => {
render%%ComponentName%%()
})
watch(ctx, () => {
render%%ComponentName%%()
})
watch(
() => props,
(current, old) => {
if (!shallowEqualObjects(current, old)) {
render%%ComponentName%%()
}
},
)
return () => h('div', { ref: containerRef })
},
})
`
const SVELTE_TEMPLATE = `\
<script lang="ts">
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { getContext, mount } from 'svelte'
import {
%%ComponentName%% as %%PreactComponentName%%,
type %%PropsTypeName%%,
type UppyContext,
} from '@uppy/components'
import { h as preactH, render as preactRender } from 'preact'
import { UppyContextKey } from '../UppyContextProvider.svelte'
const props: Omit<%%PropsTypeName%%, 'ctx'> = $props()
const ctx = getContext<UppyContext>(UppyContextKey)
let container: HTMLElement
$effect(() => {
if (container) {
preactRender(
preactH(%%PreactComponentName%%, {
...props,
ctx,
} satisfies %%PropsTypeName%%),
container,
)
}
})
</script>
<div bind:this={container}></div>
`
try {
// Check if components directory exists
await fs.access(COMPONENTS_DIR).catch(() => {
throw new Error(`Components directory not found: ${COMPONENTS_DIR}`)
})
await Promise.all(
[REACT_DIR, VUE_DIR, SVELTE_DIR].map(async (dir) => {
if (!existsSync(dir)) {
await fs.mkdir(dir, { recursive: true })
}
}),
)
// Read all files in components directory
const files = await fs.readdir(COMPONENTS_DIR)
// Filter for .tsx files
const tsxFiles = files.filter((file) => file.endsWith('.tsx'))
console.log(`Found ${tsxFiles.length} Preact component(s) to process\n`)
// Track generated components for index files
const reactComponents = []
const vueComponents = []
const svelteComponents = []
// Process each tsx file
for (const file of tsxFiles) {
try {
const componentName = path.basename(file, '.tsx')
const propsTypeName = `${componentName}Props`
const preactComponentName = `Preact${componentName}`
// Generate React wrapper
const reactContent = REACT_TEMPLATE.replace(
/%%ComponentName%%/g,
componentName,
)
.replace(/%%PreactComponentName%%/g, preactComponentName)
.replace(/%%PropsTypeName%%/g, propsTypeName)
// Generate Vue wrapper
const vueContent = VUE_TEMPLATE.replace(
/%%ComponentName%%/g,
componentName,
)
.replace(/%%PreactComponentName%%/g, preactComponentName)
.replace(/%%PropsTypeName%%/g, propsTypeName)
// Generate Svelte wrapper
const svelteContent = SVELTE_TEMPLATE.replace(
/%%ComponentName%%/g,
componentName,
)
.replace(/%%PreactComponentName%%/g, preactComponentName)
.replace(/%%PropsTypeName%%/g, propsTypeName)
// Write files
const reactFilePath = path.join(REACT_DIR, `${componentName}.tsx`)
const vueFilePath = path.join(VUE_DIR, `${componentName}.ts`)
const svelteFilePath = path.join(SVELTE_DIR, `${componentName}.svelte`)
await fs.writeFile(reactFilePath, reactContent)
await fs.writeFile(vueFilePath, vueContent)
await fs.writeFile(svelteFilePath, svelteContent)
// Add to component lists for index files
reactComponents.push(componentName)
vueComponents.push(componentName)
svelteComponents.push(componentName)
console.log(`${componentName}`)
} catch (error) {
console.error(`Error processing component ${file}:`, error)
}
}
// Generate index files
if (reactComponents.length > 0) {
const reactIndexContent = reactComponents
.map((name) => `export { default as ${name} } from './${name}.js'`)
.join('\n')
await fs.writeFile(
path.join(REACT_DIR, 'index.ts'),
`${reactIndexContent}\n`,
)
console.log(`\nExporting React components from ${REACT_DIR}`)
}
if (vueComponents.length > 0) {
const vueIndexContent = vueComponents
.map((name) => `export { default as ${name} } from './${name}.js'`)
.join('\n')
await fs.writeFile(path.join(VUE_DIR, 'index.ts'), `${vueIndexContent}\n`)
console.log(`Exporting Vue components from ${VUE_DIR}`)
}
if (svelteComponents.length > 0) {
const svelteIndexContent = svelteComponents
.map((name) => `export { default as ${name} } from './${name}.svelte'`)
.join('\n')
await fs.writeFile(
path.join(SVELTE_DIR, 'index.ts'),
`${svelteIndexContent}\n`,
)
console.log(`Exporting Svelte components from ${SVELTE_DIR}`)
}
console.log('\nAll wrappers and index files generated successfully!')
} catch (error) {
console.error('Error generating wrappers:', error)
process.exit(1)
}