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

Building multiple projects with same top-level configs (Rollup, tsconfig) results in declarations for *all* projects output for each project #72

Closed
Alxandr opened this issue Mar 30, 2018 · 22 comments
Labels
kind: support Asking for support with something or a specific use case problem: stale Issue has not been responded to in some time solution: intended behavior This is not a bug and is expected behavior solution: tsc behavior This is tsc's behavior as well, so this is not a bug with this plugin topic: monorepo / symlinks Related to monorepos and/or symlinks (Lerna, Yarn, PNPM, Rush, etc)

Comments

@Alxandr
Copy link

Alxandr commented Mar 30, 2018

What happens and why it is wrong

I'm building 2 projects with one rollup.config file, and the result is this:

image

As can be seen, I get the .d.ts files for both projects in both dist folders.

Environment

OSX.

Versions

  • typescript: 2.8.1
  • rollup: 0.57.1
  • rollup-plugin-typescript2: 0.12.0

rollup.config.js

It's auto-generated, so I can't really paste it.

tsconfig.json

{
  "compilerOptions": {
    "strict": true,
    "module": "esnext",
    "target": "es2016",
    "declaration": true
  },
  "exclude": ["node_modules", "**/node_modules/*"]
}

plugin output with verbosity 3

log
/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/src/before-exit.ts → packages/before-exit/dist/before-exit.js, packages/before-exit/dist/before-exit.mjs...
rpt2: typescript version: 2.8.1
rpt2: rollup-plugin-typescript2 version: 0.12.0
rpt2: plugin options:
{
    "verbosity": 3,
    "cacheRoot": "/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/.rpt2_cache",
    "exclude": [
        "*.d.ts",
        "**/*.d.ts",
        "/Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/*"
    ],
    "check": true,
    "clean": false,
    "include": [
        "*.ts+(|x)",
        "**/*.ts+(|x)"
    ],
    "abortOnError": true,
    "rollupCommonJSResolveHack": false,
    "typescript": "version 2.8.1",
    "useTsconfigDeclarationDir": false,
    "tsconfigOverride": {},
    "tsconfigDefaults": {}
}
rpt2: rollup config:
{
    "external": [
        "",
        "assert",
        "buffer",
        "child_process",
        "cluster",
        "console",
        "constants",
        "crypto",
        "dgram",
        "dns",
        "domain",
        "events",
        "fs",
        "http",
        "https",
        "module",
        "net",
        "os",
        "path",
        "punycode",
        "querystring",
        "readline",
        "repl",
        "stream",
        "string_decoder",
        "sys",
        "timers",
        "tls",
        "tty",
        "url",
        "util",
        "vm",
        "zlib",
        "v8",
        "process",
        "async_hooks",
        "http2",
        "perf_hooks"
    ],
    "input": "/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/src/before-exit.ts",
    "perf": false,
    "plugins": [
        {
            "name": "auto-external"
        },
        {
            "name": "rpt2"
        }
    ],
    "entry": "/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/src/before-exit.ts"
}
rpt2: built-in options overrides: {
    "noEmitHelpers": false,
    "importHelpers": true,
    "noResolve": false,
    "noEmit": false,
    "outDir": "/Users/alxandr/hub/yolodev-aspnet-utils",
    "moduleResolution": 2,
    "declarationDir": "/Users/alxandr/hub/yolodev-aspnet-utils"
}
rpt2: parsed tsconfig: {
    "options": {
        "module": 6,
        "strict": true,
        "target": 3,
        "declaration": true,
        "noEmitHelpers": false,
        "importHelpers": true,
        "noResolve": false,
        "noEmit": false,
        "outDir": "/Users/alxandr/hub/yolodev-aspnet-utils",
        "moduleResolution": 2,
        "declarationDir": "/Users/alxandr/hub/yolodev-aspnet-utils",
        "configFilePath": "/Users/alxandr/hub/yolodev-aspnet-utils/tsconfig.json"
    },
    "fileNames": [
        "/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/dist/before-exit/src/before-exit.d.ts",
        "/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/dist/workdir-pid/src/workdir-pid.d.ts",
        "/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/src/before-exit.ts",
        "/Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/dist/before-exit/src/before-exit.d.ts",
        "/Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/dist/workdir-pid/src/workdir-pid.d.ts",
        "/Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/src/workdir-pid.ts"
    ],
    "typeAcquisition": {
        "enable": false,
        "include": [],
        "exclude": []
    },
    "raw": {
        "compilerOptions": {
            "module": "esnext",
            "strict": true,
            "target": "es2016",
            "declaration": true
        },
        "exclude": [
            "node_modules",
            "**/node_modules/*"
        ]
    },
    "errors": [],
    "wildcardDirectories": {
        "/users/alxandr/hub/yolodev-aspnet-utils": 1
    },
    "compileOnSave": false,
    "configFileSpecs": {
        "includeSpecs": [
            "**/*"
        ],
        "excludeSpecs": [
            "node_modules",
            "**/node_modules/*"
        ],
        "validatedIncludeSpecs": [
            "**/*"
        ],
        "validatedExcludeSpecs": [
            "node_modules",
            "**/node_modules/*"
        ],
        "wildcardDirectories": {
            "/users/alxandr/hub/yolodev-aspnet-utils": 1
        }
    }
}
rpt2: included:
'[
    "*.ts+(|x)",
    "**/*.ts+(|x)"
]'
rpt2: excluded:
'[
    "*.d.ts",
    "**/*.d.ts",
    "/Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/*"
]'
rpt2: Ambient types:
rpt2:     /Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/dist/before-exit/src/before-exit.d.ts
rpt2:     /Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/dist/workdir-pid/src/workdir-pid.d.ts
rpt2:     /Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/dist/before-exit/src/before-exit.d.ts
rpt2:     /Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/dist/workdir-pid/src/workdir-pid.d.ts
rpt2:     /Users/alxandr/hub/yolodev-aspnet-utils/node_modules/@types/acorn/index.d.ts
rpt2:     /Users/alxandr/hub/yolodev-aspnet-utils/node_modules/@types/estree/index.d.ts
rpt2:     /Users/alxandr/hub/yolodev-aspnet-utils/node_modules/@types/node/index.d.ts
rpt2: ambient types changed, redoing all semantic diagnostics
rpt2: transpiling '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/src/before-exit.ts'
rpt2:     cache: '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/.rpt2_cache/0df88d81930e5b98b75cf91f784bf1ae5bbd236a/code/cache/2d9c6a3ef228d04e8594fac73f6ee4cdd1c12da5'
rpt2:     cache miss
rpt2:     cache: '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/.rpt2_cache/0df88d81930e5b98b75cf91f784bf1ae5bbd236a/syntacticDiagnostics/cache/2d9c6a3ef228d04e8594fac73f6ee4cdd1c12da5'
rpt2:     cache miss
rpt2:     cache: '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/.rpt2_cache/0df88d81930e5b98b75cf91f784bf1ae5bbd236a/semanticDiagnostics/cache/2d9c6a3ef228d04e8594fac73f6ee4cdd1c12da5'
rpt2:     cache miss
rpt2: generated declarations for '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/src/before-exit.ts'
rpt2: generating target 1
rpt2: rolling caches
rpt2: generating missed declarations for '/Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/src/workdir-pid.ts'
rpt2: writing declarations for '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/src/before-exit.ts' to '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/dist/before-exit/src/before-exit.d.ts'
rpt2: writing declarations for '/Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/src/workdir-pid.ts' to '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/dist/workdir-pid/src/workdir-pid.d.ts'
rpt2: generating target 2
rpt2: rolling caches
rpt2: writing declarations for '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/src/before-exit.ts' to '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/dist/before-exit/src/before-exit.d.ts'
rpt2: writing declarations for '/Users/alxandr/hub/yolodev-aspnet-utils/packages/workdir-pid/src/workdir-pid.ts' to '/Users/alxandr/hub/yolodev-aspnet-utils/packages/before-exit/dist/workdir-pid/src/workdir-pid.d.ts'
created packages/before-exit/dist/before-exit.js, packages/before-exit/dist/before-exit.mjs in 1s
@ezolenko
Copy link
Owner

