Skip to content

Commit 0a6cfdf

Browse files
authored
refactor(rsc): organize internal plugins (#745)
1 parent 2640add commit 0a6cfdf

3 files changed

Lines changed: 146 additions & 140 deletions

File tree

packages/plugin-rsc/src/plugin.ts

Lines changed: 3 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createHash } from 'node:crypto'
33
import fs from 'node:fs'
44
import { createRequire } from 'node:module'
55
import path from 'node:path'
6-
import { fileURLToPath, pathToFileURL } from 'node:url'
6+
import { pathToFileURL } from 'node:url'
77
import { createRequestListener } from '@remix-run/node-fetch-server'
88
import * as esModuleLexer from 'es-module-lexer'
99
import MagicString from 'magic-string'
@@ -41,6 +41,8 @@ import { cjsModuleRunnerPlugin } from './plugins/cjs'
4141
import { evalValue, parseIdQuery } from './plugins/utils'
4242
import { createDebug } from '@hiogawa/utils'
4343
import { transformScanBuildStrip } from './plugins/scan'
44+
import { validateImportPlugin } from './plugins/validate-import'
45+
import { vitePluginFindSourceMapURL } from './plugins/find-source-map-url'
4446

4547
// state for build orchestration
4648
let serverReferences: Record<string, string> = {}
@@ -1657,102 +1659,6 @@ function collectAssetDepsInner(
16571659
}
16581660
}
16591661

1660-
//
1661-
// support findSourceMapURL
1662-
// https://github.com/facebook/react/pull/29708
1663-
// https://github.com/facebook/react/pull/30741
1664-
//
1665-
1666-
export function vitePluginFindSourceMapURL(): Plugin[] {
1667-
return [
1668-
{
1669-
name: 'rsc:findSourceMapURL',
1670-
apply: 'serve',
1671-
configureServer(server) {
1672-
server.middlewares.use(async (req, res, next) => {
1673-
const url = new URL(req.url!, `http://localhost`)
1674-
if (url.pathname === '/__vite_rsc_findSourceMapURL') {
1675-
let filename = url.searchParams.get('filename')!
1676-
let environmentName = url.searchParams.get('environmentName')!
1677-
try {
1678-
const map = await findSourceMapURL(
1679-
server,
1680-
filename,
1681-
environmentName,
1682-
)
1683-
res.setHeader('content-type', 'application/json')
1684-
if (!map) res.statusCode = 404
1685-
res.end(JSON.stringify(map ?? {}))
1686-
} catch (e) {
1687-
next(e)
1688-
}
1689-
return
1690-
}
1691-
next()
1692-
})
1693-
},
1694-
},
1695-
]
1696-
}
1697-
1698-
export async function findSourceMapURL(
1699-
server: ViteDevServer,
1700-
filename: string,
1701-
environmentName: string,
1702-
): Promise<object | undefined> {
1703-
// this is likely server external (i.e. outside of Vite processing)
1704-
if (filename.startsWith('file://')) {
1705-
filename = fileURLToPath(filename)
1706-
if (fs.existsSync(filename)) {
1707-
// line-by-line identity source map
1708-
const content = fs.readFileSync(filename, 'utf-8')
1709-
return {
1710-
version: 3,
1711-
sources: [filename],
1712-
sourcesContent: [content],
1713-
mappings: 'AAAA' + ';AACA'.repeat(content.split('\n').length),
1714-
}
1715-
}
1716-
return
1717-
}
1718-
1719-
// server component stack, replace log, `registerServerReference`, etc...
1720-
let mod: EnvironmentModuleNode | undefined
1721-
let map:
1722-
| NonNullable<EnvironmentModuleNode['transformResult']>['map']
1723-
| undefined
1724-
if (environmentName === 'Server') {
1725-
mod = server.environments.rsc!.moduleGraph.getModuleById(filename)
1726-
// React extracts stacktrace via resetting `prepareStackTrace` on the server
1727-
// and let browser devtools handle the mapping.
1728-
// https://github.com/facebook/react/blob/4a36d3eab7d9bbbfae62699989aa95e5a0297c16/packages/react-server/src/ReactFlightStackConfigV8.js#L15-L20
1729-
// This means it has additional +2 line offset due to Vite's module runner
1730-
// function wrapper. We need to correct it just like Vite module runner.
1731-
// https://github.com/vitejs/vite/blob/d94e7b25564abb81ab7b921d4cd44d0f0d22fec4/packages/vite/src/shared/utils.ts#L58-L69
1732-
// https://github.com/vitejs/vite/blob/d94e7b25564abb81ab7b921d4cd44d0f0d22fec4/packages/vite/src/node/ssr/fetchModule.ts#L142-L146
1733-
map = mod?.transformResult?.map
1734-
if (map && map.mappings) {
1735-
map = { ...map, mappings: (';;' + map.mappings) as any }
1736-
}
1737-
}
1738-
1739-
const base = server.config.base.slice(0, -1)
1740-
1741-
// `createServerReference(... findSourceMapURL ...)` called on browser
1742-
if (environmentName === 'Client') {
1743-
try {
1744-
const url = new URL(filename).pathname.slice(base.length)
1745-
mod = server.environments.client.moduleGraph.urlToModuleMap.get(url)
1746-
map = mod?.transformResult?.map
1747-
} catch (e) {}
1748-
}
1749-
1750-
if (mod && map) {
1751-
// fix sources to match Vite's module url on browser
1752-
return { ...map, sources: [base + mod.url] }
1753-
}
1754-
}
1755-
17561662
//
17571663
// css support
17581664
//
@@ -2211,49 +2117,6 @@ export function __fix_cloudflare(): Plugin {
22112117
}
22122118
}
22132119

