diff --git a/packages/slidev/node/commands/build.ts b/packages/slidev/node/commands/build.ts index 57fdc53dd9..3bbe3f4d5e 100644 --- a/packages/slidev/node/commands/build.ts +++ b/packages/slidev/node/commands/build.ts @@ -6,7 +6,7 @@ import { build as viteBuild } from 'vite' import connect from 'connect' import sirv from 'sirv' import type { BuildArgs, ResolvedSlidevOptions } from '@slidev/types' -import { getIndexHtml, resolveViteConfigs } from './shared' +import { resolveViteConfigs } from './shared' export async function build( options: ResolvedSlidevOptions, @@ -19,7 +19,7 @@ export async function build( if (fs.existsSync(indexPath)) originalIndexHTML = await fs.readFile(indexPath, 'utf-8') - await fs.writeFile(indexPath, await getIndexHtml(options), 'utf-8') + await fs.writeFile(indexPath, options.utils.indexHtml, 'utf-8') let config: ResolvedConfig = undefined! try { diff --git a/packages/slidev/node/commands/shared.ts b/packages/slidev/node/commands/shared.ts index c7f21f6c9c..5bb16d803e 100644 --- a/packages/slidev/node/commands/shared.ts +++ b/packages/slidev/node/commands/shared.ts @@ -1,14 +1,11 @@ -import { existsSync, promises as fs } from 'node:fs' +import { existsSync } from 'node:fs' import { join } from 'node:path' -import { loadConfigFromFile, mergeConfig } from 'vite' -import type { ConfigEnv, InlineConfig } from 'vite' import type { ResolvedSlidevOptions, SlidevData, SlidevServerOptions } from '@slidev/types' import MarkdownIt from 'markdown-it' -import { slash } from '@antfu/utils' +import type { ConfigEnv, InlineConfig } from 'vite' +import { loadConfigFromFile, mergeConfig } from 'vite' import markdownItLink from '../syntax/markdown-it/markdown-it-link' -import { generateGoogleFontsUrl, stringifyMarkdownTokens } from '../utils' -import { toAtFS } from '../resolver' -import { version } from '../../package.json' +import { stringifyMarkdownTokens } from '../utils' import { ViteSlidevPlugin } from '../vite' export const sharedMd = MarkdownIt({ html: true }) @@ -21,58 +18,6 @@ export function getSlideTitle(data: SlidevData) { return slideTitle === 'Slidev - Slidev' ? 'Slidev' : slideTitle } -function escapeHtml(unsafe: unknown) { - return JSON.stringify( - String(unsafe) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''), - ) -} - -export async function getIndexHtml({ mode, entry, clientRoot, roots, data }: ResolvedSlidevOptions): Promise { - let main = await fs.readFile(join(clientRoot, 'index.html'), 'utf-8') - let head = '' - let body = '' - - const { info, author, keywords } = data.headmatter - head += [ - ``, - mode === 'dev' && ``, - ``, - `${getSlideTitle(data)}`, - info && ``, - author && ``, - keywords && ``, - ].filter(Boolean).join('\n') - - for (const root of roots) { - const path = join(root, 'index.html') - if (!existsSync(path)) - continue - - const index = await fs.readFile(path, 'utf-8') - - head += `\n${(index.match(/([\s\S]*?)<\/head>/i)?.[1] || '').trim()}` - body += `\n${(index.match(/([\s\S]*?)<\/body>/i)?.[1] || '').trim()}` - } - - if (data.features.tweet) - body += '\n' - - if (data.config.fonts.webfonts.length && data.config.fonts.provider !== 'none') - head += `\n` - - main = main - .replace('__ENTRY__', toAtFS(join(clientRoot, 'main.ts'))) - .replace('', head) - .replace('', body) - - return main -} - export async function resolveViteConfigs( options: ResolvedSlidevOptions, baseConfig: InlineConfig, diff --git a/packages/slidev/node/options.ts b/packages/slidev/node/options.ts index a76e19635e..861805d646 100644 --- a/packages/slidev/node/options.ts +++ b/packages/slidev/node/options.ts @@ -9,6 +9,7 @@ import { getThemeMeta, resolveTheme } from './integrations/themes' import { resolveAddons } from './integrations/addons' import { getRoots, resolveEntry } from './resolver' import setupShiki from './setups/shiki' +import setupIndexHtml from './setups/indexHtml' const debug = Debug('slidev:options') @@ -50,7 +51,7 @@ export async function resolveOptions( themeMeta, } - const resolved: ResolvedSlidevOptions = { + const resolved: Omit = { ...rootsInfo, ...entryOptions, data, @@ -61,21 +62,24 @@ export async function resolveOptions( themeRoots, addonRoots, roots, - utils: await createDataUtils(data, rootsInfo.clientRoot, roots), } - return resolved + return { + ...resolved, + utils: await createDataUtils(resolved), + } } -export async function createDataUtils(data: SlidevData, clientRoot: string, roots: string[]): Promise { - const monacoTypesIgnorePackagesMatches = (data.config.monacoTypesIgnorePackages || []) +export async function createDataUtils(resolved: Omit): Promise { + const monacoTypesIgnorePackagesMatches = (resolved.data.config.monacoTypesIgnorePackages || []) .map(i => mm.matcher(i)) let _layouts_cache_time = 0 let _layouts_cache: Record = {} return { - ...await setupShiki(roots), + ...await setupShiki(resolved.roots), + indexHtml: setupIndexHtml(resolved), isMonacoTypesIgnored: pkg => monacoTypesIgnorePackagesMatches.some(i => i(pkg)), getLayouts: () => { const now = Date.now() @@ -84,7 +88,7 @@ export async function createDataUtils(data: SlidevData, clientRoot: string, root const layouts: Record = {} - for (const root of [clientRoot, ...roots]) { + for (const root of [resolved.clientRoot, ...resolved.roots]) { const layoutPaths = fg.sync('layouts/**/*.{vue,ts}', { cwd: root, absolute: true, diff --git a/packages/slidev/node/setups/indexHtml.ts b/packages/slidev/node/setups/indexHtml.ts new file mode 100644 index 0000000000..a44ba78b39 --- /dev/null +++ b/packages/slidev/node/setups/indexHtml.ts @@ -0,0 +1,61 @@ +import { existsSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { slash } from '@antfu/utils' +import type { ResolvedSlidevOptions } from '@slidev/types' +import { white, yellow } from 'kolorist' +import { escapeHtml } from 'markdown-it/lib/common/utils.mjs' +import { version } from '../../package.json' +import { getSlideTitle } from '../commands/shared' +import { toAtFS } from '../resolver' +import { generateGoogleFontsUrl } from '../utils' + +function toAttrValue(unsafe: unknown) { + return JSON.stringify(escapeHtml(String(unsafe))) +} + +export default function setupIndexHtml({ mode, entry, clientRoot, userRoot, roots, data }: Omit): string { + let main = readFileSync(join(clientRoot, 'index.html'), 'utf-8') + let head = '' + let body = '' + + const { info, author, keywords } = data.headmatter + head += [ + ``, + mode === 'dev' && ``, + ``, + `${getSlideTitle(data)}`, + info && ``, + author && ``, + keywords && ``, + ].filter(Boolean).join('\n') + + for (const root of roots) { + const path = join(root, 'index.html') + if (!existsSync(path)) + continue + + const index = readFileSync(path, 'utf-8') + + if (root === userRoot && index.includes('([\s\S]*?)<\/head>/i)?.[1] || '').trim()}` + body += `\n${(index.match(/([\s\S]*?)<\/body>/i)?.[1] || '').trim()}` + } + + if (data.features.tweet) + body += '\n' + + if (data.config.fonts.webfonts.length && data.config.fonts.provider !== 'none') + head += `\n` + + main = main + .replace('__ENTRY__', toAtFS(join(clientRoot, 'main.ts'))) + .replace('', head) + .replace('', body) + + return main +} diff --git a/packages/slidev/node/vite/extendConfig.ts b/packages/slidev/node/vite/extendConfig.ts index 8a02be1c09..0384570832 100644 --- a/packages/slidev/node/vite/extendConfig.ts +++ b/packages/slidev/node/vite/extendConfig.ts @@ -5,7 +5,6 @@ import { mergeConfig } from 'vite' import { slash, uniq } from '@antfu/utils' import type { ResolvedSlidevOptions } from '@slidev/types' import { createResolve } from 'mlly' -import { getIndexHtml } from '../commands/shared' import { isInstalledGlobally, resolveImportPath, toAtFS } from '../resolver' const INCLUDE_GLOBAL = [ @@ -197,7 +196,7 @@ export function createConfigPlugin(options: ResolvedSlidevOptions): Plugin { if (req.url!.endsWith('.html')) { res.setHeader('Content-Type', 'text/html') res.statusCode = 200 - res.end(await getIndexHtml(options)) + res.end(options.utils.indexHtml) return } next() diff --git a/packages/types/src/options.ts b/packages/types/src/options.ts index 15f26266e9..ad03f4e131 100644 --- a/packages/types/src/options.ts +++ b/packages/types/src/options.ts @@ -48,6 +48,7 @@ export interface ResolvedSlidevOptions extends RootsInfo, SlidevEntryOptions { export interface ResolvedSlidevUtils { shiki: HighlighterGeneric shikiOptions: MarkdownItShikiOptions + indexHtml: string isMonacoTypesIgnored: (pkg: string) => boolean getLayouts: () => Record }