Skip to content

Commit

Permalink
feat: support routes config (#5968)
Browse files Browse the repository at this point in the history
* feat: support routes config

* fix: compatible with win32

* feat: add test case

* fix: optimize code

* fix: compatible with path

* Update routes.ts

* Update routes.ts
  • Loading branch information
ClarkXia authored Feb 28, 2023
1 parent efba0cc commit b8e9743
Show file tree
Hide file tree
Showing 24 changed files with 481 additions and 6 deletions.
1 change: 1 addition & 0 deletions examples/routes-config/.browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
chrome 55
9 changes: 9 additions & 0 deletions examples/routes-config/ice.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from '@ice/app';
import routeConfig from './src/routes';

export default defineConfig({
routes: {
ignoreFiles: ['**'],
config: routeConfig,
},
});
22 changes: 22 additions & 0 deletions examples/routes-config/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@examples/routes-config",
"private": true,
"version": "1.0.0",
"scripts": {
"start": "ice start",
"build": "ice build"
},
"description": "",
"author": "",
"license": "MIT",
"dependencies": {
"@ice/app": "workspace:*",
"@ice/runtime": "workspace:*",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0"
}
}
3 changes: 3 additions & 0 deletions examples/routes-config/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineAppConfig } from 'ice';

export default defineAppConfig({});
7 changes: 7 additions & 0 deletions examples/routes-config/src/components/bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Bar() {
return (
<div>
bar
</div>
);
}
22 changes: 22 additions & 0 deletions examples/routes-config/src/document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Meta, Title, Links, Main, Scripts } from 'ice';

function Document() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="description" content="ice.js example" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Title />
<Links />
</head>
<body>
<Main />
<Scripts />
</body>
</html>
);
}

export default Document;
9 changes: 9 additions & 0 deletions examples/routes-config/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Link } from 'ice';

export default function Home() {
return (
<>
<Link to="/rewrite/overview">link to sales page</Link>
</>
);
}
7 changes: 7 additions & 0 deletions examples/routes-config/src/pages/sales/favorites.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Favorites() {
return (
<>
my favorite items
</>
);
}
24 changes: 24 additions & 0 deletions examples/routes-config/src/pages/sales/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.tabs {
display: flex;
border-bottom: 1px solid #ccc;
}

.tabs a {
line-height: 36px;
color: #333;
text-decoration: none;
padding: 0 8px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 4px;
background-color: rgba(208, 215, 222, 0.32);
}

.tabs a:hover {
background-color: rgba(208, 215, 222, 0.64);
}

.container {
border-top: 1px solid #ccc;
padding-top: 20px;
}
18 changes: 18 additions & 0 deletions examples/routes-config/src/pages/sales/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Outlet, Link } from 'ice';
import styles from './index.module.css';

export default () => {
return (
<div>
<h1>Sales</h1>
<div className={styles.tabs}>
<Link to="/rewrite/overview">overview</Link>
<Link to="/rewrite/recommends">recommends</Link>
<Link to="/rewrite/favorites">favorites</Link>
</div>
<div className={styles.container}>
<Outlet />
</div>
</div>
);
};
7 changes: 7 additions & 0 deletions examples/routes-config/src/pages/sales/overview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Overview() {
return (
<h2>
overview all sale items
</h2>
);
}
7 changes: 7 additions & 0 deletions examples/routes-config/src/pages/sales/recommends.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Recommends() {
return (
<>
recommend items
</>
);
}
27 changes: 27 additions & 0 deletions examples/routes-config/src/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const routes = [
{
path: 'rewrite',
// 从 src/page 开始计算路径,并且需要写后缀
component: 'sales/layout.tsx',
children: [
{
// Test the legacy logic. It is not recommended to add slash for children path.
path: '/favorites',
component: 'sales/favorites.tsx',
},
{
path: 'overview',
component: 'sales/overview.tsx',
},
{
path: 'recommends',
component: 'sales/recommends.tsx',
},
],
},
{
path: '/',
component: 'index.tsx',
},
];
export default routes;
6 changes: 6 additions & 0 deletions examples/routes-config/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface AppData {
title: string;
auth: {
[key: string]: boolean;
};
}
1 change: 1 addition & 0 deletions examples/routes-config/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@ice/app/types" />
32 changes: 32 additions & 0 deletions examples/routes-config/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"compileOnSave": false,
"buildOnSave": false,
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"module": "esnext",
"target": "es6",
"jsx": "react-jsx",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"rootDir": "./",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": false,
"importHelpers": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"],
"ice": [".ice"]
}
},
"include": ["src", ".ice", "ice.config.*"],
"exclude": ["build", "public"]
}
7 changes: 6 additions & 1 deletion packages/ice/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { getFileExports } from './service/analyze.js';
import formatPath from './utils/formatPath.js';