2214-
// https://github.com/vercel/next.js/blob/90f564d376153fe0b5808eab7b83665ee5e08aaf/packages/next/src/build/webpack-config.ts#L1249-L1280
2215-
// https://github.com/pcattori/vite-env-only/blob/68a0cc8546b9a37c181c0b0a025eb9b62dbedd09/src/deny-imports.ts
2216-
// https://github.com/sveltejs/kit/blob/84298477a014ec471839adf7a4448d91bc7949e4/packages/kit/src/exports/vite/index.js#L513
2217-
function validateImportPlugin(): Plugin {
2218-
return {
2219-
name: 'rsc:validate-imports',
2220-
resolveId: {
2221-
order: 'pre',
2222-
async handler(source, importer, options) {
2223-
// optimizer is not aware of server/client boudnary so skip
2224-
if ('scan' in options && options.scan) {
2225-
return
2226-
}
2227-
2228-
// Validate client-only imports in server environments
2229-
if (source === 'client-only') {
2230-
if (this.environment.name === 'rsc') {
2231-
throw new Error(
2232-
`'client-only' cannot be imported in server build (importer: '${importer ?? 'unknown'}', environment: ${this.environment.name})`,
2233-
)
2234-
}
2235-
return { id: `\0virtual:vite-rsc/empty`, moduleSideEffects: false }
2236-
}
2237-
if (source === 'server-only') {
2238-
if (this.environment.name !== 'rsc') {
2239-
throw new Error(
2240-
`'server-only' cannot be imported in client build (importer: '${importer ?? 'unknown'}', environment: ${this.environment.name})`,
2241-
)
2242-
}
2243-
return { id: `\0virtual:vite-rsc/empty`, moduleSideEffects: false }
2244-
}
2245-
2246-
return
2247-
},
2248-
},
2249-
load(id) {
2250-
if (id.startsWith('\0virtual:vite-rsc/empty')) {
2251-
return `export {}`
2252-
}
2253-
},
2254-
}
2255-
}
2256-
22572120
function sortObject<T extends object>(o: T) {
22582121
return Object.fromEntries(
22592122
Object.entries(o).sort(([a], [b]) => a.localeCompare(b)),
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { fileURLToPath } from 'node:url'
2+
import type { EnvironmentModuleNode, Plugin, ViteDevServer } from 'vite'
3+
import fs from 'node:fs'
4+
5+
//
6+
// support findSourceMapURL
7+
// https://github.com/facebook/react/pull/29708
8+
// https://github.com/facebook/react/pull/30741
9+
//
10+
11+
export function vitePluginFindSourceMapURL(): Plugin[] {
12+
return [
13+
{
14+
name: 'rsc:findSourceMapURL',
15+
apply: 'serve',
16+
configureServer(server) {
17+
server.middlewares.use(async (req, res, next) => {
18+
const url = new URL(req.url!, `http://localhost`)
19+
if (url.pathname === '/__vite_rsc_findSourceMapURL') {
20+
let filename = url.searchParams.get('filename')!
21+
let environmentName = url.searchParams.get('environmentName')!
22+
try {
23+
const map = await findSourceMapURL(
24+
server,
25+
filename,
26+
environmentName,
27+
)
28+
res.setHeader('content-type', 'application/json')
29+
if (!map) res.statusCode = 404
30+
res.end(JSON.stringify(map ?? {}))
31+
} catch (e) {
32+
next(e)
33+
}
34+
return
35+
}
36+
next()
37+
})
38+
},
39+
},
40+
]
41+
}
42+
43+
async function findSourceMapURL(
44+
server: ViteDevServer,
45+
filename: string,
46+
environmentName: string,
47+
): Promise<object | undefined> {
48+
// this is likely server external (i.e. outside of Vite processing)
49+
if (filename.startsWith('file://')) {
50+
filename = fileURLToPath(filename)
51+
if (fs.existsSync(filename)) {
52+
// line-by-line identity source map
53+
const content = fs.readFileSync(filename, 'utf-8')
54+
return {
55+
version: 3,
56+
sources: [filename],
57+
sourcesContent: [content],
58+
mappings: 'AAAA' + ';AACA'.repeat(content.split('\n').length),
59+
}
60+
}
61+
return
62+
}
63+
64+
// server component stack, replace log, `registerServerReference`, etc...
65+
let mod: EnvironmentModuleNode | undefined
66+
let map:
67+
| NonNullable<EnvironmentModuleNode['transformResult']>['map']
68+
| undefined
69+
if (environmentName === 'Server') {
70+
mod = server.environments.rsc!.moduleGraph.getModuleById(filename)
71+
// React extracts stacktrace via resetting `prepareStackTrace` on the server
72+
// and let browser devtools handle the mapping.
73+
// https://github.com/facebook/react/blob/4a36d3eab7d9bbbfae62699989aa95e5a0297c16/packages/react-server/src/ReactFlightStackConfigV8.js#L15-L20
74+
// This means it has additional +2 line offset due to Vite's module runner
75+
// function wrapper. We need to correct it just like Vite module runner.
76+
// https://github.com/vitejs/vite/blob/d94e7b25564abb81ab7b921d4cd44d0f0d22fec4/packages/vite/src/shared/utils.ts#L58-L69
77+
// https://github.com/vitejs/vite/blob/d94e7b25564abb81ab7b921d4cd44d0f0d22fec4/packages/vite/src/node/ssr/fetchModule.ts#L142-L146
78+
map = mod?.transformResult?.map
79+
if (map && map.mappings) {
80+
map = { ...map, mappings: (';;' + map.mappings) as any }
81+
}
82+
}
83+
84+
const base = server.config.base.slice(0, -1)
85+
86+
// `createServerReference(... findSourceMapURL ...)` called on browser
87+
if (environmentName === 'Client') {
88+
try {
89+
const url = new URL(filename).pathname.slice(base.length)
90+
mod = server.environments.client.moduleGraph.urlToModuleMap.get(url)
91+
map = mod?.transformResult?.map
92+
} catch (e) {}
93+
}
94+
95+
if (mod && map) {
96+
// fix sources to match Vite's module url on browser
97+
return { ...map, sources: [base + mod.url] }
98+
}
99+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { Plugin } from 'vite'
2+
3+
// https://github.com/vercel/next.js/blob/90f564d376153fe0b5808eab7b83665ee5e08aaf/packages/next/src/build/webpack-config.ts#L1249-L1280
4+
// https://github.com/pcattori/vite-env-only/blob/68a0cc8546b9a37c181c0b0a025eb9b62dbedd09/src/deny-imports.ts
5+
// https://github.com/sveltejs/kit/blob/84298477a014ec471839adf7a4448d91bc7949e4/packages/kit/src/exports/vite/index.js#L513
6+
export function validateImportPlugin(): Plugin {
7+
return {
8+
name: 'rsc:validate-imports',
9+
resolveId: {
10+
order: 'pre',
11+
async handler(source, importer, options) {
12+
// optimizer is not aware of server/client boudnary so skip
13+
if ('scan' in options && options.scan) {
14+
return
15+
}
16+
17+
// Validate client-only imports in server environments
18+
if (source === 'client-only') {
19+
if (this.environment.name === 'rsc') {
20+
throw new Error(
21+
`'client-only' cannot be imported in server build (importer: '${importer ?? 'unknown'}', environment: ${this.environment.name})`,
22+
)
23+
}
24+
return { id: `\0virtual:vite-rsc/empty`, moduleSideEffects: false }
25+
}
26+
if (source === 'server-only') {
27+
if (this.environment.name !== 'rsc') {
28+
throw new Error(
29+
`'server-only' cannot be imported in client build (importer: '${importer ?? 'unknown'}', environment: ${this.environment.name})`,
30+
)
31+
}
32+
return { id: `\0virtual:vite-rsc/empty`, moduleSideEffects: false }
33+
}
34+
35+
return
36+
},
37+
},
38+
load(id) {
39+
if (id.startsWith('\0virtual:vite-rsc/empty')) {
40+
return `export {}`
41+
}
42+
},
43+
}
44+
}

0 commit comments

Comments
 (0)