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

Using together with Babel plugins and emitDeclarationOnly overwrites output files with no content #268

Closed
sekoyo opened this issue Apr 27, 2021 · 8 comments · Fixed by #366
Assignees
Labels
kind: feature New feature or request problem: plugin order The plugin order in this issue seems incompatible. See the "Compatibility" section in the README

Comments

@sekoyo
Copy link

sekoyo commented Apr 27, 2021

Hey, thanks for this great plugin. Always use it but this time I have to compile the code with babel and just use this for declarations and it doesn't work for that.

What happens and why it is wrong

Environment

Using babel to compile files since I need to run code through a plugin:

babel({ extensions, babelHelpers: 'bundled', exclude: 'node_modules/**' }),
typescript(),

Tried setting emitDeclarationOnly in tsconfig.json and above e.g.:

typescript({
    tsconfigOverride: {
      compilerOptions: {
        emitDeclarationOnly: true,
      },
    },
  })

Also tried putting typescript() before babel() and it still somehow overrides the output files.

Versions
 rollup: ^2.45.2 => 2.45.2
rollup-plugin-typescript2: ^0.30.0 => 0.30.0
typescript: ^4.2.4 => 4.2.4

rollup.config.js

/* eslint-env node */
import replace from '@rollup/plugin-replace'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import { babel } from '@rollup/plugin-babel'
import { terser } from 'rollup-plugin-terser'
import minifyHTML from 'rollup-plugin-minify-html-literals'
import typescript from 'rollup-plugin-typescript2'

const globals = {}
const external = Object.keys(globals)
const isProd = process.env.NODE_ENV === 'production'
const extensions = ['.js', '.jsx', '.ts', '.tsx']

const output = [
  {
    file: 'dist/index.js',
    format: 'esm',
    globals,
  },
]

if (isProd) {
  output.push({
    name: 'myAppName',
    file: 'dist/index.umd.js',
    format: 'umd',
    globals,
  })
}

export default {
  input: 'src/index.ts',
  output,
  external,
  plugins: [
    minifyHTML(),
    replace({
      preventAssignment: true,
      values: {
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      },
    }),
    commonjs(),
    nodeResolve({
      extensions,
      browser: true,
    }),
    babel({ extensions, babelHelpers: 'bundled', exclude: 'node_modules/**' }),
    typescript({
      tsconfigOverride: {
        compilerOptions: {
          emitDeclarationOnly: true,
        },
      },
    }),
    process.env.NODE_ENV === 'production' &&
      terser({
        module: true,
        output: {
          comments: false,
        },
      }),
  ],
}

#### .babelrc.json

