Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
41d677e
feat(typescript): honor allowJs and transform .js files\n\n- Expand d…
CharlieHelps Oct 20, 2025
7e8812d
feat(typescript): auto-include JS when allowJs is set; use temp outDi…
CharlieHelps Oct 20, 2025
2fa02de
fix(typescript): exclude effective outDir from filter to avoid re-pro…
CharlieHelps Oct 20, 2025
ea9aedb
fix(typescript): use path.relative for robust outDir containment check
CharlieHelps Oct 20, 2025
777d3bc
test(typescript): add regression ensuring user-configured outDir is e…
CharlieHelps Oct 20, 2025
f110caa
fix(typescript): gate temp outDir cleanup to non-watch; cleanup in cl…
CharlieHelps Oct 20, 2025
fe8e253
test(typescript): harden user outDir exclusion test (robust path chec…
CharlieHelps Oct 20, 2025
96aa8db
fix(typescript): robust outDir containment; gate temp outDir cleanup …
CharlieHelps Oct 20, 2025
0e1303f
test(typescript): make isInside helper treat equal path as inside; mi…
CharlieHelps Oct 20, 2025
a906b37
test(typescript): remove REBUILD_WITH_WATCH_OFF from incremental-watc…
CharlieHelps Oct 20, 2025
3734281
fix(typescript): guard string-only filterRoot base; close watch progr…
CharlieHelps Oct 22, 2025
3832864
fix(typescript): normalize Windows drive roots in containment; exclud…
CharlieHelps Oct 22, 2025
f52946a
docs(typescript): note default node_modules exclusion when allowJs ex…
CharlieHelps Oct 22, 2025
e1fa4f0
fix(typescript): default filter resolve to cwd when rootDir unset\n\n…
CharlieHelps Oct 22, 2025
9012668
refactor(typescript): centralize temp outDir cleanup into a helper
CharlieHelps Oct 22, 2025
933f6cd
refactor(typescript): unify program close + temp outDir cleanup via h…
CharlieHelps Oct 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patt
Type: `String` | `Array[...String]`<br>
Default: `null`

A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patterns, which specifies the files in the build the plugin should operate on. By default all `.ts` and `.tsx` files are targeted.
A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patterns, which specifies the files in the build the plugin should operate on. By default all `.ts` and `.tsx` files are targeted. If your `tsconfig.json` (or plugin `compilerOptions`) sets `allowJs: true`, the default include expands to also cover `.js`, `.jsx`, `.mjs`, and `.cjs` files so that JavaScript sources are downleveled by TypeScript as well.

> Note: When `allowJs` is enabled and no `outDir` is configured, this plugin creates a temporary output directory for TypeScript emit to avoid TS5055 (overwriting input files). The directory is excluded from processing and removed after the build.

### `filterRoot`

Expand Down
35 changes: 34 additions & 1 deletion packages/typescript/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';

import { createFilter } from '@rollup/pluginutils';

Expand Down Expand Up @@ -40,9 +42,32 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
const tsCache = new TSCache(cacheDir);
const emittedFiles = new Map<string, string>();
const watchProgramHelper = new WatchProgramHelper();
let autoOutDir: string | null = null;

const parsedOptions = parseTypescriptConfig(ts, tsconfig, compilerOptions, noForceEmit);
const filter = createFilter(include || '{,**/}*.(cts|mts|ts|tsx)', exclude, {

// When processing JS via allowJs, redirect emit output away from source files
// to avoid TS5055 (cannot write file because it would overwrite input file).
// We only set a temp outDir if the user did not configure one.
if (parsedOptions.options.allowJs && !parsedOptions.options.outDir) {
// Create a unique temporary outDir to avoid TS5055 when emitting JS
autoOutDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rollup-plugin-typescript-allowjs-'));
parsedOptions.options.outDir = autoOutDir;
}

// Determine default include pattern. By default we only process TS files.
// When the consumer enables `allowJs` in their tsconfig/compiler options,
// also include common JS extensions so modern JS syntax in .js files is
// downleveled by TypeScript as expected.
const defaultInclude = parsedOptions.options.allowJs
? '{,**/}*.{cts,mts,ts,tsx,js,jsx,mjs,cjs}'
: '{,**/}*.{cts,mts,ts,tsx}';

const filterExclude = Array.isArray(exclude) ? [...exclude] : exclude ? [exclude] : [];
if (autoOutDir) {
filterExclude.push(normalizePath(path.join(autoOutDir, '**')));
}
const filter = createFilter(include || defaultInclude, filterExclude, {
resolve: filterRoot ?? parsedOptions.options.rootDir
});
parsedOptions.fileNames = parsedOptions.fileNames.filter(filter);
Expand Down Expand Up @@ -104,6 +129,14 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
// eslint-disable-next-line
program?.close();
}
if (autoOutDir) {
try {
fs.rmSync(autoOutDir, { recursive: true, force: true });
} catch {
// ignore cleanup failures
}
autoOutDir = null;
}
},

renderStart(outputOptions) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function run() {
const obj = { prop: { nested: 1 } };
const a = obj.prop?.nested;
const b = {};
b.timeout ??= 123;
return [a, b.timeout];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"target": "ES2018",
"module": "ESNext",
"lib": ["es6"]
},
"include": ["src/**/*"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import util from './util.js';

export default function main() {
return util();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function util() {
const obj = { prop: { nested: 7 } };
const a = obj.prop?.nested;
const c = {};
c.timeout ??= 9;
return [a, c.timeout];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"target": "ES2018",
"module": "ESNext"
},
"include": ["src/**/*"]
}

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions packages/typescript/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1629,3 +1629,34 @@ test.serial('observes included declarations', async (t) => {
const files = await getCode(bundle, { format: 'es' }, true);
t.is(files.length, 1);
});

test.serial('downlevels JS when allowJs is true (default include)', async (t) => {
const bundle = await rollup({
input: 'fixtures/allow-js-downlevel/src/main.js',
plugins: [typescript({ tsconfig: 'fixtures/allow-js-downlevel/tsconfig.json' })],
onwarn
});
const code = await getCode(bundle, { format: 'es' });

// Optional chaining and nullish coalescing assignment should be transformed
t.false(code.includes('?.'), code);
t.false(code.includes('??='), code);

const result = await evaluateBundle(bundle);
t.deepEqual(result(), [1, 123]);
});

test.serial('downlevels JS imported by TS when allowJs is true', async (t) => {
const bundle = await rollup({
input: 'fixtures/allow-js-from-ts/src/main.ts',
plugins: [typescript({ tsconfig: 'fixtures/allow-js-from-ts/tsconfig.json' })],
onwarn
});
const code = await getCode(bundle, { format: 'es' });

t.false(code.includes('?.'), code);
t.false(code.includes('??='), code);

const result = await evaluateBundle(bundle);
t.deepEqual(result(), [7, 9]);
});
Loading