-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
tscPlugin.ts
117 lines (107 loc) · 4.71 KB
/
tscPlugin.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
import { Extractor, ExtractorConfig } from '@microsoft/api-extractor'
import type * as esbuild from 'esbuild'
import fs from 'fs-extra'
import path from 'path'
import { run } from '../run'
/**
* Bundle all type definitions by using the API Extractor from RushStack
* @param filename the source d.ts to bundle
* @param outfile the output bundled file
*/
function bundleTypeDefinitions(filename: string, outfile: string) {
// we give the config in its raw form instead of a file
const extractorConfig = ExtractorConfig.prepare({
configObject: {
projectFolder: process.cwd(),
mainEntryPointFilePath: filename,
bundledPackages: [
'decimal.js',
'detect-runtime',
'sql-template-tag',
'@prisma/driver-adapter-utils',
'@opentelemetry/api',
'@prisma/internals',
'@prisma/generator-helper',
'@prisma/debug',
'@prisma/ni',
],
compiler: {
tsconfigFilePath: path.join(process.cwd(), 'tsconfig.build.json'),
overrideTsconfig: {
compilerOptions: {
paths: {}, // bug with api extract + paths
},
},
},
dtsRollup: {
enabled: true,
untrimmedFilePath: path.join(process.cwd(), `${outfile}.d.ts`),
},
tsdocMetadata: {
enabled: false,
},
},
packageJsonFullPath: path.join(process.cwd(), 'package.json'),
configObjectFullPath: undefined,
})
// here we trigger the "command line" interface equivalent
const extractorResult = Extractor.invoke(extractorConfig, {
showVerboseMessages: true,
localBuild: true,
})
// we exit the process immediately if there were errors
if (extractorResult.succeeded === false) {
console.error(`API Extractor completed with errors`)
process.exit(1)
}
}
/**
* Triggers the TypeScript compiler and the type bundler.
*/
export const tscPlugin: (emitTypes?: boolean) => esbuild.Plugin = (emitTypes?: boolean) => ({
name: 'tscPlugin',
setup(build) {
const options = build.initialOptions
if (emitTypes === false) return // build has opted out of emitting types
build.onStart(async () => {
// we only call tsc if not in watch mode or in dev mode (they skip types)
if (process.env.WATCH !== 'true' && process.env.DEV !== 'true') {
// --paths null basically prevents typescript from using paths from the
// tsconfig.json that is passed from the esbuild config. We need to do
// this because TS would include types from the paths into this build.
// but our paths, in our specific case only represent separate packages.
await run(`tsc --project ${options.tsconfig} --paths null`)
}
// we bundle types if we also bundle the entry point and it is a ts file
if (options.bundle === true && options.entryPoints?.[0].endsWith('.ts')) {
const tsconfig = require(`${process.cwd()}/${options.tsconfig}`) // tsconfig
const typeOutDir = tsconfig?.compilerOptions?.outDir ?? '.' // type out dir
const entryPoint = options.entryPoints?.[0].replace(/\.ts$/, '')
const bundlePath = options.outfile!.replace(/\.js$/, '')
const typeOutPath = [
`${process.cwd()}/${typeOutDir}/${entryPoint}.d.ts`,
`${process.cwd()}/${typeOutDir}/${entryPoint.replace(/^src\//, '')}.d.ts`,
].filter(fs.existsSync)[0]
if (process.env.WATCH !== 'true' && process.env.DEV !== 'true') {
// we get the types generated by tsc and bundle them near the output
bundleTypeDefinitions(typeOutPath, bundlePath)
// when types are already exported by us, it is not always guaranteed
// that the type bundler will also export them. It won't be able to
// trace them correctly back to runtime module and fail with either
// "... is using the type X but can not be named" or "The inferred
// type of this node exceeds the maximum length" error, or even worse
// the compiler could inline the types in the users' project causing
// very bad performance issues. To fix this, we export all the types.
let dtsContents = await fs.readFile(`${bundlePath}.d.ts`, 'utf-8')
dtsContents = dtsContents.replace(/(?<!export )declare (type|interface|const)/g, 'export declare $1')
await fs.outputFile(`${bundlePath}.d.ts`, dtsContents)
} else {
// in watch mode, it wouldn't be viable to bundle the types every time
// we haven't built any types with tsc at this stage, but we want types
// we link the types locally by re-exporting them from the entry point
await fs.outputFile(`${bundlePath}.d.ts`, `export * from '${process.cwd()}/${entryPoint}'`)
}
}
})
},
})