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

Declaration generated in wrong folder when using object as input (multi-entry) #136

Open
benkeen opened this issue Feb 5, 2019 · 35 comments
Labels
help wanted kind: bug Something isn't working properly solution: workaround available There is a workaround available for this issue

Comments

@benkeen
Copy link

benkeen commented Feb 5, 2019

What happens and why it is wrong

Possibly this is a misconfiguration on my end, but it looks like a bug. When you use an object as the input property for the rollup configuration, the typings file for the input files are generated in the root folder of the repo (same folder as the rollup config file) and not the target output directory (here, ./dist) along with the bundled JS file.

Versions

  • typescript: 3.2.4
  • rollup: 1.1.2
  • rollup-plugin-typescript2: 0.19.2

rollup.config.js

{
    ...
    input: {
        Lib1: './src/Lib1.tsx',
        Lib2: './src/Lib2.tsx'
    },
    output: {
        dir: './dist',
        format: 'cjs',
        sourcemap: true,
        entryFileNames: '[name].js'
    }
}

tsconfig.json

{
  ...
  "compilerOptions": {
    "outDir": "./dist"
  },
}

Result:

// These files are created
./Lib1.d.ts
./Lib2.d.ts

// instead of (expected):
./dist/Lib1.d.ts
./dist/Lib2.d.ts

Curiously, if you enter entryFileNames: 'dist/[name].js' it creates a superfluous subfolder called dist within the dist folder and creates them there.

@ezolenko ezolenko self-assigned this Feb 6, 2019
@ezolenko ezolenko added the kind: bug Something isn't working properly label Feb 19, 2019
@jakearchibald
Copy link

The useTsconfigDeclarationDir plugin option is a workaround for now.

@ezolenko
Copy link
Owner

ezolenko commented Mar 11, 2019

This might be fixed by #142, released in 0.20.0

@benkeen
Copy link
Author

benkeen commented Mar 25, 2019

Hi @ezolenkom & @jakearchibald, no luck on either actually.

When adding the useTsconfigDeclarationDir option in my rollup plugin the declaration files are no longer created at all. And updating to 0.20.1 didn't make a difference, unfortunately.

@ezolenko
Copy link
Owner

@benkeen when using useTsconfigDeclarationDir: true plugin option you also need to have declarationDir value in tsconfig.json

@AntonPilyak
Copy link

AntonPilyak commented Apr 15, 2019

have similar trouble, my project layout is like this:

export default [ 
  {
    input: 'src/subFolder1/one.ts',
    output: [
                    {
                      file: 'dist/one.js'
                      // .....                      
                     }
    ],
    plugins: [
           typescript({
                typescript: require('typescript'),
            }),
    ]
  }, {
      input: 'src/subFolder2/two.ts',
      output: [
                    {
                      file: 'dist/two.js'
                      // .....                      
                     }
    ],
    // ...
  },
]

As an output I have:

dist
  |---subFolder1
  |          |--------one.d.ts
  |
  |---subFolder2
  |          |---------two.d.ts
  |-----one.js
  |-----two.js

@ezolenko
Copy link
Owner

ezolenko commented Apr 23, 2019

Works for me on 0.20.1 with useTsconfigDeclarationDir: false -- d.ts files go into the folders specified in output.[].dir. @benkeen could you try again?

@AntonPilyak that is by design -- if d.ts were going into one folder without sub paths, then if you had subFolder1/one.ts and subFolder2/one.ts, one of those would be overwriting another.

@ezolenko ezolenko added the problem: needs more info This issue needs more information in order to handle it label Apr 23, 2019
@AntonPilyak
Copy link

@ezolenko : this feature, I think, creates more inconveniences rather then solves a real problem (due to it is hard to maintain all those directory structure). I have to create a script that copies all these type declarations to the root of the bundle, and add the directories to .npmignore in order to create a bundle that is easy to include. I would suggest you to create these additional directories ONLY for the type files with the same names. And use a directory defined in 'output' section of rollup.config.json for the rest.

@ezolenko
Copy link
Owner

@AntonPilyak you might be able to use useTsconfigDeclarationDir: true option, setup declarationDir in tsconfig, this should let typescript itself handle the typings output paths.

@ezolenko
Copy link
Owner

Deciding if subdirectories should be used based on uniqueness of the paths would be a nightmare in an evolving project, suddenly your typing would move to a subfolder only because you added a new file with the same name in a different folder.

Normally if you want pretty typings you will want to merge them into one file anyway (there was an npm module I keep forgetting the name of for that)

@maranomynet
Copy link

maranomynet commented Jul 25, 2019

I am using version 0.22.0 and I have pretty much the same problem as @AntonPilyak

