Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
44 changes: 44 additions & 0 deletions packages/plugin-rsc/e2e/sourcemap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import fs from 'node:fs'
import path from 'node:path'
import { expect, test } from '@playwright/test'
import { x } from 'tinyexec'

test.describe('sourcemap', () => {
const root = 'examples/starter'

test('build --sourcemap produces valid sourcemaps without rsc:patch-react-server-dom-webpack warnings', async () => {
// Clean previous build
fs.rmSync(path.join(root, 'dist'), { recursive: true, force: true })

const result = await x('pnpm', ['build', '--sourcemap'], {
nodeOptions: { cwd: root },
throwOnError: true,
})
expect(result.exitCode).toBe(0)

// The rsc:patch-react-server-dom-webpack plugin replaces
// __webpack_require__ with __vite_rsc_require__ (different lengths).
// With the MagicString fix, this transform preserves the sourcemap
// chain and must not appear in any "Sourcemap" warnings.
const output = result.stdout + result.stderr
expect(output).not.toContain(
'[plugin rsc:patch-react-server-dom-webpack] Sourcemap is likely to be incorrect',
)

// Verify the rsc build output has a valid sourcemap with non-empty mappings.
// The rsc bundle contains the vendored react-server-dom-webpack code
// that goes through the __webpack_require__ transform.
const rscDir = path.join(root, 'dist/rsc')
const mapFiles = fs.readdirSync(rscDir).filter((f) => f.endsWith('.js.map'))
expect(mapFiles.length).toBeGreaterThan(0)

for (const mapFile of mapFiles) {
const map = JSON.parse(
fs.readFileSync(path.join(rscDir, mapFile), 'utf-8'),
)
// Sourcemap must have non-empty mappings
expect(map.mappings).toBeTruthy()
expect(map.mappings.length).toBeGreaterThan(0)
}
})
})
50 changes: 34 additions & 16 deletions packages/plugin-rsc/src/core/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import MagicString from 'magic-string'
import type { Plugin } from 'vite'

export default function vitePluginRscCore(): Plugin[] {
Expand All @@ -6,25 +7,42 @@ export default function vitePluginRscCore(): Plugin[] {
name: 'rsc:patch-react-server-dom-webpack',
transform: {
filter: { code: '__webpack_require__' },
handler(originalCode, _id, _options) {
let code = originalCode
if (code.includes('__webpack_require__.u')) {
// avoid accessing `__webpack_require__` on import side effect
// https://github.com/facebook/react/blob/a9bbe34622885ef5667d33236d580fe7321c0d8b/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser.js#L16-L17
code = code.replaceAll('__webpack_require__.u', '({}).u')
}
handler(code, id, _options) {
if (!code.includes('__webpack_require__')) return

// Use MagicString to perform replacements with a proper sourcemap,
// so the Rollup sourcemap chain stays intact and doesn't emit
// 'Can't resolve original location of error' warnings for every
// file processed by this transform (e.g. all "use client" modules).
const s = new MagicString(code)

// the existance of `__webpack_require__` global can break some packages
// https://github.com/TooTallNate/node-bindings/blob/c8033dcfc04c34397384e23f7399a30e6c13830d/bindings.js#L90-L94
if (code.includes('__webpack_require__')) {
code = code.replaceAll(
'__webpack_require__',
'__vite_rsc_require__',
)
// Match `__webpack_require__.u` first (longer pattern), then bare
// `__webpack_require__`, in a single left-to-right pass to avoid
// overlapping overwrites into MagicString.
const re = /__webpack_require__(?:\.u)?/g
let match: RegExpExecArray | null
while ((match = re.exec(code)) !== null) {
const { index } = match
if (match[0] === '__webpack_require__.u') {
// avoid accessing `__webpack_require__` on import side effect
// https://github.com/facebook/react/blob/a9bbe34622885ef5667d33236d580fe7321c0d8b/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser.js#L16-L17
s.overwrite(index, index + match[0].length, '({}).u')
} else {
// the existance of `__webpack_require__` global can break some packages
// https://github.com/TooTallNate/node-bindings/blob/c8033dcfc04c34397384e23f7399a30e6c13830d/bindings.js#L90-L94
s.overwrite(
index,
index + match[0].length,
'__vite_rsc_require__',
)
}
}

if (code !== originalCode) {
return { code, map: null }
if (s.hasChanged()) {
return {
code: s.toString(),
map: s.generateMap({ hires: true, source: id }),
}
}
},
},
Expand Down
Loading