ezolenko commented Mar 31, 2018

Looks like your tsconfig includes everything under yolodev-aspnet-utils/packages/ including dist apparently so typescript compiler uses output definitions from previous build as ambient types for current build -- this is almost always wrong.

This plugin generates definitions for all ts files typescript sees during compilation (instead of only generating types for file rollup requests during build) because rollup ignores type-only imports. In your case you see a side-effect of that.

If you run tsc in the folder your current tsconfig is in, it will build both of your projects as well.

What tool do you use for generating rollup config? If it has a way to pass options to this plugin you can either create two tsconfigs with appropriate include/exclude sections and pass their names as tsconfig parameter, or you could use one tsconfig, but pass in tsconfigOverride with appropriate includes.

@zerkalica
Copy link

zerkalica commented Apr 9, 2018

Same problem, plugin generates wrongs dts paths in lerna environment.
d.ts generates in packages/*/src/dist/<package.name>/src

Look at my project https://github.com/zerkalica/zerollup
rollup.config.js

    plugins: [
        typescript({
            abortOnError: true,
            check: true,
            exclude: ['*.spec*', '**/*.spec*'],
            tsconfig: path.join(__dirname, 'tsconfig.json'),
            tsconfigOverride: {
                // compilerOptions: {
                //     rootDir: cwd 
                // },
                include: [
                    __dirname + '/packages/*/src'
                ]
            }
        })
    ],

