-
-
Notifications
You must be signed in to change notification settings - Fork 611
feat(typescript): process .js when allowJs is enabled #1920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
41d677e
7e8812d
2fa02de
ea9aedb
777d3bc
f110caa
fe8e253
96aa8db
0e1303f
a906b37
3734281
3832864
f52946a
e1fa4f0
9012668
933f6cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'; | ||
|
|
||
|
|
@@ -40,10 +42,69 @@ 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, { | ||
| resolve: filterRoot ?? parsedOptions.options.rootDir | ||
|
|
||
| // 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}'; | ||
|
|
||
| // Build filter exclusions, ensuring we never re-process TypeScript emit outputs. | ||
| // Always exclude the effective outDir (user-provided or the auto-created temp dir). | ||
| const filterExclude = Array.isArray(exclude) ? [...exclude] : exclude ? [exclude] : []; | ||
| const effectiveOutDir = parsedOptions.options.outDir | ||
| ? path.resolve(parsedOptions.options.outDir) | ||
| : null; | ||
shellscape marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Determine the base used for containment checks. If pattern resolution is disabled | ||
| // (filterRoot === false), fall back to process.cwd() so we don't accidentally | ||
| // exclude sources when e.g. outDir='.'. | ||
| const willResolvePatterns = filterRoot !== false; | ||
| // Only treat string values of `filterRoot` as a base directory; booleans (e.g., true) | ||
| // should not flow into path resolution. Fallback to the tsconfig `rootDir` when not set. | ||
| const configuredBase = willResolvePatterns | ||
| ? typeof filterRoot === 'string' | ||
| ? filterRoot | ||
| : parsedOptions.options.rootDir | ||
| : null; | ||
| const filterBaseAbs = configuredBase ? path.resolve(configuredBase) : null; | ||
| if (effectiveOutDir) { | ||
| // Avoid excluding sources: skip when the filter base (or cwd fallback) lives inside outDir. | ||
| // Use path.relative with root equality guard for cross-platform correctness. | ||
| const baseForContainment = filterBaseAbs ?? process.cwd(); | ||
| const outDirContainsFilterBase = (() => { | ||
| // Different roots (e.g., drive letters on Windows) cannot be in a parent/child relationship | ||
| if (path.parse(effectiveOutDir).root !== path.parse(baseForContainment).root) return false; | ||
| const rel = path.relative(effectiveOutDir, baseForContainment); | ||
| // rel === '' -> same dir; absolute or '..' => outside | ||
| return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel)); | ||
shellscape marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
shellscape marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| })(); | ||
| if (!outDirContainsFilterBase) { | ||
| filterExclude.push(normalizePath(path.join(effectiveOutDir, '**'))); | ||
| } | ||
| } | ||
| const filter = createFilter(include || defaultInclude, filterExclude, { | ||
| // Guard against non-string truthy values (e.g., boolean true). Only strings are valid | ||
| // for `resolve`; `false` disables resolution. Otherwise, fall back to `rootDir`. | ||
| resolve: | ||
| typeof filterRoot === 'string' | ||
| ? filterRoot | ||
| : filterRoot === false | ||
| ? false | ||
| : parsedOptions.options.rootDir | ||
|
||
| }); | ||
| parsedOptions.fileNames = parsedOptions.fileNames.filter(filter); | ||
|
|
||
|
|
@@ -103,6 +164,28 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi | |
| // ESLint doesn't understand optional chaining | ||
| // eslint-disable-next-line | ||
| program?.close(); | ||
| if (autoOutDir) { | ||
| try { | ||
| fs.rmSync(autoOutDir, { recursive: true, force: true }); | ||
| } catch { | ||
| // ignore cleanup failures | ||
| } | ||
| autoOutDir = null; | ||
| } | ||
| } | ||
| }, | ||
|
|
||
| // Ensure program is closed and temp outDir is removed exactly once when watch stops | ||
| closeWatcher() { | ||
| // eslint-disable-next-line | ||
| program?.close(); | ||
| if (autoOutDir) { | ||
| try { | ||
| fs.rmSync(autoOutDir, { recursive: true, force: true }); | ||
shellscape marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } catch { | ||
| // ignore cleanup failures | ||
| } | ||
| autoOutDir = null; | ||
| } | ||
| }, | ||
|
|
||
|
|
||
| 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.
Uh oh!
There was an error while loading. Please reload this page.