diff --git a/.changeset/angry-carrots-occur.md b/.changeset/angry-carrots-occur.md new file mode 100644 index 0000000000..844e79439f --- /dev/null +++ b/.changeset/angry-carrots-occur.md @@ -0,0 +1,5 @@ +--- +'@ice/app': patch +--- + +feat: support htmlGeneratingMode option diff --git a/examples/basic-project/compatHtml.config.mts b/examples/basic-project/compatHtml.config.mts new file mode 100644 index 0000000000..e039dcaadd --- /dev/null +++ b/examples/basic-project/compatHtml.config.mts @@ -0,0 +1,9 @@ +import { defineConfig } from '@ice/app'; +import defaultConfig from './ice.config.mjs'; + +export default defineConfig(() => ({ + ...defaultConfig, + htmlGenerating: { + mode: 'compat' + } +})); diff --git a/packages/ice/src/bundler/config/output.ts b/packages/ice/src/bundler/config/output.ts index 56ecbd9393..413bd18730 100644 --- a/packages/ice/src/bundler/config/output.ts +++ b/packages/ice/src/bundler/config/output.ts @@ -5,6 +5,7 @@ import injectInitialEntry from '../../utils/injectInitialEntry.js'; import { SERVER_OUTPUT_DIR } from '../../constant.js'; import { logger } from '../../utils/logger.js'; import type { BundlerOptions } from '../types.js'; +import type { HtmlGeneratingMode } from '../../types/index.js'; export async function getOutputPaths(options: { rootDir: string; @@ -21,7 +22,8 @@ export async function getOutputPaths(options: { } } if (serverEntry && userConfig.htmlGenerating) { - outputPaths = await buildCustomOutputs(rootDir, outputDir, serverEntry, bundleOptions); + const htmlGeneratingMode = typeof userConfig.htmlGenerating === 'boolean' ? undefined : userConfig.htmlGenerating.mode; + outputPaths = await buildCustomOutputs(rootDir, outputDir, serverEntry, bundleOptions, htmlGeneratingMode); } return outputPaths; } @@ -37,6 +39,7 @@ async function buildCustomOutputs( outputDir: string, serverEntry: string, bundleOptions: Pick, + generatingMode?: HtmlGeneratingMode, ) { const { userConfig, appConfig, routeManifest } = bundleOptions; const { ssg } = userConfig; @@ -52,6 +55,7 @@ async function buildCustomOutputs( renderMode: ssg ? 'SSG' : undefined, routeType: appConfig?.router?.type, routeManifest, + generatingMode, }); if (routeType === 'memory' && userConfig?.routes?.injectInitialEntry) { injectInitialEntry(routeManifest, outputDir); diff --git a/packages/ice/src/config.ts b/packages/ice/src/config.ts index 0dd14ca72b..c754ce74a5 100644 --- a/packages/ice/src/config.ts +++ b/packages/ice/src/config.ts @@ -508,7 +508,7 @@ const userConfig = [ }, { name: 'htmlGenerating', - validation: 'boolean', + validation: 'boolean|object', defaultValue: true, }, ]; diff --git a/packages/ice/src/types/userConfig.ts b/packages/ice/src/types/userConfig.ts index ae252c00a9..79ad24c4ee 100644 --- a/packages/ice/src/types/userConfig.ts +++ b/packages/ice/src/types/userConfig.ts @@ -49,6 +49,20 @@ interface Fetcher { method?: string; } +export type HtmlGeneratingMode = 'cleanUrl' | 'compat'; + +export interface HtmlGeneratingConfig { + /** + * Control how file structure to generation html. + * Route: '/' '/foo' '/foo/bar' + * `cleanUrl`: '/index.html' '/foo.html' '/foo/bar.html' + * `compat`: '/index.html' '/foo/index.html' '/foo/bar/index.html' + * @see https://v3.ice.work/docs/guide/basic/config#htmlgeneratingmode + * @default 'cleanUrl' + */ + mode?: HtmlGeneratingMode; +} + export interface UserConfig { /** * Feature polyfill for legacy browsers, which can not polyfilled by core-js. @@ -178,7 +192,7 @@ export interface UserConfig { * HTML will not be generated when build, If it is false. * @see https://v3.ice.work/docs/guide/basic/config#htmlgenerating */ - htmlGenerating?: boolean; + htmlGenerating?: boolean | HtmlGeneratingConfig; /** * Choose a style of souce mapping to enhance the debugging process. * @see https://v3.ice.work/docs/guide/basic/config#sourcemap diff --git a/packages/ice/src/utils/generateEntry.ts b/packages/ice/src/utils/generateEntry.ts index 198e59d8c1..5e9038fe51 100644 --- a/packages/ice/src/utils/generateEntry.ts +++ b/packages/ice/src/utils/generateEntry.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import fse from 'fs-extra'; import type { ServerContext, RenderMode, AppConfig } from '@ice/runtime'; +import type { HtmlGeneratingMode } from '../types/index.js'; import dynamicImport from './dynamicImport.js'; import { logger } from './logger.js'; import type RouteManifest from './routeManifest.js'; @@ -12,6 +13,7 @@ interface Options { documentOnly: boolean; routeType: AppConfig['router']['type']; renderMode?: RenderMode; + generatingMode?: HtmlGeneratingMode; routeManifest: RouteManifest; } @@ -28,6 +30,7 @@ export default async function generateEntry(options: Options): Promise { expect((await page.$$text('h2')).length).toEqual(0); }); + test('using "compat" html generating mode', async () => { + await buildFixture(example, { + config: 'compatHtml.config.mts', + }); + expect(fs.existsSync(path.join(__dirname, `../../examples/${example}/build/index.html`))).toBeTruthy(); + expect(fs.existsSync(path.join(__dirname, `../../examples/${example}/build/blog/index.html`))).toBeTruthy(); + }); + afterAll(async () => { await browser.close(); }); diff --git a/website/docs/guide/basic/config.md b/website/docs/guide/basic/config.md index 25ad5ed6fa..7602082cd1 100644 --- a/website/docs/guide/basic/config.md +++ b/website/docs/guide/basic/config.md @@ -625,6 +625,21 @@ export default defineConfig(() => ({ 如果产物不想生成 html,可以设置为 `false`,在 SSG 开启的情况下,强制关闭 html 生成,将导致 SSG 失效。 +### htmlGeneratingMode + +- 类型: `'cleanUrl' | 'compat'` +- 默认值 `'cleanUrl'` + +当开启 html 生成的时候,可以用来配置生成目录的规则,避免在某些服务器下出现非首页内容 404 的情况。目前主要由两种,分别是: + +- `cleanUrl` 生成的文件路径和路由一致,只不过多了 `.html`。通常用于支持此模式的现代服务器 +- `compat` 生成兼容模式的路径文件,通常用于一些只能省略 `index.html` 的服务器 + +| 路径 | `/` | `/foo` | `/foo/bar` | +|------------|---------------|-------------------|-----------------------| +| `cleanUrl` | `/index.html` | `/foo.html` | `/foo/bar.html` | +| `compat` | `/index.html` | `/foo/index.html` | `/foo/bar/index.html` | + ### plugins - 类型:`PluginList`