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)