diff --git a/adex/package.json b/adex/package.json index d7d076d..b7a19b4 100644 --- a/adex/package.json +++ b/adex/package.json @@ -17,6 +17,14 @@ "type": "module", "exports": { "./package.json": "./package.json", + "./router": { + "types": "./src/router.d.ts", + "import": "./src/router.js" + }, + "./utils/isomorphic": { + "types": "./src/utils/isomorphic.d.ts", + "import": "./src/utils/isomorphic.js" + }, "./ssr": { "types": "./src/ssr.d.ts", "import": "./src/ssr.js" @@ -61,6 +69,7 @@ "hoofd": "^1.7.1", "mri": "^1.2.0", "node-stream-zip": "^1.15.0", + "preact-iso": "^2.9.0", "preact-render-to-string": "^6.5.5", "regexparam": "^3.0.0", "sirv": "^2.0.4", diff --git a/adex/runtime/client.js b/adex/runtime/client.js index 96c5ee1..d2bb424 100644 --- a/adex/runtime/client.js +++ b/adex/runtime/client.js @@ -1,20 +1,51 @@ -import { hydrate as preactHydrate, h } from 'preact' +import { h } from 'preact' +import { + LocationProvider, + Router, + Route, + lazy, + hydrate as preactHydrate, + ErrorBoundary, +} from 'adex/router' + import 'virtual:adex:global.css' -const pageRoutes = import.meta.glob('/src/pages/**/*.{tsx,jsx,js}') +// @ts-expect-error injected by vite +import { routes } from '~routes' -async function hydrate() { - const entryPage = document.getElementById('app').dataset.entryPage - const routeParams = document.getElementById('app').dataset.routeParams - const componentModule = await pageRoutes[entryPage]() - const Component = - 'default' in componentModule ? componentModule.default : componentModule - preactHydrate( - h(Component, { - routeParams: routeParams ? JSON.parse(atob(routeParams)) : {}, - }), - document.getElementById('app') +const withComponents = routes.map(d => { + return { + ...d, + component: lazy(d.module), + } +}) + +function ComponentWrapper({ url = '' }) { + return h( + LocationProvider, + //@ts-expect-error no types for non-jsx function + { url: url }, + h( + ErrorBoundary, + {}, + h( + Router, + {}, + withComponents.map(d => + h(Route, { path: d.routePath, component: d.component }) + ) + ) + ) ) } -hydrate() +export const App = ({ url = '' }) => { + return h(ComponentWrapper, { url }) +} + +async function hydrate() { + preactHydrate(h(ComponentWrapper, {}), document.getElementById('app')) +} +if (typeof window !== 'undefined') { + hydrate() +} diff --git a/adex/runtime/handler.js b/adex/runtime/handler.js index 5cc6023..02db02c 100644 --- a/adex/runtime/handler.js +++ b/adex/runtime/handler.js @@ -1,8 +1,12 @@ import { CONSTANTS, emitToHooked } from 'adex/hook' import { prepareRequest, prepareResponse } from 'adex/http' -import { renderToString, toStatic } from 'adex/ssr' +import { toStatic } from 'adex/ssr' +import { renderToString } from 'adex/utils/isomorphic' import { h } from 'preact' +// @ts-expect-error injected by vite +import { App } from 'virtual:adex:client' + // @ts-expect-error injected by vite import { routes as apiRoutes } from '~apiRoutes' // @ts-expect-error injected by vite @@ -16,7 +20,8 @@ export async function handler(req, res) { prepareRequest(req) prepareResponse(res) - const [baseURL] = req.url.split('?') + const [url, search] = req.url.split('?') + const baseURL = normalizeRequestUrl(url) const { metas, links, title, lang } = toStatic() @@ -50,10 +55,15 @@ export async function handler(req, res) { }) if (matchedInPages) { - const module = await matchedInPages.module() - const render = 'default' in module ? module.default : module const routeParams = getRouteParams(baseURL, matchedInPages) + // @ts-expect-error + global.location = new URL(req.url, 'http://localhost') + + const rendered = await renderToString( + h(App, { url: [baseURL, search].filter(Boolean).join('?') }) + ) + const htmlString = HTMLTemplate({ metas, links, @@ -63,7 +73,7 @@ export async function handler(req, res) { routeParams: Buffer.from(JSON.stringify(routeParams), 'utf8').toString( 'base64' ), - body: renderToString(h(render, { routeParams })), + body: rendered.html, }) const modifiableContext = { req: req, @@ -105,13 +115,7 @@ function HTMLTemplate({ ${headString} -
- ${body} -
+
${body}
` @@ -146,3 +150,7 @@ const stringify = (title, metas, links) => { ${stringifyTag('link', links)} ` } + +function normalizeRequestUrl(url) { + return url.replace(/\/(index\.html)$/, '/') +} diff --git a/adex/runtime/pages.js b/adex/runtime/pages.js index 4505c17..3c02cea 100644 --- a/adex/runtime/pages.js +++ b/adex/runtime/pages.js @@ -1,4 +1,4 @@ -import { pathToRegex } from 'adex/ssr' +import { pathToRegex } from 'adex/utils/isomorphic' const pages = import.meta.glob('#{__PLUGIN_PAGES_ROOT}') diff --git a/adex/src/fonts.js b/adex/src/fonts.js index 0b3a9b4..b105c12 100644 --- a/adex/src/fonts.js +++ b/adex/src/fonts.js @@ -49,7 +49,7 @@ export function fonts({ providers = [], families = [] } = {}) { }, async transform(code, id) { const resolvedData = await this.resolve('virtual:adex:client') - if (id === resolvedData.id) { + if (resolvedData?.id == id) { return { code: `import "${fontVirtualId}";\n` + code, } diff --git a/adex/src/router.d.ts b/adex/src/router.d.ts new file mode 100644 index 0000000..a6d3fa8 --- /dev/null +++ b/adex/src/router.d.ts @@ -0,0 +1,8 @@ +export { + hydrate, + Router, + Route, + lazy, + LocationProvider, + ErrorBoundary, +} from 'preact-iso' diff --git a/adex/src/router.js b/adex/src/router.js new file mode 100644 index 0000000..0b4089d --- /dev/null +++ b/adex/src/router.js @@ -0,0 +1,8 @@ +export { + hydrate, + Router, + ErrorBoundary, + Route, + lazy, + LocationProvider, +} from 'preact-iso' diff --git a/adex/src/ssr.d.ts b/adex/src/ssr.d.ts index 7cff11b..fb95baa 100644 --- a/adex/src/ssr.d.ts +++ b/adex/src/ssr.d.ts @@ -1,6 +1,4 @@ export { toStatic } from 'hoofd/preact' -export { renderToString } from 'preact-render-to-string' -export { parse as pathToRegex } from 'regexparam' export { use as useMiddleware } from '@barelyhuman/tiny-use' export { default as sirv } from 'sirv' -export { default as mri } from 'mri' \ No newline at end of file +export { default as mri } from 'mri' diff --git a/adex/src/ssr.js b/adex/src/ssr.js index 5ab3821..f76c097 100644 --- a/adex/src/ssr.js +++ b/adex/src/ssr.js @@ -1,6 +1,5 @@ -export { renderToString } from 'preact-render-to-string' +export { prerender as renderToString } from 'preact-iso' export { default as sirv } from 'sirv' export { default as mri } from 'mri' -export { parse as pathToRegex } from 'regexparam' export { toStatic } from 'hoofd/preact' export { use as useMiddleware } from '@barelyhuman/tiny-use' diff --git a/adex/src/utils/isomorphic.js b/adex/src/utils/isomorphic.js new file mode 100644 index 0000000..16f21ee --- /dev/null +++ b/adex/src/utils/isomorphic.js @@ -0,0 +1,2 @@ +export { parse as pathToRegex } from 'regexparam' +export { prerender as renderToString } from 'preact-iso' diff --git a/adex/src/vite.d.ts b/adex/src/vite.d.ts index 50e9c32..6fa03f9 100644 --- a/adex/src/vite.d.ts +++ b/adex/src/vite.d.ts @@ -7,6 +7,7 @@ export interface AdexOptions { fonts?: FontOptions islands?: boolean adapter?: Adapters + ssr?: boolean __clientConfig?: UserConfig } diff --git a/adex/src/vite.js b/adex/src/vite.js index 78fccd7..17ad98d 100644 --- a/adex/src/vite.js +++ b/adex/src/vite.js @@ -34,6 +34,7 @@ const adapterMap = { export function adex({ fonts, islands = false, + ssr = true, adapter: adapter = 'node', __clientConfig = {}, } = {}) { @@ -52,86 +53,78 @@ export function adex({ '', 'src/global.css' ), + createVirtualModule( + 'virtual:adex:client', + readFileSync(join(__dirname, '../runtime/client.js'), 'utf8') + ), createVirtualModule( 'virtual:adex:handler', readFileSync(join(__dirname, '../runtime/handler.js'), 'utf8') ), - createVirtualModule( - 'virtual:adex:server', - `import { createServer } from '${adapterMap[adapter]}' - import { dirname, join } from 'node:path' - import { fileURLToPath } from 'node:url' - import { existsSync, readFileSync } from 'node:fs' - import { env } from 'adex/env' - - import 'virtual:adex:font.css' - import 'virtual:adex:global.css' - - const __dirname = dirname(fileURLToPath(import.meta.url)) + addFontsPlugin(fonts), + adexDevServer({ islands }), + adexBuildPrep({ islands }), + adexClientBuilder({ islands }), + adexIslandsBuilder(), - const PORT = parseInt(env.get('PORT', '3000'), 10) - const HOST = env.get('HOST', 'localhost') + // SSR/Render Server Specific plugins + ssr && adexServerBuilder({ fonts, adapter, islands }), + ...adexGuards(), + ] +} - const paths = { - assets: join(__dirname, './assets'), - islands: join(__dirname, './islands'), - client: join(__dirname, '../client'), - } - - function getServerManifest() { - const manifestPath = join(__dirname, 'manifest.json') - if (existsSync(manifestPath)) { - const manifestFile = readFileSync(manifestPath, 'utf8') - return parseManifest(manifestFile) - } - return {} +/** + * @returns {import("vite").Plugin} + */ +function adexClientBuilder({ islands = false } = {}) { + return { + name: 'adex-client-builder', + config(cfg) { + const out = cfg.build.outDir ?? 'dist' + return { + appType: 'custom', + build: { + write: !islands, + manifest: 'manifest.json', + outDir: join(out, 'client'), + rollupOptions: { + input: 'virtual:adex:client', + }, + output: { + entryFileNames: '[name]-[hash].js', + format: 'esm', + }, + }, } - - function getClientManifest() { - const manifestPath = join(__dirname, '../client/manifest.json') - if (existsSync(manifestPath)) { - const manifestFile = readFileSync(manifestPath, 'utf8') - return parseManifest(manifestFile) + }, + generateBundle(opts, bundle) { + let clientEntryPath + for (const key in bundle) { + if (bundle[key].name == '_virtual_adex_client') { + clientEntryPath = key } - return {} } - function parseManifest(manifestString) { - try { - const manifestJSON = JSON.parse(manifestString) - return manifestJSON - } catch (err) { - return {} - } - } + const links = [ + // @ts-expect-error invalid types by vite? figure this out + ...(bundle[clientEntryPath]?.viteMetadata?.importedCss ?? new Set()), + ].map(d => { + return `` + }) - const server = createServer({ - port: PORT, - host: HOST, - adex:{ - manifests:{server:getServerManifest(),client:getClientManifest()}, - paths, - } + this.emitFile({ + type: 'asset', + fileName: 'index.html', + source: ` + + ${links.join('\n')} + +
+ + `, }) - - if ('run' in server) { - server.run() - } - - export default server.fetch - ` - ), - createVirtualModule( - 'virtual:adex:client', - readFileSync(join(__dirname, '../runtime/client.js'), 'utf8') - ), - addFontsPlugin(fonts), - adexServerBuilder({ islands }), - adexBuildPrep({ islands }), - !islands && adexClientBuilder({ config: __clientConfig }), - islands && adexIslandsBuilder({ config: __clientConfig }), - ...adexGuards(), - ] + }, + } } /** @@ -163,11 +156,9 @@ function adexBuildPrep({ islands = false }) { } /** - * @param {object} opts - * @param {import("vite").UserConfig} opts.config * @returns {import("vite").Plugin[]} */ -function adexIslandsBuilder(opts) { +function adexIslandsBuilder() { const clientVirtuals = {} let isBuild = false let outDir @@ -176,7 +167,7 @@ function adexIslandsBuilder(opts) { name: 'adex-islands', enforce: 'pre', config(d, e) { - outDir = d.build.outDir + outDir = d.build?.outDir ?? 'dist' isBuild = e.command === 'build' }, transform(code, id, viteEnv) { @@ -197,6 +188,7 @@ function adexIslandsBuilder(opts) { if (!islands.length) return islands.forEach(node => { + //@ts-expect-error FIX: in preland injectIslandAST(node.ast, node) const clientCode = generateClientTemplate(node.id).replace( IMPORT_PATH_PLACEHOLDER, @@ -242,27 +234,30 @@ function adexIslandsBuilder(opts) { if (runningIslandBuild) return await build( - mergeConfig(opts.config, { - configFile: false, - plugins: [preact()], - build: { - ssr: false, - outDir: join(outDir, 'islands'), - emptyOutDir: true, - rollupOptions: { - output: { - format: 'esm', - entryFileNames: '[name].js', + mergeConfig( + {}, + { + configFile: false, + plugins: [preact()], + build: { + ssr: false, + outDir: join(outDir, 'islands'), + emptyOutDir: true, + rollupOptions: { + output: { + format: 'esm', + entryFileNames: '[name].js', + }, + input: Object.fromEntries( + Object.entries(clientVirtuals).map(([k, v]) => { + const key = getIslandName(k) + return [key, join(islandsDir, key + '.js')] + }) + ), }, - input: Object.fromEntries( - Object.entries(clientVirtuals).map(([k, v]) => { - const key = getIslandName(k) - return [key, join(islandsDir, key + '.js')] - }) - ), }, - }, - }) + } + ) ) }, }, @@ -347,60 +342,72 @@ function createUserDefaultVirtualModule(id, content, userPath) { * @param {import('vite').UserConfig} options.config * @returns {import("vite").Plugin} */ -function adexClientBuilder(opts) { +function adexClientSSRBuilder(opts) { let options return { name: 'adex-client', enforce: 'post', + config(conf) { + return { + appType: 'custom', + build: { + outDir: join(conf.build.outDir ?? 'dist', 'client'), + emptyOutDir: true, + ssr: false, + manifest: 'manifest.json', + rollupOptions: { + input: { + index: 'virtual:adex:client', + }, + output: { + entryFileNames: '[name]-[hash].js', + format: 'esm', + }, + }, + }, + } + }, configResolved(config) { options = config }, closeBundle() { - process.nextTick(async () => { - const usablePlugins = options.plugins - .filter(d => !d.name.startsWith('vite:')) - .filter(d => !d.name.startsWith('adex-') || d.name === 'adex-fonts') - await build( - mergeConfig(opts, { - configFile: false, - appType: 'custom', - base: '/client', - plugins: [ - ...usablePlugins, - createVirtualModule( - 'virtual:adex:client', - readFileSync(join(__dirname, '../runtime/client.js'), 'utf8') - ), - createUserDefaultVirtualModule( - 'virtual:adex:index.html', - '', - 'src/index.html' - ), - createUserDefaultVirtualModule( - 'virtual:adex:global.css', - '', - 'src/global.css' - ), - preact({ prefreshEnabled: false }), - ], - build: { - outDir: 'dist/client', - emptyOutDir: true, - ssr: false, - manifest: 'manifest.json', - rollupOptions: { - input: { - index: 'virtual:adex:client', - }, - output: { - entryFileNames: '[name]-[hash].js', - format: 'esm', - }, - }, - }, - }) - ) - }) + // process.nextTick(async () => { + // const usablePlugins = options.plugins + // .filter(d => !d.name.startsWith('vite:')) + // .filter(d => !d.name.startsWith('adex-') || d.name === 'adex-fonts') + // await build( + // mergeConfig(opts, { + // plugins: [ + // ...usablePlugins, + // createVirtualModule( + // 'virtual:adex:client', + // readFileSync(join(__dirname, '../runtime/client.js'), 'utf8') + // ), + // createUserDefaultVirtualModule( + // 'virtual:adex:index.html', + // '', + // 'src/index.html' + // ), + // preact({ prefreshEnabled: false }), + // ], + // build: { + // outDir: 'dist/client', + // emptyOutDir: true, + // ssr: false, + // manifest: 'manifest.json', + // rollupOptions: { + // input: { + // index: 'virtual:adex:client', + // }, + // output: { + // entryFileNames: '[name]-[hash].js', + // format: 'esm', + // }, + // }, + // }, + // }) + // ) + // }) }, } } @@ -408,43 +415,13 @@ function adexClientBuilder(opts) { /** * @returns {import("vite").Plugin} */ -function adexServerBuilder({ islands = false } = {}) { - let input = 'src/entry-server.js' +function adexDevServer({ islands = false } = {}) { const devCSSMap = new Map() let cfg return { - name: `adex-server`, + name: adexDevServer.name, + apply: 'serve', enforce: 'pre', - /** - * @returns {import("vite").UserConfig} - */ - config(conf, env) { - if (env.command === 'build') { - input = 'virtual:adex:server' - } - return { - appType: 'custom', - ssr: { - external: ['preact', 'adex', 'preact-render-to-string'], - noExternal: Object.values(adapterMap), - }, - build: { - outDir: join(conf.build?.outDir ?? 'dist', 'server'), - emptyOutDir: true, - assetsDir: 'assets', - ssrEmitAssets: true, - ssr: true, - manifest: 'manifest.json', - ssrManifest: 'ssr.manifest.json', - rollupOptions: { - input: { - index: input, - }, - external: ['adex/ssr'], - }, - }, - } - }, configResolved(_cfg) { cfg = _cfg }, @@ -513,6 +490,155 @@ function adexServerBuilder({ islands = false } = {}) { } } +/** + * @param {object} options + * @param {import("./fonts.js").Options} options.fonts + * @param {string} options.adapter + * @param {boolean} options.islands + * @returns {import("vite").Plugin} + */ +function adexServerBuilder({ fonts, adapter, islands }) { + let input = 'src/entry-server.js' + let cfg + return { + name: `adex-server`, + enforce: 'pre', + apply: 'build', + config(conf, env) { + if (env.command === 'build') { + input = 'virtual:adex:server' + } + }, + configResolved(config) { + cfg = config + }, + async generateBundle() { + const defOut = cfg.build?.outDir ?? 'dist' + const serverOutDir = defOut.endsWith('client') + ? join(dirname(defOut), 'server') + : join(defOut, 'server') + + console.log(`\nBuilding Server: ${serverOutDir}\n`) + + await build({ + configFile: false, + ssr: { + external: ['preact', 'adex', 'preact-render-to-string'], + noExternal: Object.values(adapterMap), + }, + appType: 'custom', + plugins: [ + preact(), + preactPages({ + root: '/src/pages', + id: '~routes', + }), + preactPages({ + root: '/src/api', + id: '~apiRoutes', + replacer: '/api', + }), + createUserDefaultVirtualModule( + 'virtual:adex:global.css', + '', + 'src/global.css' + ), + createVirtualModule( + 'virtual:adex:client', + readFileSync(join(__dirname, '../runtime/client.js'), 'utf8') + ), + createVirtualModule( + 'virtual:adex:handler', + readFileSync(join(__dirname, '../runtime/handler.js'), 'utf8') + ), + createVirtualModule( + 'virtual:adex:server', + `import { createServer } from '${adapterMap[adapter]}' + import { dirname, join } from 'node:path' + import { fileURLToPath } from 'node:url' + import { existsSync, readFileSync } from 'node:fs' + import { env } from 'adex/env' + + import 'virtual:adex:font.css' + import 'virtual:adex:global.css' + + const __dirname = dirname(fileURLToPath(import.meta.url)) + + const PORT = parseInt(env.get('PORT', '3000'), 10) + const HOST = env.get('HOST', 'localhost') + + const paths = { + assets: join(__dirname, './assets'), + islands: join(__dirname, './islands'), + client: join(__dirname, '../client'), + } + + function getServerManifest() { + const manifestPath = join(__dirname, 'manifest.json') + if (existsSync(manifestPath)) { + const manifestFile = readFileSync(manifestPath, 'utf8') + return parseManifest(manifestFile) + } + return {} + } + + function getClientManifest() { + const manifestPath = join(__dirname, '../client/manifest.json') + if (existsSync(manifestPath)) { + const manifestFile = readFileSync(manifestPath, 'utf8') + return parseManifest(manifestFile) + } + return {} + } + + function parseManifest(manifestString) { + try { + const manifestJSON = JSON.parse(manifestString) + return manifestJSON + } catch (err) { + return {} + } + } + + const server = createServer({ + port: PORT, + host: HOST, + adex:{ + manifests:{server:getServerManifest(),client:getClientManifest()}, + paths, + } + }) + + if ('run' in server) { + server.run() + } + + export default server.fetch + ` + ), + addFontsPlugin(fonts), + islands && adexIslandsBuilder(), + ], + build: { + outDir: serverOutDir, + emptyOutDir: false, + assetsDir: 'assets', + ssrEmitAssets: true, + ssr: true, + manifest: 'manifest.json', + ssrManifest: 'ssr.manifest.json', + rollupOptions: { + input: { + index: input, + }, + external: ['adex/ssr'], + }, + }, + }) + }, + } +} + /** * @returns {import("vite").Plugin[]} */ @@ -554,7 +680,9 @@ function adexGuards() { checkTree(d, [...importStack, importPath, d]) ) } - info.importers.forEach(i => checkTree(i)) + if (info) { + info.importers.forEach(i => checkTree(i)) + } }, }, ] diff --git a/packages/adapters/node/lib/index.js b/packages/adapters/node/lib/index.js index 61f51ae..efe2f1b 100644 --- a/packages/adapters/node/lib/index.js +++ b/packages/adapters/node/lib/index.js @@ -5,6 +5,8 @@ import { sirv, useMiddleware } from 'adex/ssr' import { handler } from 'virtual:adex:handler' +let islandMode = false + function createHandler({ manifests, paths }) { const serverAssets = sirv(paths.assets, { maxAge: 31536000, @@ -20,6 +22,7 @@ function createHandler({ manifests, paths }) { } if (islandsWereGenerated) { + islandMode = true islandAssets = sirv(paths.islands, { maxAge: 31536000, immutable: true, @@ -75,8 +78,6 @@ function createHandler({ manifests, paths }) { return islandAssets(req, res, next) }, async (req, res, next) => { - // @ts-expect-error shared-state between the middlewares - req.url = req.__originalUrl.replace(/(\/?client\/?)/, '/') return clientAssets(req, res, next) }, async (req, res) => { @@ -134,6 +135,22 @@ function manifestToHTML(manifest, filePath) { ) } + // TODO: move it up the chain + const rootClientFile = 'virtual:adex:client' + // if root manifest, also add it's css imports in + if (!islandMode && manifest[rootClientFile]) { + const graph = manifest[rootClientFile] + links = links.concat( + (graph.css || []).map( + d => + `` + ) + ) + } + if (manifest[filePath]) { const graph = manifest[filePath] links = links.concat( diff --git a/playground/tailwind.config.js b/playground/tailwind.config.js index d340572..0e2ccbb 100644 --- a/playground/tailwind.config.js +++ b/playground/tailwind.config.js @@ -5,7 +5,7 @@ export default { theme: { extend: {}, fontFamily: { - sans: 'Inter', + sans: 'Inter, sans-serif', }, }, plugins: [forms], diff --git a/playground/vite.config.js b/playground/vite.config.js index 2ddcaeb..4026052 100644 --- a/playground/vite.config.js +++ b/playground/vite.config.js @@ -8,6 +8,7 @@ export default defineConfig({ plugins: [ adex({ islands: false, + ssr: false, fonts: { providers: [providers.google()], families: [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c07d3c0..dcc7e5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: preact: specifier: ^10.22.0 version: 10.24.2 + preact-iso: + specifier: ^2.9.0 + version: 2.9.0(preact-render-to-string@6.5.5(preact@10.24.2))(preact@10.24.2) preact-render-to-string: specifier: ^6.5.5 version: 6.5.5(preact@10.24.2) @@ -2207,6 +2210,12 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==, tarball: https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz} engines: {node: ^10 || ^12 || >=14} + preact-iso@2.9.0: + resolution: {integrity: sha512-pJ3j4Aeqoe5MTGM96wB5JXaHbW3HB9PF5WxYJb//+pySc3hP1FoDIKwxi2OlLZtMW59gG8LvPwE8lUo1Ndk9ZA==, tarball: https://registry.npmjs.org/preact-iso/-/preact-iso-2.9.0.tgz} + peerDependencies: + preact: '>=10' + preact-render-to-string: '>=6.4.0' + preact-render-to-string@6.5.5: resolution: {integrity: sha512-KiMFTKNTmT/ccE79BURR/r6XRc2I2TCTZ0MpeWqHW2XnllbeghXvwGsdAfF/MzMilUcTfODtSmMxgoRFL9TM5g==, tarball: https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.5.tgz} peerDependencies: @@ -4995,6 +5004,11 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.1 + preact-iso@2.9.0(preact-render-to-string@6.5.5(preact@10.24.2))(preact@10.24.2): + dependencies: + preact: 10.24.2 + preact-render-to-string: 6.5.5(preact@10.24.2) + preact-render-to-string@6.5.5(preact@10.24.2): dependencies: preact: 10.24.2