Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ssr): avoid transforming json file in ssrTransform #6597

Merged
merged 15 commits into from May 10, 2022
22 changes: 22 additions & 0 deletions packages/playground/json/index.ssr.html
@@ -0,0 +1,22 @@
<div class="fetch-json-module">
json-module:
<pre></pre>
<code></code>
</div>
<div class="fetch-json-fs">
json-fs:
<pre></pre>
<code></code>
</div>

<script type="module">
const start = Date.now()
text('.fetch-json-module pre', await (await fetch('/json-module')).text())
text('.fetch-json-module code', Date.now() - start)

text('.fetch-json-fs pre', await (await fetch('/json-fs')).text())
text('.fetch-json-fs code', Date.now() - start)
bluwy marked this conversation as resolved.
Show resolved Hide resolved
function text(sel, text) {
document.querySelector(sel).textContent = text
}
</script>
9 changes: 7 additions & 2 deletions packages/playground/json/package.json
Expand Up @@ -6,9 +6,14 @@
"dev": "vite",
"build": "vite build",
"debug": "node --inspect-brk ../../vite/bin/vite",
"preview": "vite preview"
"preview": "vite preview",
"dev:ssr": "node server",
"serve:ssr": "cross-env NODE_ENV=production node server",
"debug:ssr": "node --inspect-brk server"
},
"devDependencies": {
"vue": "^3.2.25"
"vue": "^3.2.25",
"cross-env": "^7.0.3",
"express": "^4.17.1"
}
}
89 changes: 89 additions & 0 deletions packages/playground/json/server.js
@@ -0,0 +1,89 @@
// @ts-check
const fs = require('fs')
const path = require('path')
const express = require('express')

const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD

async function createServer(
root = process.cwd(),
isProd = process.env.NODE_ENV === 'production'
) {
const resolve = (p) => path.resolve(__dirname, p)
const app = express()

/**
* @type {import('vite').ViteDevServer}
*/
let vite
vite = await require('vite').createServer({
root,
logLevel: isTest ? 'error' : 'info',
server: {
middlewareMode: 'ssr',
watch: {
// During tests we edit the files too fast and sometimes chokidar
// misses change events, so enforce polling for consistency
usePolling: true,
interval: 100
}
},
json: {
stringify: true
}
})
// use vite's connect instance as middleware
app.use(vite.middlewares)

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

if (url === '/json-module') {
console.time('load module')
const json = JSON.stringify(await vite.ssrLoadModule('/test.json'))
console.timeEnd('load module')
res.status(200).end('' + json.length)
return
}

if (url === '/json-fs') {
console.time('transform module')
const source = fs.readFileSync('./test.json', { encoding: 'utf-8' })
const json = await vite.ssrTransform(
`export default ${source}`,
null,
'./output.json',
{ json: { stringify: true } }
)
console.timeEnd('transform module')
res.status(200).end(String(json.code.length))
return
}

const htmlLoc = resolve(`.${url}`)
let html = fs.readFileSync(htmlLoc, 'utf8')
html = await vite.transformIndexHtml(url, html)

res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
vite && vite.ssrFixStacktrace(e)
console.log(e.stack)
res.status(500).end(e.stack)
}
})

return { app, vite }
}

if (!isTest) {
createServer().then(({ app }) =>
app.listen(3000, () => {
console.log('http://localhost:3000')
})
)
}

// for test use
exports.createServer = createServer
7 changes: 6 additions & 1 deletion packages/vite/src/node/plugins/json.ts
Expand Up @@ -7,8 +7,8 @@
*/

import { dataToEsm } from '@rollup/pluginutils'
import type { Plugin } from 'rollup'
import { SPECIAL_QUERY_RE } from '../constants'
import type { Plugin } from '../plugin'