export async function generateRoutesInfo(rootDir: string, routesConfig: UserConfig['routes'] = {}) {
const routeManifest = generateRouteManifest(rootDir, routesConfig.ignoreFiles, routesConfig.defineRoutes);
const routeManifest = generateRouteManifest(
rootDir,
routesConfig.ignoreFiles,
routesConfig.defineRoutes,
routesConfig.config,
);

const analyzeTasks = Object.keys(routeManifest).map(async (key) => {
const routeItem = routeManifest[key];
Expand Down
3 changes: 2 additions & 1 deletion packages/ice/src/types/userConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DefineRouteFunction } from '@ice/route-manifest';
import type { DefineRouteFunction, RouteItem } from '@ice/route-manifest';
import type { PluginList } from 'build-scripts';
import type { UnpluginOptions } from '@ice/bundles/compiled/unplugin/index.js';
import type { ProcessOptions } from '@ice/bundles';
Expand Down Expand Up @@ -51,6 +51,7 @@ export interface UserConfig {
routes?: {
ignoreFiles?: string[];
defineRoutes?: (defineRoute: DefineRouteFunction) => void;
config?: RouteItem[];
injectInitialEntry?: boolean;
};
plugins?: PluginList<Config, OverwritePluginAPI>;
Expand Down
53 changes: 51 additions & 2 deletions packages/route-manifest/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import fs from 'fs';
import path from 'path';
import minimatch from 'minimatch';
import { createRouteId, defineRoutes, normalizeSlashes } from './routes.js';
import { createComponentName, createRouteId, defineRoutes, normalizeSlashes } from './routes.js';
import type { RouteManifest, DefineRouteFunction, NestedRouteManifest, ConfigRoute } from './routes.js';

export type {
Expand All @@ -12,6 +12,12 @@ export type {
ConfigRoute,
};

export interface RouteItem {
path: string;
component: string;
children?: RouteItem[];
}

const validRouteChar = ['-', '\\w', '/', ':', '*'];

const routeModuleExts = [
Expand All @@ -28,10 +34,12 @@ export function isRouteModuleFile(filename: string): boolean {
return routeModuleExts.includes(path.extname(filename));
}


export function generateRouteManifest(
rootDir: string,
ignoreFiles: string[] = [],
defineExtraRoutes?: (defineRoute: DefineRouteFunction) => void,
routeConfig?: RouteItem[],
) {
const srcDir = path.join(rootDir, 'src');
const routeManifest: RouteManifest = {};
Expand Down Expand Up @@ -61,9 +69,50 @@ export function generateRouteManifest(
};
}
}

// Add routes by routes config.
if (routeConfig) {
routeConfig.forEach((routeItem) => {
const routes = parseRoute(routeItem);
routes.forEach((route) => {
routeManifest[route.id] = route;
});
});
}

return routeManifest;
}

export function parseRoute(routeItem: RouteItem, parentId?: string, parentPath?: string) {
const routes = [];
const { path: routePath, component, children } = routeItem;
const id = createRouteId(component);
let index;
const currentPath = path.join(parentPath || '/', routePath).split(path.sep).join('/');
const isRootPath = currentPath === '/';
if (!children && isRootPath) {
index = true;
}
const route: ConfigRoute = {
// An absolute child route path must start with the combined path of all its parent routes
// Replace the first slash with an empty string to compatible with the route definintion, e.g. /foo
path: parentId && routePath !== '/' ? routePath.replace(/^\//, '') : routePath,
index,
id,
parentId,
file: component,
componentName: createComponentName(id),
layout: !!children,
};
routes.push(route);
if (children) {
children.forEach((childRoute) => {
routes.push(...parseRoute(childRoute, id, currentPath));
});
}
return routes;
}

export function formatNestedRouteManifest(routeManifest: RouteManifest, parentId?: string): NestedRouteManifest[] {
return Object.keys(routeManifest)
.filter(key => routeManifest[key].parentId === parentId)
Expand Down Expand Up @@ -133,7 +182,7 @@ function defineConventionalRoutes(
if (uniqueRouteId) {
if (uniqueRoutes.has(uniqueRouteId)) {
throw new Error(
`Path ${JSON.stringify(fullPath)} defined by route ${JSON.stringify(routeFilePath)}
`Path ${JSON.stringify(fullPath)} defined by route ${JSON.stringify(routeFilePath)}
conflicts with route ${JSON.stringify(uniqueRoutes.get(uniqueRouteId))}`,
);
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/route-manifest/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ function stripFileExtension(file: string) {
return file.replace(/\.[a-z0-9]+$/i, '');
}

function createComponentName(id: string) {
export function createComponentName(id: string) {
return id.replace('.', '/') // 'pages/home.news' -> pages/home/news
.split('/')
.map((item: string) => item.toLowerCase())
.join('-');
}
}
Loading

0 comments on commit b8e9743

Please sign in to comment.