|
1 | 1 | import { Either, left, right } from 'fp-ts/lib/Either'
|
2 | 2 | import * as fs from 'fs-jetpack'
|
| 3 | +import * as path from 'path' |
3 | 4 | import { addHook } from 'pirates'
|
| 5 | +import slash from 'slash' |
| 6 | +import * as sourceMapSupport from 'source-map-support' |
4 | 7 | import * as ts from 'typescript'
|
5 | 8 | import { Layout } from './layout'
|
6 | 9 | import { rootLogger } from './nexus-logger'
|
@@ -141,20 +144,84 @@ function createTSError(diagnostics: ReadonlyArray<ts.Diagnostic>) {
|
141 | 144 | * This is strictly about transpilation, no type checking is done.
|
142 | 145 | */
|
143 | 146 | export function registerTypeScriptTranspile(compilerOptions?: ts.CompilerOptions) {
|
| 147 | + const outputCache = new Map< |
| 148 | + string, |
| 149 | + { |
| 150 | + content: string |
| 151 | + } |
| 152 | + >() |
| 153 | + const options = compilerOptions ?? {} |
| 154 | + sourceMapSupport.install({ |
| 155 | + environment: 'node', |
| 156 | + retrieveFile(path) { |
| 157 | + return outputCache.get(slash(path))?.content || '' |
| 158 | + }, |
| 159 | + }) |
| 160 | + |
| 161 | + /** |
| 162 | + * Get the extension for a transpiled file. |
| 163 | + */ |
| 164 | + const getExtension = |
| 165 | + options.jsx === ts.JsxEmit.Preserve |
| 166 | + ? (path: string) => (/\.[tj]sx$/.test(path) ? '.jsx' : '.js') |
| 167 | + : (_: string) => '.js' |
| 168 | + |
144 | 169 | addHook(
|
145 | 170 | (source, fileName) => {
|
146 | 171 | const transpiled = ts.transpileModule(source, {
|
147 | 172 | reportDiagnostics: true,
|
148 | 173 | fileName,
|
149 |
| - compilerOptions, |
| 174 | + compilerOptions: { |
| 175 | + ...options, |
| 176 | + sourceMap: true, |
| 177 | + }, |
150 | 178 | })
|
151 | 179 |
|
152 | 180 | if (transpiled.diagnostics && transpiled.diagnostics.length > 0) {
|
153 | 181 | throw createTSError(transpiled.diagnostics)
|
154 | 182 | }
|
155 | 183 |
|
| 184 | + const normalizedFileName = slash(fileName) |
| 185 | + const output = updateOutput( |
| 186 | + transpiled.outputText, |
| 187 | + normalizedFileName, |
| 188 | + transpiled.sourceMapText!, |
| 189 | + getExtension |
| 190 | + ) |
| 191 | + outputCache.set(normalizedFileName, { content: output }) |
| 192 | + |
156 | 193 | return transpiled.outputText
|
157 | 194 | },
|
158 | 195 | { exts: ['.ts'] }
|
159 | 196 | )
|
160 | 197 | }
|
| 198 | + |
| 199 | +/** |
| 200 | + * Update the output remapping the source map. |
| 201 | + * Taken from ts-node |
| 202 | + */ |
| 203 | +function updateOutput( |
| 204 | + outputText: string, |
| 205 | + fileName: string, |
| 206 | + sourceMap: string, |
| 207 | + getExtension: (fileName: string) => string |
| 208 | +) { |
| 209 | + const base64Map = Buffer.from(updateSourceMap(sourceMap, fileName), 'utf8').toString('base64') |
| 210 | + const sourceMapContent = `data:application/json;charset=utf-8;base64,${base64Map}` |
| 211 | + const sourceMapLength = |
| 212 | + `${path.basename(fileName)}.map`.length + (getExtension(fileName).length - path.extname(fileName).length) |
| 213 | + |
| 214 | + return outputText.slice(0, -sourceMapLength) + sourceMapContent |
| 215 | +} |
| 216 | + |
| 217 | +/** |
| 218 | + * Update the source map contents for improved output. |
| 219 | + * Taken from ts-node |
| 220 | + */ |
| 221 | +function updateSourceMap(sourceMapText: string, fileName: string) { |
| 222 | + const sourceMap = JSON.parse(sourceMapText) |
| 223 | + sourceMap.file = fileName |
| 224 | + sourceMap.sources = [fileName] |
| 225 | + delete sourceMap.sourceRoot |
| 226 | + return JSON.stringify(sourceMap) |
| 227 | +} |
0 commit comments