feat: support remove router even if route count is greater than 1 (#6382)

* feat: support remove router even if route count is greater than 1

* fix: add test case

* Update createService.ts

* Update createService.ts
This commit is contained in:
ClarkXia 2023-07-18 15:20:26 +08:00 committed by GitHub
parent 018238f904
commit b691b9e96c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 101 additions and 18 deletions

View File

@ -0,0 +1,6 @@
---
'@ice/runtime': patch
'@ice/app': patch
---
feat: support remove router even if route count is greater than 1

View File

@ -3,6 +3,6 @@ import { defineConfig } from '@ice/app';
export default defineConfig(() => ({ export default defineConfig(() => ({
publicPath: '/', publicPath: '/',
optimization: { optimization: {
router: true, disableRouter: true,
}, },
})); }));

View File

@ -0,0 +1,7 @@
const home = () => {
return (
<>home</>
);
};
export default home;

View File

@ -232,9 +232,10 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
const hasExportAppData = (await getFileExports({ rootDir, file: 'src/app' })).includes('dataLoader'); const hasExportAppData = (await getFileExports({ rootDir, file: 'src/app' })).includes('dataLoader');
const csr = !userConfig.ssr && !userConfig.ssg; const csr = !userConfig.ssr && !userConfig.ssg;
const disableRouter = userConfig?.optimization?.router && routesInfo.routesCount <= 1; const disableRouter = (userConfig?.optimization?.router && routesInfo.routesCount <= 1) ||
userConfig?.optimization?.disableRouter;
if (disableRouter) { if (disableRouter) {
logger.info('`optimization.router` is enabled and only have one route, ice build will remove react-router and history which is unnecessary.'); logger.info('`optimization.router` is enabled, ice build will remove react-router and history which is unnecessary.');
taskConfigs = mergeTaskConfig(taskConfigs, { taskConfigs = mergeTaskConfig(taskConfigs, {
alias: { alias: {
'@ice/runtime/router': '@ice/runtime/single-router', '@ice/runtime/router': '@ice/runtime/single-router',

View File

@ -13,9 +13,15 @@ interface SyntaxFeatures {
interface Optimization { interface Optimization {
/** /**
* Optimize code by remove react-router dependencies when set to true. * Optimize code by remove react-router dependencies when set to true,
* it only works when route count is 1.
*/ */
router?: boolean; router?: boolean;
/**
* @private
* Remove react-router dependencies by force, even if route count is greater than 1.
*/
disableRouter?: boolean;
} }
interface MinifyOptions { interface MinifyOptions {

View File

@ -139,7 +139,7 @@ interface RenderOptions {
async function render({ history, runtime, needHydrate }: RenderOptions) { async function render({ history, runtime, needHydrate }: RenderOptions) {
const appContext = runtime.getAppContext(); const appContext = runtime.getAppContext();
const { appConfig, loaderData, routes, basename } = appContext; const { appConfig, loaderData, routes, basename, routePath } = appContext;
const appRender = runtime.getRender(); const appRender = runtime.getRender();
const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment;
const AppRouter = runtime.getAppRouter<ClientAppRouterProps>(); const AppRouter = runtime.getAppRouter<ClientAppRouterProps>();
@ -154,8 +154,9 @@ async function render({ history, runtime, needHydrate }: RenderOptions) {
} }
const hydrationData = needHydrate ? { loaderData } : undefined; const hydrationData = needHydrate ? { loaderData } : undefined;
const routeModuleCache = {}; const routeModuleCache = {};
const location = history.location ? history.location : { pathname: routePath || window.location.pathname };
if (needHydrate) { if (needHydrate) {
const lazyMatches = matchRoutes(routes, history.location, basename).filter((m) => m.route.lazy); const lazyMatches = matchRoutes(routes, location, basename).filter((m) => m.route.lazy);
if (lazyMatches?.length > 0) { if (lazyMatches?.length > 0) {
// Load the lazy matches and update the routes before creating your router // Load the lazy matches and update the routes before creating your router
// so we can hydrate the SSR-rendered content synchronously. // so we can hydrate the SSR-rendered content synchronously.
@ -182,8 +183,9 @@ async function render({ history, runtime, needHydrate }: RenderOptions) {
let singleComponent = null; let singleComponent = null;
let routeData = null; let routeData = null;
if (process.env.ICE_CORE_ROUTER !== 'true') { if (process.env.ICE_CORE_ROUTER !== 'true') {
const { Component, loader } = await loadRouteModule(routes[0], routeModuleCache); const singleRoute = matchRoutes(routes, location, basename)[0];
singleComponent = Component || routes[0].Component; const { Component, loader } = await loadRouteModule(singleRoute.route, routeModuleCache);
singleComponent = Component || singleRoute.route.Component;
routeData = loader && await loader(); routeData = loader && await loader();
} }
const renderRoot = appRender( const renderRoot = appRender(

View File

@ -42,15 +42,30 @@ export const createHistory = (): History => {
}; };
}; };
export const matchRoutes = (routes: any[]) => { const stripString = (str: string) => {
return routes.map(item => { const regexForSlash = /^\/|\/$/g;
return { return str.replace(regexForSlash, '');
params: {}, };
pathname: '',
pathnameBase: '', export const matchRoutes = (routes: any[], location: Partial<Location> | string, basename: string) => {
route: item, const stripedBasename = stripString(basename);
}; const pathname = typeof location === 'string' ? location : location.pathname;
let stripedPathname = stripString(pathname);
if (stripedBasename) {
stripedPathname = stripedPathname.replace(new RegExp(`^${stripedBasename}/`), '');
}
const route = routes.length === 1 ? routes[0] : routes.find(item => {
return stripString(item.path || '') === stripedPathname;
}); });
if (!route) {
throw new Error(`No route matched pathname: ${pathname}`);
}
return [{
route,
params: {},
pathname,
pathnameBase: '',
}];
}; };
export const Link = () => null; export const Link = () => null;

View File

@ -37,8 +37,54 @@ describe('single route api', () => {
expect(createHistory().location).toBe(''); expect(createHistory().location).toBe('');
}); });
it('matchRoutes', () => { it('matchRoutes - single route', () => {
expect(matchRoutes([{}])[0].pathname).toBe(''); const routes = [
{
path: 'users',
element: <div>user</div>,
},
];
const location = {
pathname: '/test',
};
const matchedRoutes = matchRoutes(routes, location, '/');
expect(matchedRoutes).toHaveLength(1);
expect(matchedRoutes[0].route.path).toBe('users');
});
it('matchRoutes - mutiple route', () => {
const routes = [
{
path: 'users',
element: <div>user</div>,
},
{
path: 'posts',
element: <div>post</div>,
},
];
const location = {
pathname: '/posts',
};
const matchedRoutes = matchRoutes(routes, location, '/');
expect(matchedRoutes).toHaveLength(1);
expect(matchedRoutes[0].route.path).toBe('posts');
});
it('matchRoutes - basename', () => {
const routes = [
{
path: 'users',
element: <div>user</div>,
},
{
path: 'posts',
element: <div>post</div>,
},
];
const matchedRoutes = matchRoutes(routes, '/basename/posts', '/basename');
expect(matchedRoutes).toHaveLength(1);
expect(matchedRoutes[0].route.path).toBe('posts');
}); });
it('Link', () => { it('Link', () => {