框架的環境 API
實驗性功能
環境 API 為實驗性功能。在 Vite 6 期間,我們將保持 API 的穩定性,以便生態系統能夠在其基礎上進行實驗和建構。我們計劃在 Vite 7 中穩定這些新的 API,並可能會有重大變更。
資源
請與我們分享您的意見回饋。
環境與框架
隱式的 ssr
環境和其他非客戶端環境在開發期間預設使用 RunnableDevEnvironment
。雖然這要求執行環境與 Vite 伺服器運行的執行環境相同,但其運作方式與 ssrLoadModule
類似,並允許框架遷移並為其 SSR 開發流程啟用 HMR。您可以使用 isRunnableDevEnvironment
函數來保護任何可運行的環境。
export class RunnableDevEnvironment extends DevEnvironment {
public readonly runner: ModuleRunner
}
class ModuleRunner {
/**
* URL to execute.
* Accepts file path, server path, or id relative to the root.
* Returns an instantiated module (same as in ssrLoadModule)
*/
public async import(url: string): Promise<Record<string, any>>
/**
* Other ModuleRunner methods...
*/
}
if (isRunnableDevEnvironment(server.environments.ssr)) {
await server.environments.ssr.runner.import('/entry-point.js')
}
警告
當首次存取 runner
時,它會被立即評估。請注意,當透過呼叫 process.setSourceMapsEnabled
或在不可用時覆寫 Error.prepareStackTrace
來建立 runner
時,Vite 會啟用原始碼對應支援。
預設的 RunnableDevEnvironment
假設有一個 Vite 伺服器以 SSR 設定指南所述的中介軟體模式設定,讓我們使用環境 API 實作 SSR 中介軟體。錯誤處理已省略。
import { createServer } from 'vite'
const server = await createServer({
server: { middlewareMode: true },
appType: 'custom',
environments: {
server: {
// by default, modules are run in the same process as the vite server
},
},
})
// You might need to cast this to RunnableDevEnvironment in TypeScript or
// use isRunnableDevEnvironment to guard the access to the runner
const environment = server.environments.node
app.use('*', async (req, res, next) => {
const url = req.originalUrl
// 1. Read index.html
const indexHtmlPath = path.resolve(__dirname, 'index.html')
let template = fs.readFileSync(indexHtmlPath, 'utf-8')
// 2. Apply Vite HTML transforms. This injects the Vite HMR client,
// and also applies HTML transforms from Vite plugins, e.g. global
// preambles from @vitejs/plugin-react
template = await server.transformIndexHtml(url, template)
// 3. Load the server entry. import(url) automatically transforms
// ESM source code to be usable in Node.js! There is no bundling
// required, and provides full HMR support.
const { render } = await environment.runner.import('/src/entry-server.js')
// 4. render the app HTML. This assumes entry-server.js's exported
// `render` function calls appropriate framework SSR APIs,
// e.g. ReactDOMServer.renderToString()
const appHtml = await render(url)
// 5. Inject the app-rendered HTML into the template.
const html = template.replace(`<!--ssr-outlet-->`, appHtml)
// 6. Send the rendered HTML back.
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})
與執行環境無關的 SSR
由於 RunnableDevEnvironment
只能用於在與 Vite 伺服器相同的執行環境中執行程式碼,因此需要一個可以運行 Vite 伺服器的執行環境(與 Node.js 相容的執行環境)。這表示您需要使用原始的 DevEnvironment
,使其與執行環境無關。
FetchableDevEnvironment
提案
最初的提案在 DevEnvironment
類別上具有 run
方法,該方法允許消費者使用 transport
選項在執行器端調用導入。在我們的測試中,我們發現 API 不夠通用,無法開始推薦它。目前,我們正在尋求關於 FetchableDevEnvironment
提案的意見回饋。
RunnableDevEnvironment
具有一個 runner.import
函數,該函數會回傳模組的值。但是,此函數在原始的 DevEnvironment
中不可用,並且要求使用 Vite API 的程式碼和使用者模組分離。
例如,以下範例使用來自使用 Vite API 的程式碼的使用者模組值
// code using the Vite's APIs
import { createServer } from 'vite'
const server = createServer()
const ssrEnvironment = server.environment.ssr
const input = {}
const { createHandler } = await ssrEnvironment.runner.import('./entry.js')
const handler = createHandler(input)
const response = handler(new Request('/'))
// -------------------------------------
// ./entrypoint.js
export function createHandler(input) {
return function handler(req) {
return new Response('hello')
}
}
如果您的程式碼可以在與使用者模組相同的執行環境中執行(即,它不依賴於 Node.js 特定的 API),則可以使用虛擬模組。這種方法消除了從使用 Vite API 的程式碼存取值的需要。
// code using the Vite's APIs
import { createServer } from 'vite'
const server = createServer({
plugins: [
// a plugin that handles `virtual:entrypoint`
{
name: 'virtual-module',
/* plugin implementation */
},
],
})
const ssrEnvironment = server.environment.ssr
const input = {}
// use exposed functions by each environment factories that runs the code
// check for each environment factories what they provide
if (ssrEnvironment instanceof RunnableDevEnvironment) {
ssrEnvironment.runner.import('virtual:entrypoint')
} else if (ssrEnvironment instanceof CustomDevEnvironment) {
ssrEnvironment.runEntrypoint('virtual:entrypoint')
} else {
throw new Error(`Unsupported runtime for ${ssrEnvironment.name}`)
}
// -------------------------------------
// virtual:entrypoint
const { createHandler } = await import('./entrypoint.js')
const handler = createHandler(input)
const response = handler(new Request('/'))
// -------------------------------------
// ./entrypoint.js
export function createHandler(input) {
return function handler(req) {
return new Response('hello')
}
}
例如,若要在使用者模組上呼叫 transformIndexHtml
,可以使用以下外掛
function vitePluginVirtualIndexHtml(): Plugin {
let server: ViteDevServer | undefined
return {
name: vitePluginVirtualIndexHtml.name,
configureServer(server_) {
server = server_
},
resolveId(source) {
return source === 'virtual:index-html' ? '\0' + source : undefined
},
async load(id) {
if (id === '\0' + 'virtual:index-html') {
let html: string
if (server) {
this.addWatchFile('index.html')
html = fs.readFileSync('index.html', 'utf-8')
html = await server.transformIndexHtml('/', html)
} else {
html = fs.readFileSync('dist/client/index.html', 'utf-8')
}
return `export default ${JSON.stringify(html)}`
}
return
},
}
}
如果您的程式碼需要 Node.js API,您可以使用 hot.send
從使用者模組與使用 Vite API 的程式碼進行通訊。但是,請注意,此方法在建構過程之後可能無法以相同方式運作。
// code using the Vite's APIs
import { createServer } from 'vite'
const server = createServer({
plugins: [
// a plugin that handles `virtual:entrypoint`
{
name: 'virtual-module',
/* plugin implementation */
},
],
})
const ssrEnvironment = server.environment.ssr
const input = {}
// use exposed functions by each environment factories that runs the code
// check for each environment factories what they provide
if (ssrEnvironment instanceof RunnableDevEnvironment) {
ssrEnvironment.runner.import('virtual:entrypoint')
} else if (ssrEnvironment instanceof CustomDevEnvironment) {
ssrEnvironment.runEntrypoint('virtual:entrypoint')
} else {
throw new Error(`Unsupported runtime for ${ssrEnvironment.name}`)
}
const req = new Request('/')
const uniqueId = 'a-unique-id'
ssrEnvironment.send('request', serialize({ req, uniqueId }))
const response = await new Promise((resolve) => {
ssrEnvironment.on('response', (data) => {
data = deserialize(data)
if (data.uniqueId === uniqueId) {
resolve(data.res)
}
})
})
// -------------------------------------
// virtual:entrypoint
const { createHandler } = await import('./entrypoint.js')
const handler = createHandler(input)
import.meta.hot.on('request', (data) => {
const { req, uniqueId } = deserialize(data)
const res = handler(req)
import.meta.hot.send('response', serialize({ res: res, uniqueId }))
})
const response = handler(new Request('/'))
// -------------------------------------
// ./entrypoint.js
export function createHandler(input) {
return function handler(req) {
return new Response('hello')
}
}
建構期間的環境
在 CLI 中,呼叫 vite build
和 vite build --ssr
仍將為了向後相容性而僅建構用戶端和僅 SSR 環境。
當 builder
不是 undefined
時(或在呼叫 vite build --app
時),vite build
將選擇建構整個應用程式。這稍後將在未來的重大版本中成為預設值。將建立一個 ViteBuilder
實例(相當於建構時的 ViteDevServer
),以建構所有為生產環境設定的環境。預設情況下,環境的建構會按照 environments
記錄的順序依序執行。框架或使用者可以使用以下方式進一步設定如何建構環境
export default {
builder: {
buildApp: async (builder) => {
const environments = Object.values(builder.environments)
return Promise.all(
environments.map((environment) => builder.build(environment)),
)
},
},
}
與環境無關的程式碼
大多數情況下,目前的 environment
實例將會作為正在執行的程式碼內容的一部分提供,因此很少需要透過 server.environments
來存取它們。例如,在外掛鉤子內部,環境會作為 PluginContext
的一部分公開,因此可以使用 this.environment
來存取它。請參閱 外掛的環境 API,以瞭解如何建構具有環境感知能力的外掛。