Skip to content

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

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

Closed
jzalucki opened this issue Oct 3, 2014 · 14 comments
Closed

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

jzalucki opened this issue Oct 3, 2014 · 14 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@jzalucki
Copy link

jzalucki commented Oct 3, 2014

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.

@mhegazy
Copy link
Contributor

mhegazy commented Oct 3, 2014

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.

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Oct 3, 2014
@jzalucki
Copy link
Author

jzalucki commented Oct 4, 2014

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".

@basarat
Copy link
Contributor

basarat commented Oct 4, 2014

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

@swalters
Copy link

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

@mhegazy
Copy link
Contributor

mhegazy commented Oct 15, 2014

@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.

@swalters
Copy link

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

@2001spaceoddity
Copy link

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

@RyanCavanaugh
Copy link
Member

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

@2001spaceoddity
Copy link

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.

@basarat
Copy link
Contributor

basarat commented Jan 21, 2015

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.

@synaesthesia
Copy link

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 @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).

@bsandhu
Copy link

bsandhu commented Mar 5, 2015

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

@DanielRosenwasser
Copy link
Member

@mhegazy, does #2772 appropriately address this?

@mhegazy
Copy link
Contributor

mhegazy commented Apr 21, 2015

Yup. Should close now probably.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

9 participants