-
-
Notifications
You must be signed in to change notification settings - Fork 207
/
cjs-interop.ts
125 lines (116 loc) · 3.35 KB
/
cjs-interop.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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import type {
ExportDefaultExpression,
ModuleDeclaration,
ParseOptions,
} from '@swc/core'
import type { Visitor } from '@swc/core/Visitor'
import fs from 'fs/promises'
import path from 'path'
import { PrettyError } from '../errors'
import { Plugin } from '../plugin'
import { localRequire } from '../utils'
export const cjsInterop = (): Plugin => {
return {
name: 'cjs-interop',
async renderChunk(code, info) {
const { entryPoint } = info
if (
!this.options.cjsInterop ||
this.format !== 'cjs' ||
info.type !== 'chunk' ||
!/\.(js|cjs)$/.test(info.path) ||
!entryPoint
) {
return
}
if (this.splitting) {
// there is exports metadata when cjs+splitting is set
if (info.exports?.length !== 1 || info.exports[0] !== 'default') return
} else {
const swc: typeof import('@swc/core') = localRequire('@swc/core')
const { Visitor }: typeof import('@swc/core/Visitor') =
localRequire('@swc/core/Visitor')
if (!swc || !Visitor) {
throw new PrettyError(
`@swc/core is required for cjsInterop when splitting is not enabled. Please install it with \`npm install @swc/core -D\``
)
}
try {
const entrySource = await fs.readFile(entryPoint, {
encoding: 'utf8',
})
const ast = await swc.parse(entrySource, getParseOptions(entryPoint))
const visitor = createExportVisitor(Visitor)
visitor.visitProgram(ast)
if (
!visitor.hasExportDefaultExpression ||
visitor.hasNonDefaultExportDeclaration
)
return
} catch {
return
}
}
return {
code: code + '\nmodule.exports=module.exports.default;\n',
map: info.map,
}
},
}
}
function getParseOptions(filename: string): ParseOptions {
switch (path.extname(filename).toLowerCase()) {
case '.js':
return {
syntax: 'ecmascript',
decorators: true,
}
case '.jsx':
return {
syntax: 'ecmascript',
decorators: true,
jsx: true,
}
case '.ts':
return {
syntax: 'typescript',
decorators: true,
}
case '.tsx':
return {
syntax: 'typescript',
decorators: true,
tsx: true,
}
default:
throw new Error(`Unknown file type: ${filename}`)
}
}
function createExportVisitor(VisitorCtor: typeof Visitor) {
class ExportVisitor extends VisitorCtor {
hasNonDefaultExportDeclaration = false
hasExportDefaultExpression = false
constructor() {
super()
type ExtractDeclName<T> = T extends `visit${infer N}` ? N : never
const nonDefaultExportDecls: ExtractDeclName<keyof Visitor>[] = [
'ExportDeclaration', // export const a = {}
'ExportNamedDeclaration', // export {}, export * as a from './a'
'ExportAllDeclaration', // export * from './a'
]
nonDefaultExportDecls.forEach((decl) => {
this[`visit${decl}`] = (n: any) => {
this.hasNonDefaultExportDeclaration = true
return n
}
})
}
visitExportDefaultExpression(
n: ExportDefaultExpression
): ModuleDeclaration {
this.hasExportDefaultExpression = true
return n
}
}
return new ExportVisitor()
}