Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion packages/plugin-rsc/e2e/basic.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { createHash } from 'node:crypto'
import { readFileSync } from 'node:fs'
import { type Page, expect, test } from '@playwright/test'
import { type Fixture, useFixture } from './fixture'
import { type Fixture, useCreateEditor, useFixture } from './fixture'
import {
expectNoPageError,
expectNoReload,
testNoJs,
waitForHydration,
} from './helper'
import { x } from 'tinyexec'

test.describe('dev-default', () => {
const f = useFixture({ root: 'examples/basic', mode: 'dev' })
Expand Down Expand Up @@ -98,6 +99,56 @@ test.describe('dev-inconsistent-client-optimization', () => {
})
})

test.describe('build-stable-chunks', () => {
const root = 'examples/basic'
const createEditor = useCreateEditor(root)

test('basic', async () => {
// 1st build
await x('pnpm', ['build'], {
throwOnError: true,
nodeOptions: {
cwd: root,
},
})
const manifest1: import('vite').Manifest = JSON.parse(
createEditor('dist/client/.vite/manifest.json').read(),
)

// edit src/routes/client.tsx
const editor = createEditor('src/routes/client.tsx')
editor.edit((s) => s.replace('client-counter', 'client-counter-v2'))

// 2nd build
await x('pnpm', ['build'], {
throwOnError: true,
nodeOptions: {
cwd: root,
},
})
const manifest2: import('vite').Manifest = JSON.parse(
createEditor('dist/client/.vite/manifest.json').read(),
)

// compare two mainfest.json
const files1 = new Set(Object.values(manifest1).map((v) => v.file))
const files2 = new Set(Object.values(manifest2).map((v) => v.file))
const oldChunks = Object.entries(manifest2)
.filter(([_k, v]) => !files1.has(v.file))
.map(([k]) => k)
.sort()
const newChunks = Object.entries(manifest1)
.filter(([_k, v]) => !files2.has(v.file))
.map(([k]) => k)
.sort()
expect(newChunks).toEqual([
'src/framework/entry.browser.tsx',
'src/routes/client.tsx',
])
expect(oldChunks).toEqual(newChunks)
})
})

function defineTest(f: Fixture) {
test('basic', async ({ page }) => {
using _ = expectNoPageError(page)
Expand Down
33 changes: 20 additions & 13 deletions packages/plugin-rsc/e2e/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,33 @@ export function useFixture(options: {
await cleanup?.()
})

const createEditor = useCreateEditor(cwd)

return {
mode: options.mode,
root: cwd,
url: (url: string = './') => new URL(url, baseURL).href,
createEditor,
proc: () => proc,
}
}

export function useCreateEditor(cwd: string) {
const originalFiles: Record<string, string> = {}

test.afterAll(async () => {
for (const [filepath, content] of Object.entries(originalFiles)) {
fs.writeFileSync(filepath, content)
}
})

function createEditor(filepath: string) {
filepath = path.resolve(cwd, filepath)
const init = fs.readFileSync(filepath, 'utf-8')
originalFiles[filepath] ??= init
let current = init
return {
read: () => current,
edit(editFn: (data: string) => string): void {
const next = editFn(current)
assert(next !== current, 'Edit function did not change the content')
Expand All @@ -148,19 +167,7 @@ export function useFixture(options: {
}
}

test.afterAll(async () => {
for (const [filepath, content] of Object.entries(originalFiles)) {
fs.writeFileSync(filepath, content)
}
})

return {
mode: options.mode,
root: cwd,
url: (url: string = './') => new URL(url, baseURL).href,
createEditor,
proc: () => proc,
}
return createEditor
}

export async function setupIsolatedFixture(options: {
Expand Down
81 changes: 80 additions & 1 deletion packages/plugin-rsc/examples/basic/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ import assert from 'node:assert'
import rsc, { transformHoistInlineDirective } from '@vitejs/plugin-rsc'
import tailwindcss from '@tailwindcss/vite'
import react from '@vitejs/plugin-react'
import { type Plugin, defineConfig, normalizePath, parseAstAsync } from 'vite'
import {
type Plugin,
type Rollup,
defineConfig,
normalizePath,
parseAstAsync,
} from 'vite'
// import inspect from 'vite-plugin-inspect'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

export default defineConfig({
clearScreen: false,
Expand Down Expand Up @@ -88,6 +95,78 @@ export default defineConfig({
}
},
},
{
name: 'optimize-chunks',
apply: 'build',
config() {
const resolvePackageSource = (source: string) =>
normalizePath(fileURLToPath(import.meta.resolve(source)))

// TODO: this package entry isn't a public API.
const reactServerDom = resolvePackageSource(
'@vitejs/plugin-rsc/react/browser',
)

return {
environments: {
client: {
build: {
rollupOptions: {
output: {
manualChunks: (id) => {
// need to use functional form to handle commonjs plugin proxy module
// e.g. `(id)?commonjs-es-import`
if (
id.includes('node_modules/react/') ||
id.includes('node_modules/react-dom/') ||
id.includes(reactServerDom)
) {
return 'lib-react'
}
if (id === '\0vite/preload-helper.js') {
return 'lib-vite'
}
},
},
},
},
},
},
}
},
// verify chunks are "stable"
writeBundle(_options, bundle) {
if (this.environment.name === 'client') {
const entryChunks: Rollup.OutputChunk[] = []
const libChunks: Record<string, Rollup.OutputChunk[]> = {}
for (const chunk of Object.values(bundle)) {
if (chunk.type === 'chunk') {
if (chunk.isEntry) {
entryChunks.push(chunk)
}
if (chunk.name.startsWith('lib-')) {
;(libChunks[chunk.name] ??= []).push(chunk)
}
}
}

// react vendor chunk has no import
assert.equal(libChunks['lib-react'].length, 1)
assert.deepEqual(
// https://rolldown.rs/guide/in-depth/advanced-chunks#why-there-s-always-a-runtime-js-chunk
libChunks['lib-react'][0].imports.filter(
(f) => !f.includes('rolldown-runtime'),
),
[],
)
assert.deepEqual(libChunks['lib-react'][0].dynamicImports, [])

// entry chunk has no export
assert.equal(entryChunks.length, 1)
assert.deepEqual(entryChunks[0].exports, [])
}
},
},
{
name: 'cf-build',
enforce: 'post',
Expand Down
Loading