export interface JsonOptions {
/**
Expand All @@ -27,6 +27,11 @@ export interface JsonOptions {
// Custom json filter for vite
const jsonExtRE = /\.json($|\?)(?!commonjs-(proxy|external))/

const jsonLangs = `\\.(json|json5)($|\\?)`
const jsonLangRE = new RegExp(jsonLangs)
export const isJSONRequest = (request: string): boolean =>
jsonLangRE.test(request)

export function jsonPlugin(
options: JsonOptions = {},
isBuild: boolean
Expand Down
9 changes: 7 additions & 2 deletions packages/vite/src/node/server/index.ts
Expand Up @@ -219,7 +219,12 @@ export interface ViteDevServer {
ssrTransform(
code: string,
inMap: SourceMap | null,
url: string
url: string,
config?: {
json?: {
stringify?: boolean
}
}
): Promise<TransformResult | null>
/**
* Load a given URL as an instantiated module for SSR.
Expand Down Expand Up @@ -368,7 +373,7 @@ export async function createServer(
? Object.keys(server._optimizeDepsMetadata.optimized)
: []
)
return ssrLoadModule(url, server)
return ssrLoadModule(url, server, undefined, undefined, config)
},
ssrFixStacktrace(e) {
if (e.stack) {
Expand Down
6 changes: 5 additions & 1 deletion packages/vite/src/node/server/transformRequest.ts
Expand Up @@ -36,6 +36,9 @@ export interface TransformResult {
export interface TransformOptions {
ssr?: boolean
html?: boolean
json?: {
stringify?: boolean
}
}

export function transformRequest(
Expand Down Expand Up @@ -183,7 +186,8 @@ async function doTransform(
return (mod.ssrTransformResult = await ssrTransform(
code,
map as SourceMap,
url
url,
{ json: { stringify: !!options?.json?.stringify } }
))
} else {
return (mod.transformResult = {
Expand Down
20 changes: 16 additions & 4 deletions packages/vite/src/node/ssr/ssrModuleLoader.ts
Expand Up @@ -21,6 +21,7 @@ import type { InternalResolveOptions } from '../plugins/resolve'
import { tryNodeResolve } from '../plugins/resolve'
import { hookNodeResolve } from '../plugins/ssrRequireHook'
import { NULL_BYTE_PLACEHOLDER } from '../constants'
import type { ResolvedConfig } from '..'

interface SSRContext {
global: typeof globalThis
Expand All @@ -35,7 +36,8 @@ export async function ssrLoadModule(
url: string,
server: ViteDevServer,
context: SSRContext = { global },
urlStack: string[] = []
urlStack: string[] = [],
config?: ResolvedConfig
bluwy marked this conversation as resolved.
Show resolved Hide resolved
): Promise<SSRModule> {
url = unwrapId(url).replace(NULL_BYTE_PLACEHOLDER, '\0')

Expand All @@ -48,7 +50,13 @@ export async function ssrLoadModule(
return pending
}

const modulePromise = instantiateModule(url, server, context, urlStack)
const modulePromise = instantiateModule(
url,
server,
context,
urlStack,
config
)
pendingModules.set(url, modulePromise)
modulePromise
.catch(() => {
Expand All @@ -64,7 +72,8 @@ async function instantiateModule(
url: string,
server: ViteDevServer,
context: SSRContext = { global },
urlStack: string[] = []
urlStack: string[] = [],
config?: ResolvedConfig
): Promise<SSRModule> {
const { moduleGraph } = server
const mod = await moduleGraph.ensureEntryFromUrl(url, true)
Expand All @@ -75,7 +84,10 @@ async function instantiateModule(

const result =
mod.ssrTransformResult ||
(await transformRequest(url, server, { ssr: true }))
(await transformRequest(url, server, {
ssr: true,
json: { stringify: !!config?.json?.stringify }
}))
if (!result) {
// TODO more info? is this even necessary?
throw new Error(`failed to load module for ssr: ${url}`)
Expand Down
31 changes: 31 additions & 0 deletions packages/vite/src/node/ssr/ssrTransform.ts
Expand Up @@ -13,19 +13,50 @@ import { extract_names as extractNames } from 'periscopic'
import { walk as eswalk } from 'estree-walker'
import { combineSourcemaps } from '../utils'
import type { RawSourceMap } from '@ampproject/remapping/dist/types/types'
import { isJSONRequest } from '../plugins/json'

type Node = _Node & {
start: number
end: number
}

interface TransformOptions {
json?: {
stringify?: boolean
}
}

export const ssrModuleExportsKey = `__vite_ssr_exports__`
export const ssrImportKey = `__vite_ssr_import__`
export const ssrDynamicImportKey = `__vite_ssr_dynamic_import__`
export const ssrExportAllKey = `__vite_ssr_exportAll__`
export const ssrImportMetaKey = `__vite_ssr_import_meta__`

export async function ssrTransform(
code: string,
inMap: SourceMap | null,
url: string,
options?: TransformOptions
): Promise<TransformResult | null> {
if (options?.json?.stringify && url && isJSONRequest(url)) {
return ssrTransformJSON(code, inMap)
}
return ssrTransformScript(code, inMap, url!)
bluwy marked this conversation as resolved.
Show resolved Hide resolved
}

async function ssrTransformJSON(
code: string,
inMap: SourceMap | null
): Promise<TransformResult> {
return {
code: code.replace('export default', `${ssrModuleExportsKey}.default =`),
map: inMap,
deps: [],
dynamicDeps: []
}
}

async function ssrTransformScript(
code: string,
inMap: SourceMap | null,
url: string
Expand Down
25 changes: 5 additions & 20 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.