-
Notifications
You must be signed in to change notification settings - Fork 14
#15 added basic tsconfig extends support #16
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
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,139 +1,199 @@ | ||
| import { join } from 'path'; | ||
| import path from 'path'; | ||
| import { Plugin } from 'rollup'; | ||
| import { | ||
| CompilerOptions, | ||
| findConfigFile, | ||
| nodeModuleNameResolver, | ||
| parseConfigFileTextToJson, | ||
| sys, | ||
| CompilerOptions, | ||
| findConfigFile, | ||
| nodeModuleNameResolver, | ||
| parseConfigFileTextToJson, | ||
| sys, | ||
| } from 'typescript'; | ||
|
|
||
| export const typescriptPaths = ({ | ||
| absolute = true, | ||
| nonRelative = false, | ||
| preserveExtensions = false, | ||
| tsConfigPath = findConfigFile('./', sys.fileExists), | ||
| transform, | ||
| absolute = true, | ||
| nonRelative = false, | ||
| preserveExtensions = false, | ||
| tsConfigPath = findConfigFile('./', sys.fileExists), | ||
| transform, | ||
| }: Options = {}): Plugin => { | ||
| const { compilerOptions, outDir } = getTsConfig(tsConfigPath); | ||
|
|
||
| return { | ||
| name: 'resolve-typescript-paths', | ||
| resolveId: (importee: string, importer?: string) => { | ||
| const enabled = Boolean( | ||
| compilerOptions.paths || (compilerOptions.baseUrl && nonRelative), | ||
| ); | ||
|
|
||
| if ( | ||
| typeof importer === 'undefined' || | ||
| importee.startsWith('\0') || | ||
| !enabled | ||
| ) { | ||
| return null; | ||
| } | ||
|
|
||
| const hasMatchingPath = | ||
| !!compilerOptions.paths && | ||
| Object.keys(compilerOptions.paths).some((path) => | ||
| new RegExp('^' + path.replace('*', '.+') + '$').test(importee), | ||
| ); | ||
|
|
||
| if (!hasMatchingPath && !nonRelative) { | ||
| return null; | ||
| } | ||
|
|
||
| if (importee.startsWith('.')) { | ||
| return null; // never resolve relative modules, only non-relative | ||
| } | ||
|
|
||
| const { resolvedModule } = nodeModuleNameResolver( | ||
| importee, | ||
| importer, | ||
| compilerOptions, | ||
| sys, | ||
| ); | ||
|
|
||
| if (!resolvedModule) { | ||
| return null; | ||
| } | ||
|
|
||
| const { resolvedFileName } = resolvedModule; | ||
|
|
||
| if (!resolvedFileName || resolvedFileName.endsWith('.d.ts')) { | ||
| return null; | ||
| } | ||
|
|
||
| const targetFileName = join( | ||
| outDir, | ||
| preserveExtensions | ||
| ? resolvedFileName | ||
| : resolvedFileName.replace(/\.tsx?$/i, '.js'), | ||
| ); | ||
|
|
||
| const resolved = absolute | ||
| ? sys.resolvePath(targetFileName) | ||
| : targetFileName; | ||
|
|
||
| return transform ? transform(resolved) : resolved; | ||
| }, | ||
| }; | ||
| const { compilerOptions } = getTsConfig(tsConfigPath); | ||
| const outDir = compilerOptions.outDir ?? '.'; | ||
|
|
||
| return { | ||
| name: 'resolve-typescript-paths', | ||
| resolveId: (importee: string, importer?: string) => { | ||
| const enabled = Boolean( | ||
| compilerOptions.paths || (compilerOptions.baseUrl && nonRelative) | ||
| ); | ||
|
|
||
| if ( | ||
| typeof importer === 'undefined' || | ||
| importee.startsWith('\0') || | ||
| !enabled | ||
| ) { | ||
| return null; | ||
| } | ||
|
|
||
| const hasMatchingPath = | ||
| !!compilerOptions.paths && | ||
| Object.keys(compilerOptions.paths).some((path) => | ||
| new RegExp('^' + path.replace('*', '.+') + '$').test(importee) | ||
| ); | ||
|
|
||
| if (!hasMatchingPath && !nonRelative) { | ||
| return null; | ||
| } | ||
|
|
||
| if (importee.startsWith('.')) { | ||
| return null; // never resolve relative modules, only non-relative | ||
| } | ||
|
|
||
| const { resolvedModule } = nodeModuleNameResolver( | ||
| importee, | ||
| importer, | ||
| compilerOptions, | ||
| sys | ||
| ); | ||
|
|
||
| if (!resolvedModule) { | ||
| return null; | ||
| } | ||
|
|
||
| const { resolvedFileName } = resolvedModule; | ||
|
|
||
| if (!resolvedFileName || resolvedFileName.endsWith('.d.ts')) { | ||
| return null; | ||
| } | ||
|
|
||
| const targetFileName = path.join( | ||
| outDir, | ||
| preserveExtensions | ||
| ? resolvedFileName | ||
| : resolvedFileName.replace(/\.tsx?$/i, '.js') | ||
| ); | ||
|
|
||
| const resolved = absolute | ||
| ? sys.resolvePath(targetFileName) | ||
| : targetFileName; | ||
|
|
||
| return transform ? transform(resolved) : resolved; | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| const getTsConfig = (configPath?: string): TsConfig => { | ||
| const defaults: TsConfig = { compilerOptions: {}, outDir: '.' }; | ||
|
|
||
| if (!configPath) { | ||
| return defaults; | ||
| } | ||
|
|
||
| const configJson = sys.readFile(configPath); | ||
|
|
||
| if (!configJson) { | ||
| return defaults; | ||
| } | ||
|
|
||
| const { config } = parseConfigFileTextToJson(configPath, configJson); | ||
| const defaults: TsConfig = { compilerOptions: { outDir: '.' } }; | ||
| if (typeof configPath !== 'string') { | ||
| return defaults; | ||
| } | ||
|
|
||
| // Read in tsconfig.json | ||
| const configJson = sys.readFile(configPath); | ||
| if (configJson == null) { | ||
| return defaults; | ||
| } | ||
|
|
||
| const { config: rootConfig } = parseConfigFileTextToJson( | ||
| configPath, | ||
| configJson | ||
| ); | ||
| const rootConfigWithDefaults = { | ||
| ...rootConfig, | ||
| ...defaults, | ||
| compilerOptions: { | ||
| ...defaults.compilerOptions, | ||
| ...(rootConfig.compilerOptions ?? {}), | ||
| }, | ||
| }; | ||
| const resolvedConfig = handleTsConfigExtends( | ||
| rootConfigWithDefaults, | ||
| configPath | ||
| ); | ||
|
|
||
| return resolvedConfig; | ||
| }; | ||
|
|
||
| return { ...defaults, ...config }; | ||
| const handleTsConfigExtends = ( | ||
| config: TsConfig, | ||
| rootConfigPath: string | ||
| ): TsConfig => { | ||
| if (!('extends' in config) || typeof config.extends !== 'string') { | ||
| return config; | ||
| } | ||
|
|
||
| let extendedConfigPath; | ||
| try { | ||
| // Try to resolve as a module (npm) | ||
| extendedConfigPath = require.resolve(config.extends); | ||
| } catch (e) { | ||
| // Try to resolve as a file relative to the current config | ||
| extendedConfigPath = path.join( | ||
| path.dirname(rootConfigPath), | ||
| config.extends | ||
|
||
| ); | ||
| } | ||
|
|
||
| // Read in extended tsconfig.json | ||
| const extendedConfig = getTsConfig(extendedConfigPath); | ||
|
|
||
| // Merge base config and current config. | ||
| // This does not handle array concatenation or nested objects, | ||
| // besides 'compilerOptions' paths as the other options are not relevant | ||
| config = { | ||
| ...extendedConfig, | ||
| ...config, | ||
| compilerOptions: { | ||
| ...extendedConfig.compilerOptions, | ||
| ...config.compilerOptions, | ||
| paths: { | ||
| ...(extendedConfig.compilerOptions.paths ?? {}), | ||
| ...(config.compilerOptions.paths ?? {}), | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| // Remove the "extends" field | ||
| delete config.extends; | ||
|
|
||
| return config; | ||
| }; | ||
|
|
||
| export interface Options { | ||
| /** | ||
| * Whether to resolve to absolute paths; defaults to `true`. | ||
| */ | ||
| absolute?: boolean; | ||
|
|
||
| /** | ||
| * Whether to resolve non-relative paths based on tsconfig's `baseUrl`, even | ||
| * if none of the `paths` are matched; defaults to `false`. | ||
| * | ||
| * @see https://www.typescriptlang.org/docs/handbook/module-resolution.html#relative-vs-non-relative-module-imports | ||
| * @see https://www.typescriptlang.org/docs/handbook/module-resolution.html#base-url | ||
| */ | ||
| nonRelative?: boolean; | ||
|
|
||
| /** | ||
| * Whether to preserve `.ts` and `.tsx` file extensions instead of having them | ||
| * changed to `.js`; defaults to `false`. | ||
| */ | ||
| preserveExtensions?: boolean; | ||
|
|
||
| /** | ||
| * Custom path to your `tsconfig.json`. Use this if the plugin can't seem to | ||
| * find the correct one by itself. | ||
| */ | ||
| tsConfigPath?: string; | ||
|
|
||
| /** | ||
| * If the plugin successfully resolves a path, this function allows you to | ||
| * hook into the process and transform that path before it is returned. | ||
| */ | ||
| transform?(path: string): string; | ||
| /** | ||
| * Whether to resolve to absolute paths; defaults to `true`. | ||
| */ | ||
| absolute?: boolean; | ||
|
|
||
| /** | ||
| * Whether to resolve non-relative paths based on tsconfig's `baseUrl`, even | ||
| * if none of the `paths` are matched; defaults to `false`. | ||
| * | ||
| * @see https://www.typescriptlang.org/docs/handbook/module-resolution.html#relative-vs-non-relative-module-imports | ||
| * @see https://www.typescriptlang.org/docs/handbook/module-resolution.html#base-url | ||
| */ | ||
| nonRelative?: boolean; | ||
|
|
||
| /** | ||
| * Whether to preserve `.ts` and `.tsx` file extensions instead of having them | ||
| * changed to `.js`; defaults to `false`. | ||
| */ | ||
| preserveExtensions?: boolean; | ||
|
|
||
| /** | ||
| * Custom path to your `tsconfig.json`. Use this if the plugin can't seem to | ||
| * find the correct one by itself. | ||
| */ | ||
| tsConfigPath?: string; | ||
|
|
||
| /** | ||
| * If the plugin successfully resolves a path, this function allows you to | ||
| * hook into the process and transform that path before it is returned. | ||
| */ | ||
| transform?(path: string): string; | ||
| } | ||
|
|
||
| interface TsConfig { | ||
| compilerOptions: CompilerOptions; | ||
| outDir: string; | ||
| compilerOptions: CompilerOptions; | ||
| extends?: string; | ||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was pointed out here already: #14 (comment)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right in my case,
outDirwas always the default (".") since I've defined it in mycompilerOptions-> never was read in.Since the
resolvedFileNameis already an absolute path, joining (withpath.join()) it with the defaultoutDir(".") resulted in the correct absolute path, asresolvedFileNameseems already correct?Example
resolvedFileName: "C:/path/to/my/monorepo/packages/dtif-to-svg/src/d3.ts"However, now that it reads in the
outDircorrectly due to your suggestedgetParsedCommandLineOfConfigFile()method that works like a charm, theoutDiris something like "C:/path/to/my/monorepo/packages/dtif-to-svg/dist" which I defined in thecompilerOptionsof mytsconfig.json. However, nowpath.join()will join these both absolute paths that result in: "C:/path/to/my/monorepo/packages\dtif-to-svg\dist\C:/path/to/my/monorepo/packages\dtif-to-svg\src\d3.ts". And thats not correct.-> For me it works best without any defined
outDirasresolvedFileNamealready points to the correct location-> Do we actually need to define the
outDirand when might it be required, because for me it isn't?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the same issue comments I think further down there's a solution with using
path.relativeinstead ofpath.join.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I tested that but the resulting relative path was something like
../src/path/to/fileand since the script was executed in the root of the package it navigated out of the package and tried to find thesrc/path/to/file.tsthere..There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but anyway if the MR doesn't fit the project's philosophy that's ok too :) I've implemented your very useful plugin in my scripts and updated it to my project's needs (represented in this MR).. but I've not tested it in other environments and everyone has different requirements and expectations, which is of course hard to cover in a single project.. cheers :)