From dee6ff272ea33353822c8cca848597e7c596d260 Mon Sep 17 00:00:00 2001 From: reaper Date: Sun, 16 Feb 2025 12:13:32 +0530 Subject: [PATCH 1/2] feat: move it to be client first instead of server first --- adex/runtime/client.js | 3 +- adex/runtime/handler.js | 12 +- adex/src/fonts.js | 2 +- adex/src/vite.js | 316 ++++++++++++++++------------ packages/adapters/node/lib/index.js | 18 +- playground/vite.config.js | 3 +- 6 files changed, 215 insertions(+), 139 deletions(-) diff --git a/adex/runtime/client.js b/adex/runtime/client.js index f43b2ab..d2bb424 100644 --- a/adex/runtime/client.js +++ b/adex/runtime/client.js @@ -23,13 +23,14 @@ const withComponents = routes.map(d => { function ComponentWrapper({ url = '' }) { return h( LocationProvider, + //@ts-expect-error no types for non-jsx function { url: url }, h( ErrorBoundary, {}, h( Router, - { url: url }, + {}, withComponents.map(d => h(Route, { path: d.routePath, component: d.component }) ) diff --git a/adex/runtime/handler.js b/adex/runtime/handler.js index 6f566e0..02db02c 100644 --- a/adex/runtime/handler.js +++ b/adex/runtime/handler.js @@ -1,7 +1,6 @@ import { CONSTANTS, emitToHooked } from 'adex/hook' import { prepareRequest, prepareResponse } from 'adex/http' import { toStatic } from 'adex/ssr' -import { LocationProvider, ErrorBoundary, Router } from 'adex/router' import { renderToString } from 'adex/utils/isomorphic' import { h } from 'preact' @@ -21,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() @@ -60,7 +60,9 @@ export async function handler(req, res) { // @ts-expect-error global.location = new URL(req.url, 'http://localhost') - const rendered = await renderToString(h(App, { url: req.url })) + const rendered = await renderToString( + h(App, { url: [baseURL, search].filter(Boolean).join('?') }) + ) const htmlString = HTMLTemplate({ metas, @@ -148,3 +150,7 @@ const stringify = (title, metas, links) => { ${stringifyTag('link', links)} ` } + +function normalizeRequestUrl(url) { + return url.replace(/\/(index\.html)$/, '/') +} 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/vite.js b/adex/src/vite.js index 4094a80..1d1741d 100644 --- a/adex/src/vite.js +++ b/adex/src/vite.js @@ -61,80 +61,14 @@ export function adex({ '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), + // addFontsPlugin(fonts), adexDevServer({ islands }), adexBuildPrep({ islands }), + adexClientBuilder(), - !ssr && adexClientBuilder({ config: __clientConfig }), - - // SSR Specific plugins - ssr && adexServerBuilder(), - ssr && !islands && adexClientSSRBuilder({ config: __clientConfig }), + // SSR/Render Server Specific plugins + ssr && adexServerBuilder({ fonts, adapter }), + // ssr && !islands && adexClientSSRBuilder({ config: __clientConfig }), ssr && islands && adexIslandsBuilder({ config: __clientConfig }), ...adexGuards(), @@ -142,27 +76,32 @@ export function adex({ } /** - * @param {object} options - * @param {import('vite').UserConfig} options.config * @returns {import("vite").Plugin} */ -function adexClientBuilder({ config }) { +function adexClientBuilder() { return { name: 'adex-client-builder', config(cfg) { const out = cfg.build.outDir ?? 'dist' return { + appType: 'custom', build: { + manifest: 'manifest.json', outDir: join(out, 'client'), rollupOptions: { input: 'virtual:adex:client', }, + output: { + entryFileNames: '[name]-[hash].js', + format: 'esm', + }, }, } }, generateBundle(opts, bundle) { let clientEntryPath for (const key in bundle) { + console.log({ key }) if (bundle[key].name == '_virtual_adex_client') { clientEntryPath = key } @@ -171,7 +110,7 @@ function adexClientBuilder({ config }) { const links = [ ...(bundle[clientEntryPath]?.viteMetadata?.importedCss ?? new Set()), ].map(d => { - return `` + return `` }) this.emitFile({ @@ -182,7 +121,7 @@ function adexClientBuilder({ config }) { ${links.join('\n')}
- + `, }) }, @@ -231,7 +170,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) { @@ -407,55 +346,67 @@ function adexClientSSRBuilder(opts) { 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', + // }, + // }, + // }, + // }) + // ) + // }) }, } } @@ -539,29 +490,132 @@ function adexDevServer({ islands = false } = {}) { } /** + * @param {object} options + * @param {import("./fonts.js").Options} options.fonts + * * @param {string} options.adapter * @returns {import("vite").Plugin} */ -function adexServerBuilder() { +function adexServerBuilder({ fonts, adapter }) { let input = 'src/entry-server.js' + let cfg return { name: `adex-server`, enforce: 'pre', - /** - * @returns {import("vite").UserConfig} - */ + apply: 'build', config(conf, env) { if (env.command === 'build') { input = 'virtual:adex:server' } - return { - appType: 'custom', + }, + configResolved(config) { + cfg = config + }, + async generateBundle() { + const defOut = cfg.build?.outDir ?? 'dist' + const serverOutDir = defOut.endsWith('client') + ? join(dirname(defOut), 'server') + : join(defOut, 'server') + + await build({ + configFile: false, ssr: { external: ['preact', 'adex', 'preact-render-to-string'], noExternal: Object.values(adapterMap), }, + appType: 'custom', + plugins: [ + 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), + ], build: { - outDir: join(conf.build?.outDir ?? 'dist', 'server'), - emptyOutDir: true, + outDir: serverOutDir, + emptyOutDir: false, assetsDir: 'assets', ssrEmitAssets: true, ssr: true, @@ -574,7 +628,7 @@ function adexServerBuilder() { external: ['adex/ssr'], }, }, - } + }) }, } } diff --git a/packages/adapters/node/lib/index.js b/packages/adapters/node/lib/index.js index 61f51ae..7d314d1 100644 --- a/packages/adapters/node/lib/index.js +++ b/packages/adapters/node/lib/index.js @@ -75,8 +75,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 +132,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 (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/vite.config.js b/playground/vite.config.js index 71e71b5..4026052 100644 --- a/playground/vite.config.js +++ b/playground/vite.config.js @@ -7,7 +7,8 @@ import { providers } from 'adex/fonts' export default defineConfig({ plugins: [ adex({ - islands: true, + islands: false, + ssr: false, fonts: { providers: [providers.google()], families: [ From 3052127ea298e2f1a0a963782c2e8ef6f0fd4992 Mon Sep 17 00:00:00 2001 From: reaper Date: Sun, 16 Feb 2025 20:42:27 +0530 Subject: [PATCH 2/2] fix: builds for islands --- adex/src/vite.js | 70 ++++++++++++++++------------- packages/adapters/node/lib/index.js | 5 ++- playground/tailwind.config.js | 2 +- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/adex/src/vite.js b/adex/src/vite.js index 1d1741d..17ad98d 100644 --- a/adex/src/vite.js +++ b/adex/src/vite.js @@ -61,16 +61,14 @@ export function adex({ 'virtual:adex:handler', readFileSync(join(__dirname, '../runtime/handler.js'), 'utf8') ), - // addFontsPlugin(fonts), + addFontsPlugin(fonts), adexDevServer({ islands }), adexBuildPrep({ islands }), - adexClientBuilder(), + adexClientBuilder({ islands }), + adexIslandsBuilder(), // SSR/Render Server Specific plugins - ssr && adexServerBuilder({ fonts, adapter }), - // ssr && !islands && adexClientSSRBuilder({ config: __clientConfig }), - ssr && islands && adexIslandsBuilder({ config: __clientConfig }), - + ssr && adexServerBuilder({ fonts, adapter, islands }), ...adexGuards(), ] } @@ -78,7 +76,7 @@ export function adex({ /** * @returns {import("vite").Plugin} */ -function adexClientBuilder() { +function adexClientBuilder({ islands = false } = {}) { return { name: 'adex-client-builder', config(cfg) { @@ -86,6 +84,7 @@ function adexClientBuilder() { return { appType: 'custom', build: { + write: !islands, manifest: 'manifest.json', outDir: join(out, 'client'), rollupOptions: { @@ -101,13 +100,13 @@ function adexClientBuilder() { generateBundle(opts, bundle) { let clientEntryPath for (const key in bundle) { - console.log({ key }) if (bundle[key].name == '_virtual_adex_client') { clientEntryPath = key } } const links = [ + // @ts-expect-error invalid types by vite? figure this out ...(bundle[clientEntryPath]?.viteMetadata?.importedCss ?? new Set()), ].map(d => { return `` @@ -157,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 @@ -191,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, @@ -236,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')] - }) - ), }, - }, - }) + } + ) ) }, }, @@ -492,10 +493,11 @@ function adexDevServer({ islands = false } = {}) { /** * @param {object} options * @param {import("./fonts.js").Options} options.fonts - * * @param {string} options.adapter + * @param {string} options.adapter + * @param {boolean} options.islands * @returns {import("vite").Plugin} */ -function adexServerBuilder({ fonts, adapter }) { +function adexServerBuilder({ fonts, adapter, islands }) { let input = 'src/entry-server.js' let cfg return { @@ -516,6 +518,8 @@ function adexServerBuilder({ fonts, adapter }) { ? join(dirname(defOut), 'server') : join(defOut, 'server') + console.log(`\nBuilding Server: ${serverOutDir}\n`) + await build({ configFile: false, ssr: { @@ -524,6 +528,7 @@ function adexServerBuilder({ fonts, adapter }) { }, appType: 'custom', plugins: [ + preact(), preactPages({ root: '/src/pages', id: '~routes', @@ -612,6 +617,7 @@ function adexServerBuilder({ fonts, adapter }) { ` ), addFontsPlugin(fonts), + islands && adexIslandsBuilder(), ], build: { outDir: serverOutDir, diff --git a/packages/adapters/node/lib/index.js b/packages/adapters/node/lib/index.js index 7d314d1..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, @@ -135,7 +138,7 @@ 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 (manifest[rootClientFile]) { + if (!islandMode && manifest[rootClientFile]) { const graph = manifest[rootClientFile] links = links.concat( (graph.css || []).map( 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],