Skip to content

wip: full bundle mode compat #482

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

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ jobs:
- name: Test serve
run: pnpm run test-serve

- name: Test full bundle mode serve
run: pnpm run test-full-bundle-mode

- name: Test build
run: pnpm run test-build

Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
"format": "prettier --write --cache .",
"lint": "eslint --cache .",
"typecheck": "tsc -p scripts && tsc -p playground && tsc -p packages/plugin-react",
"test": "pnpm run test-unit && pnpm run test-serve && pnpm run test-build && pnpm --filter ./packages/plugin-react-swc run test",
"test": "pnpm run test-unit && pnpm run test-serve && pnpm run test-build && pnpm --filter ./packages/plugin-react-swc run test && npm run test-full-bundle-mode",
"test-unit": "pnpm -r --filter='./packages/*' run test-unit",
"test-serve": "vitest run -c playground/vitest.config.e2e.ts",
"test-full-bundle-mode": "VITE_TEST_FULL_BUNDLE_MODE=1 vitest run -c playground/vitest.config.e2e.ts",
"test-build": "VITE_TEST_BUILD=1 vitest run -c playground/vitest.config.e2e.ts",
"debug-serve": "VITE_DEBUG_SERVE=1 vitest run -c playground/vitest.config.e2e.ts",
"debug-build": "VITE_TEST_BUILD=1 VITE_PRESERVE_BUILD_ARTIFACTS=1 vitest run -c playground/vitest.config.e2e.ts",
Expand Down Expand Up @@ -72,6 +73,10 @@
]
},
"pnpm": {
"overrides": {
"vitest>vite": "npm:vite@^6.2.6",
"vite": "https://pkg.pr.new/vitejs/rolldown-vite@bdb70a9"
},
"packageExtensions": {
"generouted": {
"peerDependencies": {
Expand Down
9 changes: 7 additions & 2 deletions packages/common/refresh-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -632,8 +632,13 @@ function predicateOnExport(ignoredExports, moduleExports, predicate) {
for (const key in moduleExports) {
if (key === '__esModule') continue
if (ignoredExports.includes(key)) continue
const desc = Object.getOwnPropertyDescriptor(moduleExports, key)
if (desc && desc.get) return key
// NOTE: this condition was added in https://github.com/vitejs/vite/pull/10239
// this is needed to avoid triggering side effects in getters
// but this is not needed when `moduleExports` is an ESM module namespace
// also this is problematic for full-bundle mode because rolldown converts
// exports to getters for live bindings
// const desc = Object.getOwnPropertyDescriptor(moduleExports, key)
// if (desc && desc.get) return key
if (!predicate(key, moduleExports[key])) return key
}
return true
Expand Down
3 changes: 2 additions & 1 deletion packages/common/refresh-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof
newCode = `${sharedHead}${newCode}

if (import.meta.hot && !inWebWorker) {
RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
// NOTE: import(import.meta.url) does not work in full-bundle mode
import.meta.hot.getExports().then((currentExports) => {
RefreshRuntime.registerExportsForReactRefresh(${JSON.stringify(
id,
)}, currentExports);
Expand Down
27 changes: 18 additions & 9 deletions packages/plugin-react-oxc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,15 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
}

let skipFastRefresh = false
let base: string

const viteRefreshWrapper: Plugin = {
name: 'vite:react-oxc:refresh-wrapper',
apply: 'serve',
configResolved(config) {
base = config.base
skipFastRefresh = config.isProduction || config.server.hmr === false
base = config.base
},
transform: {
filter: {
Expand Down Expand Up @@ -152,15 +155,21 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
return { code: newCode, map: null }
},
},
transformIndexHtml(_, config) {
if (!skipFastRefresh)
return [
{
tag: 'script',
attrs: { type: 'module' },
children: getPreambleCode(config.server!.config.base),
},
]
transformIndexHtml: {
// TODO: maybe we can inject this to entrypoints instead of index.html?
handler() {
if (!skipFastRefresh)
return [
{
tag: 'script',
attrs: { type: 'module' },
children: getPreambleCode(base),
},
]
},
// In unbundled mode, Vite transforms any requests.
// But in full bundled mode, Vite only transforms / bundles the scripts injected in `order: 'pre'`.
order: 'pre',
},
}

Expand Down
29 changes: 19 additions & 10 deletions packages/plugin-react-swc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type Options = {

const react = (_options?: Options): PluginOption[] => {
let hmrDisabled = false
let base: string
const options = {
jsxImportSource: _options?.jsxImportSource ?? 'react',
tsDecorators: _options?.tsDecorators,
Expand Down Expand Up @@ -137,7 +138,10 @@ const react = (_options?: Options): PluginOption[] => {
},
}),
configResolved(config) {
base = config.base
if (config.server.hmr === false) hmrDisabled = true
base = config.base

const mdxIndex = config.plugins.findIndex(
(p) => p.name === '@mdx-js/rollup',
)
Expand All @@ -162,16 +166,21 @@ const react = (_options?: Options): PluginOption[] => {
)
}
},
transformIndexHtml: (_, config) => {
if (!hmrDisabled) {
return [
{
tag: 'script',
attrs: { type: 'module' },
children: getPreambleCode(config.server!.config.base),
},
]
}
transformIndexHtml: {
// TODO: maybe we can inject this to entrypoints instead of index.html?
handler() {
if (!hmrDisabled)
return [
{
tag: 'script',
attrs: { type: 'module' },
children: getPreambleCode(base),
},
]
},
// In unbundled mode, Vite transforms any requests.
// But in full bundled mode, Vite only transforms / bundles the scripts injected in `order: 'pre'`.
order: 'pre',
},
async transform(code, _id, transformOptions) {
const id = _id.split('?')[0]
Expand Down
29 changes: 18 additions & 11 deletions packages/plugin-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,12 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
let runningInVite = false
let isProduction = true
let projectRoot = process.cwd()
let skipFastRefresh = true
let skipFastRefresh = false
let base: string
let runPluginOverrides:
| ((options: ReactBabelOptions, context: ReactBabelHookContext) => void)
| undefined
let staticBabelOptions: ReactBabelOptions | undefined

// Support patterns like:
// - import * as React from 'react';
// - import React from 'react';
Expand Down Expand Up @@ -170,6 +170,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
}
},
configResolved(config) {
base = config.base
runningInVite = true
projectRoot = config.root
isProduction = config.isProduction
Expand Down Expand Up @@ -390,15 +391,21 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
}
},
},
transformIndexHtml(_, config) {
if (!skipFastRefresh)
return [
{
tag: 'script',
attrs: { type: 'module' },
children: getPreambleCode(config.server!.config.base),
},
]
transformIndexHtml: {
// TODO: maybe we can inject this to entrypoints instead of index.html?
handler() {
if (!skipFastRefresh)
return [
{
tag: 'script',
attrs: { type: 'module' },
children: getPreambleCode(base),
},
]
},
// In unbundled mode, Vite transforms any requests.
// But in full bundled mode, Vite only transforms / bundles the scripts injected in `order: 'pre'`.
order: 'pre',
},
}

Expand Down
20 changes: 11 additions & 9 deletions playground/react-classic/__tests__/react.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ test.runIf(isServe)('should hmr', async () => {
expect(await page.textContent('button')).toMatch('count is: 1')
})

test.runIf(isServe)(
'should have annotated jsx with file location metadata',
async () => {
const res = await page.request.get(viteTestUrl + '/App.jsx')
const code = await res.text()
expect(code).toMatch(/lineNumber:\s*\d+/)
expect(code).toMatch(/columnNumber:\s*\d+/)
},
)
if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) {
test.runIf(isServe)(
'should have annotated jsx with file location metadata',
async () => {
const res = await page.request.get(viteTestUrl + '/App.jsx')
const code = await res.text()
expect(code).toMatch(/lineNumber:\s*\d+/)
expect(code).toMatch(/columnNumber:\s*\d+/)
},
)
}
55 changes: 29 additions & 26 deletions playground/react/__tests__/react.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,38 @@ test.runIf(isServe)('should hmr', async () => {
await expect.poll(() => page.textContent('h1')).toMatch('Hello Vite + React')
})

test.runIf(isServe)('should not invalidate when code is invalid', async () => {
editFile('App.jsx', (code) =>
code.replace('<div className="App">', '<div className="App"}>'),
)
// test.runIf(isServe)('should not invalidate when code is invalid', async () => {
// editFile('App.jsx', (code) =>
// code.replace('<div className="App">', '<div className="App"}>'),
// )

await expect
.poll(() => page.textContent('vite-error-overlay .message-body'))
.toMatch('Unexpected token')
// if import.meta.invalidate happened, the old page won't be shown because the page is reloaded
expect(await page.textContent('h1')).toMatch('Hello Vite + React')
// await expect
// .poll(() => page.textContent('vite-error-overlay .message-body'))
// .toMatch('Unexpected token')
// // if import.meta.invalidate happened, the old page won't be shown because the page is reloaded
// expect(await page.textContent('h1')).toMatch('Hello Vite + React')

await untilBrowserLogAfter(
() =>
editFile('App.jsx', (code) =>
code.replace('<div className="App"}>', '<div className="App">'),
),
'[vite] hot updated: /App.jsx',
)
})
// await untilBrowserLogAfter(
// () =>
// editFile('App.jsx', (code) =>
// code.replace('<div className="App"}>', '<div className="App">'),
// ),
// '[vite] hot updated: /App.jsx',
// )
// })

test.runIf(isServe)(
'should have annotated jsx with file location metadata',
async () => {
const res = await page.request.get(viteTestUrl + '/App.jsx')
const code = await res.text()
expect(code).toMatch(/lineNumber:\s*\d+/)
expect(code).toMatch(/columnNumber:\s*\d+/)
},
)
// The module file can't be visited at full bundle mode
if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) {
test.runIf(isServe)(
'should have annotated jsx with file location metadata',
async () => {
const res = await page.request.get(viteTestUrl + '/App.jsx')
const code = await res.text()
expect(code).toMatch(/lineNumber:\s*\d+/)
expect(code).toMatch(/columnNumber:\s*\d+/)
},
)
}

test('import attributes', async () => {
expect(await page.textContent('.import-attributes')).toBe('ok')
Expand Down
15 changes: 14 additions & 1 deletion playground/vitest.config.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,20 @@ export default defineConfig({
},
test: {
pool: 'forks',
include: ['./playground/**/*.spec.[tj]s'],
include: process.env.VITE_TEST_FULL_BUNDLE_MODE
? [
'./playground/class-components/**/*.spec.[tj]s',
'./playground/compiler/**/*.spec.[tj]s',
'./playground/compiler-react-18/**/*.spec.[tj]s',
'./playground/mdx/**/*.spec.[tj]s',
'./playground/react/**/*.spec.[tj]s',
'./playground/react-classic/**/*.spec.[tj]s',
'./playground/react-emotion/**/*.spec.[tj]s',
'./playground/react-env/**/*.spec.[tj]s',
'./playground/react-sourcemap/**/*.spec.[tj]s',
// './playground/ssr-react/**/*.spec.[tj]s',
]
: ['./playground/**/*.spec.[tj]s'],
setupFiles: ['./playground/vitestSetup.ts'],
globalSetup: ['./playground/vitestGlobalSetup.ts'],
testTimeout: timeout,
Expand Down
24 changes: 21 additions & 3 deletions playground/vitestSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,21 +214,39 @@ async function loadConfig(configEnv: ConfigEnv) {
// tests are flaky when `emptyOutDir` is `true`
emptyOutDir: false,
},
experimental: {
fullBundleMode: !!process.env.VITE_TEST_FULL_BUNDLE_MODE,
},
customLogger: createInMemoryLogger(serverLogs),
}
return mergeConfig(options, config || {})
}

export async function startDefaultServe(): Promise<void> {
const { build, createBuilder, createServer, mergeConfig, preview } =
await importVite()
const {
build,
createBuilder,
createServer,
mergeConfig,
preview,
createServerWithResolvedConfig,
} = await importVite()

setupConsoleWarnCollector(serverLogs)

if (!isBuild) {
process.env.VITE_INLINE = 'inline-serve'
const config = await loadConfig({ command: 'serve', mode: 'development' })
viteServer = server = await (await createServer(config)).listen()

if (process.env.VITE_TEST_FULL_BUNDLE_MODE) {
const builder = await createBuilder(config, null, 'serve')
viteServer = server = await createServerWithResolvedConfig(builder.config)
await server.listen()
await builder.buildApp(server)
} else {
viteServer = server = await (await createServer(config)).listen()
}

viteTestUrl = stripTrailingSlashIfNeeded(
server.resolvedUrls.local[0],
server.config.base,
Expand Down
Loading
Loading