Skip to content

Generation of javascript files with --outDir is foolish. #812

Closed

Description

Check the examples:
//generated/example.js
tsc --outDir generated src/directory/subdirectory/example.ts

//generated/example.js
tsc --outDir generated src/directory/example.ts

//generated/example.js
tsc --outDir generated src/example.ts

//generated/example.js
//generated/subdirectory/example.js
tsc --outDir generated src/example.ts src/directory/subdirectory/example.ts

//generated/example.js
//generated/directory/subdirectory/example.js
//generated/directory/example.js
tsc --outDir generated src/example.ts src/directory/subdirectory/example.ts src/directory/example.ts

Why files path is not reflected in output folder all the time? When you define outDir you are loosing information about location of your generated file.

Activity

  1. mhegazy commented on Oct 3, 2014

    @mhegazy
    Contributor

    in each example you pass in different files to the compiler. you need to pass the files you want to build. outDir controls the output, and not the input. so in

    //generated/example.js
    tsc --outDir generated src/directory/example.ts
    you only want to build example.ts, so you get only example.js

    OutDir mirrors the source tree in the output. this is reliant on finding a "common root". so in:

    //generated/example.js
    //generated/directory/subdirectory/example.js
    //generated/directory/example.js
    tsc --outDir generated src/example.ts src/directory/subdirectory/example.ts src/directory/example.ts
    the common root is "src", so we replicate the source structure under "src" in the output under "generated".

    hope that helps.

  2. jzalucki commented on Oct 4, 2014

    @jzalucki
    Author

    Consider situation when I want to compile project incrementally so after file is created and saved I am executing tsc and then I want to add my custom logic that will be executed over generated file. As described in the previous example you will see that it is impossible, because files will be overwritten and until you compile all in one go you won't get expected result and even so considering 4th example still it is impossible to recognize location of your generated files.

    What am I asking here is to add flag that will force compiler to reflect the structure of folders just like in the example where common root is "src".

  3. basarat commented on Oct 4, 2014

    @basarat
    Contributor

    As described in the previous example you will see that it is impossible, because files will be overwritten and until you compile all in one go you won't get expected result

    Solvable by running the "finding a common root" algorithm yourself. Grunt-ts does this and then creates a baseDir file in the common root to pass along with the changed file to achieve incremental compile times : TypeStrong/grunt-ts#77

  4. swalters commented on Oct 15, 2014

    @swalters

    I think this is broken.

    I'm trying to use Webstorm to compile each file on save while developing and use grunt ts task to compile the files on the build server. grunt tsc works correctly because it compiles all the files at once as given in your example.

    Compiling each file individually fails to create the proper folder structured in the outDir

    I think it should take the prefix of the file passed on input and map that to output. It seems to only be taking a portion of the file path given on input which makes no sense to me, even after reading the explanations.

    tsc app\js\controllers\reporting\reportsController.ts --target ES5 --outDir build\ts
    results in build/ts/controllers/reporting/reportsController.js

    Why is app\js ignored while controllers\reporting is honored?

    I've tried it with 1.0.1 and 1.1.0

  5. mhegazy commented on Oct 15, 2014

    @mhegazy
    Contributor

    Shane Walters (@swalters) this should go to the Webstorm folks. the interface the compiler provides allows you to emit a single file given its context, that is give me output for file a.ts knowing that the compilation contains (a,.ts, b\c\d\e.ts). I am not familiar with how WebStorm implemetns this features, so I can not help further.

    In Visual Studio, if you are using a project context, you should be getting this working as expected.

    similarly, if you are using --watch on the commandline, the right thing should be happening. the idea is that Compile-on-Save output, on every file separately, should result in identical results if you built all of them in one shot.

  6. swalters commented on Oct 15, 2014

    @swalters

    The problem is I'm not building all of them at once.

    I was able to hack it by including one root dummy file, baseDir.ts, to always be compiled along with the current file

    For Webstorm users, my hack is:

    Create a baseDir.ts file with nothing in it in your project root dir.

    the Watcher settings I'm using are:

    Program: %userProfile%\AppData\Roaming\npm\tsc.cmd
    Arguments: $FilePathRelativeToProjectRoot$ baseDir.ts --target ES5 --outDir build\ts
    Working Directory: $ProjectFileDir
    
  7. 2001spaceoddity commented on Jan 21, 2015

    @2001spaceoddity

    I'm using IntelliJ and use the same hack as swalters, it works but it's not ideal. Currently, it seems we only have two options to maintain directory structure:

    1. Compile all ts files at once. This is overkill when you're just making small changes, and can be slow if you're working on a large project.
    2. Use a dummy file at the root of your source files that always gets compiled. This produces unwanted files in the root of your output directory.

    This should be solvable by adding an option to specify a root or source directory. For example:

    // source file is src/foo/bar/script.ts
    // output file is out/foo/bar/script.js
    tsc --srcDir src --outDir out foo/bar/script.ts
    
  8. RyanCavanaugh commented on Jan 21, 2015

    @RyanCavanaugh
    Member

    Upcoming tsconfig support should help with this scenario (see #1667)

  9. 2001spaceoddity commented on Jan 21, 2015

    @2001spaceoddity

    I like the idea of tsconfig and I think it would definitely help when you need to compile your entire project, but it doesn't look like it'll help when you just want to compile a single ts file. If I have 100 ts files and only made changes to 1 of them, I don't think I should have to recompile the other 99 just to make sure the output goes in the right directory.

  10. basarat commented on Jan 21, 2015

    @basarat
    Contributor

    make sure the output goes in the right directory

    More : TypeStrong/grunt-ts#77

    I don't think I should have to recompile the other 99

    With tsconfig.json you will not need to. Just run tsc -watch and it will do the right thing for the right files ;) For now grunt-ts has good workarounds.

  11. synaesthesia commented on Jan 26, 2015

    @synaesthesia

    OutDir mirrors the source tree in the output. this is reliant on finding a "common root"

    The problem is on finding the "common root", why the common root is not just an argument ? TypeScript knows best ? Probably to simplify the usage but it's just incoherent.

    Let's see a very unexpected behaviour.

    # display version
    tsc --version
    message TS6029: Version 1.4.1.0
    
    # go in a test directory to setup testcase
    mkdir testDir
    cd testDir
    mkdir -p ts/lib
    mkdir -p ts/app
    
    # create a library file
    echo "// my lib" > ts/lib/core.ts
    
    # create a file using the library file
    echo -e "/// <reference path=\"../lib/core.ts\" />\n// great app\n" > ts/app/main.ts
    
    # creating the output directory
    mkdir js
    
    # what we have done
    +-- js
    +-- ts
        +-- app
        ¦   +-- main.ts
        +-- lib
            +-- core.ts
    
    # we want ts/app/main.ts to be into js/app/main.js and ts/lib/core.ts into js/lib/core.js
    
    # compile, relative path to relative directory
    tsc ts/lib/core.ts -outDir js
    => creation of "js/core.js" => relative path lost
    
    # absolute path to absolute directory (other combination fail too, rel/abs and abs/rel, it seems logic)
    tsc $(pwd)/ts/lib/core.ts -outDir $(pwd)/js
    => creation of "js/core.js" => relative path lost
    
    # why not, no problem, easy solution:
    tsc ts/lib/core.ts -outDir js/lib
    => creation of "js/lib/core.js" => wonderful
    
    # tsc just compiles in the output directory
    # I used this solution during a long time, adapting the outDir each time, but one day, I had a particular case
    
    # now, same thing for the application file:
    tsc ts/app/main.ts -outDir js/app
    => creation of "js/app/app/main.js" => WTF
    => creation of "js/app/lib/core.js" => WTF
    
    # the tree is more explicit
    +-- js
    ¦   +-- app
    ¦       +-- app
    ¦       ¦   +-- main.js
    ¦       +-- lib
    ¦           +-- core.js
    +-- ts
        +-- app
        ¦   +-- main.ts
        +-- lib
            +-- core.ts
    

    The file I compile "main.ts" SHOULD be in js/app, but the compiler detects a relative path to a sub-directory, and it decides to generate the file elsewhere that the directory expected. It means that depending the content of the file, it can be generated in a totally different directory :-/

    The immediate solution is to do:
    tsc ts/app/main.ts -outDir js

    but like we have seen before with the library:
    tsc ts/lib/core.ts -outDir js
    Does not work as expected.

    Depending of the content of the file (it's really critical), we have to adapt the command line to run. It's impossible to make automation with this unpredictable behaviour.

    # Now, without using -outDir,
    tsc ts/app/main.ts -out js/app/main.js
    => creation of "js/app/main.js" => cool, but now "ts/lib/core.ts" is inside, I do not want that
    
    # so, I remove resolved inclusion:
    tsc --noResolve ts/app/main.ts -out js/app/main.js
    => creation of "js/app/main.js" => wonderful, but I have a lot of resolve error (false positives, but not in this example)
    
    # to get real resolve error:
    tsc ts/app/main.ts -out /dev/null
    

    So, my solution was to compile twice: once with noResolve to get a nice javascript file, once with "resolve" to get error output...

    But thanks to open source, I have patched myself this bug. You can see the devil here: https://github.com/Microsoft/TypeScript/blob/master/src/compiler/program.ts#L376

    TypeScript considers each referenced files as input file.

    tsc -outDir js/app ts/app/main.ts 
    # is like
    tsc -outDir js/app ts/app/main.ts ts/lib/core.ts 
    

    Then, it looks for the "common root" like Mohamed Hegazy (@mhegazy) said. Which is "ts/", and use this common root as input source root (to mirror the file structure in the output). That's why a solution (TypeStrong/grunt-ts#77) is to create a typescript file at the root and to include it each time, in this way, we can predict that tsc will always use the same common root. This typescript file at the root can be can created before the compilation, and removed after, it's maybe the solution used by grunt-ts with their option "baseDir", and I have hesitated to use it too. But it's an ugly fix.

    A compiler in which we cannot predict where the output files will be is very weird. But it looks like not so critical because it's only when you do not have a root typescript file and when you do not concatenate all files in one. So, not everyone is concerned with this problem. Probably with Visual Studio this problem does not exists.

    At least, an argument "--auto-dir" or "--auto-root" or whatever would be fine for people who want weird behaviour. And an argument --root-dir or --base-dir should exist for the other who want a predictable behaviour. I don't know if the ".tsconfig" file will solve this problem, in particular if the "common root" algorithm stills exist.

    The solution was to patch this mess. If only one file is in input (I do not care about multiple files as input, because multiple files really need a "base dir" argument), and if the common root dir is different than the directory of the file, then the output directory is adjusted. For example, if the input file is /dir/ts/app/main.ts and the output directory is /dir/js/app, and the common root detected is /dir/ts (one level change), then the new output directory is changed to /dir/js (one level change too).

    You can view the commit here: https://github.com/synaesthesia/TypeScript/commit/8b0590686bb60b09f9e4ebf53bc38257d26994b3

    I hope the ".tsconfig" will solve the problem (but by definition, we will need to have a ".tsconfig" file, it won't solve anything except that it will be a new workaround for this bug).

  12. bsandhu commented on Mar 5, 2015

    @bsandhu

    Agree, very annoying and non-standard behavior. Especially if you want other tooling to pick up generated JS, it must be consistent

  13. DanielRosenwasser commented on Apr 21, 2015

    @DanielRosenwasser
    Member

    Mohamed Hegazy (@mhegazy), does #2772 appropriately address this?

  14. mhegazy commented on Apr 21, 2015

    @mhegazy
    Contributor

    Yup. Should close now probably.

  15. locked and limited conversation to collaborators on Jun 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions