theseanl / tscc

A collection of tools to seamlessly bundle, minify Typescript with Closure Compiler
MIT License
161 stars 11 forks source link

Including `.js` files in input causes "error TS5055: Cannot write file '_filename.js_' because it would overwrite input file." #768

Open cpcallen opened 1 year ago

cpcallen commented 1 year ago

I'm trying to establish if tscc would be a suitable tool for building Blockly, which is currently built using tsc and Closure Compiler (running in SIMPLE_OPTIMIZATIONS mode in part due to incompatibility between tsc output for enum and Closure Compiler's prohibition on quoting property names—presumably one of the motivations for the creation of tscikle in the first place).

At the moment I have checked out our develop branch and am trying to compile our advanced compilation test (which verifies that Blockly can be built using ADVANCED_OPTIMIZATIONS) by modifying our tsconfig.json to:

{
  "include": [
    "core/**/*",
    "closure/**/*",
    "tests/compile/**/*", // Added for advanced compilation test.
  ],
  "exclude": [
    "core/blockly.js"
  ],
  "compilerOptions": {
    "allowJs": true,
    "sourceMap": true,
    "module": "ES2015",
    "moduleResolution": "node",
    "target": "ES2020",
    "strict": true,
  }
}

and added a simple tscc.spec.json:

{
  "modules": {
    "out": "tests/compile/main.js"
  },
  "prefix": "build/"
}

Unfortunately, running tscc generates errors for each .js file included in the build:

TSCC: Module option is set. tsickle converts TypeScript modules to Closure modulesvia CommonJS internally, so it will be overridden to "commonjs".
TSCC: tsickle uses a custom tslib optimized for closure compiler. importHelpers flag is set.
TS: error TS5055: Cannot write file '/Users/cpcallen/src/blockly/closure/goog/base.js' because it would overwrite input file.
error TS5055: Cannot write file '/Users/cpcallen/src/blockly/closure/goog/base_minimal.js' because it would overwrite input file.
error TS5055: Cannot write file '/Users/cpcallen/src/blockly/closure/goog/goog.js' because it would overwrite input file.
error TS5055: Cannot write file '/Users/cpcallen/src/blockly/core/main.js' because it would overwrite input file.
error TS5055: Cannot write file '/Users/cpcallen/src/blockly/tests/compile/main.js' because it would overwrite input file.
error TS5055: Cannot write file '/Users/cpcallen/src/blockly/tests/compile/main_compressed.js' because it would overwrite input file.
error TS5055: Cannot write file '/Users/cpcallen/src/blockly/tests/compile/test_blocks.js' because it would overwrite input file.
node_modules/@types/node/globals.d.ts(42,13): error TS2403: Subsequent variable declarations must have the same type.  Variable 'gc' must be of type 'cc', but here has type '(() => void) | undefined'.

TSCC: The compilation has terminated with an error.

I tired using tsc's -outputDir` flag, but this just generated a warning that it was ignored:

$ npx tscc -- -outDir build/src
TSCC: Module option is set. tsickle converts TypeScript modules to Closure modulesvia CommonJS internally, so it will be overridden to "commonjs".
TSCC: --outDir option is set, but it is no-op for tscc.Use prefix option in spec file to control output directory.
...

which is why I added the prefix directive totscc.spec.json. But it appears that prefix is applied only to the output of closure compiler, not the output of tsc.

How can I build a project containing a mix of .ts and .js input files? Given that base.js is mandatory for Closure-based projects, this is presumably supported somehow.

cpcallen commented 1 year ago

Two additional questions, if you would be so kind:

  1. What's with the "Subsequent variable declarations" error in the error output quoted above? Nothing in our project directly uses the @types/node npm module—in fact, there are (or at least should be) no external dependencies at all in the source files supplied to tscc.
  2. Adding (paraphrased for context):
    {
     "compilerOptions": {
       "declaration": true,
       // Generate declaration maps used for api-extractor
       "declarationMap": true,
     }
    }

    to tsconfig.json generates two additional error messages:

    tsconfig.json(16,5): error TS5069: Option 'declarationMap' cannot be specified without specifying option 'declaration' or option 'composite'.
    tsconfig.json(18,5): error TS5069: Option 'declarationMap' cannot be specified without specifying option 'declaration' or option 'composite'.

    which are nonsensical, as it complains about seeing 'declarationMap' without 'declaration' on the line with the 'declaration' directive. Huh?

theseanl commented 1 year ago

Hi, thank you for the detailed report. The build setup of the blockly repository looks quite complicated, but I see some points that need to be addressed to begin with.

Firstly, I see several closure-style JS files are being fed. Those should only be fed to closure compiler, not to tsickle. To achieve this, in tsconfig.json, compilerOptions.allowJs can be changed to false, or you can specify a file extension .ts in globs in include property. In order to feed closure JS to closure compiler, jsFiles option should be used. So you can provide a glob which selects those files in blocks/**/*.js and tests/compile/**/*.js.

Next, I see several files which reference goog via the following line.

import * as goog from '../closure/goog/goog.js';

It should be replaced with

///<reference path="../node_modules/@tscc/tscc/third_party/closure_library/base.d.ts" />
import * as goog from 'goog:goog';

Another painful issue is that the entry file must be TS, not a closure-js as it is in the develop branch. Then it has to call goog.require somewhere in a TS file to reference closure JS such as Blockly.libraryBlocks.logic. How it's supposed to work in tscc is this: Every file that a TS file references but whose source is not provided as TS should be considered external via relative paths, so such files must be specified in tscc.spec.json's external key, and accompanied with an ambient module declaration (declare module "Blockly.libraryBlocks.logic" { /* ... */ }), and

goog.require('Blockly.libraryBlocks.logic');

must be changed to

import 'goog:Blockly.libraryBlocks.logic'

The goog:Blockly.libraryBlocks.logic part may need to be changed to something else, I'm not exactly sure how tsickle would treat goog.declareModuleId calls. Thankfully, it seems that the export object of such closure-JS are not being used, so no additional type declarations are needed.

All these look quite labouring, and it is definitely not the best explanation of what need to be done, but if this makes any sense, I see no fundamental issue that would prevent tscc from working in the blockly repo, so I'd say it's promising.

theseanl commented 1 year ago

Regarding your additional questions –

  1. I'm not sure. Tscc uses a slightly different method for discovering dts in node_modules/@types and it may be a reason. But I'd say it is not important, there are more pending tasks, and such an error can be dealt later on.
  2. Since tscc does not create declaration files, it silently sets declaration compiler option to false. I guess it should do the same thing with declarationMap.