diff --git a/adex/package.json b/adex/package.json
index 5e3a19e..eb01fc6 100644
--- a/adex/package.json
+++ b/adex/package.json
@@ -71,11 +71,17 @@
"@preact/preset-vite": "^2.8.2",
"@types/node": "^20.14.10",
"autoprefixer": "^10.4.19",
- "postcss": "^8.4.39",
"tailwindcss": "^3.4.4",
- "vite": "^5.3.1"
+ "vite": "^5.3.1",
+ "adex-adapter-node": "workspace:*"
+ },
+ "peerDependenciesMeta": {
+ "adex-adapter-node": {
+ "optional": true
+ }
},
"peerDependencies": {
+ "adex-adapter-node": ">=0.0.15",
"@preact/preset-vite": ">=2.8.2",
"preact": "^10.22.0"
}
diff --git a/adex/runtime/server.js b/adex/runtime/server.js
deleted file mode 100644
index 61b177a..0000000
--- a/adex/runtime/server.js
+++ /dev/null
@@ -1,214 +0,0 @@
-import { env } from 'adex/env'
-import { existsSync, readFileSync } from 'node:fs'
-import http from 'node:http'
-import { dirname, join } from 'node:path'
-import { fileURLToPath } from 'node:url'
-
-//@ts-expect-error internal requires
-import { mri, sirv, useMiddleware } from 'adex/ssr'
-
-//@ts-expect-error vite virtual import
-import { handler } from 'virtual:adex:handler'
-
-import 'virtual:adex:global.css'
-import 'virtual:adex:font.css'
-
-const flags = mri(process.argv.slice(2))
-
-const PORT = flags.port || parseInt(env.get('PORT', '3000'), 10)
-const HOST = flags.host || env.get('HOST', 'localhost')
-
-const __dirname = dirname(fileURLToPath(import.meta.url))
-
-const serverAssets = sirv(join(__dirname, './assets'), {
- maxAge: 31536000,
- immutable: true,
- onNoMatch: defaultHandler,
-})
-
-let islandsWereGenerated = existsSync(join(__dirname, './islands'))
-
-// @ts-ignore
-let islandAssets = (req, res, next) => {
- next()
-}
-
-if (islandsWereGenerated) {
- islandAssets = sirv(join(__dirname, './islands'), {
- maxAge: 31536000,
- immutable: true,
- onNoMatch: defaultHandler,
- })
-}
-
-let clientWasGenerated = existsSync(join(__dirname, '../client'))
-
-// @ts-ignore
-let clientAssets = (req, res, next) => {
- next()
-}
-
-if (clientWasGenerated) {
- clientAssets = sirv(join(__dirname, '../client'), {
- maxAge: 31536000,
- immutable: true,
- onNoMatch: defaultHandler,
- })
-}
-
-async function defaultHandler(req, res) {
- const { html: template, pageRoute, serverHandler } = await handler(req, res)
- if (serverHandler) {
- return serverHandler(req, res)
- }
-
- const templateWithDeps = addDependencyAssets(template, pageRoute)
-
- const finalHTML = templateWithDeps
- res.setHeader('content-type', 'text/html')
- res.write(finalHTML)
- res.end()
-}
-
-function parseManifest(manifestString) {
- try {
- const manifestJSON = JSON.parse(manifestString)
- return manifestJSON
- } catch (err) {
- return {}
- }
-}
-
-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 manifestToHTML(manifest, filePath) {
- let links = []
- let scripts = []
-
- // TODO: move it up the chain
- const rootServerFile = 'virtual:adex:server'
- // if root manifest, also add it's css imports in
- if (manifest[rootServerFile]) {
- const graph = manifest[rootServerFile]
- links = links.concat(
- (graph.css || []).map(
- d =>
- ``
- )
- )
- }
-
- if (manifest[filePath]) {
- const graph = manifest[filePath]
- links = links.concat(
- (graph.css || []).map(
- d =>
- ``
- )
- )
- const depsHasCSS = (manifest[filePath].imports || [])
- .map(d => manifest[d])
- .filter(d => d.css?.length)
-
- if (depsHasCSS.length) {
- links = links.concat(
- depsHasCSS.map(d =>
- d.css
- .map(
- p =>
- ``
- )
- .join('\n')
- )
- )
- }
-
- scripts = scripts.concat(
- ``
- )
- }
- return {
- scripts,
- links,
- }
-}
-
-function addDependencyAssets(template, pageRoute) {
- if (!pageRoute) {
- return template
- }
- const serverManifest = getServerManifest()
- const manifest = getClientManifest()
- const filePath = pageRoute.startsWith('/') ? pageRoute.slice(1) : pageRoute
-
- const { links: serverLinks } = manifestToHTML(serverManifest, filePath)
-
- const { links: clientLinks, scripts: clientScripts } = manifestToHTML(
- manifest,
- filePath
- )
-
- const links = [...serverLinks, ...clientLinks]
- const scripts = [...clientScripts]
-
- return template.replace(
- '',
- links.join('\n') + scripts.join('\n') + ''
- )
-}
-
-http
- .createServer(
- useMiddleware(
- async (req, res, next) => {
- // @ts-expect-error shared-state between the middlewares
- req.__originalUrl = req.url
- // @ts-expect-error shared-state between the middlewares
- req.url = req.__originalUrl.replace(/(\/?assets\/?)/, '/')
- return serverAssets(req, res, next)
- },
- async (req, res, next) => {
- // @ts-expect-error shared-state between the middlewares
- req.url = req.__originalUrl.replace(/(\/?islands\/?)/, '/')
- 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) => {
- // @ts-expect-error shared-state between the middlewares
- req.url = req.__originalUrl
- return defaultHandler(req, res)
- }
- )
- )
- .listen(PORT, HOST, () => {
- console.log(`Listening on ${HOST}:${PORT}`)
- })
diff --git a/adex/src/ssr.d.ts b/adex/src/ssr.d.ts
index 08bd139..7cff11b 100644
--- a/adex/src/ssr.d.ts
+++ b/adex/src/ssr.d.ts
@@ -2,3 +2,5 @@ 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
diff --git a/adex/src/vite.d.ts b/adex/src/vite.d.ts
index 2263706..0fef6c2 100644
--- a/adex/src/vite.d.ts
+++ b/adex/src/vite.d.ts
@@ -1,9 +1,12 @@
import { Plugin } from 'vite'
-import type { Options as FontOptions } from './fonts'
+import type { Options as FontOptions } from './fonts.js'
+
+export type Adapters = 'node'
export interface AdexOptions {
fonts?: FontOptions
islands?: boolean
+ adapter?: Adapters
}
export function adex(options: AdexOptions): Plugin[]
diff --git a/adex/src/vite.js b/adex/src/vite.js
index 3ba674d..9eb6205 100644
--- a/adex/src/vite.js
+++ b/adex/src/vite.js
@@ -23,11 +23,19 @@ const cwd = process.cwd()
const islandsDir = join(cwd, '.islands')
let runningIslandBuild = false
+const adapterMap = {
+ node: 'adex-adapter-node',
+}
+
/**
* @param {import("./vite.js").AdexOptions} [options]
* @returns
*/
-export function adex({ fonts, islands = false } = {}) {
+export function adex({
+ fonts,
+ islands = false,
+ adapter: adapter = 'node',
+} = {}) {
return [
preactPages({
root: '/src/pages',
@@ -49,7 +57,68 @@ export function adex({ fonts, islands = false } = {}) {
),
createVirtualModule(
'virtual:adex:server',
- readFileSync(join(__dirname, '../runtime/server.js'), 'utf8')
+ `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
+ `
),
createVirtualModule(
'virtual:adex:client',
@@ -333,6 +402,7 @@ function adexServerBuilder({ islands = false } = {}) {
input: {
index: input,
},
+ external: ['adex/ssr'],
},
},
}
diff --git a/lerna.json b/lerna.json
index 6217053..4b8689e 100644
--- a/lerna.json
+++ b/lerna.json
@@ -3,9 +3,5 @@
"version": "0.0.15",
"changelogPreset": "angular",
"npmClient": "pnpm",
- "packages": [
- "adex",
- "create-adex",
- "playground"
- ]
+ "packages": ["adex", "create-adex", "playground", "adex-adapter-node"]
}
diff --git a/package.json b/package.json
index 1030c7c..c6da7fc 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,8 @@
"play": "pnpm --filter='playground' -r dev",
"test": "echo 'true'",
"publish:ci": "lerna publish from-git --registry 'https://registry.npmjs.org' --yes",
- "next": "lerna version --sync-workspace-lock"
+ "next": "lerna version --sync-workspace-lock",
+ "nuke": "pnpm -r exec rm -rvf node_modules"
},
"license": "MIT",
"prettier": "@barelyhuman/prettier-config",
@@ -23,8 +24,7 @@
},
"pnpm": {
"overrides": {
- "cross-spawn@>=7.0.0 <7.0.5": ">=7.0.5",
- "nanoid@<3.3.8": ">=3.3.8"
+ "cross-spawn@>=7.0.0 <7.0.5": ">=7.0.5"
}
}
}
diff --git a/packages/adaptors/node/lib/index.d.ts b/packages/adaptors/node/lib/index.d.ts
new file mode 100644
index 0000000..dd955a7
--- /dev/null
+++ b/packages/adaptors/node/lib/index.d.ts
@@ -0,0 +1,6 @@
+type ServerOut = {
+ run: () => any
+ fetch: undefined
+}
+
+export const createServer: ({ port: number, host: string }) => ServerOut
diff --git a/packages/adaptors/node/lib/index.js b/packages/adaptors/node/lib/index.js
new file mode 100644
index 0000000..61f51ae
--- /dev/null
+++ b/packages/adaptors/node/lib/index.js
@@ -0,0 +1,228 @@
+import { existsSync } from 'node:fs'
+import http from 'node:http'
+
+import { sirv, useMiddleware } from 'adex/ssr'
+
+import { handler } from 'virtual:adex:handler'
+
+function createHandler({ manifests, paths }) {
+ const serverAssets = sirv(paths.assets, {
+ maxAge: 31536000,
+ immutable: true,
+ onNoMatch: defaultHandler,
+ })
+
+ let islandsWereGenerated = existsSync(paths.islands)
+
+ // @ts-ignore
+ let islandAssets = (req, res, next) => {
+ next()
+ }
+
+ if (islandsWereGenerated) {
+ islandAssets = sirv(paths.islands, {
+ maxAge: 31536000,
+ immutable: true,
+ onNoMatch: defaultHandler,
+ })
+ }
+
+ let clientWasGenerated = existsSync(paths.client)
+
+ // @ts-ignore
+ let clientAssets = (req, res, next) => {
+ next()
+ }
+
+ if (clientWasGenerated) {
+ clientAssets = sirv(paths.client, {
+ maxAge: 31536000,
+ immutable: true,
+ onNoMatch: defaultHandler,
+ })
+ }
+
+ async function defaultHandler(req, res) {
+ const { html: template, pageRoute, serverHandler } = await handler(req, res)
+ if (serverHandler) {
+ return serverHandler(req, res)
+ }
+
+ const templateWithDeps = addDependencyAssets(
+ template,
+ pageRoute,
+ manifests.server,
+ manifests.client
+ )
+
+ const finalHTML = templateWithDeps
+ res.setHeader('content-type', 'text/html')
+ res.write(finalHTML)
+ res.end()
+ }
+
+ return useMiddleware(
+ async (req, res, next) => {
+ // @ts-expect-error shared-state between the middlewares
+ req.__originalUrl = req.url
+ // @ts-expect-error shared-state between the middlewares
+ req.url = req.__originalUrl.replace(/(\/?assets\/?)/, '/')
+ return serverAssets(req, res, next)
+ },
+ async (req, res, next) => {
+ // @ts-expect-error shared-state between the middlewares
+ req.url = req.__originalUrl.replace(/(\/?islands\/?)/, '/')
+ 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) => {
+ // @ts-expect-error shared-state between the middlewares
+ req.url = req.__originalUrl
+ return defaultHandler(req, res)
+ }
+ )
+}
+
+// function parseManifest(manifestString) {
+// try {
+// const manifestJSON = JSON.parse(manifestString)
+// return manifestJSON
+// } catch (err) {
+// return {}
+// }
+// }
+
+// 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 manifestToHTML(manifest, filePath) {
+ let links = []
+ let scripts = []
+
+ // TODO: move it up the chain
+ const rootServerFile = 'virtual:adex:server'
+ // if root manifest, also add it's css imports in
+ if (manifest[rootServerFile]) {
+ const graph = manifest[rootServerFile]
+ links = links.concat(
+ (graph.css || []).map(
+ d =>
+ ``
+ )
+ )
+ }
+
+ if (manifest[filePath]) {
+ const graph = manifest[filePath]
+ links = links.concat(
+ (graph.css || []).map(
+ d =>
+ ``
+ )
+ )
+ const depsHasCSS = (manifest[filePath].imports || [])
+ .map(d => manifest[d])
+ .filter(d => d.css?.length)
+
+ if (depsHasCSS.length) {
+ links = links.concat(
+ depsHasCSS.map(d =>
+ d.css
+ .map(
+ p =>
+ ``
+ )
+ .join('\n')
+ )
+ )
+ }
+
+ scripts = scripts.concat(
+ ``
+ )
+ }
+ return {
+ scripts,
+ links,
+ }
+}
+
+function addDependencyAssets(
+ template,
+ pageRoute,
+ serverManifest,
+ clientManifest
+) {
+ if (!pageRoute) {
+ return template
+ }
+
+ const filePath = pageRoute.startsWith('/') ? pageRoute.slice(1) : pageRoute
+
+ const { links: serverLinks } = manifestToHTML(serverManifest, filePath)
+
+ const { links: clientLinks, scripts: clientScripts } = manifestToHTML(
+ clientManifest,
+ filePath
+ )
+
+ const links = [...serverLinks, ...clientLinks]
+ const scripts = [...clientScripts]
+
+ return template.replace(
+ '',
+ links.join('\n') + scripts.join('\n') + ''
+ )
+}
+
+export const createServer = ({
+ port = '3000',
+ host = '127.0.0.1',
+ adex = {
+ manifests: {
+ server: {},
+ client: {},
+ },
+ paths: {},
+ },
+} = {}) => {
+ const handler = createHandler(adex)
+ const server = http.createServer(handler)
+
+ return {
+ run() {
+ return server.listen(port, host, () => {
+ console.log(`Listening on ${host}:${port}`)
+ })
+ },
+ fetch: undefined,
+ }
+}
diff --git a/packages/adaptors/node/package.json b/packages/adaptors/node/package.json
new file mode 100644
index 0000000..464736e
--- /dev/null
+++ b/packages/adaptors/node/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "adex-adapter-node",
+ "version": "0.0.0",
+ "keywords": [
+ "adex",
+ "preact",
+ "minimal",
+ "server",
+ "node"
+ ],
+ "bugs": "github.com/barelyhuman/adex/issues",
+ "repository": {
+ "type": "git",
+ "url": "github.com/barelyhuman/adex"
+ },
+ "type": "module",
+ "exports": {
+ ".": {
+ "types": "./lib/index.d.ts",
+ "import": "./lib/index.js"
+ }
+ },
+ "files": [
+ "lib"
+ ]
+}
diff --git a/playground/package.json b/playground/package.json
index 5c56e7e..cd91db3 100644
--- a/playground/package.json
+++ b/playground/package.json
@@ -10,7 +10,8 @@
},
"dependencies": {
"@preact/signals": "^1.3.0",
- "preact": "^10.24.2"
+ "preact": "^10.24.2",
+ "adex-adapter-node": "workspace:*"
},
"devDependencies": {
"@preact/preset-vite": "^2.9.1",
diff --git a/playground/vite.config.js b/playground/vite.config.js
index 71e71b5..2ddcaeb 100644
--- a/playground/vite.config.js
+++ b/playground/vite.config.js
@@ -7,7 +7,7 @@ import { providers } from 'adex/fonts'
export default defineConfig({
plugins: [
adex({
- islands: true,
+ islands: false,
fonts: {
providers: [providers.google()],
families: [
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f8a6331..f5e6f81 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,7 +6,6 @@ settings:
overrides:
cross-spawn@>=7.0.0 <7.0.5: '>=7.0.5'
- nanoid@<3.3.8: '>=3.3.8'
importers:
@@ -76,12 +75,12 @@ importers:
'@types/node':
specifier: ^20.14.10
version: 20.16.10
+ adex-adapter-node:
+ specifier: workspace:*
+ version: link:../packages/adaptors/node
autoprefixer:
specifier: ^10.4.19
version: 10.4.20(postcss@8.4.47)
- postcss:
- specifier: ^8.4.39
- version: 8.4.47
tailwindcss:
specifier: ^3.4.4
version: 3.4.13
@@ -98,11 +97,16 @@ importers:
specifier: ^1.15.0
version: 1.15.0
+ packages/adaptors/node: {}
+
playground:
dependencies:
'@preact/signals':
specifier: ^1.3.0
version: 1.3.0(preact@10.24.2)
+ adex-adapter-node:
+ specifier: workspace:*
+ version: link:../packages/adaptors/node
preact:
specifier: ^10.24.2
version: 10.24.2
@@ -1900,9 +1904,9 @@ packages:
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==, tarball: https://registry.npmjs.org/mz/-/mz-2.7.0.tgz}
- nanoid@5.0.9:
- resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==, tarball: https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz}
- engines: {node: ^18 || >=20}
+ nanoid@3.3.8:
+ resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==, tarball: https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
negotiator@0.6.3:
@@ -4677,7 +4681,7 @@ snapshots:
object-assign: 4.1.1
thenify-all: 1.6.0
- nanoid@5.0.9: {}
+ nanoid@3.3.8: {}
negotiator@0.6.3: {}
@@ -4982,7 +4986,7 @@ snapshots:
postcss@8.4.47:
dependencies:
- nanoid: 5.0.9
+ nanoid: 3.3.8
picocolors: 1.1.0
source-map-js: 1.2.1
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 8fa2310..ac5f7db 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -2,3 +2,4 @@ packages:
- adex
- create-adex
- playground
+ - packages/**/*