Skip to content

Commit 6d528ac

Browse files
mrwhofabbarixshellscape
authored
feat(typescript): Implement cached incremental code (#535)
* Implement using cached incremental code * Fix windows paths issue Co-authored-by: Fabio Torchetti <[email protected]> Co-authored-by: shellscape <[email protected]>
1 parent 952a69c commit 6d528ac

File tree

9 files changed

+142
-14
lines changed

9 files changed

+142
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ output/
99
coverage.lcov
1010
pnpm-debug.log
1111
.idea
12+
.rollup.cache
1213

1314
!packages/node-resolve/test/fixtures/**/node_modules
1415
!packages/commonjs/test/**/node_modules

packages/typescript/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,21 @@ typescript({
188188
});
189189
```
190190

191+
### `cacheDir`
192+
193+
Type: `String`<br>
194+
Default: _.rollup.cache_
195+
196+
When compiling with `incremental` or `composite` options the plugin will
197+
store compiled files in this folder. This allows the use of incremental
198+
compilation.
199+
200+
```js
201+
typescript({
202+
cacheDir: '.rollup.tscache'
203+
});
204+
```
205+
191206
### Typescript compiler options
192207

193208
Some of Typescript's [CompilerOptions](https://www.typescriptlang.org/docs/handbook/compiler-options.html) affect how Rollup builds files.

packages/typescript/src/index.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ import createModuleResolver from './moduleResolution';
99
import getPluginOptions from './options/plugin';
1010
import { emitParsedOptionsErrors, parseTypescriptConfig } from './options/tsconfig';
1111
import { validatePaths, validateSourceMap } from './options/validate';
12-
import findTypescriptOutput from './outputFile';
12+
import findTypescriptOutput, { getEmittedFile } from './outputFile';
1313
import createWatchProgram, { WatchProgramHelper } from './watchProgram';
14+
import TSCache from './tscache';
1415

1516
export default function typescript(options: RollupTypescriptOptions = {}): Plugin {
1617
const {
18+
cacheDir,
19+
compilerOptions,
1720
filter,
21+
transformers,
1822
tsconfig,
19-
compilerOptions,
2023
tslib,
21-
typescript: ts,
22-
transformers
24+
typescript: ts
2325
} = getPluginOptions(options);
26+
const tsCache = new TSCache(cacheDir);
2427
const emittedFiles = new Map<string, string>();
2528
const watchProgramHelper = new WatchProgramHelper();
2629

@@ -49,6 +52,9 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
4952
resolveModule,
5053
parsedOptions,
5154
writeFile(fileName, data) {
55+
if (parsedOptions.options.composite || parsedOptions.options.incremental) {
56+
tsCache.cacheCode(fileName, data);
57+
}
5258
emittedFiles.set(fileName, data);
5359
},
5460
status(diagnostic) {
@@ -103,16 +109,16 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
103109

104110
await watchProgramHelper.wait();
105111

106-
const output = findTypescriptOutput(ts, parsedOptions, id, emittedFiles);
112+
const output = findTypescriptOutput(ts, parsedOptions, id, emittedFiles, tsCache);
107113

108114
return output.code != null ? (output as SourceDescription) : null;
109115
},
110116

111117
generateBundle(outputOptions) {
112118
parsedOptions.fileNames.forEach((fileName) => {
113-
const output = findTypescriptOutput(ts, parsedOptions, fileName, emittedFiles);
119+
const output = findTypescriptOutput(ts, parsedOptions, fileName, emittedFiles, tsCache);
114120
output.declarations.forEach((id) => {
115-
const code = emittedFiles.get(id);
121+
const code = getEmittedFile(id, emittedFiles, tsCache);
116122
if (!code) return;
117123

118124
this.emitFile({

packages/typescript/src/options/plugin.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,20 @@ import getTsLibPath from '../tslib';
1616
*/
1717
export default function getPluginOptions(options: RollupTypescriptOptions) {
1818
const {
19-
include,
19+
cacheDir,
2020
exclude,
21+
include,
22+
transformers,
2123
tsconfig,
22-
typescript,
2324
tslib,
24-
transformers,
25+
typescript,
2526
...compilerOptions
2627
} = options;
2728

2829
const filter = createFilter(include || ['*.ts+(|x)', '**/*.ts+(|x)'], exclude);
2930

3031
return {
32+
cacheDir,
3133
filter,
3234
tsconfig,
3335
compilerOptions: compilerOptions as PartialCompilerOptions,

packages/typescript/src/outputFile.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { SourceDescription } from 'rollup';
22

3+
import TSCache from './tscache';
4+
35
export interface TypescriptSourceDescription extends Partial<SourceDescription> {
46
declarations: string[];
57
}
@@ -18,6 +20,29 @@ function isMapOutputFile(name: string): boolean {
1820
return name.endsWith('.map');
1921
}
2022

23+
/**
24+
* Returns the content of a filename either from the current
25+
* typescript compiler instance or from the cached content.
26+
* @param fileName The filename for the contents to retrieve
27+
* @param emittedFiles The files emitted in the current typescript instance
28+
* @param tsCache A cache to files cached by Typescript
29+
*/
30+
export function getEmittedFile(
31+
fileName: string | undefined,
32+
emittedFiles: ReadonlyMap<string, string>,
33+
tsCache: TSCache
34+
): string | undefined {
35+
let code: string | undefined;
36+
if (fileName) {
37+
if (emittedFiles.has(fileName)) {
38+
code = emittedFiles.get(fileName);
39+
} else {
40+
code = tsCache.getCached(fileName);
41+
}
42+
}
43+
return code;
44+
}
45+
2146
/**
2247
* Finds the corresponding emitted Javascript files for a given Typescript file.
2348
* @param id Path to the Typescript file.
@@ -28,7 +53,8 @@ export default function findTypescriptOutput(
2853
ts: typeof import('typescript'),
2954
parsedOptions: import('typescript').ParsedCommandLine,
3055
id: string,
31-
emittedFiles: ReadonlyMap<string, string>
56+
emittedFiles: ReadonlyMap<string, string>,
57+
tsCache: TSCache
3258
): TypescriptSourceDescription {
3359
const emittedFileNames = ts.getOutputFileNames(
3460
parsedOptions,
@@ -40,8 +66,8 @@ export default function findTypescriptOutput(
4066
const mapFile = emittedFileNames.find(isMapOutputFile);
4167

4268
return {
43-
code: emittedFiles.get(codeFile!),
44-
map: emittedFiles.get(mapFile!),
69+
code: getEmittedFile(codeFile, emittedFiles, tsCache),
70+
map: getEmittedFile(mapFile, emittedFiles, tsCache),
4571
declarations: emittedFileNames.filter((name) => name !== codeFile && name !== mapFile)
4672
};
4773
}

packages/typescript/src/tscache.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import path from 'path';
2+
import fs from 'fs';
3+
4+
/** Creates the folders needed given a path to a file to be saved*/
5+
const createFileFolder = (filePath: string): void => {
6+
const folderPath = path.dirname(filePath);
7+
fs.mkdirSync(folderPath, { recursive: true });
8+
};
9+
10+
export default class TSCache {
11+
private _cacheFolder: string;
12+
13+
constructor(cacheFolder = '.rollup.cache') {
14+
this._cacheFolder = cacheFolder;
15+
}
16+
17+
/** Returns the path to the cached file */
18+
cachedFilename(fileName: string): string {
19+
return path.join(this._cacheFolder, fileName.replace(/^([A-Z]+):/, '$1'));
20+
}
21+
22+
/** Emits a file in the cache folder */
23+
cacheCode(fileName: string, code: string): void {
24+
const cachedPath = this.cachedFilename(fileName);
25+
createFileFolder(cachedPath);
26+
fs.writeFileSync(cachedPath, code);
27+
}
28+
29+
/** Checks if a file is in the cache */
30+
isCached(fileName: string): boolean {
31+
return fs.existsSync(this.cachedFilename(fileName));
32+
}
33+
34+
/** Read a file from the cache given the output name*/
35+
getCached(fileName: string): string | undefined {
36+
let code: string | undefined;
37+
if (this.isCached(fileName)) {
38+
code = fs.readFileSync(this.cachedFilename(fileName), { encoding: 'utf-8' });
39+
}
40+
return code;
41+
}
42+
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1-
const answer = '42';
1+
type AnswerToQuestion = string | undefined;
2+
3+
const answer: AnswerToQuestion = '42';
4+
25
// eslint-disable-next-line no-console
36
console.log(`the answer is ${answer}`);

packages/typescript/test/test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,33 @@ test.serial('supports incremental rebuild', async (t) => {
846846
);
847847
});
848848

849+
test.serial('supports consecutive incremental rebuilds', async (t) => {
850+
process.chdir('fixtures/incremental');
851+
852+
const firstBundle = await rollup({
853+
input: 'main.ts',
854+
plugins: [typescript()],
855+
onwarn
856+
});
857+
858+
const firstRun = await getCode(firstBundle, { format: 'esm', dir: 'dist' }, true);
859+
t.deepEqual(
860+
firstRun.map((out) => out.fileName),
861+
['main.js', '.tsbuildinfo']
862+
);
863+
864+
const secondBundle = await rollup({
865+
input: 'main.ts',
866+
plugins: [typescript()],
867+
onwarn
868+
});
869+
const secondRun = await getCode(secondBundle, { format: 'esm', dir: 'dist' }, true);
870+
t.deepEqual(
871+
secondRun.map((out) => out.fileName),
872+
['main.js', '.tsbuildinfo']
873+
);
874+
});
875+
849876
test.serial.skip('supports project references', async (t) => {
850877
process.chdir('fixtures/project-references');
851878

packages/typescript/types/index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import { Plugin } from 'rollup';
33
import { CompilerOptions, CustomTransformers } from 'typescript';
44

55
export interface RollupTypescriptPluginOptions {
6+
/**
7+
* If using incremental this is the folder where the cached
8+
* files will be created and kept for Typescript incremental
9+
* compilation.
10+
*/
11+
cacheDir?: string;
612
/**
713
* Determine which files are transpiled by Typescript (all `.ts` and
814
* `.tsx` files by default).

0 commit comments

Comments
 (0)