{
    // ...
    input: {
        foo: 'src/lorem/foo.ts',
        bar: 'src/ipsum/bar.ts'
    },
    output: {
        dir: 'dist',
        format: 'cjs',
        sourcemap: true,
    }
}

And I get this out:

dist/
    lorem/
        foo.d.ts
    ipsum/
        bar.d.ts
    foo.js
    foo.js.map
    bar.js
    bar.js.map

Obviously, I'd like the *.d.ts files to sit alongside the *.js and *.js.map files.

I've tried fiddling with useTsconfigDeclarationDir and declarationDir to no avail.

The definition files always end up inside lorem and ipsum sub-folders respectively, and never in a flat list.

@maranomynet
Copy link

maranomynet commented Jul 25, 2019

Moreover it seems like it creates declaration files for every .ts file it finds, not just the entry points.

dist/
    lorem/
        foo.d.ts
    ipsum/
        utils/
            bar-helper.d.ts
        bar.d.ts
     some_other_ts_file/
         not_imported_by/
             neither_foo_nor_bar/
                  wat.d.ts
    foo.js
    foo.js.map
    bar.js
    bar.js.map

I've tried scoping options.tsconfigOverride.input to just the entry points, but that seems to have no effect whatsover. In fact I'm not even sure if tsconfigOverride.input works at all.

This is all a bit confusing.

...

I mean, fair enough if it needs to create a declaration file for utils/bar-helper.ts but at least wat.ts should be left out of the whole thing.

@ezolenko
Copy link
Owner

Yeah, it generates declarations for all files found by tsconfig, if you don't want particular file touched, make sure it is excluded or not included in tsconfig. To see list of files found by typescript, run plugin with verbosity 3 and look for "parsed tsconfig" entry.

@ezolenko
Copy link
Owner

For definitions path, where in your example would you want definition for 'src/ipsum/foo.ts' to sit?

@maranomynet
Copy link

Since it is a definition for dist/foo.js I had assumed it would end up next to it - just like the source map file – i.e. in dist/foo.d.ts.

Will TypeScript know to pair up dist/foo.js and dist/lorem/foo.d.ts?

@ezolenko
Copy link
Owner

ezolenko commented Jul 25, 2019

The full scenario where this breaks down is:
src/dir1/foo.ts
src/dir2/foo.ts

dir1/foo.ts us set as rollup input and it imports dir2/foo.ts. Rollup will create dist/foo.js bundle that will contain relevant parts of both source files, but typescript needs to create 2 type definition files with the same name somewhere.

Easy solution is to use subfolders, mirroring source layout. I think that's what tsc does as well, unless you tell it to merge type definitions (rpt2 doesn't support definition merging).

And yes, typescript will know where to find type definitions. So aside from being a bit messy, this shouldn't be a problem. If you want to deploy package with types on npm, set "types" in package.json to d.ts file for your entry point. Typescript will find things from there.

@maranomynet
Copy link

maranomynet commented Jul 25, 2019

Ah, so this plugin doesn't really manage any part of the declaration-build process. It just fires up tsc to do its thing, irrelevant of Rollup's process?

Still, if I'm only exposing/publishing a couple of modules, why would I want TypeScript to generate *.d.ts files for every single ts file it encounters? Is there no way to limit it to just the entry points?

@maranomynet
Copy link

maranomynet commented Jul 25, 2019

Thing is, I'm exporting a set of standalone modules (import tool1 from 'myTools/tool1'; etc..) so there's no single entry point to use in pkg.types. Each definition file must reside next to its *.js counterpart for VSCode to pick it up.

Worse still, if I have

{
    // ...
    input: {
        fooscript: 'src/foo/index.ts',
        barscript: 'src/bar/index.ts'
    },
    output: {
        dir: 'dist',
        format: 'cjs',
        sourcemap: true,
    }
}

I get:

dist/
    foo/
        index.d.ts
    bar/
        index.d.ts
    fooscript.js
    fooscript.js.map
    barscript.js
    barscript.js.map

And now there's no way for TypeScript to pair up dist/fooscript.js and dist/foo/index.d.ts.

...

Is there no way your plugin could feed TypeScript the destination/output path for each JavaScript bundle file? It seems that tsc is receiving the original entry filename and holding on to it tight when generating the declarations.
...or for each of the input files hunt for the corresponding *.d.ts file and move+rename it to the input-file map destination. (You may notice I am talking out of my ass here, so... ;-)

This is something I'm bound to try manually after Rollup has finished - but it would be so much nicer if the plugin would handle it automatically.

@ezolenko
Copy link
Owner

ezolenko commented Jul 26, 2019

No, plugin uses LanguageService (same typescript API IDEs are usually using), so I have some control on what to write and where.

To write one type definition per rollup input we'll need to merge definitions for all imports for that input. Rollup might be seeing one input ts file, but it could be importing and bundling any number of source files when building that, those need types generated as well.

