diff --git a/.changeset/soft-spoons-greet.md b/.changeset/soft-spoons-greet.md new file mode 100644 index 000000000000..8986cf22c62b --- /dev/null +++ b/.changeset/soft-spoons-greet.md @@ -0,0 +1,7 @@ +--- +'@modern-js/app-tools': patch +'@modern-js/server-core': patch +--- + +fix: should get server routes from route.json in serve command +fix: 在 serve 命令下应该从 route.json 中获取 server routes diff --git a/packages/server/core/src/adapters/node/plugins/resource.ts b/packages/server/core/src/adapters/node/plugins/resource.ts index bde2467393b2..a311df9b90be 100644 --- a/packages/server/core/src/adapters/node/plugins/resource.ts +++ b/packages/server/core/src/adapters/node/plugins/resource.ts @@ -16,6 +16,7 @@ import type { ServerManifest, ServerPlugin, } from '../../../types'; +import { uniqueKeyByRoute } from '../../../utils'; export async function getHtmlTemplates(pwd: string, routes: ServerRoute[]) { const htmls = await Promise.all( @@ -27,7 +28,7 @@ export async function getHtmlTemplates(pwd: string, routes: ServerRoute[]) { } catch (e) { // ignore error } - return [route.entryName!, html]; + return [uniqueKeyByRoute(route), html]; }) || [], ); diff --git a/packages/server/core/src/plugins/render/render.ts b/packages/server/core/src/plugins/render/render.ts index 13770f13d0bc..7b538479151f 100644 --- a/packages/server/core/src/plugins/render/render.ts +++ b/packages/server/core/src/plugins/render/render.ts @@ -12,6 +12,7 @@ import type { } from '../../types'; import type { Render } from '../../types'; import type { Params } from '../../types/requestHandler'; +import { uniqueKeyByRoute } from '../../utils'; import { ErrorDigest, createErrorHtml, @@ -139,8 +140,7 @@ export async function createRender({ }); } - const html = templates[routeInfo.entryName!]; - + const html = templates[uniqueKeyByRoute(routeInfo)]; if (!html) { return new Response(createErrorHtml(404), { status: 404, diff --git a/packages/server/core/src/utils/entry.ts b/packages/server/core/src/utils/entry.ts index 32b92558a674..b29aca850d14 100644 --- a/packages/server/core/src/utils/entry.ts +++ b/packages/server/core/src/utils/entry.ts @@ -3,3 +3,8 @@ import type { ServerRoute } from '@modern-js/types'; export const sortRoutes = (route1: ServerRoute, route2: ServerRoute) => { return route2.urlPath.length - route1.urlPath.length; }; + +// Because the same entryName may have different urlPath(ssg), so we need to add urlPath as key +export const uniqueKeyByRoute = (route: ServerRoute) => { + return `${route.entryName!}-${route.urlPath}`; +}; diff --git a/packages/solutions/app-tools/src/plugins/analyze/getServerRoutes.ts b/packages/solutions/app-tools/src/plugins/analyze/getServerRoutes.ts index 1f250db56ae1..1c21b233d7a7 100644 --- a/packages/solutions/app-tools/src/plugins/analyze/getServerRoutes.ts +++ b/packages/solutions/app-tools/src/plugins/analyze/getServerRoutes.ts @@ -1,8 +1,8 @@ -import fs from 'fs'; import path from 'path'; -import type { IAppContext } from '@modern-js/core'; import type { Entrypoint, ServerRoute } from '@modern-js/types'; import { + fs, + ROUTE_SPEC_FILE, SERVER_BUNDLE_DIRECTORY, SERVER_WORKER_BUNDLE_DIRECTORY, getEntryOptions, @@ -252,3 +252,15 @@ export const getServerRoutes = ( const toPosix = (pathStr: string) => pathStr.split(path.sep).join(path.posix.sep); + +export const getProdServerRoutes = (distDirectory: string) => { + const routeJSON = path.join(distDirectory, ROUTE_SPEC_FILE); + try { + const { routes } = fs.readJSONSync(routeJSON); + return routes; + } catch (e) { + throw new Error( + `Failed to read routes from ${routeJSON}, please check if the file exists.`, + ); + } +}; diff --git a/packages/solutions/app-tools/src/plugins/analyze/index.ts b/packages/solutions/app-tools/src/plugins/analyze/index.ts index a3ad4b81da26..3d0ccf01f1c6 100644 --- a/packages/solutions/app-tools/src/plugins/analyze/index.ts +++ b/packages/solutions/app-tools/src/plugins/analyze/index.ts @@ -1,4 +1,5 @@ import * as path from 'path'; +import type { ServerRoute } from '@modern-js/types'; import { fs, createDebugger, @@ -18,7 +19,7 @@ import { emitResolvedConfig } from '../../utils/config'; import { getSelectedEntries } from '../../utils/getSelectedEntries'; import { printInstructions } from '../../utils/printInstructions'; import { generateRoutes } from '../../utils/routes'; -import { checkIsBuildCommands } from './utils'; +import { checkIsBuildCommands, checkIsServeCommand } from './utils'; const debug = createDebugger('plugin-analyze'); @@ -54,8 +55,21 @@ export default ({ ); await hooks.addRuntimeExports.call(); + const [{ getProdServerRoutes }] = await Promise.all([ + import('./getServerRoutes.js'), + ]); + if (apiOnly) { - const { routes } = await hooks.modifyServerRoutes.call({ routes: [] }); + const routes: ServerRoute[] = []; + if (checkIsServeCommand()) { + routes.push(...getProdServerRoutes(appContext.distDirectory)); + } else { + const { routes: modifiedRoutes } = + await hooks.modifyServerRoutes.call({ + routes: [], + }); + routes.push(...modifiedRoutes); + } debug(`server routes: %o`, routes); @@ -80,14 +94,20 @@ export default ({ debug(`entrypoints: %o`, entrypoints); - const initialRoutes = getServerRoutes(entrypoints, { - appContext, - config: resolvedConfig, - }); + const routes: ServerRoute[] = []; + if (checkIsServeCommand()) { + routes.push(...getProdServerRoutes(appContext.distDirectory)); + } else { + const initialRoutes = getServerRoutes(entrypoints, { + appContext, + config: resolvedConfig, + }); - const { routes } = await hooks.modifyServerRoutes.call({ - routes: initialRoutes, - }); + const { routes: modifiedRoutes } = await hooks.modifyServerRoutes.call({ + routes: initialRoutes, + }); + routes.push(...modifiedRoutes); + } debug(`server routes: %o`, routes); diff --git a/packages/solutions/app-tools/src/plugins/analyze/utils.ts b/packages/solutions/app-tools/src/plugins/analyze/utils.ts index 100a977dc67a..9716221ff9d1 100644 --- a/packages/solutions/app-tools/src/plugins/analyze/utils.ts +++ b/packages/solutions/app-tools/src/plugins/analyze/utils.ts @@ -74,6 +74,12 @@ export const checkIsBuildCommands = () => { return buildCommands.includes(command); }; +export const checkIsServeCommand = () => { + const command = getCommand(); + + return command === 'serve'; +}; + export const isSubDirOrEqual = (parent: string, child: string): boolean => { if (parent === child) { return true; diff --git a/tests/integration/ssg/fixtures/nested-routes/package.json b/tests/integration/ssg/fixtures/nested-routes/package.json index f2379a59a368..2482bbb36ee1 100644 --- a/tests/integration/ssg/fixtures/nested-routes/package.json +++ b/tests/integration/ssg/fixtures/nested-routes/package.json @@ -3,7 +3,8 @@ "name": "ssg-fixtures-nested-routes", "version": "2.9.0", "scripts": { - "build": "modern build" + "build": "modern build", + "serve": "modern serve" }, "dependencies": { "@modern-js/app-tools": "workspace:*", diff --git a/tests/integration/ssg/tests/nested-routes.test.ts b/tests/integration/ssg/tests/nested-routes.test.ts index db121d0c095e..efe3613e5eca 100644 --- a/tests/integration/ssg/tests/nested-routes.test.ts +++ b/tests/integration/ssg/tests/nested-routes.test.ts @@ -1,17 +1,22 @@ import path, { join } from 'path'; import { fs } from '@modern-js/utils'; -import { killApp, modernBuild } from '../../../utils/modernTestUtils'; +import puppeteer from 'puppeteer'; +import { + getPort, + killApp, + launchOptions, + modernBuild, + modernServe, +} from '../../../utils/modernTestUtils'; const fixtureDir = path.resolve(__dirname, '../fixtures'); - +const appDir = join(fixtureDir, 'nested-routes'); jest.setTimeout(1000 * 60 * 3); describe('ssg', () => { let app: any; - let appDir: string; let distDir: string; beforeAll(async () => { - appDir = join(fixtureDir, 'nested-routes'); distDir = join(appDir, './dist'); await modernBuild(appDir); }); @@ -31,3 +36,38 @@ describe('ssg', () => { expect(html.includes('Hello, User')).toBe(true); }); }); + +describe('test ssg request', () => { + let buildRes: { code: number }; + let app: any; + let port: any; + beforeAll(async () => { + port = await getPort(); + + buildRes = await modernBuild(appDir); + app = await modernServe(appDir, port, { + cwd: appDir, + }); + }); + + afterAll(async () => { + await killApp(app); + }); + + test('should support enableInlineScripts', async () => { + const host = `http://localhost`; + expect(buildRes.code === 0).toBe(true); + const browser = await puppeteer.launch(launchOptions as any); + const page = await browser.newPage(); + await page.goto(`${host}:${port}/user`); + + const description = await page.$('#data'); + const targetText = await page.evaluate(el => el?.textContent, description); + try { + expect(targetText?.trim()).toEqual('Hello, User'); + } finally { + await page.close(); + await browser.close(); + } + }); +});