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: 约定式路由支持配置额外属性 #10527

Merged
merged 10 commits into from
Feb 23, 2023
Merged
26 changes: 26 additions & 0 deletions docs/docs/api/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,32 @@ function App() {
}
```

### useRouteProps

读取当前路由在路由配置里的 props 属性,你可以用此 hook 来获取路由配置中的额外信息。

```ts
// .umirc.ts
routes: [
{
path: '/',
custom_key: '1',
}
]
```

```ts
import { useRouteProps } from 'umi'

export default function Page() {
const routeProps = useRouteProps()

// use `routeProps.custom_key`
}
```

注:同样适用于约定式路由。

### useSelectedRoutes

用于读取当前路径命中的所有路由信息。比如在 `layout` 布局中可以获取到当前命中的所有子路由信息,同时可以获取到在 `routes` 配置中的参数,这格外有用。
Expand Down
3 changes: 3 additions & 0 deletions examples/route-props/.umirc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
routeProps: {},
};
5 changes: 5 additions & 0 deletions examples/route-props/layouts/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Outlet } from 'umi';

export default function Layout() {
return <Outlet />;
}
13 changes: 13 additions & 0 deletions examples/route-props/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@example/route-props",
"private": true,
"scripts": {
"build": "umi build",
"dev": "umi dev",
"setup": "umi setup",
"start": "npm run dev"
},
"dependencies": {
"umi": "4.0.0-canary.20230213.1"
}
}
7 changes: 7 additions & 0 deletions examples/route-props/pages/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { share } from '@/props';

export default function Page() {
return <div>demo</div>;
}

export const routeProps = share;
29 changes: 29 additions & 0 deletions examples/route-props/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { share } from '@/props';
import { useRouteProps } from 'umi';

export default function Page() {
const routes: typeof routeProps = useRouteProps();

return (
<div>
index, props: {routes.a}
{routes.b()}
{routes.c}
{JSON.stringify(routes.d)}
</div>
);
}

export const routeProps = {
// internal props, will occur error if you set it
// path: "/",

// static props
a: 1,

// dynamic props
b: () => 2,
// @ts-ignore
c: window.c,
d: share,
};
4 changes: 4 additions & 0 deletions examples/route-props/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const share = {
a: 1,
b: '2',
};
3 changes: 3 additions & 0 deletions examples/route-props/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "./.umi/tsconfig.json"
}
4 changes: 2 additions & 2 deletions packages/preset-umi/src/commands/dev/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { basename, join } from 'path';
import { Worker } from 'worker_threads';
import { DEFAULT_HOST, DEFAULT_PORT } from '../../constants';
import { LazySourceCodeCache } from '../../libs/folderCache/LazySourceCodeCache';
import type { GenerateFilesFn, IApi, OnConfigChangeFn } from '../../types';
import type { GenerateFilesFn, IApi } from '../../types';
import { lazyImportFromCurrentPkg } from '../../utils/lazyImportFromCurrentPkg';
import { createRouteMiddleware } from './createRouteMiddleware';
import { faviconMiddleware } from './faviconMiddleware';
Expand Down Expand Up @@ -192,7 +192,7 @@ PORT=8888 umi dev
await generate({ isFirstTime: false });
}
for await (const fn of data.fns) {
await (fn as OnConfigChangeFn)({ generate });
await fn();
}
},
}),
Expand Down
9 changes: 0 additions & 9 deletions packages/preset-umi/src/features/appData/appData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,6 @@ export default (api: IApi) => {
return memo;
});

api.registerMethod({
name: '_refreshRoutes',
async fn() {
api.appData.routes = await routesApi.getRoutes({
api,
});
},
});

// Execute earliest, so that other onGenerateFiles can get it
api.register({
key: 'onGenerateFiles',
Expand Down
108 changes: 8 additions & 100 deletions packages/preset-umi/src/features/clientLoader/clientLoader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import esbuild from '@umijs/bundler-utils/compiled/esbuild';
import { join, resolve } from 'path';
import type { IApi } from '../../types';
import { setupRouteExportExtractor } from '../../utils/routeExportExtractor';

export default (api: IApi) => {
api.describe({
Expand All @@ -12,104 +11,13 @@ export default (api: IApi) => {
enableBy: api.EnableBy.config,
});

api.onGenerateFiles(() => {
const clientLoaderImports: string[] = [];
const clientLoaderDefines: string[] = [];
const routeIds = Object.keys(api.appData.routes);
let index = 0;
for (const id of routeIds) {
const route = api.appData.routes[id];
if (route.__hasClientLoader) {
index += 1;
clientLoaderImports.push(
`import { clientLoader as loader_${index} } from '${route.__absFile}';`,
);
clientLoaderDefines.push(` '${id}': loader_${index},`);
}
}
api.writeTmpFile({
noPluginDir: true,
path: join('core/loaders.ts'),
content: `
${clientLoaderImports.join('\n')}
export default {
${clientLoaderDefines.join('\n')}
};
`,
});
});
const entryFile = 'core/loaders.ts';
const outFile = 'core/loaders.js';

// 把 core/loader.ts (在 tmpFile.ts 的 onGenerateFiles 产生的) 编译成 core/loader.js
// core/loader.js 会被 core/route.ts 引用,将每个 route 的 clientLoader 注入进去
api.onBeforeCompiler(async () => {
await esbuild.build({
format: 'esm',
platform: 'browser',
target: 'esnext',
loader,
watch: api.env === 'development' && {},
bundle: true,
logLevel: 'error',
entryPoints: [join(api.paths.absTmpPath, 'core/loaders.ts')],
outfile: join(api.paths.absTmpPath, 'core/loaders.js'),
plugins: [
{
name: 'imports',
setup(build) {
let entry: string | undefined;
build.onResolve({ filter: /.*/ }, (args) => {
if (args.kind === 'entry-point') entry = args.path;
if (args.kind === 'entry-point' || args.importer === entry) {
return { path: resolve(args.resolveDir, args.path) };
}
return {
path:
!args.path.startsWith('.') && !args.path.startsWith('/')
? args.path
: resolve(args.resolveDir, args.path),
external: true,
sideEffects: false,
};
});
},
},
],
});
setupRouteExportExtractor({
api,
entryFile,
outFile,
propertyName: 'clientLoader',
});
};

const loader: { [ext: string]: esbuild.Loader } = {
'.aac': 'file',
'.css': 'text',
'.less': 'text',
'.sass': 'text',
'.scss': 'text',
'.eot': 'file',
'.flac': 'file',
'.gif': 'file',
'.htm': 'file',
'.html': 'file',
'.ico': 'file',
'.icon': 'file',
'.jpeg': 'file',
'.jpg': 'file',
'.js': 'jsx',
'.jsx': 'jsx',
'.json': 'json',
'.md': 'jsx',
'.mdx': 'jsx',
'.mp3': 'file',
'.mp4': 'file',
'.ogg': 'file',
'.otf': 'file',
'.png': 'file',
'.svg': 'file',
'.ts': 'ts',
'.tsx': 'tsx',
'.ttf': 'file',
'.wav': 'file',
'.webm': 'file',
'.webp': 'file',
'.woff': 'file',
'.woff2': 'file',
};
12 changes: 4 additions & 8 deletions packages/preset-umi/src/features/configPlugins/configPlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getSchemas as getViteSchemas } from '@umijs/bundler-vite/dist/schema';
import { getSchemas as getWebpackSchemas } from '@umijs/bundler-webpack/dist/schema';
import { resolve } from '@umijs/utils';
import { dirname, join } from 'path';
import type { IApi, OnConfigChangeFn, IApiInternalProps } from '../../types';
import type { IApi } from '../../types';
import { getSchemas as getExtraSchemas } from './schema';

function resolveProjectDep(opts: { pkg: any; cwd: string; dep: string }) {
Expand Down Expand Up @@ -90,14 +90,10 @@ export default (api: IApi) => {
config.default = configDefaults[key];
}

// when `routes#icon` changes, need to refresh the `appData.routes`
// otherwise the `icon` will not update
// when routes change, not need restart server
// routes data will auto update in `onGenerateFiles` (../tmpFiles/tmpFiles.ts)
if (['routes'].includes(key)) {
const onRoutesChange: OnConfigChangeFn = async ({ generate }) => {
await (api as any as IApiInternalProps)._refreshRoutes();
await generate({ isFirstTime: false });
};
config.onChange = onRoutesChange;
config.onChange = api.ConfigChangeType.regenerateTmpFiles;
}

