question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

tsickle breaks module names when outDir specified

See original GitHub issue

I have an extremely simple TypeScript project, in total it is:

// classB.ts:
export class ClassB {
  constructor() {  }

  toString(): string {
    return "this is a class B instance";
  }
}
// index.ts:
import { ClassB } from "./classB";

export let myLib: any = {}

// very semantic version
myLib.version = "lalala";

// runtime API
myLib.ClassB = ClassB;
// tsconfig.json:
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./tsout",
    "rootDir": "./src",
    "noEmitHelpers": true, // needed for tsickle
    "strict": true
  }
}

Running tsickle produces almost valid output, except index.js looks like this:

goog.module('tsout.index'); exports = {}; var module = {id: 'tsout/index.js'};
Object.defineProperty(exports, "__esModule", { value: true });
var classB_1 = goog.require('tsout_classB');
exports.myLib = {};
// very semantic version
exports.myLib.version = "lalala";
// runtime API
exports.myLib.ClassB = classB_1.ClassB;

goog.require('tsout_classB'); is causing the closure compiler to fail. Shouldn’t tsickle produce 'tsout.classB' instead? If I change that line by hand so that the underscore is a period, it works.

PS C:\VerySimpleTSProject> java -jar .\closure-compiler.jar --js_output_file=myLib.js './tsout/*.js' --jscomp_off=deprecatedAnnotations --compilation_level ADVANCED_OPTIMIZATIONS --formatting=pretty_print
tsout/index.js:3: ERROR - Required namespace "tsout_classB" never defined.
var classB_1 = goog.require('tsout_classB');
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

What explains this error? Running tsc with the above, there are no similar require errors.

(Unrelated to the bug, I’m trying to write TS to produce a browser library called myLib. If I’m doing anything obviously wrong on that front please let me know).

Issue Analytics

  • State:open
  • Created 6 years ago
  • Comments:39 (20 by maintainers)

github_iconTop GitHub Comments

2reactions
simonsarriscommented, Apr 28, 2017

I’m going to try and be concise so let me know if you need more information. What’s passed to pathToModuleName or emitGoogRequire are strings which are sometimes relative paths, and other times full paths. Why? Well, it looks like TypeScript is doing this, not your code. When TS is taking the input, remember I’m only passing tsc/tsickle one file right now:

node tsickle-master\build\src\main.js src/index.ts

the typescript code (node_modules/typescript/lib/typescript.js) goes through the passed-in code and gathers lists of relevant files. Typically they are stored in arrays of SourceFileObjects, and if you look at the arrays and the SourceFileObject.fileName in each you find:

fileName:"c:/mini/src/classB.ts"
fileName:"c:/mini/src/classA.ts"
fileName:"src/index.ts"

In other words, sometimes fileName is the relative path and other times it resolved to a full path. So this is what TypeScript gathers, and its what you’re using as ES5Processor.file.fileName.

So in typescript.js, (Line 9047 of this file in the TypeScript project)

    function writeFile(host, diagnostics, fileName, data, writeByteOrderMark, sourceFiles) {
        host.writeFile(fileName, data, writeByteOrderMark, function (hostErrorMessage) {
            diagnostics.add(ts.createCompilerDiagnostic(ts.Diagnostics.Could_not_write_file_0_Colon_1, fileName, hostErrorMessage));
        }, sourceFiles);
    }

