Skip to content
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

Resolve path alias in declaration files (.d.ts) #201

Closed
alshdavid opened this issue Jan 1, 2020 · 25 comments
Closed

Resolve path alias in declaration files (.d.ts) #201

alshdavid opened this issue Jan 1, 2020 · 25 comments
Labels
controversial kind: discussion Discussion on changes to make kind: feature New feature or request problem: stale Issue has not been responded to in some time solution: out-of-scope This is out of scope for this project solution: tsc behavior This is tsc's behavior as well, so this is not a bug with this plugin solution: workaround available There is a workaround available for this issue

Comments

@alshdavid
Copy link

alshdavid commented Jan 1, 2020

When applying a path alias to a typescript project, the path is resolved in the emitted javascript files, but not in the declaration files.

The TypeScript team have explicitly stated that path resolution is not something that will be included in the compiler (ever) and is the domain of utilities like rollup/webpack.

Versions

  • typescript: 3.7.4
  • rollup: 1.27.14
  • rollup-plugin-typescript2: 0.25.3

In the following project I have aliased ~/* to ./src/*. Allowing for imports using absolute paths from the base directory.

https://github.com/alshdavid-sandbox/rollup-typescript-library

npm install && make
cat dist/a/index.d.ts | grep "~"

cat should print nothing, however it currently prints

import { B } from '~/b';

This illustrates that we are not applying path resolution to the declaration files.

@ezolenko ezolenko added kind: bug Something isn't working properly controversial kind: feature New feature or request labels Jan 2, 2020
@ezolenko
Copy link
Owner

ezolenko commented Jan 2, 2020

According to typescript people, you are using it backwards:

Our general take on this is that you should write the import path that works at runtime, and set your TS flags to satisfy the compiler's module resolution step, rather than writing the import that works out-of-the-box for TS and then trying to have some other step "fix" the paths to what works at runtime.

microsoft/TypeScript#15479

Currently this plugin doesn't post process or even parse type declarations once they are emitted by typescript.

It might be possible to do something slightly better than search/replace, but to solve this in general we'll basically need to reimplement rollup for type declarations (unless they already added language service API for generating bundled declarations while I wasn't looking)

@alshdavid
Copy link
Author

alshdavid commented Jan 3, 2020

Thanks for the reply. Is there a "post emit" hook that runs on both watch and build modes which could be used to attach a crude search/replace post processing step?

@ezolenko
Copy link
Owner

ezolenko commented Jan 3, 2020

d.ts files are emitted directly into rollup pipeline (when useTsconfigDeclarationDir: false option is used), so you could make a rollup plugin that transforms them.

They get emitted when bundle is written though, so you might have to use generateBundle hook instead of transform.

@ezolenko
Copy link
Owner

ezolenko commented Jan 3, 2020

If that doesn't work. I can add a pre-write hook to rpt2 itself.

@MarkoCen
Copy link

MarkoCen commented Jan 22, 2020

Encounter the same issue, found a walkaround here
currently using ttsc with typescript-transform-paths to generate declaration files after rollup emitted ts files, kind of redundant but works...

@alshdavid
Copy link
Author

alshdavid commented Feb 27, 2020

Yeah, I am achieving this using ttsc as well. I was unable to get the transformers working directly with rollup-plugin-typescript2 transformers options, so I opted to use ttsc

My rollup config looks like:

import typescript from 'rollup-plugin-typescript2'

export default {
  input: `src/index.ts`,
  preserveModules: true,
  output: {
    format: 'esm',
    dir: './dist'
  },
  external: [],
  watch: {
    include: 'src/**',
  },
  plugins: [
    typescript({ 
      typescript: require('ttypescript'),
      tsconfigDefaults: {
        compilerOptions: {
          plugins: [
            { "transform": "typescript-transform-paths" },
            { "transform": "typescript-transform-paths", "afterDeclarations": true }
          ]
        }
      }
    }),
  ],
}

@whmori17
Copy link

whmori17 commented Feb 28, 2020

Thanks man @alshdavid ! You've saved my life! Yesterday I had the same issue and by using typescript-transform-paths I solved it! Thanks 💪

// webpack.config.js
module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        use: [
          {
            loader: require.resolve('ts-loader'),
            options: {
              compiler: 'ttypescript',
            },
          },
          {
            loader: require.resolve('react-docgen-typescript-loader'),
          },
        ],
        exclude: /(node_modules)/,
      },
    ],
  },
// tsconfig.json
compilerOptions: {
  "plugins": [
     { "transform": "typescript-transform-paths" },
     { "transform": "typescript-transform-paths", "afterDeclarations": true }
   ]
}

@yankeeinlondon
Copy link

this solution has worked for me in the past but I'm finding many situations now where I get an error like this:

UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'text' of undefined

if I move away from using aliases (and these plugins) then it works without issue. Anyone else have this issue?

@grtan

This comment was marked as duplicate.

@mantlebee
Copy link

mantlebee commented Nov 11, 2020

this solution has worked for me in the past but I'm finding many situations now where I get an error like this:

UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'text' of undefined

if I move away from using aliases (and these plugins) then it works without issue. Anyone else have this issue?

Hi, i'm issueing the same problem, but in a different context: I'm using rollup to bundle a components library written with Vue and Typescript. This is the rollup.config.js file:

import path from "path";

import alias from "@rollup/plugin-alias";
import cleaner from "rollup-plugin-cleaner";
import css from "rollup-plugin-css-only";
import scss from "rollup-plugin-scss";
import typescript from "rollup-plugin-typescript2";
import vue from "rollup-plugin-vue";

export default [
  {
    input: "./src/index.ts",
    preserveModules: true,
    output: {
      format: "esm",
      dist: "./dist"
    },
    plugins: [
      cleaner({ targets: ["dist/"] }),
      alias({
        resolve: [".ts", ".vue"],
        entries: { "@/": path.resolve(__dirname, "./src/") }
      }),
      css({ output: "dist/bundle.css" }),
      scss(),
      typescript({
        typescript: require("ttypescript"),
        tsconfigDefaults: {
          compilerOptions: {
            plugins: [
              { transform: "typescript-transform-paths" },
              {
                transform: "typescript-transform-paths",
                afterDeclarations: true
              }
            ]
          }
        }
      }),
      vue({ css: false })
    ]
  }
];

I already use the ttsc --emitDeclarationOnly command after the rollup --config one, in order to resolve aliases, but .vue.d.ts files generated by rollup are not processed (of course).

@luanorlandi
Copy link

luanorlandi commented Nov 11, 2020

Same issue with ttypescript and typescript-transofrm-paths, it didn't work :disappointed. When running build:

[!] (plugin rpt2) TypeError: Cannot read property 'text' of undefined

But using @zerollup/ts-transform-paths and ttypescript it works 🎉

There is an awesome setup here: https://www.npmjs.com/package/@zerollup/ts-transform-paths#setup-for-rollup-plugin-typescript2

rollup.config.js

import ttypescript from 'ttypescript'
import tsPlugin from 'rollup-plugin-typescript2'
 
export default {
    input: 'src/lib.ts',
    output: [{ file : 'dist/lib.js', name : 'mylib', format : 'iife', sourcemap : true }],
    plugins: [
        tsPlugin({
            typescript: ttypescript
        })
    ]
}

tsconfig.json

{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "my-lib/*": ["src/*"]
        },
        "plugins": [
            {
                "transform": "@zerollup/ts-transform-paths",
                "exclude": ["*"]
            }
        ]
    }
}

@mantlebee
Copy link

Great! I solved in a different way, but it is not related to this plugin. I removed typescript-transform-path and ttypescript and leaved current plugin as is (typescript()). I used tsc-alias after rollup to solve the problem. It replaces even vue files because it processes .d.ts files after their generation.

@yankeeinlondon
Copy link

yankeeinlondon commented Nov 12, 2020

I used tsc-alias after rollup to solve the problem. It replaces even vue files because it processes .d.ts files after their generation.

@mantlebee Do you mind posting an example of your tsconfig.json as well as your rollup config?

@yankeeinlondon
Copy link

yankeeinlondon commented Nov 12, 2020

But using @zerollup/ts-transform-paths and ttypescript it works 🎉

@luanorlandi doesn't ts-transform-path work at runtime? Probably not a great idea for library code.

@mantlebee
Copy link

I used tsc-alias after rollup to solve the problem. It replaces even vue files because it processes .d.ts files after their generation.

@mantlebee Do you mind posting an example of your tsconfig.json as well as your rollup config?

Important properties are declaration, declarationDir and outDir

// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "declaration": true,
    "declarationDir": "dist/src",
    "baseUrl": ".",
    "outDir": "dist/src",
    "types": [
      "webpack-env",
      "jest"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

Simple rollup.config.js file

import path from "path";

import alias from "@rollup/plugin-alias";
import cleaner from "rollup-plugin-cleaner";
import scss from "rollup-plugin-scss";
import typescript from "rollup-plugin-typescript2";
import vue from "rollup-plugin-vue";

export default [
  {
    input: "src/index.ts",
    output: {
      format: "esm",
      file: "dist/elegere-ui.js"
    },
    plugins: [
      cleaner({ targets: ["dist/"] }),
      alias({
        resolve: [".ts", ".vue"],
        entries: { "@/": path.resolve(__dirname, "src/") }
      }),
      scss(),
      typescript({
        useTsconfigDeclarationDir: true
      }),
      vue()
      //{ css: false }
    ]
  }
];

Then I put everything into the prepublishOnly command (just because it's usefull to me)

"prepublishOnly": "npm run rollup && tsc-alias && npm run docs"

TSC-ALIAS will perform the replace inside the dist/src folder; I choose to put everything inside that because ts-alias will replace aliases thinking to be inside the src folder.

Not an optimal conclusion, but it works. I'll checkout the solution from @luanorlandi asap.

Of course, every other lib must be configured to avoid dist/src folder, like jest

@luanorlandi
Copy link

But using @zerollup/ts-transform-paths and ttypescript it works 🎉

@luanorlandi doesn't ts-transform-path work at runtime? Probably not a great idea for library code.

I don't think so, the transformer is just for build step, as the library describes it:

...tsconfig baseUrl + paths alias rewriting in bundles and declaration files. All them will be rewritted to relative in transpiled js and in d.ts files.

https://www.npmjs.com/package/@zerollup/ts-transform-paths

@tillsanders
Copy link

It took me quite a while to find the root cause of a very strange typing issue and it turned out to be this. I understand how this feature is intended, but since it appears to be so similar to what people are used from path aliases in various bundlers, maybe there should be at least a warning sign in the documentation? What do you think? This really caught me off guard ;)

@Stevemoretz
Copy link

Took me a while but here's my solution,

  1. install tsc-alias
  2. add my plugin to your config file
const tscAlias = () => {
    return {
        name: "tsAlias",
        buildStart: () => {
            return new Promise((resolve, reject) => {
                exec("tsc-alias", function callback(error, stdout, stderr) {
                    if (stderr || error) {
                        reject(stderr || error);
                    } else {
                        resolve(stdout);
                    }
                });
            });
        },
    };
};

add it to your plugins :

        plugins: [
            tscAlias(),
            dts(),
        ],

in my case, I'm using this before dts, because I'm building my plugin, so it runs first, if you want to run it at the end of your other plugins :

just change buildStart to writeBundle:

const tscAlias = () => {
    return {
        name: "tsAlias",
        writeBundle: () => {
            return new Promise((resolve, reject) => {
                exec("tsc-alias", function callback(error, stdout, stderr) {
                    if (stderr || error) {
                        reject(stderr || error);
                    } else {
                        resolve(stdout);
                    }
                });
            });
        },
    };
};

@neo15201788751
Copy link

A simple way: use tscpaths.
and shell: tscpaths -p tsconfig.json -s ./src -o ./dist

@michelecocuccio
Copy link

michelecocuccio commented Jan 17, 2022

@Stevemoretz hello. Thanks for this. I am having issues with dts() as well. Could you please show me an example of your rollup.config.js? I tried to reproduce yours but I have issues!

@Stevemoretz
Copy link

@Stevemoretz hello. Thanks for this. I am having issues with dts() as well. Could you please show me an example of your rollup.config.js? I tried to reproduce yours but I have issues!

Hello, yes of course, I'm using it for 4 libraries now and it has worked great ever since, here's the function:

const tscAlias = (number = 1) => {
    return {
        name: "tsAlias",
        writeBundle: () => {
            return new Promise((resolve, reject) => {
                exec("tsc-alias", function callback(error, stdout, stderr) {
                    if (stderr || error) {
                        reject(stderr || error);
                    } else {
                        moveToSubFolder(number)
                            .writeBundle(number)
                            .then(() => {
                                resolve(stdout);
                            });
                    }
                });
            });
        },
    };
};

that moveToSubFolder runs after the resolve, I wouldn't have needed it if rollup plugin orders wasn't messed up, which it is, anyways you don't probably need it.

and here's my config:

{
        input: `src/index.ts`,
        plugins: [
            nodeResolve(),
            nodePolyfills(),
            sourcemaps(),
            commonjs(),
            typescript({
                clean: true,
                tsconfigOverride: {
                    compilerOptions: {
                        sourceMap: true,
                    },
                },
            }),
            tscAlias(number),
        ],
        external: external(),
        output: [
            {
                file: `lib/${fileName}.es.js`,
                format: "es",
                sourcemap: !PRODUCTION,
                globals: [],
            },
        ],
        ...args,
    }

This just works fine and even source maps are fine, feel free to do it exactly the way I've done it if you have problems regarding source maps and other stuff.

@michelecocuccio
Copy link

michelecocuccio commented Jan 17, 2022

Thanks a lot for the quickly reply!!! So in my case (sorry to be a pain, is the first time I am setting up a rollup project from scratch to create a library and I followed this guide ) I have this in my rollup.config.js. Where should I put your function? Still in the same file? is exec something that I have to install as well?

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
import external from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';
import dts from 'rollup-plugin-dts';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';

const packageJson = require('./package.json');

export default [
  {
    input: 'src/index.ts',
    output: [
      {
        file: packageJson.main,
        format: 'cjs',
        sourcemap: true,
        name: 'react-ts-lib',
      },
      {
        file: packageJson.module,
        format: 'esm',
        sourcemap: true,
      },
    ],
    plugins: [
      peerDepsExternal(),
      external(),
      resolve(),
      commonjs(),
      typescript({ tsconfig: './tsconfig.json' }),
      postcss(),
      terser(),
    ],
  },
  {
    input: 'dist/esm/types/index.d.ts',
    output: [{ file: 'dist/index.d.ts', format: 'esm' }],
    external: [/\.css$/],
    plugins: [dts()],
  },
];

Thanks a lot @Stevemoretz

@Stevemoretz
Copy link

Stevemoretz commented Jan 17, 2022

Thanks a lot for the quickly reply!!! So in my case (sorry to be a pain, is the first time I am setting up a rollup project from scratch to create a library and I followed this guide ) I have this in my rollup.config.js. Where should I put your function? Still in the same file? is exec something that I have to install as well?

[... quote code block removed for readability ... ]

Thanks a lot @Stevemoretz

No problem! no exec is already included in NodeJs, you could try something like this:

import {exec} from "child_process";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import {terser} from "rollup-plugin-terser";
import external from "rollup-plugin-peer-deps-external";
import postcss from "rollup-plugin-postcss";
import dts from "rollup-plugin-dts";
import peerDepsExternal from "rollup-plugin-peer-deps-external";

const packageJson = require("./package.json");

const tscAlias = () => {
    return {
        name: "tsAlias",
        writeBundle: () => {
            return new Promise((resolve, reject) => {
                exec("tsc-alias", function callback(error, stdout, stderr) {
                    if (stderr || error) {
                        reject(stderr || error);
                    } else {
                        resolve(stdout);
                    }
                });
            });
        },
    };
};

export default [
    {
        input: "src/index.ts",
        output: [
            {
                file: packageJson.main,
                format: "cjs",
                sourcemap: true,
                name: "react-ts-lib",
            },
            {
                file: packageJson.module,
                format: "esm",
                sourcemap: true,
            },
        ],
        plugins: [
            peerDepsExternal(),
            external(),
            resolve(),
            commonjs(),
            typescript({tsconfig: "./tsconfig.json"}),
            postcss(),
            terser(),
            tscAlias(),
        ],
    },
    {
        input: "dist/esm/types/index.d.ts",
        output: [{file: "dist/index.d.ts", format: "esm"}],
        external: [/\.css$/],
        plugins: [dts()],
    },
];

also don't forget to run:

yarn add tsc-alias --dev

@agilgur5
Copy link
Collaborator

agilgur5 commented Sep 5, 2022

Hi folks, just doing some housekeeping in the issues. I'm going to close this one out as it's gone quite stale (no comments in 9 months) and is more or less out-of-scope for this plugin.

The various alternatives mentioned here, such as using transformers (particularly, @zerollup/ts-transform-paths and ttypescript) and in other issues (such as @rollup/plugin-alias), seem to be very well-used and successful.

These also seem to be a more appropriate way of handling this issue than, say, something built into rpt2, as rpt2 is intended to be a simple integration between Rollup and the TS Compiler API. Per above comments, rpt2 does not perform any transformations on your code other than running the TS Compiler API on it.

Path aliases are an additional transformation that TS does not do and has explicitly stated intent to not do (per above comments and issue references, tsconfig.json paths are intended for the opposite as well).

For the best compatibility with the ecosystem, rpt2 tries to stay as close as possible to tsc (with some Rollup semantics where necessary). So a feature like this would be a breaking change and also not necessarily desirable by all users.
Other tools in the ecosystem, such as ts-jest and ts-loader, are similar in being close to tsc. They also do not have this feature built-in, but can similarly achieve this through TS transformers.

As this is out-of-scope for rpt2 and broadly achievable through various other integrations, will be closing this one out.
That being said, we do want rpt2 to remain compatible with these various integrations for path aliases and do want users in need of such a transformation to find a way to do so, so please feel free to continue suggesting other methods of achieving path alias transformations!

@agilgur5 agilgur5 closed this as completed Sep 5, 2022
@agilgur5 agilgur5 added the kind: discussion Discussion on changes to make label Sep 5, 2022
@agilgur5 agilgur5 pinned this issue Sep 5, 2022
@agilgur5 agilgur5 changed the title Resolve path alias in declaration files (d.ts) Resolve path alias in declaration files (.d.ts) Sep 5, 2022
@ezolenko ezolenko unpinned this issue Sep 12, 2022
dlguswo333 added a commit to dlguswo333/react-window-infinite-scroll that referenced this issue Jul 25, 2023
This bug is related to rollup-plugin-typescript2 plugin.
ezolenko/rollup-plugin-typescript2#201
@IrinaStoetski
Copy link

@Stevemoretz thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
controversial kind: discussion Discussion on changes to make kind: feature New feature or request problem: stale Issue has not been responded to in some time solution: out-of-scope This is out of scope for this project solution: tsc behavior This is tsc's behavior as well, so this is not a bug with this plugin solution: workaround available There is a workaround available for this issue
Projects
None yet
Development

No branches or pull requests