With rootDir uncommented, ts generates errors:

rpt2: options error TS6059 File '/user/projects/zerollup/packages/injector/src/index.ts' is not under 'rootDir' '/user/projects/zerollup/packages/helpers'. 'rootDir' is expected to contain all source files.

tsconfig.json

{
	"compilerOptions": {
		"baseUrl": "packages",
		"paths": {
			"@zerollup/*": [ "./*/src" ]
		},
		"allowSyntheticDefaultImports": false,
		"declaration": true,
		"module": "esnext",
		"target": "es5",
		"moduleResolution": "node",
		"sourceMap": true,
		"lib": [
			"dom",
			"esnext"
		],
		"jsx": "react",
		"noImplicitAny": false,
		"noUnusedLocals": true,
		"noImplicitReturns": true,
		"alwaysStrict": true,
		"strictNullChecks": false,
		"strictFunctionTypes": true,
		"removeComments": false,
		"experimentalDecorators": true
	},
	"include": [
		"packages/*/src"
	],
	"exclude": ["node_modules", "**/node_modules/*"]
}

Inferno uses ts2 rollup plugin and have same problem with path: join(__dirname, '../../build/packages/', pkgJSON.name, 'src/index.d.ts')

https://github.com/infernojs/inferno/blob/master/scripts/typings/build.js#L13

@Alxandr
Copy link
Author

Alxandr commented Apr 9, 2018

I managed to get it working. For reference, this is the main part of the script I'm using to generate rollup config:

import autoExternal from 'rollup-plugin-auto-external';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import typescript from 'rollup-plugin-typescript2';

const readFile = promisify(fs.readFile);

export default async ({ location, main, name }) => {
  const pkg = JSON.parse(
    await readFile(path.resolve(location, 'package.json'), {
      encoding: 'utf-8',
    }),
  );

  return {
    input: path.resolve(location, main),
    output: [
      { format: 'cjs', file: path.resolve(location, 'dist', `${name}.js`) },
      { format: 'es', file: path.resolve(location, 'dist', `${name}.mjs`) },
    ],
    plugins: [
      autoExternal({
        builtins: true,
        dependencies: true,
        packagePath: location,
        peerDependencies: true,
      }),
      typescript({
        tsconfigOverride: {
          compilerOptions: {
            rootDir: path.resolve(location, 'src'),
          },
          include: [path.resolve(location, 'src')],
        },
      }),
    ],
  };
};

@ezolenko ezolenko self-assigned this Apr 9, 2018
@zerkalica
Copy link

zerkalica commented Apr 9, 2018

It's not works for package in package directory which depends on another package in package directory.
in my repo packages/plugin-template has dependency packages/helpers

npm run build.plugin-template

rpt2: options error TS6059 File '/home/user/projects/zerollup/packages/helpers/src/index.ts' is not under 'rootDir' '/home/user/projects/zerollup/packages/plugin-template'. 'rootDir' is expected to contain all source files.

@Alxandr
Copy link
Author

Alxandr commented Apr 9, 2018

@zerkalica You should remove your paths configuration, and rely on node resolution. If you have lerna setup correct, then lerna bootstrap should create symlinks for you under the projects. So for instance, if you have two modules (A, and B), and A depends on B, then you get a symlink from packages/a/node_modules/B to packages/b. Then you just need to configure typings in packages/b/package.json.

@Alxandr
Copy link
Author

Alxandr commented Apr 9, 2018

You can see my setup here: https://github.com/YoloDev/aspnet-node-utils

@zerkalica
Copy link

Thank you @Alxandr
It looks like some workaround, but paths overriding in rollup config works.
Removing paths from tsconfig.json breaks ts resolution on clean project without builds.

Working configs:

rollup.config.js

    plugins: [
        typescript({
            abortOnError: true,
            check: true,
            exclude: ['*.spec*', '**/*.spec*'],
            tsconfig: path.join(__dirname, 'tsconfig.json'),
            tsconfigOverride: {
                compilerOptions: {
                    paths: [],
                    rootDir: path.join(cwd, 'src')
                },
                include: [path.join(cwd, 'src')]
            }
        })
    ],

tsconfig.json

{
        "compilerOptions": {
                "baseUrl": "packages",
                "paths": {
                        "@zerollup/*": [ "./*/src" ]
                },
                "allowSyntheticDefaultImports": false,
                "declaration": true,
                "module": "esnext",
                "target": "es5",
                "moduleResolution": "node",
                "sourceMap": true,
                "lib": [
                        "dom",
                        "esnext"
                ],
                "jsx": "react",
                "noImplicitAny": false,
                "noUnusedLocals": true,
                "noImplicitReturns": true,
                "alwaysStrict": true,
                "strictNullChecks": false,
                "strictFunctionTypes": true,
                "removeComments": false,
                "experimentalDecorators": true
        },
        "exclude": ["node_modules", "**/node_modules/*"]
}

@ezolenko
Copy link
Owner

ezolenko commented Apr 9, 2018

@Alxandr @zerkalica looks like you both have a workaround. Doesn't look like I can do much to handle this situation automatically -- project structure information is unavailable to typescript before you define it in those overrides.

Another option, possibly cleaner, is to have a base tsconfig and individual tsconfigs for subprojects (located in subproject folders) that all extend base file (see extends option in tsconfig) and provide correct include values. Then give the plugin just the tsconfig option with correct file path for a given subproject.

@Alxandr
Copy link
Author

Alxandr commented Apr 9, 2018

@ezolenko Yes. I get that, but I was trying to get this working with minimal things having to be replaced per package. Also, I didn't know that it was caused by where my tsconfig lives, but thought instead it was caused by where my rollup config lived. Therefore I split my rollup config up into one per package, but that didn't help, and as such I made this issue. I would suggest having some documentation/examples for this in the readme :).

