diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index e75c439dd1bfa7..747c2df86e477c 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -289,7 +289,10 @@ async function handleMessage(payload: HotPayload) { } await activeHmrClient.notifyListeners('vite:beforeFullReload', payload) if (hasDocument) { - if (payload.path && payload.path.endsWith('.html')) { + if ( + payload.path && + (payload.path.endsWith('.html') || payload.path.endsWith('.htm')) + ) { // if html file is edited, only reload the page if the browser is // currently on that page. const pagePath = decodeURI(location.pathname) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index b61689a1cd05f6..ee76670f79b51e 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -624,7 +624,11 @@ export function resolveRolldownOptions( : options.rolldownOptions.input || (topLevelInput ?? resolve('index.html')) - if (ssr && typeof input === 'string' && input.endsWith('.html')) { + if ( + ssr && + typeof input === 'string' && + (input.endsWith('.html') || input.endsWith('.htm')) + ) { throw new Error( `rolldownOptions.input should not be an html file when building for SSR. ` + `Please specify a dedicated SSR entry.`, diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 812db849877819..8d42acf7de34be 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -457,7 +457,7 @@ function rolldownScanPlugin( let raw = await fsp.readFile(id, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') - const isHtml = id.endsWith('.html') + const isHtml = id.endsWith('.html') || id.endsWith('.htm') let js = '' let scriptId = 0 const matches = raw.matchAll(scriptRE) diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index b7a0a1b3251bc8..dcc514aa3b0530 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -557,7 +557,7 @@ function shouldInline( if (buildPluginContext.getModuleInfo(id)?.isEntry) return false } if (forceInline !== undefined) return forceInline - if (file.endsWith('.html')) return false + if (file.endsWith('.html') || file.endsWith('.htm')) return false // Don't inline SVG with fragments, as they are meant to be reused if (file.endsWith('.svg') && id.includes('#')) return false let limit: number diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index 36dfafd4fca36b..09ac6e597b2824 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -436,7 +436,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { }, transform: { - filter: { id: /\.html$/ }, + filter: { id: /\.(?:html|htm)$/ }, async handler(html, id) { id = normalizePath(id) const relativeUrlPath = normalizePath(path.relative(config.root, id)) diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index aa454d43de0d0f..801cf00795b6d3 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -427,7 +427,8 @@ function optimizerResolvePlugin( ...resolveOptions, scan: resolveOpts.scan ?? resolveOptions.scan, } - options.preferRelative ||= importer?.endsWith('.html') + options.preferRelative ||= + importer?.endsWith('.html') || importer?.endsWith('.htm') // resolve pre-bundled deps requests, these could be resolved by // tryFileResolve or /fs/ resolution but these files may not yet diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 84e9616b5d45f1..beb6d06c78418b 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -630,7 +630,10 @@ export async function handleHMRUpdate( } if (!options.modules.length) { // html file cannot be hot updated - if (file.endsWith('.html') && environment.name === 'client') { + if ( + (file.endsWith('.html') || file.endsWith('.htm')) && + environment.name === 'client' + ) { environment.logger.info( colors.green(`page reload `) + colors.dim(shortFile), { @@ -747,7 +750,7 @@ export function updateModules( // html file cannot be hot updated because it may be used as the template for a top-level request response. const isClientHtmlChange = - file.endsWith('.html') && + (file.endsWith('.html') || file.endsWith('.htm')) && environment.name === 'client' && // if the html file is imported as a module, we assume that this file is // not used as the template for top-level request response diff --git a/packages/vite/src/node/server/middlewares/htmlFallback.ts b/packages/vite/src/node/server/middlewares/htmlFallback.ts index 1ab4f5fc4c6a6f..22a42bb4ad45fe 100644 --- a/packages/vite/src/node/server/middlewares/htmlFallback.ts +++ b/packages/vite/src/node/server/middlewares/htmlFallback.ts @@ -49,25 +49,33 @@ export function htmlFallbackMiddleware( return next() } - // .html files are not handled by serveStaticMiddleware + // .html/.htm files are not handled by serveStaticMiddleware // so we need to check if the file exists - if (pathname.endsWith('.html')) { + if (pathname.endsWith('.html') || pathname.endsWith('.htm')) { if (checkFileExists(pathname)) { debug?.(`Rewriting ${req.method} ${req.url} to ${url}`) req.url = url return next() } } - // trailing slash should check for fallback index.html + // trailing slash should check for fallback index.html / index.htm else if (pathname.endsWith('/')) { - if (checkFileExists(joinUrlSegments(pathname, 'index.html'))) { + const indexHtml = joinUrlSegments(pathname, 'index.html') + const indexHtm = joinUrlSegments(pathname, 'index.htm') + if (checkFileExists(indexHtml)) { const newUrl = url + 'index.html' debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`) req.url = newUrl return next() } + if (checkFileExists(indexHtm)) { + const newUrl = url + 'index.htm' + debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`) + req.url = newUrl + return next() + } } - // non-trailing slash should check for fallback .html + // non-trailing slash should check for fallback .html / .htm else { if (checkFileExists(pathname + '.html')) { const newUrl = url + '.html' @@ -75,6 +83,12 @@ export function htmlFallbackMiddleware( req.url = newUrl return next() } + if (checkFileExists(pathname + '.htm')) { + const newUrl = url + '.htm' + debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`) + req.url = newUrl + return next() + } } if (spaFallback) { diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index ea734f41d0f03a..2c5acb98cd4532 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -463,7 +463,10 @@ export function indexHtmlMiddleware( const url = req.url && cleanUrl(req.url) // htmlFallbackMiddleware appends '.html' to URLs - if (url?.endsWith('.html') && req.headers['sec-fetch-dest'] !== 'script') { + if ( + (url?.endsWith('.html') || url?.endsWith('.htm')) && + req.headers['sec-fetch-dest'] !== 'script' + ) { if (fullBundle) { let pathname try { diff --git a/packages/vite/src/node/server/middlewares/memoryFiles.ts b/packages/vite/src/node/server/middlewares/memoryFiles.ts index 308d7b4a5d06ca..300386afc220ef 100644 --- a/packages/vite/src/node/server/middlewares/memoryFiles.ts +++ b/packages/vite/src/node/server/middlewares/memoryFiles.ts @@ -15,7 +15,7 @@ export function memoryFilesMiddleware( return function viteMemoryFilesMiddleware(req, res, next) { const cleanedUrl = cleanUrl(req.url!) - if (cleanedUrl.endsWith('.html')) { + if (cleanedUrl.endsWith('.html') || cleanedUrl.endsWith('.htm')) { return next() } diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index bb093f127f5d13..d901a9073a939a 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -143,6 +143,7 @@ export function serveStaticMiddleware( if ( cleanedUrl.endsWith('/') || path.extname(cleanedUrl) === '.html' || + path.extname(cleanedUrl) === '.htm' || isInternalRequest(req.url!) || // skip url starting with // as these will be interpreted as // scheme relative URLs by new URL() and will not be a valid file path diff --git a/packages/vite/src/node/server/warmup.ts b/packages/vite/src/node/server/warmup.ts index 7911e0b647df07..9e1cf3ce043bb6 100644 --- a/packages/vite/src/node/server/warmup.ts +++ b/packages/vite/src/node/server/warmup.ts @@ -27,7 +27,7 @@ async function warmupFile( // transform html with the `transformIndexHtml` hook as Vite internals would // pre-transform the imported JS modules linked. this may cause `transformIndexHtml` // plugins to be executed twice, but that's probably fine. - if (file.endsWith('.html')) { + if (file.endsWith('.html') || file.endsWith('.htm')) { const url = htmlFileToUrl(file, server.config.root) if (url) { try {