diff --git a/src/plugins/prerender-plugin.js b/src/plugins/prerender-plugin.js index 36e1e98..1f01a4e 100644 --- a/src/plugins/prerender-plugin.js +++ b/src/plugins/prerender-plugin.js @@ -75,6 +75,7 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere let viteConfig = {}; let userEnabledSourceMaps; let ssrBuild = false; + let prerenderEntryHtml; /** @type {import('./types.d.ts').PrerenderedRoute[]} */ let routes = []; @@ -90,12 +91,9 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere const tmpDirId = 'headless-prerender'; /** - * From the non-external scripts in entry HTML document, find the one (if any) - * that provides a `prerender` export - * * @param {import('vite').Rollup.InputOption} input */ - const getPrerenderScriptFromHTML = async (input) => { + const getPrerenderEntryHtml = (input) => { // prettier-ignore const entryHtml = typeof input === "string" @@ -106,7 +104,17 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere if (!entryHtml) throw new Error('Unable to detect entry HTML'); - const htmlDoc = htmlParse(await fs.readFile(entryHtml, 'utf-8')); + return path.resolve(viteConfig.root, entryHtml); + }; + + /** + * From the non-external scripts in the entry HTML document, find the one (if any) + * that provides a `prerender` export + */ + const getPrerenderScriptFromHtml = async () => { + if (!prerenderEntryHtml) throw new Error('Unable to detect entry HTML'); + + const htmlDoc = htmlParse(await fs.readFile(prerenderEntryHtml, 'utf-8')); const entryScriptTag = htmlDoc .getElementsByTagName('script') @@ -118,7 +126,9 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere if (!entrySrc || /^https:/.test(entrySrc)) throw new Error('Prerender entry script must have a `src` attribute and be local'); - return path.join(viteConfig.root, entrySrc); + return entrySrc.startsWith('/') + ? path.join(viteConfig.root, entrySrc) + : path.resolve(path.dirname(prerenderEntryHtml), entrySrc); }; return { @@ -134,7 +144,7 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere // Only required for Vite 5 and older. In 6+, this is handled by the // Environment API (`applyToEnvironment`) if (config.build?.ssr) { - ssrBuild = true + ssrBuild = true; return; } @@ -194,8 +204,9 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere }, async options(opts) { if (ssrBuild || !opts.input) return; + prerenderEntryHtml = getPrerenderEntryHtml(opts.input); if (!prerenderScript) { - prerenderScript = await getPrerenderScriptFromHTML(opts.input); + prerenderScript = await getPrerenderScriptFromHtml(); } // prettier-ignore @@ -288,9 +299,25 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere }; // Grab the generated HTML file, we'll use it as a template for all pages: - const tpl = /** @type {string} */ ( - /** @type {OutputAsset} */ (bundle['index.html']).source - ); + const entryHtmlAsset = + (prerenderEntryHtml && + Object.values(bundle).find( + (output) => + output.type === 'asset' && + output.fileName.endsWith('.html') && + (output.originalFileName === prerenderEntryHtml || + output.originalFileNames?.includes(prerenderEntryHtml)), + )) || + /** @type {OutputAsset | undefined} */ (bundle['index.html']) || + Object.values(bundle).find( + (output) => output.type === 'asset' && output.fileName.endsWith('.html'), + ); + + if (!entryHtmlAsset) { + this.error('Unable to detect generated entry HTML asset'); + } + + const tpl = /** @type {string} */ (entryHtmlAsset.source); // Create a tmp dir to allow importing & consuming the built modules, // before Rollup writes them to the disk @@ -506,8 +533,7 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere // Add generated HTML to compilation: route.url == '/' - ? (/** @type {OutputAsset} */ (bundle['index.html']).source = - htmlDoc.toString()) + ? (entryHtmlAsset.source = htmlDoc.toString()) : this.emitFile({ type: 'asset', fileName: assetName, @@ -535,6 +561,6 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere } } } - } + }, }; } diff --git a/tests/fixtures/nested-entry/src/dashboard/index.html b/tests/fixtures/nested-entry/src/dashboard/index.html new file mode 100644 index 0000000..ede5005 --- /dev/null +++ b/tests/fixtures/nested-entry/src/dashboard/index.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/fixtures/nested-entry/src/dashboard/main.js b/tests/fixtures/nested-entry/src/dashboard/main.js new file mode 100644 index 0000000..cd16f51 --- /dev/null +++ b/tests/fixtures/nested-entry/src/dashboard/main.js @@ -0,0 +1,3 @@ +export async function prerender() { + return `

Nested Entry Test Result

`; +} diff --git a/tests/fixtures/nested-entry/vite.config.js b/tests/fixtures/nested-entry/vite.config.js new file mode 100644 index 0000000..57e9544 --- /dev/null +++ b/tests/fixtures/nested-entry/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import path from 'node:path'; +import url from 'node:url'; +import { vitePrerenderPlugin } from 'vite-prerender-plugin'; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +export default defineConfig({ + build: { + rollupOptions: { + input: { + dashboard: path.resolve(__dirname, 'src/dashboard/index.html'), + }, + }, + }, + plugins: [vitePrerenderPlugin()], +}); diff --git a/tests/index.test.js b/tests/index.test.js index 82ab0ac..7b6bbfc 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -37,6 +37,14 @@ test('Should merge preload and entry chunks', async () => { assert.equal((await fs.readdir(outDirAssets)).length, 1); }); +test('Should support nested HTML entrypoints', async () => { + await loadFixture('nested-entry', env); + await viteBuild(env.tmp.path); + + const prerenderedHtml = await getOutputFile(env.tmp.path, 'src/dashboard/index.html'); + assert.match(prerenderedHtml, '

Nested Entry Test Result

'); +}); + test('Should bail on merging preload & entry chunks if user configures `manualChunks`', async () => { await loadFixture('simple', env); await writeConfig(env.tmp.path, `