Skip to content

Commit

Permalink
refactor: normalize pages to it's own adex sub-plugin (#20)
Browse files Browse the repository at this point in the history
* refactor: move pages to it's own plugin

* fix: null scenario
  • Loading branch information
barelyhuman authored Dec 29, 2024
1 parent 432cce9 commit 7f590ba
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 106 deletions.
24 changes: 5 additions & 19 deletions adex/runtime/handler.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { CONSTANTS, emitToHooked } from 'adex/hook'
import { prepareRequest, prepareResponse } from 'adex/http'
import { normalizeRouteImports, renderToString, toStatic } from 'adex/ssr'
import { renderToString, toStatic } from 'adex/ssr'
import { h } from 'preact'

const apiRoutes = import.meta.glob('/src/api/**/*.{js,ts}')
const pageRoutes = import.meta.glob('/src/pages/**/*.{tsx,jsx,js}')
// @ts-expect-error injected by vite
import { routes as apiRoutes } from '~apiRoutes'
// @ts-expect-error injected by vite
import { routes as pageRoutes } from '~routes'

const html = String.raw

Expand All @@ -14,7 +16,6 @@ export async function handler(req, res) {
prepareRequest(req)
prepareResponse(res)

const { pageRoutes, apiRoutes } = await getRouterMaps()
const baseURL = req.url

const { metas, links, title, lang } = toStatic()
Expand Down Expand Up @@ -116,21 +117,6 @@ function HTMLTemplate({
`
}

async function getRouterMaps() {
const apiRouteMap = normalizeRouteImports(apiRoutes, [
/^\/(src\/api)/,
'/api',
])
const pageRouteMap = normalizeRouteImports(pageRoutes, [
/^\/(src\/pages)/,
'',
])
return {
pageRoutes: pageRouteMap,
apiRoutes: apiRouteMap,
}
}

function getRouteParams(baseURL, matchedRoute) {
const matchedParams = baseURL.match(matchedRoute.regex.pattern)
const routeParams = regexToParams(matchedRoute, matchedParams)
Expand Down
76 changes: 76 additions & 0 deletions adex/runtime/pages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { pathToRegex } from 'adex/ssr'

const pages = import.meta.glob('#{__PLUGIN_PAGES_ROOT}')

export const routes = normalizeRouteImports(pages, [
new RegExp('#{__PLUGIN_PAGES_ROOT_REGEX}'),
'#{__PLUGIN_PAGES_ROOT_REGEX_REPLACER}',
])

// taken from
// https://github.com/cyco130/smf/blob/c4b601f48cd3b3b71bea6d76b52b9a85800813e4/smf/shared.ts#L22
// as it's decently tested and aligns to what we want for our routing
function compareRoutePatterns(a, b) {
// Non-catch-all routes first: /foo before /$$rest
const catchAll = Number(a.match(/\$\$(\w+)$/)) - Number(b.match(/\$\$(\w+)$/))
if (catchAll) return catchAll

// Split into segments
const aSegments = a.split('/')
const bSegments = b.split('/')

// Routes with fewer dynamic segments first: /foo/bar before /foo/$bar
const dynamicSegments =
aSegments.filter(segment => segment.includes('$')).length -
bSegments.filter(segment => segment.includes('$')).length
if (dynamicSegments) return dynamicSegments

// Routes with fewer segments first: /foo/bar before /foo/bar
const segments = aSegments.length - bSegments.length
if (segments) return segments

// Routes with earlier dynamic segments first: /foo/$bar before /$foo/bar
for (let i = 0; i < aSegments.length; i++) {
const aSegment = aSegments[i]
const bSegment = bSegments[i]
const dynamic =
Number(aSegment.includes('$')) - Number(bSegment.includes('$'))
if (dynamic) return dynamic

// Routes with more dynamic subsegments at this position first: /foo/$a-$b before /foo/$a
const subsegments = aSegment.split('$').length - bSegment.split('$').length
if (subsegments) return subsegments
}

// Equal as far as we can tell
return 0
}

function normalizeRouteImports(imports, baseKeyMatcher) {
return Object.keys(imports)
.sort(compareRoutePatterns)
.map(route => {
const routePath = simplifyPath(route).replace(
baseKeyMatcher[0],
baseKeyMatcher[1]
)

const regex = pathToRegex(routePath)

return {
route,
regex,
routePath,
module: imports[route],
}
})
}

function simplifyPath(path) {
return path
.replace(/(\.(js|ts)x?)/, '')
.replace(/index/, '/')
.replace('//', '/')
.replace(/\$\$/, '*')
.replace(/\$/, ':')
}
16 changes: 1 addition & 15 deletions adex/src/ssr.d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
export { toStatic } from 'hoofd/preact'
export { renderToString } from 'preact-render-to-string'

export function normalizeRouteImports<Routes>(
obj: Routes,
matcher: [RegExp, string]
): {
route: string
regex: {
pattern: RegExp
keys: string[]
}
routePath: string
module: () => Promise<{
default: () => any
}>
}[]
export { parse as pathToRegex } from 'regexparam'
73 changes: 1 addition & 72 deletions adex/src/ssr.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,5 @@
export { renderToString } from 'preact-render-to-string'
export { default as sirv } from 'sirv'
export { default as mri } from 'mri'
import { parse } from 'regexparam'
export { parse as pathToRegex } from 'regexparam'
export { toStatic } from 'hoofd/preact'

// taken from
// https://github.com/cyco130/smf/blob/c4b601f48cd3b3b71bea6d76b52b9a85800813e4/smf/shared.ts#L22
// as it's decently tested and aligns to what we want for our routing
export function compareRoutePatterns(a, b) {
// Non-catch-all routes first: /foo before /$$rest
const catchAll = Number(a.match(/\$\$(\w+)$/)) - Number(b.match(/\$\$(\w+)$/))
if (catchAll) return catchAll

// Split into segments
const aSegments = a.split('/')
const bSegments = b.split('/')

// Routes with fewer dynamic segments first: /foo/bar before /foo/$bar
const dynamicSegments =
aSegments.filter(segment => segment.includes('$')).length -
bSegments.filter(segment => segment.includes('$')).length
if (dynamicSegments) return dynamicSegments

// Routes with fewer segments first: /foo/bar before /foo/bar
const segments = aSegments.length - bSegments.length
if (segments) return segments

// Routes with earlier dynamic segments first: /foo/$bar before /$foo/bar
for (let i = 0; i < aSegments.length; i++) {
const aSegment = aSegments[i]
const bSegment = bSegments[i]
const dynamic =
Number(aSegment.includes('$')) - Number(bSegment.includes('$'))
if (dynamic) return dynamic

// Routes with more dynamic subsegments at this position first: /foo/$a-$b before /foo/$a
const subsegments = aSegment.split('$').length - bSegment.split('$').length
if (subsegments) return subsegments
}

// Equal as far as we can tell
return 0
}

export function normalizeRouteImports(imports, baseKeyMatcher) {
return Object.keys(imports)
.sort(compareRoutePatterns)
.map(route => {
const routePath = simplifyPath(route).replace(
baseKeyMatcher[0],
baseKeyMatcher[1]
)
const regex = pathToRegex(routePath)

return {
route,
regex,
routePath,
module: imports[route],
}
})
}

function simplifyPath(path) {
return path
.replace(/(\.(js|ts)x?)/, '')
.replace(/index/, '/')
.replace('//', '/')
.replace(/\$\$/, '*')
.replace(/\$/, ':')
}

function pathToRegex(path) {
return parse(path)
}
49 changes: 49 additions & 0 deletions adex/src/vite.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { addImportToAST, codeFromAST } from '@dumbjs/preland/ast'
import preact from '@preact/preset-vite'
import { mkdirSync, readFileSync, writeFileSync } from 'fs'
import { readFile } from 'fs/promises'
import { dirname, join, resolve } from 'path'
import { fileURLToPath } from 'url'
import { build } from 'vite'
Expand All @@ -28,6 +29,15 @@ let runningIslandBuild = false
*/
export function adex({ fonts, islands = false } = {}) {
return [
preactPages({
root: '/src/pages',
id: '~routes',
}),
preactPages({
root: '/src/api',
id: '~apiRoutes',
replacer: '/api',
}),
createUserDefaultVirtualModule(
'virtual:adex:global.css',
'',
Expand Down Expand Up @@ -332,6 +342,7 @@ function adexServerBuilder({ islands = false } = {}) {
},
async resolveId(id, importer, meta) {
if (id.endsWith('.css')) {
if (!importer) return
const importerFromRoot = importer.replace(resolve(cfg.root), '')
const resolvedCss = await this.resolve(id, importer, meta)
devCSSMap.set(
Expand Down Expand Up @@ -438,3 +449,41 @@ function adexGuards() {
},
]
}

/**
* @returns {import("vite").Plugin}
*/
function preactPages({
root = '/src/pages',
id: virtualId = '~routes',
extensions = ['js', 'ts', 'tsx', 'jsx'],
replacer = '',
} = {}) {
return {
name: 'routes',
enforce: 'pre',
resolveId(id) {
if (id !== virtualId) {
return
}
return `/0${virtualId}`
},
async load(id) {
if (id !== `/0${virtualId}`) {
return
}

const extsString = extensions.join(',')
const code = (
await readFile(join(__dirname, '../runtime/pages.js'), 'utf8')
)
.replaceAll('#{__PLUGIN_PAGES_ROOT}', root + `/**/*.{${extsString}}`)
.replaceAll('#{__PLUGIN_PAGES_ROOT_REGEX}', `^${root}`)
.replaceAll('#{__PLUGIN_PAGES_ROOT_REGEX_REPLACER}', replacer)

return {
code,
}
},
}
}

0 comments on commit 7f590ba

Please sign in to comment.