-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
/
ssrRequireHook.ts
90 lines (85 loc) · 2.62 KB
/
ssrRequireHook.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import { createRequire } from 'node:module'
import MagicString from 'magic-string'
import type { ResolvedConfig } from '..'
import type { Plugin } from '../plugin'
import { arraify } from '../utils'
/**
* This plugin hooks into Node's module resolution algorithm at runtime,
* so that SSR builds can benefit from `resolve.dedupe` like they do
* in development.
*/
export function ssrRequireHookPlugin(config: ResolvedConfig): Plugin | null {
if (
config.command !== 'build' ||
!config.build.ssr ||
!config.resolve.dedupe.length ||
config.ssr?.noExternal === true ||
config.ssr?.format !== 'cjs' ||
isBuildOutputEsm(config)
) {
return null
}
return {
name: 'vite:ssr-require-hook',
transform(code, id) {
const moduleInfo = this.getModuleInfo(id)
if (moduleInfo?.isEntry) {
const s = new MagicString(code)
s.prepend(
`;(${dedupeRequire.toString()})(${JSON.stringify(
config.resolve.dedupe
)});\n`
)
return {
code: s.toString(),
map: s.generateMap({
source: id,
hires: true
})
}
}
}
}
}
type NodeResolveFilename = (
request: string,
parent: NodeModule,
isMain: boolean,
options?: Record<string, any>
) => string
/** Respect the `resolve.dedupe` option in production SSR. */
function dedupeRequire(dedupe: string[]) {
// eslint-disable-next-line no-restricted-globals
const Module = require('node:module') as {
_resolveFilename: NodeResolveFilename
}
const resolveFilename = Module._resolveFilename
Module._resolveFilename = function (request, parent, isMain, options) {
if (request[0] !== '.' && request[0] !== '/') {
const parts = request.split('/')
const pkgName = parts[0][0] === '@' ? parts[0] + '/' + parts[1] : parts[0]
if (dedupe.includes(pkgName)) {
// Use this module as the parent.
parent = module
}
}
return resolveFilename!(request, parent, isMain, options)
}
}
const _require = createRequire(import.meta.url)
export function hookNodeResolve(
getResolver: (resolveFilename: NodeResolveFilename) => NodeResolveFilename
): () => void {
const Module = _require('module') as { _resolveFilename: NodeResolveFilename }
const prevResolver = Module._resolveFilename
Module._resolveFilename = getResolver(prevResolver)
return () => {
Module._resolveFilename = prevResolver
}
}
function isBuildOutputEsm(config: ResolvedConfig) {
const outputs = arraify(config.build.rollupOptions?.output)
return outputs.some(
(output) => output?.format === 'es' || output?.format === 'esm'
)
}