{
  "presets": ["@babel/preset-typescript", "babel-preset-solid"]
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "lib": ["es2018", "dom", "dom.iterable"],
    "declaration": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "emitDeclarationOnly": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "forceConsistentCasingInFileNames": true,
    "jsx": "preserve",
    "jsxImportSource": "solid-js"
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

plugin output with verbosity 3

plugin output with verbosity 3:
src/index.ts → dist/index.js, dist/index.umd.js...
rpt2: built-in options overrides: {
    "noEmitHelpers": false,
    "importHelpers": true,
    "noResolve": false,
    "noEmit": false,
    "inlineSourceMap": false,
    "outDir": "/Users/infensus/Projects/lightfin/packages/core/node_modules/.cache/rollup-plugin-typescript2/placeholder",
    "moduleResolution": 2,
    "allowNonTsExtensions": true
}
rpt2: parsed tsconfig: {
    "options": {
        "target": 5,
        "module": 99,
        "lib": [
            "lib.es2018.d.ts",
            "lib.dom.d.ts",
            "lib.dom.iterable.d.ts"
        ],
        "declaration": true,
        "outDir": "/Users/infensus/Projects/lightfin/packages/core/node_modules/.cache/rollup-plugin-typescript2/placeholder",
        "rootDir": "/Users/infensus/Projects/lightfin/packages/core/src",
        "strict": true,
        "emitDeclarationOnly": true,
        "esModuleInterop": true,
        "moduleResolution": 2,
        "allowSyntheticDefaultImports": true,
        "experimentalDecorators": true,
        "forceConsistentCasingInFileNames": true,
        "jsx": 1,
        "jsxImportSource": "solid-js",
        "configFilePath": "/Users/infensus/Projects/lightfin/packages/core/tsconfig.json",
        "noEmitHelpers": false,
        "importHelpers": true,
        "noResolve": false,
        "noEmit": false,
        "inlineSourceMap": false,
        "allowNonTsExtensions": true
    },
    "fileNames": [
        "/Users/infensus/Projects/lightfin/packages/core/src/index.ts",
        "/Users/infensus/Projects/lightfin/packages/core/src/components/grid.tsx",
        "/Users/infensus/Projects/lightfin/packages/core/src/components/index.ts",
        "/Users/infensus/Projects/lightfin/packages/core/src/components/registerGridElements.ts",
        "/Users/infensus/Projects/lightfin/packages/core/src/utils/css.ts",
        "/Users/infensus/Projects/lightfin/packages/core/src/utils/index.ts"
    ],
    "typeAcquisition": {
        "enable": false,
        "include": [],
        "exclude": []
    },
    "raw": {
        "compilerOptions": {
            "target": "es2018",
            "module": "esnext",
            "lib": [
                "es2018",
                "dom",
                "dom.iterable"
            ],
            "declaration": true,
            "outDir": "./dist",
            "rootDir": "./src",
            "strict": true,
            "emitDeclarationOnly": true,
            "esModuleInterop": true,
            "moduleResolution": "node",
            "allowSyntheticDefaultImports": true,
            "experimentalDecorators": true,
            "forceConsistentCasingInFileNames": true,
            "jsx": "preserve",
            "jsxImportSource": "solid-js"
        },
        "include": [
            "src"
        ],
        "exclude": [
            "node_modules"
        ],
        "compileOnSave": false
    },
    "errors": [],
    "wildcardDirectories": {
        "/users/infensus/projects/lightfin/packages/core/src": 1
    },
    "compileOnSave": false
}
rpt2: typescript version: 4.2.4
rpt2: tslib version: 2.1.0
rpt2: rollup version: 2.45.2
rpt2: rollup-plugin-typescript2 version: 0.30.0
rpt2: plugin options:
{
    "verbosity": 3,
    "tsconfigOverride": {
        "compilerOptions": {
            "emitDeclarationOnly": true
        }
    },
    "check": true,
    "clean": false,
    "cacheRoot": "/Users/infensus/Projects/lightfin/packages/core/node_modules/.cache/rollup-plugin-typescript2",
    "include": [
        "*.ts+(|x)",
        "**/*.ts+(|x)"
    ],
    "exclude": [
        "*.d.ts",
        "**/*.d.ts"
    ],
    "abortOnError": true,
    "rollupCommonJSResolveHack": false,
    "useTsconfigDeclarationDir": false,
    "transformers": [],
    "tsconfigDefaults": {},
    "objectHashIgnoreUnknownHack": false,
    "cwd": "/Users/infensus/Projects/lightfin/packages/core",
    "typescript": "version 4.2.4"
}
rpt2: rollup config:
{
    "external": [],
    "input": "src/index.ts",
    "plugins": [
        {
            "name": "minify-html-literals"
        },
        {
            "name": "replace"
        },
        {
            "name": "commonjs"
        },
        {
            "name": "node-resolve"
        },
        {
            "name": "babel"
        },
        {
            "name": "rpt2"
        },
        {
            "name": "terser"
        },
        {
            "name": "stdin"
        }
    ],
    "output": [
        {
            "file": "dist/index.js",
            "format": "esm",
            "globals": {},
            "plugins": []
        },
        {
            "file": "dist/index.umd.js",
            "format": "umd",
            "globals": {},
            "name": "lightfin",
            "plugins": []
        }
    ]
}
rpt2: tsconfig path: /Users/infensus/Projects/lightfin/packages/core/tsconfig.json
rpt2: included:
[
    "*.ts+(|x)",
    "**/*.ts+(|x)"
]
rpt2: excluded:
[
    "*.d.ts",
    "**/*.d.ts"
]

dist/index.js becomes empty if typescript plugin is loaded and dist/index.umd.js just has a stub - var e;e=function(){},"function"==typeof define&&define.amd&&define(e);

Thanks!

@ezolenko
Copy link
Owner

Does babel work without rpt2 in the chain when emitDeclarationOnly is set in tsconfig?

You might have to create a separate rollup config for declarations.

@tushar1998
Copy link

Doesnt Work for me either with babel 7

    nodeResolve({
      browser: true,
    }),
    commonjs({
      include: /node_modules/,
    }),
    babel({
      exclude: /node_modules/,
      babelHelpers: 'runtime',
    })
// package.json
"rollup-plugin-typescript2": "^0.31.0"
"rollup": "2.59.0",

Output:

./src/index.ts → lib/index.cjs.js, lib/index.ems.js, lib/index.umd.js...
(!) Generated empty chunks
index, index, index
created lib/index.cjs.js, lib/index.ems.js, lib/index.umd.js in 1.8s

@ldennis87

This comment was marked as duplicate.

@ling1726
Copy link

ling1726 commented Apr 7, 2022

Experiencing this too, using only babel, with typescript preset works fine. When add rpt2 to the list of plugins the output is empty files

@agilgur5
Copy link
Collaborator

agilgur5 commented Apr 29, 2022

This should probably be investigated in more depth as this is a good feature to have.

try rpt2 first if possible

That being said, my suspicion at first glance is that this might actually be correct behavior: once @babel/preset-typescript processes the TS, it's now JS and so rpt2 doesn't detect any files. Setting allowJs in your tsconfig and changing rpt2's include to read JS extensions too may workaround this... But it would be reading JS now, since Babel has already stripped the types, so it would likely not generate a very useful declaration.

If you run rpt2 first (with emitDeclarationOnly), before Babel, I believe this should work properly.

Could also run Rollup twice with two different configs as mentioned above, one for type declarations and the other for JS

if Babel must be first, this may not be possible (may require TS transformers instead)

Of course, if you're using a Babel plugin as OP is (particularly ones with syntax transforms I believe), you may not be able to change the Rollup plugin order like that.

This is kind of a chicken-and-egg problem in that sense: either TS reads it and doesn't understand the Babel plugin syntax, or Babel reads it and strips the TS.
I'm not entirely sure if there's a way around this in current Rollup as this may require non-sequential communication between plugins -- similar to Vue integration #129 (comment).
This also reminds me of React prop-types auto-generation in jaredpalmer/tsdx#418 ; the only viable workaround there was to use a TS transformer instead: TS's equivalent to a Babel plugin.

With new syntax in the mix, it's basically no longer valid "TS" as it has some custom syntax, so I believe that will also cause issues with other tooling as well unfortunately. ttypescript and transformers are the way around that.

@agilgur5 agilgur5 changed the title Overwrites output files with no content even with emitDeclarationOnly: true Using together with Babel plugins and emitDeclarationOnly overwrites output files with no content Apr 29, 2022
@agilgur5 agilgur5 added the problem: plugin order The plugin order in this issue seems incompatible. See the "Compatibility" section in the README label May 13, 2022
@agilgur5
Copy link
Collaborator

agilgur5 commented Jun 9, 2022

Ah so I hypothesized that Babel plugins might be possible with a two-pass Babel set-up and I believe they are. @babel/plugin-syntax-typescript allows Babel to understand TS, so from there it can be used with other plugins, so long as the TS isn't stripped. Then you can pass it through rpt2 and then, say, back to Babel to use things like preset-env etc.
It's totally inefficient and hacky, but should be doable if someone wants to do so. Also code returned by Babel plugins is likely pure JS so it may complicate type-checking.

I'm also gonna investigate emitDeclarationOnly more as after #338 I'm thinking there's possibly a bug/feature needed to make it work as emit settings can change what the TS Compiler API returns to rpt2 (hence why some emit options are forced)

@agilgur5
Copy link
Collaborator

agilgur5 commented Jun 23, 2022

root cause analysis

So I dug a bit more into this and found an interesting result when logging out output from this line while having emitDeclarationOnly set:

{
  outputFiles: [
    {
      name: '<project root>/node_modules/.cache/rollup-plugin-typescript2/placeholder/index.d.ts.map',
      writeByteOrderMark: false,
      text: '{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,UAE/C;AAED,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAA"}'
    },
    {
      name: '<project root>/node_modules/.cache/rollup-plugin-typescript2/placeholder/index.d.ts',
      writeByteOrderMark: false,
      text: 'export default function sum(a: number, b: number): number;\r\n' +
        "export { default as difference } from './difference';\r\n" +
        '//# sourceMappingURL=index.d.ts.map'
    }
  ],
  emitSkipped: false,
  diagnostics: [],
  exportedModulesFromDeclarationEmit: undefined
}

Some key pieces:

  1. Only the declaration and declaration map are here (declaration: true and declarationMap: true, respectively), but notably there is no JS code returned from the TS Compiler (the LanguageService API's getEmitOutput method, to be specific). This is why emitDeclarationOnly causes an empty chunk with "no content" to be returned to Rollup (and subsequent plugins).
  2. emitSkipped is false. This is an important difference from fix: force noEmitOnError: false #338, as if it were true, rpt2 would error out instead (this what is used to determine if a fatal error occurred).

feature incoming!

Changing the code to instead return undefined in the case of emitDeclarationOnly will allow other plugins on the chain to process/transform/compile the TS. So there is indeed a missing feature needed to support emitDeclarationOnly! (note that rpt2 was used before the emitDeclarationOnly compilerOption even existed)

I wrote up the code for this and already tested it and confirmed that is working! It's actually a very small feature that opens up quite a bit of new use-cases (which is why I wanted to prioritize it after joining on as a maintainer!) 🙂
For instance, you can use with Babel as mentioned here, but also with other tools that can compile TS like ESBuild and swc etc. So rpt2 does the type-checking and declaration generation, while Babel (with its various plugins, such as babel-plugin-typescript-to-proptypes as the use-case downstream in TSDX asks for) or others do the TS -> JS compilation (or JS -> JS transpilation when allowJs).
Note that rpt2 will have to come before those plugins in the chain (as I mentioned 2 comments above), so that it actually reads TS and produces declarations based on source TS and not compiled JS that is lacking types.

I'll have a PR out for this tomorrow (heading to bed now) 🚀 EDIT: see #366

@agilgur5
Copy link
Collaborator

#366 has been released in 0.33.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: feature New feature or request problem: plugin order The plugin order in this issue seems incompatible. See the "Compatibility" section in the README
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants