Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 34 additions & 59 deletions packages/qwik-city/buildtime/build.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import type { BuildContext, BuildLayout, BuildRoute } from './types';
import type { BuildContext } from './types';
import fs from 'fs';
import { basename, join } from 'path';
import { isMenuFileName, createMenu } from './markdown/menu';
import { createPageRoute, updatePageRoute } from './routing/page';
import { dirname, join } from 'path';
import { updatePageRoute } from './routing/page';
import { addError } from './utils/format';
import { createEndpointRoute } from './routing/endpoint';
import { sortRoutes } from './routing/sort-routes';
import { createLayout, isLayoutFileName } from './routing/layout';
import {
IGNORE_NAMES,
isEndpointFileName,
isMarkdownFileName,
isPageFileName,
isTestFileName,
} from './utils/fs';
import { normalizePath } from './utils/fs';
import { parseFsRoute } from './routing/parse-fs-route';
import { updateMenu } from './markdown/menu';

export async function build(ctx: BuildContext) {
try {
if (ctx.dirty) {
const routesDirItems = await fs.promises.readdir(ctx.opts.routesDir);
await loadRoutes(ctx, ctx.opts.routesDir, ctx.opts.routesDir, routesDirItems);
const routesDir = ctx.opts.routesDir;

await loadRoutes(ctx, routesDir, dirname(routesDir));

for (const route of ctx.routes) {
if (route.type === 'page') {
updatePageRoute(routesDir, route, ctx.layouts);
}
}

await Promise.all(ctx.menus.map((m) => updateMenu(ctx, m)));

updateRoutes(ctx.opts.routesDir, ctx.routes, ctx.layouts);
sort(ctx);
validateBuild(ctx);
ctx.dirty = false;
Expand All @@ -31,45 +32,27 @@ export async function build(ctx: BuildContext) {
}
}

async function loadRoutes(ctx: BuildContext, routesDir: string, dir: string, dirItems: string[]) {
async function loadRoutes(ctx: BuildContext, dirPath: string, dirName: string) {
let dirItems: string[];
try {
dirItems = await fs.promises.readdir(dirPath);
} catch (e) {
// if it error'd then it must not be a directory so let's ignore
// top routes dir already validated it exists
return;
}

await Promise.all(
dirItems.map(async (itemName) => {
if (!IGNORE_NAMES[itemName]) {
try {
const dirName = basename(dir);
const itemPath = join(dir, itemName);
try {
const itemPath = normalizePath(join(dirPath, itemName));
const wasHandled = parseFsRoute(ctx, dirPath, dirName, itemPath, itemName);

if (isTestFileName(itemName)) {
addError(
ctx,
`Test directory or file "${itemPath}" should not be included within the routes directory. Please move test files to a different location.`
);
} else if (isLayoutFileName(dirName, itemName)) {
const layout = createLayout(ctx, routesDir, itemPath);
ctx.layouts.push(layout);
} else if (isMenuFileName(itemName)) {
const menu = await createMenu(ctx, routesDir, itemPath);
ctx.menus.push(menu);
} else if (isEndpointFileName(itemName)) {
const endpointRoute = createEndpointRoute(ctx, routesDir, itemPath);
ctx.routes.push(endpointRoute);
} else if (isMarkdownFileName(itemName)) {
const markdownRoute = createPageRoute(ctx, routesDir, itemPath, 'markdown');
ctx.routes.push(markdownRoute);
} else if (isPageFileName(itemName)) {
const pageRoute = createPageRoute(ctx, routesDir, itemPath, 'module');
ctx.routes.push(pageRoute);
} else {
try {
const childDirItems = await fs.promises.readdir(itemPath);
await loadRoutes(ctx, routesDir, itemPath, childDirItems);
} catch (e) {
// if it error'd then it must not be a directory so let's ignore
}
}
} catch (e) {
addError(ctx, e);
if (!wasHandled) {
await loadRoutes(ctx, itemPath, itemName);
}
} catch (e) {
addError(ctx, e);
}
})
);
Expand All @@ -91,14 +74,6 @@ function sort(ctx: BuildContext) {
});
}

function updateRoutes(routesDir: string, routes: BuildRoute[], layouts: BuildLayout[]) {
for (const route of routes) {
if (route.type === 'page') {
updatePageRoute(routesDir, route, layouts);
}
}
}

function validateBuild(ctx: BuildContext) {
const pathnames = Array.from(new Set(ctx.routes.map((r) => r.pathname))).sort();

Expand Down
3 changes: 1 addition & 2 deletions packages/qwik-city/buildtime/markdown/frontmatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { visit } from 'unist-util-visit';

export function parseFrontmatter(ctx: BuildContext): Transformer {
return (mdast, vfile) => {
const filePath = normalizePath(vfile.path);
const attrs: string[] = [];

visit(mdast, 'yaml', (node: any) => {
Expand All @@ -15,7 +14,7 @@ export function parseFrontmatter(ctx: BuildContext): Transformer {
});

if (attrs.length > 0) {
ctx.frontmatter.set(filePath, attrs);
ctx.frontmatter.set(normalizePath(vfile.path), attrs);
}
};
}
27 changes: 12 additions & 15 deletions packages/qwik-city/buildtime/markdown/menu.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
import type { BuildContext, ParsedMenu, ParsedMenuItem } from '../types';
import { marked } from 'marked';
import { getMenuPathname, getMenuLinkHref } from '../utils/pathname';
import { createFileId, normalizePath } from '../utils/fs';
import { createFileId } from '../utils/fs';
import fs from 'fs';

export async function createMenu(ctx: BuildContext, routesDir: string, filePath: string) {
const content = await fs.promises.readFile(filePath, 'utf-8');
return createMenuFromMarkdown(ctx, routesDir, filePath, content);
}

export function createMenuFromMarkdown(
ctx: BuildContext,
routesDir: string,
filePath: string,
content: string
) {
filePath = normalizePath(filePath);
const id = createFileId(ctx, routesDir, filePath);

export function createMenu(ctx: BuildContext, filePath: string) {
const id = createFileId(ctx, filePath);
const menu: ParsedMenu = {
pathname: getMenuPathname(ctx.opts, filePath),
filePath,
text: '',
items: [],
id,
};
return menu;
}

export async function updateMenu(ctx: BuildContext, menu: ParsedMenu) {
const content = await fs.promises.readFile(menu.filePath, 'utf-8');
updateMenuFromMarkdown(ctx, menu, content);
}

export function updateMenuFromMarkdown(ctx: BuildContext, menu: ParsedMenu, content: string) {
const filePath = menu.filePath;
const tokens = marked.lexer(content, {});
let hasH1 = false;
let h2: ParsedMenuItem | null = null;
Expand Down
6 changes: 3 additions & 3 deletions packages/qwik-city/buildtime/routing/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type { BuildContext, EndpointRoute } from '../types';
import { createFileId } from '../utils/fs';
import { getPathnameFromFilePath } from '../utils/pathname';
import { parsePathname } from './parse-route';
import { parsePathname } from './parse-pathname';

export function createEndpointRoute(ctx: BuildContext, routesDir: string, filePath: string) {
export function createEndpointRoute(ctx: BuildContext, filePath: string) {
const pathname = getPathnameFromFilePath(ctx.opts, filePath);
const route = parsePathname(pathname);

const endpointRoute: EndpointRoute = {
type: 'endpoint',
id: createFileId(ctx, routesDir, filePath),
id: createFileId(ctx, filePath),
filePath,
pathname,
...route,
Expand Down
24 changes: 13 additions & 11 deletions packages/qwik-city/buildtime/routing/layout.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import { basename, dirname } from 'path';
import { dirname } from 'path';
import type { BuildContext, BuildLayout } from '../types';
import {
createFileId,
isPageFileName,
isPageIndexFileName,
getExtensionLessBasename,
normalizePath,
removeExtension,
} from '../utils/fs';

export function createLayout(ctx: BuildContext, routesDir: string, filePath: string) {
let dirPath = dirname(filePath);
const dirName = basename(dirPath);
const fileName = removeExtension(basename(filePath));
export function createLayout(
ctx: BuildContext,
dirPath: string,
dirName: string,
filePath: string
) {
let layoutName = '';

if (dirName.startsWith('_layout')) {
layoutName = parseLayoutName(dirName);
dirPath = dirname(dirPath);
dirPath = normalizePath(dirname(dirPath));
} else {
layoutName = parseLayoutName(fileName);
layoutName = parseLayoutName(getExtensionLessBasename(filePath));
}

const type = layoutName !== '' ? 'top' : 'nested';

const layout: BuildLayout = {
id: createFileId(ctx, routesDir, filePath),
filePath: normalizePath(filePath),
dir: normalizePath(dirPath),
id: createFileId(ctx, filePath),
filePath: filePath,
dir: dirPath,
type,
name: layoutName,
};
Expand Down
7 changes: 3 additions & 4 deletions packages/qwik-city/buildtime/routing/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ import { dirname } from 'path';
import type { BuildContext, BuildLayout, PageRoute } from '../types';
import { createFileId, normalizePath } from '../utils/fs';
import { getPathnameFromFilePath } from '../utils/pathname';
import { parsePathname } from './parse-route';
import { parsePathname } from './parse-pathname';

export function createPageRoute(
ctx: BuildContext,
routesDir: string,
filePath: string,
source: 'markdown' | 'module'
) {
const pageRoute: PageRoute = {
type: 'page',
id: createFileId(ctx, routesDir, filePath),
filePath: normalizePath(filePath),
id: createFileId(ctx, filePath),
filePath,
pathname: getPathnameFromFilePath(ctx.opts, filePath),
pattern: undefined as any,
paramNames: undefined as any,
Expand Down
74 changes: 74 additions & 0 deletions packages/qwik-city/buildtime/routing/parse-fs-route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { createMenu, isMenuFileName } from '../markdown/menu';
import type { BuildContext } from '../types';
import { addError } from '../utils/format';
import {
IGNORE_FS_NAMES,
isEndpointFileName,
isMarkdownFileName,
isPageFileName,
isTestDirName,
isTestFileName,
} from '../utils/fs';
import { createEndpointRoute } from './endpoint';
import { createLayout, isLayoutFileName } from './layout';
import { createPageRoute } from './page';

export function parseFsRoute(
ctx: BuildContext,
dirPath: string,
dirName: string,
filePath: string,
fileName: string
) {
if (IGNORE_FS_NAMES[fileName]) {
return true;
}

if (isTestDirName(fileName)) {
addError(
ctx,
`Test directory "${filePath}" should not be included within the routes directory. Please move test directories to a different location.`
);
return true;
}

if (isTestFileName(fileName)) {
addError(
ctx,
`Test file "${filePath}" should not be included within the routes directory. Please move test files to a different location.`
);
return true;
}

if (isLayoutFileName(dirName, fileName)) {
const layout = createLayout(ctx, dirPath, dirName, filePath);
ctx.layouts.push(layout);
return true;
}

if (isMenuFileName(fileName)) {
const menu = createMenu(ctx, filePath);
ctx.menus.push(menu);
return true;
}

if (isEndpointFileName(fileName)) {
const endpointRoute = createEndpointRoute(ctx, filePath);
ctx.routes.push(endpointRoute);
return true;
}

if (isMarkdownFileName(fileName)) {
const markdownRoute = createPageRoute(ctx, filePath, 'markdown');
ctx.routes.push(markdownRoute);
return true;
}

if (isPageFileName(fileName)) {
const pageRoute = createPageRoute(ctx, filePath, 'module');
ctx.routes.push(pageRoute);
return true;
}

return false;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test } from 'uvu';
import * as assert from 'uvu/assert';
import { parsePathname } from './parse-route';
import { parsePathname } from './parse-pathname';

/**
* Adopted from SvelteKit
Expand Down
Loading