@zerkalica I can't speak to why you're not getting it working without keeping paths, but I can guess (as I don't have all the info). As said, the important part is in package.json, not tsconfig.json. You need to set typings to point to the entry ts file to get it to work before build. Again, you can see this working in my project.

[Edit]
I just noticed that you actually linked to your repository @zerkalica, and the "issue" is that your typings points to built files. Which does not exist before you build them. On the other hand, I have issues in that I need to ship the ts sources (which is probably even worse, and something I'm working on a fix for).

@zerkalica
Copy link

@Alxandr
Yes, I try to use prebuilded d.ts files in dist and do not include src into package

"types": "dist/index.d.ts",

With src directory included, we can completely disable d.ts generation. May be you right.

@ezolenko Have you some examples, how to do it without customizing tsconfig.json per each package in packages directory?

@ezolenko
Copy link
Owner

ezolenko commented Apr 10, 2018

@zerkalica I haven't tried that, but I suspect if you create base tsconfig file with all the interesting options in the root, then create per-package tsconfigs that extend base file but have no other options at all, then tell plugin the path to a given package tsconfig in tsconfig option -- then everything would work.

Typescript will use tsconfig location as your source root and default values for includes would do the right thing.

@Alxandr
Copy link
Author

Alxandr commented Apr 10, 2018

If I'm not mistaken, that doesn't work. When you extend a tsconfig, you get it's base path and include path. At least that used to be the case.

@Alxandr
Copy link
Author

Alxandr commented Apr 10, 2018

@zerkalica You're using lerna, right? I'm trying to set up a script (using lerna lifecycle methods) such that when the package is being published it changes the types value in package.json. So you can keep it pointed at src for dev (I even use it to get the entry-point). Then, just before deploy, it's changed to point to dist. I think this is likely the way to go.

@ezolenko
Copy link
Owner

If derived tsconfig inherits default includes from base, then you might need to provide includes in derived file (they replace ones from base). If project structure is similar, that can be the same value in all copies of derived files. It is only a cosmetic difference vs using override even if it works though.

@zerkalica
Copy link

zerkalica commented Apr 20, 2018

I try to use separate tsconfig.json per project (lerna demo project). And found a bug with multiple instances of ts plugin and declaration files. Use npm run bug for build with rollup.config.js.

rollup.config.js

import * as path from 'path'
import typescript from 'rollup-plugin-typescript2'
import resolve from 'rollup-plugin-node-resolve'
import replace from 'rollup-plugin-replace'
import commonjs from 'rollup-plugin-commonjs'

const pkgRoot = path.join(__dirname, 'packages')

export default [
    {
        input: path.join(pkgRoot, 'lib1', 'src', 'index.ts'),
        output: {
            format: 'es',
            file: path.join(pkgRoot, 'lib1', 'dist', 'index.mjs')
        },
        plugins: [
            resolve(),
            commonjs(),
            typescript({
                // verbosity: 3,
                tsconfig: path.join(pkgRoot, 'lib1', 'tsconfig.json')
            })
        ]
    },
    {
        input: path.join(pkgRoot, 'lib2', 'src', 'index.ts'),
        output: {
            format: 'es',
            file: path.join(pkgRoot, 'lib2', 'dist', 'index.mjs')
        },
        plugins: [
            resolve(),
            commonjs(),
            typescript({
                // verbosity: 3,
                tsconfig: path.join(pkgRoot, 'lib2', 'tsconfig.json')
            })
        ]
    }
]
ls packages/lib1/dist
index.d.ts  index.mjs  some
ls packages/lib2/dist
index.mjs  lib2
ls packages/lib2/dist/lib2/src
index.d.ts  some.d.ts

packages/lib2/tsconfig.json

{
        "extends": "../../tsconfig.json",
        "compilerOptions": {
                "paths": {
                        "~/*": ["lib1/src/*"],
                        "zerollup-demo-lib1/*": ["lib1/src/*"],
                        "zerollup-demo-*": ["*/src"]
                }
        }
}

tsconfig.json

{
	"compilerOptions": {
		"baseUrl": "packages",
		"newLine": "LF",
		"allowSyntheticDefaultImports": false,
		"module": "esnext",
		"target": "es5",
                "declaration": true,
		"moduleResolution": "node",
		"sourceMap": true,
		"lib": [
			"dom",
			"esnext"
		],
		"jsx": "react",
		"noImplicitAny": false,
		"noUnusedLocals": true,
		"noImplicitReturns": true,
		"alwaysStrict": true,
		"strictNullChecks": false,
		"strictFunctionTypes": true,
		"removeComments": false,
		"experimentalDecorators": true
	}
}

@Alxandr
Copy link
Author

Alxandr commented Apr 20, 2018

@zerkalica bug being? Just having a tsconfig does not make that the project root path. If you inherit from another tsconfig, and don't override base path or includes, it will get those from the parent one.

@ezolenko
Copy link
Owner

Does it work if you add include option in all derived tsconfigs?

@zerkalica
Copy link

@Alxandr What do you mean? baseUrl resolved relative main tsconfig.json. lib1 and lib2 configs inherits resolved baseUrl and typescript in vscode works fine with this aliases.

I try this, but this not helps:

packages/lib1/tsconfig.json

{
	"extends": "../../tsconfig.json",
	"compilerOptions": {
		"rootDir": ".",
		"paths": {
			"~/*": ["lib1/src/*"],
			"zerollup-demo-lib1/*": ["lib1/src/*"],
			"zerollup-demo-*": ["*/src"]
		}
	}
}

packages/lib2/tsconfig.json

{
	"extends": "../../tsconfig.json",
	"compilerOptions": {
		"rootDir": ".",
		"paths": {
			"~/*": ["lib2/src/*"],
			"zerollup-demo-lib2/*": ["lib2/src/*"],
			"zerollup-demo-*": ["*/src"]
		}
	}
}

@ezolenko
What i need to include? This not works:

packages/lib1/tsconfig.json

{
	"extends": "../../tsconfig.json",
	"compilerOptions": {
		"paths": {
			"~/*": ["lib1/src/*"],
			"zerollup-demo-lib1/*": ["lib1/src/*"],
			"zerollup-demo-*": ["*/src"]
		}
	},
        "include": [
          "./src"
        ]
}

packages/lib2/tsconfig.json

{
	"extends": "../../tsconfig.json",
	"compilerOptions": {
		"paths": {
			"~/*": ["lib2/src/*"],
			"zerollup-demo-lib2/*": ["lib2/src/*"],
			"zerollup-demo-*": ["*/src"]
		}
	},
        "include": [
          "./src",
          "../lib1/src"
        ]
}
ls packages/lib1/dist
index.mjs
ls packages/lib2/dist
index.mjs  lib1  lib2

@zerkalica
Copy link

zerkalica commented Apr 20, 2018

After investigation, i think, generate d.ts per package is a bad idea. Tilda paths not working correctly in vscode, too complex setup (setup alias plugin, includes, excludes, rootDir in tsconfig.json per each packgage), too much caveats (alias plugin not working for index files) and boilerplate.

Cleanest solution - is a completly disable d.ts generation, add src directory into package and set typings directory in package.json to src.

Hacked solultion - use two instances of ts-plugin: first insance in first rollup config block generates d.ts for all packages in temporal declarationDir directory, second instance in other rollup config blocks with disabled declarations. This solution needs a plugin, which copies generated in temp dir d.ts to packages/*/dist/typings.

rollup.config.js

rollup.config.js

import * as path from 'path'
import typescript from 'rollup-plugin-typescript2'
import resolve from 'rollup-plugin-node-resolve'
import replace from 'rollup-plugin-replace'
import commonjs from 'rollup-plugin-commonjs'
import * as fsExtra from 'fs-extra'

const pkgRoot = path.join(__dirname, 'packages')

const tsPlugin = typescript({
    tsconfig: path.join(__dirname, 'tsconfig.json'),
    useTsconfigDeclarationDir: true,
    tsconfigOverride: {
        compilerOptions: {
            declaration: true,
            declarationDir: path.join(__dirname, 'dist')
        }
    }
})

const tsPlugin2 = typescript({
    tsconfig: path.join(__dirname, 'tsconfig.json')
})

function moveDeclarations({declarationDir, packagesDir}) {
    return {
        name: 'move-ts-declarations',
        onwrite() {
            return fsExtra.readdir(declarationDir)
                .then(dirs => Promise.all(dirs.map(dir =>
                    fsExtra.ensureDir(path.join(packagesDir, dir, 'dist'))
                        .then(() =>
                            fsExtra.move(
                                path.join(declarationDir, dir, 'src'),
                                path.join(packagesDir, dir, 'dist', 'typings'),
                                {overwrite: true}
                            )
                        )
                )))
                .then(() => fsExtra.remove(declarationDir))
        }
    }
}


export default [
    {
        input: path.join(pkgRoot, 'lib1', 'src', 'index.ts'),
        output: {
            format: 'es',
            file: path.join(pkgRoot, 'lib1', 'dist', 'index.mjs')
        },
        plugins: [
            resolve(),
            commonjs(),
            tsPlugin,
            moveDeclarations({
                declarationDir: path.join(__dirname, 'dist'),
                packagesDir: path.join(__dirname, 'packages')
            })
        ]
    },
    {
        input: path.join(pkgRoot, 'lib2', 'src', 'index.ts'),
        output: {
            format: 'es',
            file: path.join(pkgRoot, 'lib2', 'dist', 'index.mjs')
        },
        plugins: [
            resolve(),
            commonjs(),
            tsPlugin2
        ]
    }
]

PS: I found another workaround. @Alxandr was right about rootDir and include. Problem in paths.
"zerollup-demo-*": ["*/src"] needed for ide, but not needed for builds.

configs

rollup.config.js part

            typescript({
                tsconfig: path.join(__dirname, 'tsconfig.base.json'),
                useTsconfigDeclarationDir: true,
                tsconfigOverride: {
                    compilerOptions: {
                        declaration: true,
                        rootDir: path.join(pkgRoot, 'lib2', 'src'),
                        declarationDir: path.join(pkgRoot, 'lib2', 'dist', 'typings')
                    },
                    include: [path.join(pkgRoot, 'lib2', 'src')]
                }
            })

tsconfig.base.json

{
	"compilerOptions": {
		"baseUrl": "packages",
		"paths": {
			"zerollup-demo-lib1/*": ["lib1/src/*"],
			"zerollup-demo-lib2/*": ["lib2/src/*"],
			"zerollup-demo-site1/*": ["site1/src/*"]
		},
		"newLine": "LF",
		"allowSyntheticDefaultImports": false,
		"module": "esnext",
		"target": "es5",
		"moduleResolution": "node",
		"sourceMap": true,
		"lib": [
			"dom",
			"esnext"
		],
		"jsx": "react",
		"noImplicitAny": false,
		"noUnusedLocals": true,
		"noImplicitReturns": true,
		"alwaysStrict": true,
		"strictNullChecks": false,
		"strictFunctionTypes": true,
		"removeComments": false,
		"experimentalDecorators": true
	},
        "exclude": ["dist", "node_modules"],
        "include": ["packages/*/src"]
}

tsconfig.json

{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
		"paths": {
			"zerollup-demo-lib1/*": ["lib1/src/*"],
			"zerollup-demo-lib2/*": ["lib2/src/*"],
			"zerollup-demo-site1/*": ["site1/src/*"],
			"zerollup-demo-*": ["*/src"]
		}
	}
}

@ezolenko You use deep merge for "paths" in tsconfigOverride. With deep merge i can't remove some paths keys or replace whole paths from inherited config. tsc uses shallow merge for it.

@ezolenko ezolenko removed their assignment Jul 18, 2018
@ezolenko ezolenko added the problem: needs more info This issue needs more information in order to handle it label Jan 18, 2019
@slavafomin
Copy link

Cleanest solution - is a completly disable d.ts generation, add src directory into package and set typings directory in package.json to src.

Publishing source files instead of type declarations is almost always a bad idea. You will have typing issues when users will try to use your library, especially if they have older version of the TypeScript or if they tsconfig configured differently.

@slavafomin
Copy link

slavafomin commented Aug 1, 2019

Just to share our experience. We also have a monorepo setup using Rush (better Lerna alternative) and we did have issues with type declaration generation paths. The thing is that we use our own built tool, which encapsulates Rollup and all configuration (I highly recommend this approach).

We've managed to achieve the desired result using this configuration:

const entryFilePath = `${projectPath}/index.ts`;

const typeScriptPluginOptions: any = {
  tsconfig: `${builderPath}/config/tsconfig.json`, // using one base tsconfig for all packages
  tsconfigOverride: {
    compilerOptions: <CompilerOptions> {
      rootDir: projectPath,
      typeRoots: [
        `${projectPath}/node_modules/@types`,
        `${projectPath}/types`,
      ],
    },
  },
  clean: isProduction,
  verbosity: (isDebug ? 3 : 1),
  cacheRoot: `${projectPath}/node_modules/.cache/rollup-plugin-typescript2`,
  typescript: TypeScript,
};


// THIS IS THE IMPORTANT PART:
// Setting the paths manually to override default paths set by the plugin
Object.assign(typeScriptPluginOptions.tsconfigOverride, {
  files: [entryFilePath],
  include: [
    `${projectPath}/*.ts`,
    `${projectPath}/src/**/*.ts`,
    `${projectPath}/src/**/*.tsx`,
  ],
  exclude: [],
});

if (declarations) {

  Object.assign(typeScriptPluginOptions.tsconfigOverride.compilerOptions, <CompilerOptions> {
    declaration: true,
    declarationDir: outputPath,
  });

  // Preventing plugin from overwriting the "declarationDir" option
  typeScriptPluginOptions.useTsconfigDeclarationDir = true;

}

kayw pushed a commit to kayw/astok that referenced this issue Jan 30, 2021
@agilgur5 agilgur5 added the solution: workaround available There is a workaround available for this issue label Jun 14, 2022
@agilgur5 agilgur5 changed the title Multi-build screws up definitions Building multiple projects with same top-level configs (Rollup, tsconfig) results in declarations for *all* projects output for each project Jun 14, 2022
@agilgur5 agilgur5 added solution: intended behavior This is not a bug and is expected behavior solution: tsc behavior This is tsc's behavior as well, so this is not a bug with this plugin topic: monorepo / symlinks Related to monorepos and/or symlinks (Lerna, Yarn, PNPM, Rush, etc) kind: support Asking for support with something or a specific use case problem: stale Issue has not been responded to in some time and removed problem: needs more info This issue needs more information in order to handle it solution: workaround available There is a workaround available for this issue labels Jun 14, 2022
@agilgur5
Copy link
Collaborator

agilgur5 commented Jun 14, 2022

not a bug, this is tsc behavior as well

Going to close this one out as this intentional behavior / tsc's behavior as well, so not a bug per se. It's also gone stale over the years.
I'll leave this unlocked though so people can share solutions with each other as this type of monorepo set-up with the same configs does require more configuration, but that is not because of a bug.

This plugin generates definitions for all ts files typescript sees during compilation (instead of only generating types for file rollup requests during build) because rollup ignores type-only imports. In your case you see a side-effect of that.

If you run tsc in the folder your current tsconfig is in, it will build both of your projects as well.

Per ezolenko's first comment, tsc will do the same thing.
The "side-effect" mentioned may be fixed by fixing #211, but I'm still not sure of my approach for that, as generating declarations for everything in include is tsc behavior, and we don't want to diverge from that too often (only when necessary for Rollup semantics) to follow the principle of least privilege and preserve high compatibility with the ecosystem.

solutions

Another option, possibly cleaner, is to have a base tsconfig and individual tsconfigs for subprojects (located in subproject folders) that all extend base file (see extends option in tsconfig) and provide correct include values. Then give the plugin just the tsconfig option with correct file path for a given subproject.

This is a common pattern I've seen in monorepos and I too think it's cleaner and would probably recommend it (off the top of my head).

we use our own built tool, which encapsulates Rollup and all configuration (I highly recommend this approach).

That's also an option, although having solo-maintained TSDX for ~1.5 years (which is an encapsulated build tool), I will definitely warn that you can also run into too many edge cases this way. Extending a base tsconfig and base Rollup config grants more flexibility and compatibility (that's what I'm doing now in my own library boilerplate: agilgur5/ts-library-base#4).

But you may not need as much flexibility within a smaller monorepo; due to TSDX's popularity one sees the 80/20 problem (Pareto principle): while an encapsulated build can handle 80% of use-cases fairly well, the other 20% of use-cases can create a lot of complexity and compatibility issues.

gotchas

When you extend a tsconfig, you get it's base path and include path. At least that used to be the case.

This is definitely a common gotcha to watch out for -- most relative paths (e.g. include, exclude, files, etc) will need to be repeated in inheriting tsconfigs due to microsoft/TypeScript#29172 (also mentioned in the TS docs for extends).
That is similarly tsc behavior though, so rpt2 inherits that behavior from the TS API.

@ezolenko You use deep merge for "paths" in tsconfigOverride. With deep merge i can't remove some paths keys or replace whole paths from inherited config. tsc uses shallow merge for it.

It's linked above already but I think that's come to be considered a bug over time: #86.
The fix is not altogether that difficult (though I believe the TS docs are incomplete with regard to what is affected by this), but it will technically be a breaking change, so that's been a bit lower on my list of priorities

// Setting the paths manually to override default paths set by the plugin

rpt2 doesn't set any default paths. All forced defaults are explicitly documented (see also the source code here) and none of those touch any path'd config.

TypeScript itself has default paths though, such as for include and exclude, so rpt2 inherits those defaults from the TS API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: support Asking for support with something or a specific use case problem: stale Issue has not been responded to in some time solution: intended behavior This is not a bug and is expected behavior solution: tsc behavior This is tsc's behavior as well, so this is not a bug with this plugin topic: monorepo / symlinks Related to monorepos and/or symlinks (Lerna, Yarn, PNPM, Rush, etc)
Projects
None yet
Development

No branches or pull requests

5 participants