FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat(rsc): initial bundledDev support by nihgwu · Pull Request #1297 · vitejs/vite-plugin-react · GitHub

Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .md  (1) .ts  (5) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
7 changes: 7 additions & 0 deletions packages/plugin-rsc/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ You can create a starter project by:
npm create vite@latest -- --template rsc
```

### Bundled development

Vite's experimental `bundledDev` mode supports initial RSC rendering and client
HMR. Changes to the server graph, including client-boundary changes, require a
dev-server restart. Vite does not yet expose an atomic bundledDev
rebuild-and-reload operation that plugins can safely request from `watchChange`.

## Examples

**Start here:** [`./examples/starter`](./examples/starter) - Recommended for understanding the package
Expand Down
72 changes: 72 additions & 0 deletions packages/plugin-rsc/e2e/bundled-dev.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { expect, test } from '@playwright/test'
import { setupInlineFixture, useFixture } from './fixture'
import { expectNoPageError, expectNoReload, waitForHydration } from './helper'

test.describe('bundled dev', () => {
const root = 'examples/e2e/temp/bundled-dev'

test.beforeAll(async () => {
await setupInlineFixture({
src: 'examples/starter',
dest: root,
files: {
'vite.config.ts': {
edit: (source) =>
source.replace(
'export default defineConfig({',
'export default defineConfig({\n experimental: { bundledDev: true },',
),
},
},
})
})

const fixture = useFixture({ root, mode: 'dev' })

test('serves the initial bundled RSC app', async ({ page }) => {
using _ = expectNoPageError(page)
const requests: string[] = []
page.on('request', (request) => requests.push(request.url()))

await page.goto(fixture.url())
await waitForHydration(page)
await page.getByRole('button', { name: 'Client Counter: 0' }).click()

await expect(
page.getByRole('button', { name: 'Client Counter: 1' }),
).toBeVisible()
await expect(page.locator('.card').first()).toHaveCSS(
'padding-left',
'16px',
)
await expect(page.getByAltText('React logo')).not.toHaveJSProperty(
'naturalWidth',
0,
)
expect(requests).toContain(fixture.url('assets/index.js'))
expect(requests).not.toContain(
fixture.url('src/framework/entry.browser.tsx'),
)
})

test('keeps client HMR', async ({ page }) => {
using _errors = expectNoPageError(page)
await page.goto(fixture.url())
await waitForHydration(page)
await page.getByRole('button', { name: 'Client Counter: 0' }).click()
await using _ = await expectNoReload(page)

const editor = fixture.createEditor('src/client.tsx')
editor.edit((source) =>
source.replace('Client Counter', 'Client [edit] Counter'),
)
await expect(
page.getByRole('button', { name: 'Client [edit] Counter: 1' }),
).toBeVisible()

editor.reset()
await expect(
page.getByRole('button', { name: 'Client Counter: 1' }),
).toBeVisible()
})
})
5 changes: 4 additions & 1 deletion packages/plugin-rsc/src/browser.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ initialize()
function initialize(): void {
setRequireModule({
load: async (id) => {
if (!import.meta.env.__vite_rsc_build__) {
if (
!import.meta.env.__vite_rsc_build__ &&
!import.meta.env.__vite_rsc_bundled_dev__
) {
// @ts-ignore
return __vite_rsc_raw_import__(
withTrailingSlash(import.meta.env.BASE_URL) + id.slice(1),
Expand Down
128 changes: 128 additions & 0 deletions packages/plugin-rsc/src/bundled-dev.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import fs from 'node:fs'
import path from 'node:path'
import { type DevEnvironment, isCSSRequest, normalizePath } from 'vite'
import { withResolvedIdProxy } from './plugins/resolved-id-proxy'
import { parseIdQuery } from './plugins/shared'
import { cleanUrl } from './plugins/vite-utils'

export type BundledDevServerGraph = {
moduleIds: Set<string>
cssImports: Set<string>
assetImports: Set<string>
}

export async function crawlBundledDevServerGraph(
environment: DevEnvironment,
sources: string[],
): Promise<BundledDevServerGraph> {
const moduleIds = new Set<string>()
const cssImports = new Set<string>()
const assetImports = new Set<string>()
const config = environment.getTopLevelConfig()

async function crawl(source: string): Promise<void> {
const resolved = await environment.pluginContainer.resolveId(source)
if (!resolved || resolved.external || moduleIds.has(resolved.id)) return
moduleIds.add(resolved.id)
if (resolved.id.includes('virtual:vite-rsc/assets-manifest')) return

const { filename, query } = parseIdQuery(resolved.id)
let file = fs.existsSync(filename) ? filename : undefined
if (!file && filename.startsWith('/') && config.publicDir) {
const publicFile = path.join(config.publicDir, filename.slice(1))
if (fs.existsSync(publicFile)) file = publicFile
}
file = file && normalizePath(file)
const hasRawQuery = 'raw' in query
const hasInlineQuery = 'inline' in query
const hasUrlQuery = 'url' in query
const isCss = isCSSRequest(resolved.id)
const isAsset = config.assetsInclude(filename) || hasUrlQuery
const isResource =
isCss || isAsset || hasRawQuery || hasInlineQuery || hasUrlQuery
if (file && isResource) {
if (isCss && !hasRawQuery && !hasInlineQuery && !hasUrlQuery) {
cssImports.add(resolved.id)
} else if (isAsset && !hasRawQuery && !hasInlineQuery) {
assetImports.add(resolved.id)
}
return
}

const requestId =
environment.moduleGraph.getModuleById(resolved.id)?.url ?? resolved.id
const result = await environment.transformRequest(requestId)
await environment.waitForRequestsIdle()
const module = environment.moduleGraph.getModuleById(resolved.id)
if (!module) return

for (const imported of module.importedModules) {
await crawl(imported.url)
}
for (const imported of [
...(result?.deps ?? []),
...(result?.dynamicDeps ?? []),
]) {
await crawl(imported)
}
}

for (const source of sources) {
await crawl(source)
}
return {
moduleIds,
cssImports,
assetImports,
}
}

type ClientReference = {
importId: string
referenceKey: string
}

export function filterBundledDevClientReferences<T extends ClientReference>(
references: Record<string, T>,
moduleIds: Set<string>,
): Record<string, T> {
const reachableIds = new Set(
[...moduleIds].map((id) => normalizePath(cleanUrl(id))),
)
return Object.fromEntries(
Object.entries(references).filter(([id]) =>
reachableIds.has(normalizePath(cleanUrl(id))),
),
)
}

export function renderBundledDevClientReferences(
graph: BundledDevServerGraph,
references: Record<string, ClientReference>,
): string {
const entries: string[] = []
let imports = ''
for (const [index, meta] of Object.values(references)
.sort((a, b) => a.referenceKey.localeCompare(b.referenceKey))
.entries()) {
const name = `__vite_rsc_client_reference_${index}`
imports += `import * as ${name} from ${JSON.stringify(withResolvedIdProxy(meta.importId))};\n`
entries.push(
`${JSON.stringify(meta.referenceKey)}: () => Promise.resolve(${name})`,
)
}
for (const id of [...graph.cssImports].sort()) {
imports += `import ${JSON.stringify(withResolvedIdProxy(id))};\n`
}
const assets = [...graph.assetImports].sort().map((id, index) => {
const name = `__vite_rsc_server_asset_${index}`
imports += `import ${name} from ${JSON.stringify(withResolvedIdProxy(id))};\n`
return name
})
if (assets.length > 0) {
entries.push(
`[Symbol.for("vite-rsc:server-assets")]: [${assets.join(', ')}]`,
)
}
return `${imports}export default {\n${entries.join(',\n')}\n};\n`
}
Loading

Back | FazBrowse Home | New Git URL