Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support routes config #5968

Merged
merged 7 commits into from
Feb 28, 2023
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
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"]
}
9 changes: 7 additions & 2 deletions 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 Expand Up @@ -123,4 +128,4 @@ function generateRouteConfig(
}, '');
}
return template(importConfig(routes, ''), imports);
}
}
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