Plugin to allow importing processed entry HTML files into JS/TS keeping source maps intact. #23113
Replies: 9 comments 5 replies
|
The reason you won't find one is that the middle part of what you're asking for doesn't exist to wait on — Vite doesn't process non-entry HTML at all. Checked on Vite 8.2.0 with a partial containing an asset reference and a script tag: import raw from './partial.html?raw'
So there's no "processed" version of an imported partial sitting somewhere that a plugin could pick up later. Ordering a plugin after Vite's HTML handling doesn't help, because for that file the HTML handling never ran. The good news is your sourcemap concern is already handled: the Two ways to actually get processing, depending on what "processed" means for you: If you want asset URLs resolved and hashed, add the partials as extra Rollup inputs so they go through the HTML pipeline properly, then read the emitted result rather than importing the source: build: {
rollupOptions: {
input: {
main: 'index.html',
partial: 'src/partial.html',
},
},
}If you only need the asset references rewritten and don't want extra HTML entry points, a small plugin doing it in Worth saying which one you're after — if it's the second, the shape of your partials (inline scripts? nested imports? just images?) changes how much of that walk you actually need. |
|
I think you can achieve this by using a custom Vite plugin with the transformIndexHtml hook. Vite processes HTML entry files through this hook, and with order: 'post' you can run your transformation after Vite has finished processing the HTML. Example: export default function htmlToString() { This allows you to get the final processed HTML as a JS string. For source maps, the plugin should return a proper transform result with a map if additional JS transformation is applied. Vite also supports importing assets as strings using ?raw, but that happens before HTML processing, so a post HTML transform may be more suitable for this use case. |
|
@AmritaMalik Same test as above, with your exact configuration: transformIndexHtml: {
order: 'post',
handler(html, ctx) { seen.push(ctx.filename) ; return html },
}
Returning The two routes that do work are the ones above: add the partials as extra Rollup inputs so they genuinely go through the HTML pipeline, or do the asset-URL rewriting yourself in a |
|
@AmritaMalik @aymenhmaidiwastaken I have something that works, except I can't seem to get the source maps to update correctly. I'm trying to use MagicString and have no idea what went wrong. 😔 btw I'm auto adding the html files into the rollup input. transformIndexHtml: {
order: "post",
handler(html, ctx) {
const htmlBuildPath = buildFolder.joining(ctx.path.slice(1))
const bundle = ctx.bundle!
for (const id in bundle) {
const entry = bundle[id]
if (entry.type === "chunk") {
if (entry.code.includes(postfix)) {
// Inject HTML into JS:
const scriptBuildPath = buildFolder.joining(entry.fileName)
const filePath = htmlBuildPath.relativeTo(scriptBuildPath.parent)
const newCode = new MagicString(entry.code)
const pattern = new RegExp(`import +[a-zA-Z]+ from *"${RegExp.escape(filePath.toString() + postfix)}"`, "g")
newCode.replaceAll(pattern, (sub) => {
importedHTMLFiles.push(htmlBuildPath)
return `let ${sub.split(" ")[1]}=${JSON.stringify(html)}`
})
if (entry.map) {
// TODO: Something isn't working! https://evanw.github.io/source-map-visualization
const newMap = newCode.generateMap({
source: ctx.path,
hires: true,
includeContent: true,
})
const mergedMap: SourceMap = remapping(
[JSON.stringify(newMap), JSON.stringify(entry.map)],
() => null,
)
mergedMap.file = entry.map.file
Object.assign(entry.map, mergedMap)
}
entry.code = newCode.toString()
}
}
}
},
},The only other way I can think this could work is if I first converted the html file to a js file with a default export, than ran vite again to import the new js file into the chunk. BUT then I'd have to run vite twice, which doesn't seem right. |
|
The sourcemap problem is mostly this line:
If you can move the chunk edit earlier, |
|
Update: I've got it to work with |
|
For dev it is possible, but not by waiting for Rollup output: there is no bundle/chunk to patch in dev. I would make the dev path a virtual module. Store the dev server from return `export default ${JSON.stringify(transformedHtml)}`Keep the build path separate, or make build emit the same virtual module from the processed Rollup HTML. The key bit is that |
|
Now I need ViteDevServer.tranformIndexHtml to convert URL's to absolute because when then HTML is being imported as text and then inserted into another document the URLs break. I've found someone asked the same question here #13763 |
|
I think the remaining URL issue comes from the fact that Instead of trying to rewrite the resulting HTML afterward, you can give For example, in the virtual-module loader: const html = await fs.promises.readFile(htmlFile, 'utf8')
const transformed = await server.transformIndexHtml(
targetUrl,
html,
)
return `export default ${JSON.stringify(transformed)}`If <img src="./logo.svg">
<script type="module" src="./index.ts"></script>relative to that URL during HTML transformation. The important distinction is that the URL passed to If the final HTML is always embedded into a known document location, another robust option is to make the HTML entries use root-relative URLs in the first place: <img src="/pages/test/logo.svg">
<script type="module" src="/pages/test/index.ts"></script>Then the generated HTML doesn't depend on the location where the string is eventually inserted. For the dev virtual-module approach, I'd structure the plugin roughly like this: let viteServer: ViteDevServer
export default function htmlAsString() {
return {
name: 'html-as-string',
configureServer(server) {
viteServer = server
},
async load(id) {
if (!id.endsWith('.html?processed')) {
return null
}
const file = id.slice(0, -'?processed'.length)
const html = await fs.promises.readFile(file, 'utf8')
const url = '/' + path.relative(
server.config.root,
file
).replaceAll(path.sep, '/')
const transformed = await viteServer.transformIndexHtml(
url,
html,
)
return `export default ${JSON.stringify(transformed)}`
},
}
}The exact URL mapping will depend on your
rather than using the virtual module's URL as the HTML URL. Also, I wouldn't run Vite twice. The virtual-module approach is a much better fit because it lets the dev server perform the normal HTML transformation first and then exposes the transformed result to Rollup/your application as a normal JS module. One caveat: if the HTML is ultimately inserted into a document at a different URL than the HTML entry's URL, there isn't a single relative URL that can be correct for both locations. In that case, root-relative URLs or an explicit |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Wondering if anybody knows of an existing plugin (or how to make one) that allows importing entry HTML files into JS as a string, keeping source maps intact and waiting until after the entry HTML files have already been processed by Vite before embedding them into to JS.
All reactions