Currently I'm erring on the side of generating definitions for everything typescript finds when looking at tsconfig file. If I generate types only for transpiled modules, type only files will be ignored.

I might have to revisit that part, API changed considerable since that part was done...

I'll have to look at how to make your example work. Might need better support for multiple bundles in one rollup config.

How do you consume that package after building it though? It is not something you can publish on npm and import by package name...

@maranomynet
Copy link

maranomynet commented Jul 26, 2019

I'm making a collection of standalone utilities. The root-folder of the published/installed package contains all the built module files in a flat list.

(I'm writing them in TS/ES6 in a nested folder-structure, with test-files mixed in, and relying on Rollup to convert them to a flat list of CJS files with an efficient mix of code-splitting and inlining for any shared code.)

I then consume these tools by importing them by file-name – like so:

import tool1 from 'myTools/tool1';
import tool2 from 'myTools/tool2';

The idea is that by not having a single entry point for the whole toolkit (no pkg.main or pkg.module entries), I can be care-free to add rarely-used, and even experimental, tools without adding weight/bloat for downstream consumers (which might not have efficient tree-shaking).

@maranomynet
Copy link

maranomynet commented Jul 26, 2019

...I've been playing with tsc on the commandline a bit now, and I think I see now the limitations you're dealing with.

I noticed, however, that if you run tsc and set the module/output format to either "amd" or "system", it generates a single *.d.ts file with a neat list of all the necessary declare module "..." blocks inlined.

I wonder if this behavior could be exploited somehow - via file rename/move and unwrapping/rewriting the last (main) module declaration?


Side note: I also notice that tsc doesn't seem to put much effort into tree-shaking that declaration file. In one case I see a main (entry point) module that exports a single, very simple, function signature, and yet the declaration file exposes declaration blocks for all the private modules used "behind the scenes". Funny. :-)

@maranomynet
Copy link

maranomynet commented Jul 30, 2019

Hi again.
For now, I'm setting a custom declarationDir for tsc to dump all the *.d.ts into, and then run a standalone script that imports the same entry-file-to-output-file object as I feed to Rollup, and it generates bundle-destination-level declaration files that simply export * from the actual declaration file.

Essentially, I generate dist/fooscript.d.ts which contains only:

export *  from './__types/foo/index';

It's an ugly standalone hack, but it provides predictably correct results.

I wonder if rollup-plugin-typescript2 could do something similar?

@ezolenko
Copy link
Owner

That would work, yeah.

I have a couple of ideas (when I get around to them), for example generating <bundlename>.d.ts that has bunch of /// <reference types=""/> for each d.ts involved.

First I might have to fix related bug of generating too many type declarations and limit that to only root file and anything it actually imports.

@maranomynet
Copy link

maranomynet commented Jul 31, 2019

A few thoughts:

IMO, the only truly ugly part of my hack is the fact that it stands apart from the Rollup process. The intermediary *.d.ts are actually a pretty neat and idiomatic way to bridge between Rollup's generated bundles and tsc's definitions.

Since the tsc-generated declaration files reference each other via relative paths, that file/folder structure is probably best left alone – lest you end up re-implementing parts of Rollup's functionality, for a custom language syntax.

And if rollup-plugin-typescript2 sets something like output.dir + '__types' as the default declarationDir, then any mess caused by tsc's over-eagerness is neatly swept out-of-sight inside a clearly-named folder. That way the extraneous definition files become a very minor concern IMO.


FWIW, here's the meat of my script:

const { makeInputMap, getEntrypoints, distFolder, srcFolder } = require('./buildHelpers');
const { writeFileSync } = require('fs');
const { relative } = require('path');

const srcPrefixRe = new RegExp('^' + srcFolder + '/');
const tsExtRe = /\.tsx?$/;

const declDirRelative = './' + relative(
  distFolder,
  require('../tsconfig.json').compilerOptions.declarationDir
);

const tsEntrypoints = getEntrypoints()
  .filter((fileName) => tsExtRe.test(fileName));

Object.entries(makeInputMap(tsEntrypoints))
  .forEach(([moduleName, sourcePath]) => {
    const tscDeclFile = sourcePath
      .replace(srcPrefixRe, declDirRelative + '/')
      .replace(tsExtRe, '');
    const outFile = distFolder + '/' + moduleName + '.d.ts';
    writeFileSync(
      outFile,
      [
        'export * from "' + tscDeclFile + '";',
        'import x from "' + tscDeclFile + '";',
        'export default x;',
        '',
      ].join('\n')
    );
  });

console.info('Created local declaration files for TypeScripted entrypoints.');

@maranomynet
Copy link

I updated the example above, as it seems that default exports have to be explicitly re-exported.

Preliminary testing indicates that blindly re-exporting default from a declaration file that has no default export is harmless, and silently ignored. (At least by VSCode.)

@maranomynet
Copy link

Hi, should this issue be resolved in v0.23.0 ?

(...wondering if I should try and update my project to drop all of the custom faffing)

@ezolenko
Copy link
Owner

0.23 will not generate declaration for everything in sight, but it will still generate declarations you actually import in their relative subfolders. You can probably update and have lighter types, but there is nothing to generate root type declarations yet.

@maranomynet
Copy link

My custom workaround build script (see above) is gradually creeping into more and more of my projects. o_O

@maranomynet
Copy link

FYI: Development of the official @rollup/plugin-typescript package has since resumed, and it now features type-checking and outputting declaration files, so I'm personally switching over to use that.
The limitations are much the same, however.

@chaance
Copy link

chaance commented May 18, 2020

@maranomynet Do you still rely on your script to move the declaration files after rollup does its thing? I didn't have much luck switching myself.

@maranomynet
Copy link

maranomynet commented May 18, 2020

Yup. that seems to be the only way unless you let tsc do the bundling end-to-end.
These rollup plugins seem to be effecively asking tsc to do two different tasks:

  1. "Type-check and convert all these individual modules to JavaScript" and let me (i.e. Rollup) do the bundling and writing to disk.
  2. "Make all the declarations for this TS project folder and dump them over there."

The results of 1 and 2 don't, and can't match when you're using an "input map" and code-splitting, etc. – AFAICT.

...unless you make the rollup plugin a whole lot more involved in the type-declaration generation process.

@chaance
Copy link

chaance commented May 18, 2020

Ah yeah that's sort of what I figured, thanks! Gave you a 👍 for your helpful answer, and the 👎 is for how I feel about it.

@aamirafridi

This comment was marked as duplicate.

@waleeedahmed
Copy link

waleeedahmed commented Mar 8, 2021

For any struggling soul out there that has the same src folder structure as @maranomynet and me.

I came across a quick fix for it, may be another ugly way but works for me.

I'm using rollup-plugin/typescript2 so I'll go with its config in this answer:
Define your own declarationDir in your tsconfig.json so your project can dump all *.d.ts files in its own path in the bundled dist. And make sure to set the useTsConfigDeclarationDir to true in your typescript plugin in rollup config file.
Also, where you define the output paths for your individual component bundles in your rollup.config.js (and package.json), change those paths to be the same as your 'declarationDir' + 'how your src component route is'. So if your component in src is like:

src/homepage/foo.tsx

And your declarationDir is:

dist/MyDeclarationDir

So your output path needs to be like:

dist/MyDeclarationDir/homepage/foo.js

This way, rollup will include your types in the same directory as your component main.js and your TS consumer project will pick up the typings.

So the bundle will look something like:

dist/

    declarationDirPath/
                  component1/
                        foo.js/
                              foo.js
                              foo.map.js
                        foo.d.ts
                   component2/
                        bar.js/
                               bar.js
                               bar.map.js
                        bar.d.ts

@agilgur5 agilgur5 added help wanted solution: workaround available There is a workaround available for this issue and removed problem: needs more info This issue needs more information in order to handle it labels May 2, 2022
@agilgur5
Copy link
Collaborator

agilgur5 commented May 2, 2022

So I responded in #207 (comment) which appears to duplicate this issue (although OP here might have actually been experiencing a different issue, perhaps with rootDir? the thread here has since evolved though).

Per my comment there, there are a few workarounds I can think of:

  1. using Rollup's preserveModules may get this to work out-of-the-box. Has anyone here tried this?
  2. adding "alias" stub files that just export the component in a different directory. that indirection should allow the types to work too. I think this has been mentioned here already
  3. declaration bundling with rollup-plugin-dts or something else as mentioned above

@agilgur5
Copy link
Collaborator

agilgur5 commented May 8, 2022

Another possible workaround:

  1. Use package exports to specify types for each entry. This might be the most straightforward as you may already be using package exports if you have multiple entries.

@agilgur5 agilgur5 changed the title Typings file generated in wrong folder within object as input rollup Typings file generated in wrong folder when using object as input Jul 14, 2022
@agilgur5 agilgur5 changed the title Typings file generated in wrong folder when using object as input Declaration generated in wrong folder when using object as input Jul 14, 2022
@agilgur5 agilgur5 changed the title Declaration generated in wrong folder when using object as input Declaration generated in wrong folder when using object as input (multi-entry) Aug 30, 2022
@ezolenko ezolenko removed their assignment Oct 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted kind: bug Something isn't working properly solution: workaround available There is a workaround available for this issue
Projects
None yet
Development

No branches or pull requests

9 participants