import micromatch from "micromatch"
const defaultConfig = {
useRecommendedBuildConfig: true,
removeViteModuleLoader: false,
deleteInlinedFiles: true
}
export function replaceScript(
html,
scriptFilename,
scriptCode,
removeViteModuleLoader = false
) {
const reScript = new RegExp(
``
)
// we can't use String.prototype.replaceAll since it isn't supported in Node.JS 14
const preloadMarker = /"__VITE_PRELOAD__"/g
const newCode = scriptCode.replace(preloadMarker, "void 0")
const inlined = html.replace(
reScript,
(_, beforeSrc, afterSrc) =>
``
)
return removeViteModuleLoader ? _removeViteModuleLoader(inlined) : inlined
}
export function replaceCss(html, scriptFilename, scriptCode) {
const reCss = new RegExp(`]*? href="[./]*${scriptFilename}"[^>]*?>`)
const inlined = html.replace(reCss, ``)
return inlined
}
const warnNotInlined = filename =>
console.warn(`WARNING: asset not inlined: ${filename}`)
export function viteSingleFile({
useRecommendedBuildConfig = true,
removeViteModuleLoader = false,
inlinePattern = [],
deleteInlinedFiles = true
} = defaultConfig) {
return {
name: "vite:singlefile",
config: useRecommendedBuildConfig ? _useRecommendedBuildConfig : undefined,
enforce: "post",
generateBundle: (_, bundle) => {
const jsExtensionTest = /\.[mc]?js$/
const htmlFiles = Object.keys(bundle).filter(i => i.endsWith(".html"))
const cssAssets = Object.keys(bundle).filter(i => i.endsWith(".css"))
const jsAssets = Object.keys(bundle).filter(i => jsExtensionTest.test(i))
const bundlesToDelete = []
for (const name of htmlFiles) {
const htmlChunk = bundle[name]
let replacedHtml = htmlChunk.source
for (const jsName of jsAssets) {
if (
!inlinePattern.length ||
micromatch.isMatch(jsName, inlinePattern)
) {
const jsChunk = bundle[jsName]
if (jsChunk.code != null) {
bundlesToDelete.push(jsName)
replacedHtml = replaceScript(
replacedHtml,
jsChunk.fileName,
jsChunk.code,
removeViteModuleLoader
)
}
} else {
warnNotInlined(jsName)
}
}
for (const cssName of cssAssets) {
if (
!inlinePattern.length ||
micromatch.isMatch(cssName, inlinePattern)
) {
const cssChunk = bundle[cssName]
bundlesToDelete.push(cssName)
replacedHtml = replaceCss(
replacedHtml,
cssChunk.fileName,
cssChunk.source
)
} else {
warnNotInlined(cssName)
}
}
htmlChunk.source = replacedHtml
}
if (deleteInlinedFiles) {
for (const name of bundlesToDelete) {
delete bundle[name]
}
}
for (const name of Object.keys(bundle).filter(
i =>
!jsExtensionTest.test(i) &&
!i.endsWith(".css") &&
!i.endsWith(".html")
)) {
warnNotInlined(name)
}
}
}
}
// Optionally remove the Vite module loader since it's no longer needed because this plugin has inlined all code.
// This assumes that the Module Loader is (1) the FIRST function declared in the module, (2) an IIFE, (4) is within
// a script with no unexpected attribute values, and (5) that the containing script is the first script tag that
// matches the above criteria. Changes to the SCRIPT tag especially could break this again in the future. It should
// work whether `minify` is enabled or not.
// Update example:
// https://github.com/richardtallent/vite-plugin-singlefile/issues/57#issuecomment-1263950209
const _removeViteModuleLoader = html =>
html.replace(
/(