@roxi/routify
Version:
166 lines (140 loc) • 5.31 kB
JavaScript
import { relativeUnix, stringifyWithEscape } from '../../utils.js'
const routesFileName = rootNode => `routes.${rootNode.rootName}.js`
/**
* @typedef {Object} RelativePaths
* @property {string} routesDir
* @property {string} mainEntryPoint
* @property {string} rootComponent
*/
/** @param {{instance: RoutifyBuildtime}} param0 */
export const exporter = ({ instance }) => {
const createRelativePath = path => relativeUnix(instance.options.routifyDir, path)
const relativePaths = {
routesDir: createRelativePath(instance.options.routesDir['default']),
mainEntryPoint: createRelativePath(instance.options.mainEntryPoint),
rootComponent: createRelativePath(instance.options.rootComponent),
}
const routeTreePromises = Object.values(instance.rootNodes).map(async rootNode => {
/** @ts-ignore */
rootNode.routifyDir = 'import.meta.url::_EVAL'
await exportNode(rootNode)
await exportInstance(rootNode)
if (!instance.options.watch) await exportSitemap(rootNode)
})
const toolsPromises = [
exportRender(instance, relativePaths),
exportRoutifyInit(instance, relativePaths),
exportRouteMap(instance),
]
return Promise.all([...routeTreePromises, ...toolsPromises])
}
/**
*
* @param {RNodeBuildtime} rootNode
*/
export const exportNode = async rootNode => {
// create imports
let rootImport = rootNode.meta.bundle
? `import * as bundle_${rootNode.id} from './bundles/bundle-${rootNode.id}.js'`
: ''
const treeString = stringifyWithEscape(rootNode)
// prettier-ignore
const content =
'// @ts-nocheck' +
'\n' +
`${rootImport}` +
'\n' +
'\n' +
`export const routes = ${treeString}`+
'\n' +
`export default routes`
const outputPath = routesFileName(rootNode)
await rootNode.instance.writeFile(outputPath, content)
}
/**
* creates instance.default.js
* @param {RNodeBuildtime} rootNode
*/
export const exportInstance = async rootNode => {
/** @ts-ignore rootName */
const outputPath = `instance.${rootNode.rootName}.js`
const content = [
`import { Router, createRouter } from '@roxi/routify'`,
`import routes from './${routesFileName(rootNode)}'`,
'',
'// remove previous routers to avoid bumping router names (/path => /1/path)',
'globalThis.__routify.reset()',
'export const router = createRouter({routes})',
'export { Router, routes }',
'',
]
await rootNode.instance.writeFile(outputPath, content.join('\n'))
}
/**
* Creates a sitemap file
* @param {RNodeBuildtime} rootNode
*/
export const exportSitemap = async rootNode => {
const flattenNodes = node => [node, ...node.children.map(flattenNodes).flat()]
const outputPath = `sitemap.${rootNode['rootName']}.txt`
const flatNodes = flattenNodes(rootNode)
const content = rootNode.instance.options.sitemap.generate(flatNodes)
await rootNode.instance.writeFile(outputPath, content)
}
/**
* Exports a render function for SSR
* @param {RoutifyBuildtime} instance
* @param {RelativePaths} relativePaths
*/
export const exportRender = async (instance, relativePaths) => {
const svelteVersion = instance.options.svelteApi
const body = `
import * as module from '${relativePaths.rootComponent}'
import { renderModule } from '@roxi/routify/tools/ssr${svelteVersion}.js'
import { map } from './route-map.js'
export const render = url => renderModule(module, { url, routesMap: map })`
await instance.writeFile('render.js', body)
}
/**
* Preload urls to hydrate the app
* @param {RoutifyBuildtime} instance
* @param {RelativePaths} relativePaths
*/
export const exportRoutifyInit = async (instance, relativePaths) => {
const body = `
import { appInstance, preloadUrl } from '@roxi/routify'
import { map } from './route-map.js'
appInstance.routeMaps = map
// We need to import the App module since a router is likely declared here. This saves us pre-creating the router in the preload step below.
import * as module from '${relativePaths.rootComponent}'
const preloadPromise = Promise.all([
module.load?.(),
// PreloadUrl parses the url and preloads each url chunk in a router that matches its name. So for '/hello;widget=/world',
// it will preload '/hello' in the default router and '/world' in the 'widget' router.
// If the respective routers don't exist, preloadUrl will use routesMap to pre-create a router and match it with the url chunk.
preloadUrl({ routesMap: map })
])
export const app = preloadPromise.then(() => import('${relativePaths.mainEntryPoint}'))
`
await instance.writeFile('routify-init.js', body)
}
/**
*
* @param { RoutifyBuildtime } instance
*/
export const exportRouteMap = instance => {
Object.values(instance.rootNodes)
const routeMap = Object.values(instance.rootNodes).map(
rootNode =>
// @ts-ignore rootName
`'${rootNode.rootName}': () => import('./${routesFileName(
rootNode,
)}').then(m => m.default) `,
)
const body = `
export const map = {
${routeMap.join(',\n')}
}
`
return instance.writeFile('route-map.js', body)
}