Skip to content

Commit

Permalink
fix: inline js and css paths for virtual html (#7993)
Browse files Browse the repository at this point in the history
Co-authored-by: yoho <panyoho@gmail.com>
Co-authored-by: patak-dev <matias.capeletto@gmail.com>
  • Loading branch information
3 people committed May 4, 2022
1 parent f3d15f1 commit d49e3fb
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 19 deletions.
31 changes: 27 additions & 4 deletions packages/playground/ssr-html/server.js
Expand Up @@ -14,6 +14,14 @@ const DYNAMIC_SCRIPTS = `
<script type="module" src="/src/app.js"></script>
`

const DYNAMIC_STYLES = `
<style>
h1 {
background-color: blue;
}
</style>
`

async function createServer(
root = process.cwd(),
isProd = process.env.NODE_ENV === 'production'
Expand Down Expand Up @@ -42,15 +50,30 @@ async function createServer(
// use vite's connect instance as middleware
app.use(vite.middlewares)

app.use('*', async (req, res) => {
app.use('*', async (req, res, next) => {
try {
let [url] = req.originalUrl.split('?')
if (url.endsWith('/')) url += 'index.html'

if (url.startsWith('/favicon.ico')) {
return res.status(404).end('404')
}
if (url.startsWith('/@id/__x00__')) {
return next()
}

const htmlLoc = resolve(`.${url}`)
let html = fs.readFileSync(htmlLoc, 'utf8')
html = html.replace('</body>', `${DYNAMIC_SCRIPTS}</body>`)
html = await vite.transformIndexHtml(url, html)
let template = fs.readFileSync(htmlLoc, 'utf-8')

template = template.replace(
'</body>',
`${DYNAMIC_SCRIPTS}${DYNAMIC_STYLES}</body>`
)

// Force calling transformIndexHtml with url === '/', to simulate
// usage by ecosystem that was recommended in the SSR documentation
// as `const url = req.originalUrl`
const html = await vite.transformIndexHtml('/', template)

res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
Expand Down
57 changes: 42 additions & 15 deletions packages/vite/src/node/server/middlewares/indexHtml.ts
@@ -1,6 +1,7 @@
import fs from 'fs'
import path from 'path'
import MagicString from 'magic-string'
import type { SourceMapInput } from 'rollup'
import type { AttributeNode, ElementNode, TextNode } from '@vue/compiler-dom'
import { NodeTypes } from '@vue/compiler-dom'
import type { Connect } from 'types/connect'
Expand All @@ -15,7 +16,12 @@ import {
} from '../../plugins/html'
import type { ResolvedConfig, ViteDevServer } from '../..'
import { send } from '../send'
import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants'
import {
CLIENT_PUBLIC_PATH,
FS_PREFIX,
VALID_ID_PREFIX,
NULL_BYTE_PLACEHOLDER
} from '../../constants'
import {
cleanUrl,
fsPathFromId,
Expand Down Expand Up @@ -108,32 +114,53 @@ const devHtmlHook: IndexHtmlTransformHook = async (
const { config, moduleGraph, watcher } = server!
const base = config.base || '/'

let proxyModulePath: string
let proxyModuleUrl: string

const trailingSlash = htmlPath.endsWith('/')
if (!trailingSlash && fs.existsSync(filename)) {
proxyModulePath = htmlPath
proxyModuleUrl = base + htmlPath.slice(1)
} else {
// There are users of vite.transformIndexHtml calling it with url '/'
// for SSR integrations #7993, filename is root for this case
// A user may also use a valid name for a virtual html file
// Mark the path as virtual in both cases so sourcemaps aren't processed
// and ids are properly handled
const validPath = `${htmlPath}${trailingSlash ? 'index.html' : ''}`
proxyModulePath = `\0${validPath}`
proxyModuleUrl = `${VALID_ID_PREFIX}${NULL_BYTE_PLACEHOLDER}${validPath}`
}

const s = new MagicString(html)
let inlineModuleIndex = -1
const filePath = cleanUrl(htmlPath)
const proxyCacheUrl = cleanUrl(proxyModulePath).replace(
normalizePath(config.root),
''
)
const styleUrl: AssetNode[] = []

const addInlineModule = (node: ElementNode, ext: 'js') => {
inlineModuleIndex++

const url = filePath.replace(normalizePath(config.root), '')

const contentNode = node.children[0] as TextNode

const code = contentNode.content
const map = new MagicString(html)
.snip(contentNode.loc.start.offset, contentNode.loc.end.offset)
.generateMap({ hires: true })
map.sources = [filename]
map.file = filename

let map: SourceMapInput | undefined
if (!proxyModulePath.startsWith('\0')) {
map = new MagicString(html)
.snip(contentNode.loc.start.offset, contentNode.loc.end.offset)
.generateMap({ hires: true })
map.sources = [filename]
map.file = filename
}

// add HTML Proxy to Map
addToHTMLProxyCache(config, url, inlineModuleIndex, { code, map })
addToHTMLProxyCache(config, proxyCacheUrl, inlineModuleIndex, { code, map })

// inline js module. convert to src="proxy"
const modulePath = `${
config.base + htmlPath.slice(1)
}?html-proxy&index=${inlineModuleIndex}.${ext}`
const modulePath = `${proxyModuleUrl}?html-proxy&index=${inlineModuleIndex}.${ext}`

// invalidate the module so the newly cached contents will be served
const module = server?.moduleGraph.getModuleById(modulePath)
Expand Down Expand Up @@ -190,13 +217,13 @@ const devHtmlHook: IndexHtmlTransformHook = async (

await Promise.all(
styleUrl.map(async ({ start, end, code }, index) => {
const url = filename + `?html-proxy&${index}.css`
const url = `${proxyModulePath}?html-proxy&index=${index}.css`

// ensure module in graph after successful load
const mod = await moduleGraph.ensureEntryFromUrl(url, false)
ensureWatchedFile(watcher, mod.file, config.root)

const result = await server!.pluginContainer.transform(code, url)
const result = await server!.pluginContainer.transform(code, mod.id!)
s.overwrite(start, end, result?.code || '')
})
)
Expand Down

0 comments on commit d49e3fb

Please sign in to comment.