-
-
Notifications
You must be signed in to change notification settings - Fork 629
Expand file tree
/
Copy pathoutputFile.ts
More file actions
196 lines (174 loc) Β· 6.81 KB
/
outputFile.ts
File metadata and controls
196 lines (174 loc) Β· 6.81 KB
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import * as path from 'path';
import { promises as fs } from 'fs';
import type typescript from 'typescript';
import type { ExistingRawSourceMap, OutputOptions, PluginContext, SourceDescription } from 'rollup';
import type { ParsedCommandLine } from 'typescript';
import type TSCache from './tscache';
export interface TypescriptSourceDescription extends Partial<SourceDescription> {
declarations: string[];
}
/**
* Checks if the given OutputFile represents some code
*/
export function isCodeOutputFile(name: string): boolean {
return !isMapOutputFile(name) && !isDeclarationOutputFile(name);
}
/**
* Checks if the given OutputFile represents some source map
*/
export function isMapOutputFile(name: string): boolean {
return name.endsWith('.map');
}
/**
* Checks if the given OutputFile represents some TypeScript source map
*/
export function isTypeScriptMapOutputFile(name: string): boolean {
return name.endsWith('ts.map');
}
/**
* Checks if the given OutputFile represents some declaration
*/
export function isDeclarationOutputFile(name: string): boolean {
return /\.d\.[cm]?ts$/.test(name);
}
/**
* Returns the content of a filename either from the current
* typescript compiler instance or from the cached content.
* @param fileName The filename for the contents to retrieve
* @param emittedFiles The files emitted in the current typescript instance
* @param tsCache A cache to files cached by Typescript
*/
export function getEmittedFile(
fileName: string | undefined,
emittedFiles: ReadonlyMap<string, string>,
tsCache: TSCache
): string | undefined {
let code: string | undefined;
if (fileName) {
if (emittedFiles.has(fileName)) {
code = emittedFiles.get(fileName);
} else {
code = tsCache.getCached(fileName);
}
}
return code;
}
/**
* Finds the corresponding emitted JavaScript files for a given TypeScript file.
*
* Returns the transpiled code, an optional sourcemap (with rebased source paths),
* and a list of declaration file paths.
*
* @param ts The TypeScript module instance.
* @param parsedOptions The parsed TypeScript compiler options (tsconfig).
* @param id Absolute path to the original TypeScript source file.
* @param emittedFiles Map of output file paths to their content, populated by the TypeScript compiler during emission.
* @param tsCache A cache of previously emitted files for incremental builds.
* @returns An object containing the transpiled code, sourcemap with corrected paths, and declaration file paths.
*/
export default function findTypescriptOutput(
ts: typeof typescript,
parsedOptions: ParsedCommandLine,
id: string,
emittedFiles: ReadonlyMap<string, string>,
tsCache: TSCache
): TypescriptSourceDescription {
const emittedFileNames = ts.getOutputFileNames(
parsedOptions,
id,
!ts.sys.useCaseSensitiveFileNames
);
const codeFile = emittedFileNames.find(isCodeOutputFile);
const mapFile = emittedFileNames.find(isMapOutputFile);
let map: ExistingRawSourceMap | string | undefined = getEmittedFile(
mapFile,
emittedFiles,
tsCache
);
// Rebase sourcemap `sources` paths from the map file's directory to the
// original source file's directory.
//
// Why this is needed:
// TypeScript emits sourcemaps with `sources` relative to the **output**
// map file location (inside `outDir`), optionally prefixed by `sourceRoot`.
// For example, compiling `my-project/src/test.ts` with
// `outDir: "../dist/project"` produces `dist/project/src/test.js.map`
// containing:
//
// { "sources": ["../../../my-project/src/test.ts"], "sourceRoot": "." }
//
// This resolves correctly from the map file's directory:
// resolve("dist/project/src", ".", "../../../my-project/src/test.ts")
// β "my-project/src/test.ts" β
//
// However, Rollup's `getCollapsedSourcemap` resolves these paths relative
// to `dirname(id)` β the **original source file's directory** β not the
// output map file's directory. When `outDir` differs from the source tree
// (common in monorepos), this mismatch produces incorrect absolute paths
// that escape the project root.
//
// The fix resolves each source entry to an absolute path via the map file's
// directory (honoring `sourceRoot`), then re-relativizes it against the
// source file's directory so Rollup can consume it correctly.
if (map && mapFile) {
try {
const parsedMap: ExistingRawSourceMap = JSON.parse(map);
if (parsedMap.sources) {
const mapDir = path.dirname(mapFile);
const sourceDir = path.dirname(id);
const sourceRoot = parsedMap.sourceRoot || '.';
parsedMap.sources = parsedMap.sources.map((source) => {
// Resolve to absolute using the map file's directory + sourceRoot
const absolute = path.resolve(mapDir, sourceRoot, source);
// Re-relativize against the original source file's directory
return path.relative(sourceDir, absolute);
});
// sourceRoot has been folded into the rebased paths; remove it so
// Rollup does not double-apply it during sourcemap collapse.
delete parsedMap.sourceRoot;
map = parsedMap;
}
} catch (e) {
// If the map string is not valid JSON (shouldn't happen for TypeScript
// output), fall through and return the original map string unchanged.
}
}
return {
code: getEmittedFile(codeFile, emittedFiles, tsCache),
map,
declarations: emittedFileNames.filter((name) => name !== codeFile && name !== mapFile)
};
}
export function normalizePath(fileName: string) {
return fileName.split(path.win32.sep).join(path.posix.sep);
}
export async function emitFile(
{ dir }: OutputOptions,
outputToFilesystem: boolean | undefined,
context: PluginContext,
filePath: string,
fileSource: string
) {
const normalizedFilePath = normalizePath(filePath);
// const normalizedPath = normalizePath(filePath);
// Note: `dir` can be a value like `dist` in which case, `path.relative` could result in a value
// of something like `'../.tsbuildinfo'. Our else-case below needs to mimic `path.relative`
// returning a dot-notated relative path, so the first if-then branch is entered into
const relativePath = dir ? path.relative(dir, normalizedFilePath) : '..';
// legal paths do not start with . nor .. : https://github.com/rollup/rollup/issues/3507#issuecomment-616495912
if (relativePath.startsWith('..')) {
if (outputToFilesystem == null) {
context.warn(`@rollup/plugin-typescript: outputToFilesystem option is defaulting to true.`);
}
if (outputToFilesystem !== false) {
await fs.mkdir(path.dirname(normalizedFilePath), { recursive: true });
await fs.writeFile(normalizedFilePath, fileSource);
}
} else {
context.emitFile({
type: 'asset',
fileName: relativePath,
source: fileSource
});
}
}