diff --git a/packages/playground/ssr/__tests__/ssr.spec.ts b/packages/playground/ssr/__tests__/ssr.spec.ts
index 7c3d263ed37d03..027fef090b3395 100644
--- a/packages/playground/ssr/__tests__/ssr.spec.ts
+++ b/packages/playground/ssr/__tests__/ssr.spec.ts
@@ -1,4 +1,4 @@
-import { getColor, isBuild, untilUpdated } from '../../testUtils'
+import { editFile, getColor, isBuild, untilUpdated } from '../../testUtils'
import { port } from './serve'
import fetch from 'node-fetch'
@@ -62,6 +62,13 @@ describe('Vue', () => {
await page.click('button')
expect(await page.textContent('button')).toBe('1')
})
+
+ test('hmr', async () => {
+ editFile('src/vue/Async.vue', (code) =>
+ code.replace('Hello from Vue', 'changed')
+ )
+ await untilUpdated(() => page.textContent('h1'), 'changed')
+ })
})
describe('React', () => {
@@ -82,4 +89,11 @@ describe('React', () => {
)
).toBe(true)
})
+
+ test('hmr', async () => {
+ editFile('src/react/Child.jsx', (code) =>
+ code.replace('Hello from React', 'changed')
+ )
+ await untilUpdated(() => page.textContent('h1'), 'changed')
+ })
})
diff --git a/packages/playground/ssr/server.js b/packages/playground/ssr/server.js
index 0d7a61ac779f13..68f70cb943ca50 100644
--- a/packages/playground/ssr/server.js
+++ b/packages/playground/ssr/server.js
@@ -20,24 +20,12 @@ async function createServer(
require('./dist/client/ssr-manifest.json')
: {}
- function getIndexTemplate(url) {
+ function getIndexTemplate() {
if (isProd) {
return indexProd
}
-
- // TODO handle plugin indexHtmlTransforms?
- const reactPreamble = url.startsWith('/react')
- ? ``
- : ''
-
// during dev, inject vite client + always read fresh index.html
- return (
- `` +
- reactPreamble +
- fs.readFileSync(toAbsolute('index.html'), 'utf-8')
- )
+ return fs.readFileSync(toAbsolute('index.html'), 'utf-8')
}
const app = express()
@@ -67,16 +55,22 @@ async function createServer(
app.use('*', async (req, res, next) => {
try {
+ const url = req.originalUrl
+ let template = getIndexTemplate()
+ if (!isProd) {
+ template = await vite.transformIndexHtml(url, template)
+ }
+
const { render } = isProd
? // @ts-ignore
require('./dist/server/entry-server.js')
: await vite.ssrLoadModule('/src/entry-server.ts')
- const [appHtml, preloadLinks] = await render(req.originalUrl, manifest)
+ const [appHtml, preloadLinks] = await render(url, manifest)
const html = `
${preloadLinks}
- ${getIndexTemplate(req.originalUrl).replace(``, appHtml)}
+ ${template.replace(``, appHtml)}
`
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts
index 86826f40a680f3..47dd9dc03a9922 100644
--- a/packages/vite/src/node/server/index.ts
+++ b/packages/vite/src/node/server/index.ts
@@ -20,7 +20,10 @@ import { createWebSocketServer, WebSocketServer } from '../server/ws'
import { baseMiddleware } from './middlewares/base'
import { proxyMiddleware, ProxyOptions } from './middlewares/proxy'
import { transformMiddleware } from './middlewares/transform'
-import { indexHtmlMiddleware } from './middlewares/indexHtml'
+import {
+ createDevHtmlTransformFn,
+ indexHtmlMiddleware
+} from './middlewares/indexHtml'
import history from 'connect-history-api-fallback'
import {
serveRawFsMiddleware,
@@ -189,6 +192,10 @@ export interface ViteDevServer {
url: string,
options?: TransformOptions
): Promise
+ /**
+ * Apply vite built-in HTML transforms and any plugin HTML transforms.
+ */
+ transformIndexHtml(url: string, html: string): Promise
/**
* Util for transforming a file with esbuild.
* Can be useful for certain plugins.
@@ -299,6 +306,7 @@ export async function createServer(
transformRequest(url, options) {
return transformRequest(url, server, options)
},
+ transformIndexHtml: null as any,
ssrLoadModule(url, options) {
if (!server._ssrExternals) {
server._ssrExternals = resolveSSRExternal(config)
@@ -329,6 +337,8 @@ export async function createServer(
_pendingReload: null
}
+ server.transformIndexHtml = createDevHtmlTransformFn(server)
+
process.once('SIGTERM', async () => {
try {
await server.close()
@@ -438,7 +448,7 @@ export async function createServer(
if (!middlewareMode) {
// transform index.html
- middlewares.use(indexHtmlMiddleware(server, plugins))
+ middlewares.use(indexHtmlMiddleware(server))
// handle 404s
middlewares.use((_, res) => {
res.statusCode = 404
diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts
index d28d90cb184bb1..15d25a9fdf0a0c 100644
--- a/packages/vite/src/node/server/middlewares/indexHtml.ts
+++ b/packages/vite/src/node/server/middlewares/indexHtml.ts
@@ -3,7 +3,6 @@ import path from 'path'
import MagicString from 'magic-string'
import { NodeTypes } from '@vue/compiler-dom'
import { Connect } from 'types/connect'
-import { Plugin } from '../../plugin'
import {
applyHtmlTransforms,
getScriptInfo,
@@ -14,9 +13,31 @@ import {
import { ViteDevServer } from '../..'
import { send } from '../send'
import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants'
-import { cleanUrl } from '../../utils'
+import { cleanUrl, fsPathFromId } from '../../utils'
import { assetAttrsConfig } from '../../plugins/html'
+export function createDevHtmlTransformFn(server: ViteDevServer) {
+ const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins)
+
+ return (url: string, html: string): Promise => {
+ return applyHtmlTransforms(
+ html,
+ url,
+ getHtmlFilename(url, server),
+ [...preHooks, devHtmlHook, ...postHooks],
+ server
+ )
+ }
+}
+
+function getHtmlFilename(url: string, server: ViteDevServer) {
+ if (url.startsWith(FS_PREFIX)) {
+ return fsPathFromId(url)
+ } else {
+ return path.join(server.config.root, url.slice(1))
+ }
+}
+
const devHtmlHook: IndexHtmlTransformHook = async (
html,
{ path: htmlPath, server }
@@ -101,32 +122,17 @@ const devHtmlHook: IndexHtmlTransformHook = async (
}
export function indexHtmlMiddleware(
- server: ViteDevServer,
- plugins: readonly Plugin[]
+ server: ViteDevServer
): Connect.NextHandleFunction {
- const [preHooks, postHooks] = resolveHtmlTransforms(plugins)
-
return async (req, res, next) => {
const url = req.url && cleanUrl(req.url)
// spa-fallback always redirects to /index.html
if (url?.endsWith('.html') && req.headers['sec-fetch-dest'] !== 'script') {
- let filename
- if (url.startsWith(FS_PREFIX)) {
- filename = url.slice(FS_PREFIX.length)
- } else {
- filename = path.join(server.config.root, url.slice(1))
- }
+ const filename = getHtmlFilename(url, server)
if (fs.existsSync(filename)) {
try {
let html = fs.readFileSync(filename, 'utf-8')
- // apply transforms
- html = await applyHtmlTransforms(
- html,
- url,
- filename,
- [...preHooks, devHtmlHook, ...postHooks],
- server
- )
+ html = await server.transformIndexHtml(url, html)
return send(req, res, html, 'html')
} catch (e) {
return next(e)