Skip to content

Commit

Permalink
feat: implement fetchable runner
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Oct 11, 2024
1 parent 4c5cf91 commit 6674ade
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 0 deletions.
59 changes: 59 additions & 0 deletions packages/vite/src/module-runner/fetchableRunner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ENVIRONMENT_URL_PUBLIC_PATH } from '../shared/constants'
import { ESModulesEvaluator } from './esmEvaluator'
import { ModuleRunner } from './runner'
import type { ModuleRunnerOptions } from './types'

export interface FetchableModuleRunnerOptions
extends Pick<
ModuleRunnerOptions,
'sourcemapInterceptor' | 'evaluatedModules' | 'hmr'
> {
root: string
serverURL: string
environmentName: string
}

export function createFetchableModuleRunner(
options: FetchableModuleRunnerOptions,
): ModuleRunner {
const { serverURL, environmentName } = options
// eslint-disable-next-line n/no-unsupported-features/node-builtins
const fetch = globalThis.fetch
if (!fetch) {
throw new TypeError('fetch is not available in this environment')
}
return new ModuleRunner(
{
root: options.root,
transport: {
async fetchModule(moduleUrl, importer, { cached, startOffset } = {}) {
const serverUrl = new URL(
`${ENVIRONMENT_URL_PUBLIC_PATH}/${environmentName}`,
serverURL,
)
serverUrl.searchParams.set('moduleUrl', encodeURIComponent(moduleUrl))
if (importer) {
serverUrl.searchParams.set('importer', encodeURIComponent(importer))
}
// eslint-disable-next-line n/no-unsupported-features/node-builtins
const request = new Request(serverUrl, {
headers: {
'x-vite-cache': String(cached ?? false),
'x-vite-start-offset': String(startOffset ?? ''),
},
})
const response = await fetch(request)
if (response.status !== 200) {
// TODO: better error?
throw new Error(
`Failed to fetch module ${moduleUrl}, responded with ${response.status} (${response.statusText})`,
)
}
return await response.json()
},
},
hmr: options.hmr,
},
new ESModulesEvaluator(),
)
}
2 changes: 2 additions & 0 deletions packages/vite/src/module-runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export { ModuleRunner } from './runner'
export { ESModulesEvaluator } from './esmEvaluator'
export { RemoteRunnerTransport } from './runnerTransport'

export { createFetchableModuleRunner } from './fetchableRunner'

export type { RunnerTransport } from './runnerTransport'
export type { HMRLogger, HMRConnection } from '../shared/hmr'
export type {
Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import { transformRequest } from './transformRequest'
import { searchForPackageRoot, searchForWorkspaceRoot } from './searchRoot'
import { warmupFiles } from './warmup'
import type { DevEnvironment } from './environment'
import { environmentTransformMiddleware } from './middlewares/environmentTransform'

export interface ServerOptions extends CommonServerOptions {
/**
Expand Down Expand Up @@ -829,6 +830,7 @@ export async function _createServer(
}

middlewares.use(cachedTransformMiddleware(server))
middlewares.use(environmentTransformMiddleware(server))

// proxy
const { proxy } = serverConfig
Expand Down
77 changes: 77 additions & 0 deletions packages/vite/src/node/server/middlewares/environmentTransform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { Connect } from 'dep-types/connect'
import type { ViteDevServer } from '..'
import {
ENVIRONMENT_URL_PUBLIC_PATH,
NULL_BYTE_PLACEHOLDER,
} from '../../../shared/constants'

export function environmentTransformMiddleware(
server: ViteDevServer,
): Connect.NextHandleFunction {
return async function viteEnvironmentTransformMiddleware(req, res, next) {
if (req.method !== 'GET') {
return next()
}

let url: string
try {
url = decodeURI(req.url!).replace(NULL_BYTE_PLACEHOLDER, '\0')
} catch (e) {
return next(e)
}

if (!url.startsWith(ENVIRONMENT_URL_PUBLIC_PATH)) {
return next()
}

const { pathname, searchParams } = new URL(url, 'http://localhost')
const environmentName = pathname.slice(
ENVIRONMENT_URL_PUBLIC_PATH.length + 1,
)
const environment = server.environments[environmentName]

if (!environmentName || !environment) {
res.statusCode = 404
res.end()
return
}

const moduleUrl = searchParams.get('moduleUrl')

if (!moduleUrl) {
res.statusCode = 404
res.end()
return
}

// TODO: how to check consistently for all environments(?)
// currently ignores if the consumer is a `server`
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/transformRequest.ts:271
// if (!ensureServingAccess(moduleUrl, server, res, next)) {
// return
// }

try {
const importer = searchParams.get('importer') || ''
const cached = req.headers['x-vite-cache'] === 'true'
const startOffset = req.headers['x-vite-start-offset']
? Number(req.headers['x-vite-start-offset'])
: undefined
const moduleResult = await environment.fetchModule(moduleUrl, importer, {
cached,
startOffset,
})

if (res.writableEnded) {
return
}

res.setHeader('Content-Type', 'application/json')
res.statusCode = 200
res.end(JSON.stringify(moduleResult))
return
} catch (e) {
return next(e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createRunnableDevEnvironment, createServer } from 'vite'
import { createFetchableModuleRunner } from 'vite/module-runner'
import { expect, test } from 'vitest'

test('fetchable module runner works correctly', async () => {
const server = await createServer({
root: import.meta.dirname,
server: {
port: 5010,
watch: null,
hmr: false,
},
environments: {
custom: {
dev: {
createEnvironment(name, config) {
return createRunnableDevEnvironment(name, config, {
hot: false,
})
},
},
},
},
})
await server.listen()

const runner = createFetchableModuleRunner({
root: server.config.root,
serverURL: 'http://localhost:5010',
environmentName: 'custom',
sourcemapInterceptor: false,
})

const mod = await runner.import('/fixtures/basic.js')
expect(mod.name).toBe('basic')
})
2 changes: 2 additions & 0 deletions packages/vite/src/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const VALID_ID_PREFIX = `/@id/`
*/
export const NULL_BYTE_PLACEHOLDER = `__x00__`

export const ENVIRONMENT_URL_PUBLIC_PATH = '/@vite/import'

export let SOURCEMAPPING_URL = 'sourceMa'
SOURCEMAPPING_URL += 'ppingURL'

Expand Down
1 change: 1 addition & 0 deletions playground/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const ports = {
'css/dynamic-import': 5007,
'css/lightningcss-proxy': 5008,
'backend-integration': 5009,
'runner-fetchable': 5010, // not imported but used in `server-fetchable-runner.spec.ts`
}
export const hmrPorts = {
'optimize-missing-deps': 24680,
Expand Down

0 comments on commit 6674ade

Please sign in to comment.