Skip to content

Commit

Permalink
feat: working routes codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
Reinaldy Rafli committed Oct 22, 2021
1 parent 31fd8a2 commit 8de9f99
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 63 deletions.
6 changes: 5 additions & 1 deletion examples/src/pages/index.svelte
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
<p>Index page</p>
<p>Index page</p>
<a href="/about">About</a>
<a href="/contact">Contact</a>
<a href="/projects">Projects</a>
<a href="/projects/asd">Projects slug</a>
21 changes: 16 additions & 5 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,31 @@ import { isDynamicRoute, isCatchAllRoute } from './utils/validate';
import { stringifyRoutes } from './stringify';
import { haveChildren } from './crawler/crawler';
import { extname } from 'path';
import { sortRoute } from './utils/route';

/**
* Generate
* @param pages
* @returns
*/
export function generateRoutes(pages: FileOutput[]): PreRoute[] {
const routes: PreRoute[] = [];

for (let i = 0; i < pages.length; i++) {
const node = pages[i].path.split('/')[pages[i].path.split('/').length - 1];
const isDynamic = isDynamicRoute(node);
const isCatchAll = isCatchAllRoute(node);
const fileExt = extname(node);
const isDynamic = isDynamicRoute(node.replace(fileExt, ''));
const isCatchAll = isCatchAllRoute(node.replace(fileExt, ''));
const normalizedName = isDynamic
? node
.replace(fileExt, '')
.replace(/^\[(\.{3})?/, '')
.replace(/\]$/, '')
.replace(fileExt, '')
: node.replace(fileExt, '');
const normalizedPath = normalizedName.toLowerCase();
let name: string;
if (normalizedPath === 'index') {
name = 'index';
name = '/';
} else {
if (isCatchAll) {
name = '/*';
Expand Down Expand Up @@ -50,8 +56,13 @@ export function generateRoutes(pages: FileOutput[]): PreRoute[] {
return routes;
}

/**
* This pretty much acts as a final codegen for it to be executed by Vite.
* @param {PreRoute[]} routes
* @returns {String}
*/
export function generateClientCode(routes: PreRoute[]): string {
const { imports, stringRoutes } = stringifyRoutes(routes);
const { imports, stringRoutes } = stringifyRoutes(routes.sort(sortRoute));

return (
`${imports.join(';\n')}${imports.length > 1 ? ';' : ''}\n\n` +
Expand Down
16 changes: 12 additions & 4 deletions src/stringify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { FileOutput } from './types/page';
import type { PreRoute } from './types/route';
import { pathToName } from './utils/convert';
import { haveChildren } from './crawler/crawler';
import { sortRoute } from './utils/route';

// This is not on the types/ directory because this should only
// be used in this file only.
Expand Down Expand Up @@ -45,19 +46,26 @@ export function stringifyRoutes(preparedRoutes: PreRoute[]): StringifyOutput {
* @returns {String} To be used by stringifyRoute function
*/
function compileRouteItem(route: PreRoute): RouteItem {
let out = '{';
let out = '{ ';
const imp: string[] = [];

if (haveChildren(route as FileOutput)) {
out += `name: "${route.name}", nestedRoutes: ${route.children?.map((o) => compileRouteItem(o)).join('')}`;
const children = route.children?.sort(sortRoute).map((o) => compileRouteItem(o)) as RouteItem[];
const nestedRoutes: string[] = [...children.map((o) => o.out.replace('name: "/",', 'name: "index",'))];

out += `name: "${route.name}", nestedRoutes: [${nestedRoutes.join(',')}]\n`;
const imps = children?.map((o) => o.imp).flat() as string[];
imp.push(...imps);
} else {
const importName = pathToName(route.path as string);
const importStr = `import ${importName} from "${route.path}"`;
if (!imp.includes(importStr)) {
imp.push(importStr);
}
out += `name: "${route.name}", component: ${importName},`;
out += `name: "${route.name}", component: ${importName}\n`;
}
out += '},';
out += '},\n';

return {
out,
imp,
Expand Down
21 changes: 21 additions & 0 deletions src/utils/convert.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { OutputBundle } from 'rollup';
import { basename } from 'path';

/**
* Generate a name from a given path (so it won't mess with Javascript).
* @param filepath
* @returns
*/
export function pathToName(filepath: string): string {
return filepath.replace(/[_.\-\\/]/g, '_').replace(/[[:\]()]/g, '$');
}
Expand All @@ -15,13 +20,29 @@ export function replaceSquareBrackets(bundle: OutputBundle): void {
}
}

/**
* This is originally came from @antfu/utils.
* But have we forget how to program? This is so simple, man!
*
* Basically converts Windows-like slashes to UNIX-like.
* @param {String} str
* @returns {String}
*/
export function slash(str: string): string {
return str.replace(/\\/g, '/');
}

type Nullable<T> = T | null | undefined;
type Arrayable<T> = T | T[];

/**
* This is originally came from @antfu/utils.
* But adding more dependency just to import this is not that worth it.
*
* Convert a string or array to array type.
* @param array
* @returns
*/
export function toArray<T>(array?: Nullable<Arrayable<T>>): Array<T> {
array = array || [];
if (Array.isArray(array)) return array;
Expand Down
31 changes: 19 additions & 12 deletions src/utils/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import type { Route } from '../types/route';
import type { PreRoute } from '../types/route';

export const routeBlockCache = new Map<string, Record<string, any>>();

export function findRouteByFilename(routes: Route, filename: string): Route | null {
const routeEntries = Object.entries(routes);
let result = null;
for (const route of routeEntries) {
if (typeof route[1] === 'string' && filename.endsWith(route[1])) result = { [route[0]]: route[1] };

if (result) return result;
}
return null;
/**
* Sort function for prioritizing routes.
* / only should be prioritized
* Then, it should prioritize /about /contact
* Then it should prioritize /:slug /:something
* Then just return 1 for every other edge case
* @param {PreRoute} a
* @returns {Number}
* @example
* // Just put the function inside of an array sort function.
* array.sort(sortRoute);
*
*/
export function sortRoute(a: PreRoute): number {
if (a.name === '/') return -1;
if (/^\/[A-Za-z0-9]/.test(a.name)) return -1;
if (/^\/:/.test(a.name)) return 1;
return 1;
}
6 changes: 2 additions & 4 deletions src/utils/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ export function isTarget(path: string, options: ResolvedOptions): boolean {
return isPagesDir(path, options) && options.extensionsRE.test(path);
}

const dynamicRouteRE = /^\[.+\]$/;

/**
* Check whether or not a routePath is a type of a dynamic route.
* @param {String} routePath
* @returns {Boolean}
*/
export function isDynamicRoute(routePath: string): boolean {
return dynamicRouteRE.test(routePath);
return /^\[.+\]$/.test(routePath);
}

/**
Expand All @@ -44,7 +42,7 @@ export function isDynamicRoute(routePath: string): boolean {
* @returns {Boolean}
*/
export function isCatchAllRoute(routePath: string): boolean {
return /^\[\.{3}all\]/.test(routePath);
return /^\[\.{3}all\]$/.test(routePath);
}

/**
Expand Down
113 changes: 77 additions & 36 deletions test/__snapshots__/generate.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,44 +1,107 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Generate Routes Sync: client code sync 1`] = `
"import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_about_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/about.svelte\\";
"import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_blog_today_index_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/blog/today/index.svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_blog_index_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/blog/index.svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_blog_$id$_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/blog/[id].svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_components_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/components.svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_index_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/index.svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_about_index_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/about/index.svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_about_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/about.svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$userId$_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/[userId].svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_components_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/components.svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$sensor$_current_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/[sensor]/current.svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$sensor$_$___all$_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/[sensor]/[...all].svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$___all$_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/[...all].svelte\\";
import _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages___test___index_svelte from \\"/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/__test__/index.svelte\\";
const routes = [{name: \\"/about\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_about_svelte,},{name: \\"/about\\", nestedRoutes: [object Object]},{name: \\"index\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_index_svelte,},{name: \\"/[userid]\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$userId$_svelte,},{name: \\"/components\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_components_svelte,},{name: \\"/:sensor\\", nestedRoutes: [object Object][object Object]},{name: \\"/blog\\", nestedRoutes: [object Object][object Object][object Object]},{name: \\"/*\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$___all$_svelte,},{name: \\"/__test__\\", nestedRoutes: [object Object]},];
const routes = [{ name: \\"/blog\\", nestedRoutes: [{ name: \\"/today\\", nestedRoutes: [{ name: \\"index\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_blog_today_index_svelte
},
]
},
,{ name: \\"index\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_blog_index_svelte
},
,{ name: \\"/:id\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_blog_$id$_svelte
},
]
},
{ name: \\"/components\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_components_svelte
},
{ name: \\"/\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_index_svelte
},
{ name: \\"/about\\", nestedRoutes: [{ name: \\"index\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_about_index_svelte
},
]
},
{ name: \\"/about\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_about_svelte
},
{ name: \\"/:userId\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$userId$_svelte
},
{ name: \\"/:sensor\\", nestedRoutes: [{ name: \\"/current\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$sensor$_current_svelte
},
,{ name: \\"/*\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$sensor$_$___all$_svelte
},
]
},
{ name: \\"/*\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages_$___all$_svelte
},
{ name: \\"/__test__\\", nestedRoutes: [{ name: \\"index\\", component: _home_reinaldy_repository_vite_plugin_pages_svelte_test_assets_pages___test___index_svelte
},
]
},
];
export default routes;"
`;

exports[`Generate Routes Sync: routes sync 1`] = `
Array [
Object {
"name": "/about",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/about.svelte",
"children": Array [
Object {
"children": Array [
Object {
"name": "/",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/blog/today/index.svelte",
},
],
"name": "/today",
},
Object {
"name": "/",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/blog/index.svelte",
},
Object {
"name": "/:id",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/blog/[id].svelte",
},
],
"name": "/blog",
},
Object {
"name": "/components",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/components.svelte",
},
Object {
"name": "/",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/index.svelte",
},
Object {
"children": Array [
Object {
"name": "index",
"name": "/",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/about/index.svelte",
},
],
"name": "/about",
},
Object {
"name": "index",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/index.svelte",
"name": "/about",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/about.svelte",
},
Object {
"name": "/[userid]",
"name": "/:userId",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/[userId].svelte",
},
Object {
"name": "/components",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/components.svelte",
},
Object {
"children": Array [
Object {
Expand All @@ -52,36 +115,14 @@ Array [
],
"name": "/:sensor",
},
Object {
"children": Array [
Object {
"name": "index",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/blog/index.svelte",
},
Object {
"name": "/[id]",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/blog/[id].svelte",
},
Object {
"children": Array [
Object {
"name": "index",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/blog/today/index.svelte",
},
],
"name": "/today",
},
],
"name": "/blog",
},
Object {
"name": "/*",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/[...all].svelte",
},
Object {
"children": Array [
Object {
"name": "index",
"name": "/",
"path": "/home/reinaldy/repository/vite-plugin-pages-svelte/test/assets/pages/__test__/index.svelte",
},
],
Expand Down
12 changes: 11 additions & 1 deletion test/__snapshots__/stringify.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ exports[`Stringify routes Should stringify: stringify route 1`] = `
Object {
"imports": Array [
"import _home_foo_bar_index_svelte from \\"/home/foo/bar/index.svelte\\"",
"import _home_foo_bar_about_index_svelte from \\"/home/foo/bar/about/index.svelte\\"",
"import _home_foo_bar_about_contact_svelte from \\"/home/foo/bar/about/contact.svelte\\"",
],
"stringRoutes": "[{name: \\"index\\", component: _home_foo_bar_index_svelte,},{name: \\"about\\", nestedRoutes: [object Object][object Object]},]",
"stringRoutes": "[{ name: \\"index\\", component: _home_foo_bar_index_svelte
},
{ name: \\"about\\", nestedRoutes: [{ name: \\"index\\", component: _home_foo_bar_about_index_svelte
},
,{ name: \\"contact\\", component: _home_foo_bar_about_contact_svelte
},
]
},
]",
}
`;

0 comments on commit 8de9f99

Please sign in to comment.