host.writeFile(fileName - That fileName will be sometimes relative, and sometimes absolute.

Your code, processES5 calls ts.createSourceFile which ultimately uses that code.

So either we must ask the TypeScript project to change how they’re doing this, or the tsickle project must find a way to accomodate both absolute and relative file paths.

Annoyingly, the TypeScript project returns a mixture of absolute and relative file paths even if you pass in all the files! This is because if you have:

  "files": [
    "src/index.ts",
    "src/classA.ts",
    "src/classB.ts"
  ]

TypeScript starts by looking inside of the first file, and looks at all the files it references to build its list of fileNames, so it ends up with:

"c:/mini/src/classB.ts" // got this by looking at index.ts
"c:/mini/src/classA.ts" // got this by looking at index.ts
"src/index.ts" // got this because it was specified

Presumably, “src/classA.ts” isn’t here because by the time it was ready to process the second relative file, it already found it (as an absolute reference) by searching through index.ts.

I hope this has been helpful. Unfortunately being myself a simple JavaScript farmer, I am not the right person to determine where and how tsickle may need to change to accommodate both relative and absolute file paths.

1reaction
Addeventurecommented, Nov 27, 2017

I am seeing the same problems in one of my projects using master branch of tsickle, both with Typescript 2.4.2 and 2.5.3 on Ubuntu 14.04. (Not using angular.)

  • Some files are not processed completely by tsickle, missing closure types, etc.
  • Module names are sometimes emitted with relative paths and sometimes with absolute paths.

Got no combination working by playing with different combinations of outDir, baseUrl, etc.

After some debugging I found that toClosureJS() (main.ts:124) recevied a list of relative file paths. Independent of the outDir/baseUrl settings in tsconfig.json, they were always relative to CWD. However, shouldSkipTsickleProcessing() (main.ts:130) received a mixed list of absolute and relative paths, so I tried to always work with relative paths by changing the implementation to:

shouldSkipTsickleProcessing: (fileName: string) => {
      if (fileName.indexOf(process.cwd()) === 0) {
        fileName = fileName.slice(process.cwd().length + 1);
      }
      return fileNames.indexOf(fileName) === -1;
    }

And now all files were processed by tsickle, still with broken module names.

For the broken module names, I looked at pathToModuleName() in cli_support.ts. I found that “context” that is used to resolve the relative “fileName” sent to this function are one of these:

  • absolute path to a TS file: “/full/path/to/project/src/SomeFile.ts”,
  • relative path to a TS file (relative CWD): “src/AnotherFile.ts”
  • relative path to a JS file (relative CWD): “build/SomeFile.js”

Just before generating the moduleName in cli_support.ts I added

....
  if (fileName.indexOf(process.cwd()) === 0) {
    fileName = fileName.slice(process.cwd().length + 1);
  }

  // Replace characters not supported by goog.module.
  const moduleName =
      fileName.replace(/\/|\\/g, '.').replace(/^[^a-zA-Z_$]/, '_').replace(/[^a-zA-Z0-9._$]/g, '_');

  return moduleName;
}

Now all module names are correct and it generated 100% closure typed output while all bazel tests still passes. As the same method pathToModuleName() is used to resolve source TS files and built JS files, I am not sure how this can work at all when configuring “outDir” to point to anything else than the src directory.

If pathToModuleName() could also get the values of outDir and baseUrl, etc. it could use those to map to the correct module id. I don’t have time to play with that though.

I am not that familiar with the code base yest, so not sure if this is the way to go, or if there are better places of handling paths. Not sure yet if relative paths are always relative current working directory or if we can get this from tsc somehow?

Read more comments on GitHub >

github_iconTop Results From Across the Web

bazelbuild/rules_nodejs - aspect build
a string specifying a subdirectory under the bazel-out folder where outputs are written. Equivalent to the TypeScript --outDir option. Note that Bazel always ......
Read more >
TSConfig Reference - Docs on every TSConfig option
Lets you set a base directory to resolve non-absolute module names. You can define a root folder where you can do absolute file...
Read more >
@bazel/typescript - npm
We control the output format and module syntax so that downstream rules can rely on them. They are also fast and optimized: We...
Read more >
H2 - Groups and Classes
Unless otherwise specified by contractual agreement with the Federal Government, ... Cataloging Handbook H6, the alphabetic index of item names, includes a ...
Read more >
tsickle@0.46.3 - jsDocs.io
Documentation for npm package tsickle@0.46.3 - jsDocs.io. ... an ES6 import and generates a googmodule module name for the imported module.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found