api.registerPlugins([
Expand Down
23 changes: 23 additions & 0 deletions packages/preset-umi/src/features/routeProps/routeProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { IApi } from '../../types';
import { setupRouteExportExtractor } from '../../utils/routeExportExtractor';

export default (api: IApi) => {
api.describe({
config: {
schema(Joi) {
return Joi.object({});
},
},
enableBy: api.EnableBy.config,
});

const entryFile = 'core/routeProps.ts';
const outFile = 'core/routeProps.js';

setupRouteExportExtractor({
api,
entryFile,
outFile,
propertyName: 'routeProps',
});
};
18 changes: 15 additions & 3 deletions packages/preset-umi/src/features/tmpFiles/getModuleExports.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { parseModule } from '@umijs/bundler-utils';
import { chalk } from '@umijs/utils';
import { readFileSync } from 'fs';

export async function getModuleExports(opts: {
file: string;
}): Promise<readonly string[]> {
const content = readFileSync(opts.file, 'utf-8');
const [_, exports] = await parseModule({ content, path: opts.file });
return exports || [];
const { file } = opts;
const content = readFileSync(file, 'utf-8');
try {
const [_, exports] = await parseModule({ content, path: file });
return exports || [];
} catch (e) {
console.log(
`Parse file ${chalk.red(
file,
)} exports error, please check this file esm format.`,
);
// do not kill process
return [];
}
}
30 changes: 18 additions & 12 deletions packages/preset-umi/src/features/tmpFiles/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
getConfigRoutes,
getConventionRoutes,
} from '@umijs/core';
import { lodash, resolve, tryPaths, winPath, isMonorepo } from '@umijs/utils';
import { isMonorepo, lodash, resolve, tryPaths, winPath } from '@umijs/utils';
import { existsSync, readFileSync } from 'fs';
import { isAbsolute, join } from 'path';
import { IApi } from '../../types';
Expand Down Expand Up @@ -107,22 +107,28 @@ export async function getRoutes(opts: {
routes[id].__content = readFileSync(file, 'utf-8');
routes[id].__absFile = winPath(file);
routes[id].__isJSFile = isJSFile;
if (opts.api.config.ssr || opts.api.config.clientLoader) {
routes[id].__exports =

const enableSSR = opts.api.config.ssr;
const enableClientLoader = opts.api.config.clientLoader;
const enableRouteProps = opts.api.config.routeProps;
const needCollectExports =
enableSSR || enableClientLoader || enableRouteProps;
if (needCollectExports) {
const exports =
isJSFile && existsSync(file)
? await getModuleExports({
file,
})
: [];
}
if (opts.api.config.ssr) {
routes[id].hasServerLoader =
routes[id].__exports.includes('serverLoader');
}
if (opts.api.config.clientLoader) {
routes[id].__hasClientLoader =
routes[id].__exports.includes('clientLoader');
routes[id].clientLoader = `clientLoaders['${id}']`;
if (enableSSR) {
routes[id].hasServerLoader = exports.includes('serverLoader');
}
if (enableClientLoader && exports.includes('clientLoader')) {
routes[id].clientLoader = `clientLoaders['${id}']`;
}
if (enableRouteProps && exports.includes('routeProps')) {
routes[id].routeProps = `routeProps['${id}']`;
}
}
}
}
Expand Down
Loading