microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.34k stars 12.4k forks source link

Add new `sourceRootDir` setting #31873

Open aleclarson opened 5 years ago

aleclarson commented 5 years ago

TypeScript Version: 3.5.1

Search Terms: sourceRoot, sources, sourcemap

Code

  1. Create a module like: src/nested/foo.ts

  2. Use the following tsconfig.json with tsc:

{
  "include": ["src"],
  "compilerOptions": {
    "baseUrl": ".",
    "outDir": "dist",
    "sourceMap": true,
    "sourceRoot": "src"
  }
}

Expected behavior:

Inside nested/foo.js.map, the sourceRoot should be ../src/ instead of src/. Otherwise, the paths in the sources array cannot be found.

Actual behavior:

The sourceRoot is src/ regardless of nesting.

Playground Link: N/A

Related Issues: None

rbuckton commented 5 years ago

Currently we treat sourceRoot as a verbatim path, so that you can control what the source map's final sourceRoot should be if you are deploying these files to another location, i.e.:

{ 
  "compilerOptions": {
    "sourceMap": true,
    "sourceRoot": "https://foo.com/debug/sourcemaps/"
  }
}

However, we could change this to identify absolute paths and URLs and keep those verbatim. We don't currently use the sourceRoot for any path calculations when generating source maps, but this change could be a breaking change for some users.

@aleclarson: You can work around this by specifying the following in your tsconfig.json:

{
  "include": ["src"],
  "compilerOptions": {
    "baseUrl": ".",
    "outDir": "dist",
    "sourceMap": true,
    "sourceRoot": "../src"
  }
}

@RyanCavanaugh, @DanielRosenwasser: I am concerned this could be a breaking change for some users. Do we want to pursue changing the behavior of sourceRoot, or perhaps introduce a sourceRootDir option that can be used instead of sourceRoot and performs the correct relative path calculation (with the Dir suffix indicating that we're talking about an actual file system directory)?

raijinsetsu commented 4 years ago

This is an issue for anyone using a monorepo with Typescript. Unless the definition of a function/class/type is directly in the index.ts file of the referenced module, then "go to definition" functionality lands inside a .d.ts file instead.

As far as a breaking change, could this be resolved by detecting the value is a URL and then NOT putting relative paths into the map files?

raijinsetsu commented 3 years ago

This also breaks debugging in, at least, VSCode as it is unable to resolve the source -> JS mappings when the map files have a "sourceRoot".

We have had to remove "sourceRoot" from the project being debugged (we have yet to evaluate any other side effects) but we cannot debug other modules from within the same mono-repo which are being used by that project.

Can we get confirmation of a work-around or that this will actually be in 4.1.0? This issue has been pushed off for over a year and it's crippling to our development team.

rbuckton commented 3 years ago

I discussed this with the team. To support this without introducing a breaking change we will have to add a sourceRootDir option to specify a path that should be:

raijinsetsu commented 3 years ago

It appears the "fix" is not to use "sourceRoot" when sources are on the local filesystem and only use it when source will be hosted. VSCode now correctly finds the original source both when editing and when debugging.

The documentation lead us to believe that we had to have "sourceRoot" set in order for mappings to be correct but it's actually the opposite.

Troublor commented 3 years ago

Is the solution expected to be implemented in TypeScript 4.2.0?

raijinsetsu commented 3 years ago

The solution, for us, was to remove "sourceRoot" from tsconfig.json. All the map files are now generating with the correct paths.

What I would like to see is a clarification of the documentation as to when/why you would use sourceRoot (ie. only when hosting the source at an absolute location like a URL).

rbuckton commented 3 years ago

The solution, for us, was to remove "sourceRoot" from tsconfig.json. All the map files are now generating with the correct paths.

What I would like to see is a clarification of the documentation as to when/why you would use sourceRoot (ie. only when hosting the source at an absolute location like a URL).

As I've said before, we treat sourceRoot as a verbatim path and don't do any path resolution for it. It should only really be used when its pointing to an absolute URL or some other absolute path that might be defined as part of your build process. We won't be changing the behavior of sourceRoot as that would be a breaking change. While we are considering adding something like sourceRootDir, it likely won't make it for 4.2 due to holiday schedules.

Troublor commented 3 years ago

Thanks for clarifying. I am expecting something like sourceRootDir. Hope it will come to real soon.

mistic commented 3 years ago

@rbuckton @RyanCavanaugh @andrewbranch any estimate around when this new sourceRootDirfeature will be available? I'm using Bazel to build packages in a monorepo and it is really important for us to have this flag so we can control the source files locations in relation to the sourcemaps location correctly. At the moment our IDE features when click and following implementation for those packages are only working for files in the top level folders as whatever we set in the sourceRoot, even if it is a relative path, will be immutable and not correctly changed for nested folder files.

mistic commented 3 years ago

@RyanCavanaugh this issue keeps being postponed for a long time. Is there anyway to prioritise it 😃 ? It is blocking us in a monorepo with Bazel and I believe it is also affecting others in the same setup too.

raijinsetsu commented 3 years ago

I will restate, one last time: we removed "sourceRoot" from our configuration and the files all generated with the correct roots. It is evident that "sourceRoot" should only be used when hosting your map files on a web-site.

We have a monorepo with 60+ packages. Most of those packages have the following structure:

src\ - Typescript source
dist\ - Javascript output, maps, .d.ts files, and assets

This works for debugging, VSCode link following, and everything else we've tried.

Our initial issue was a misunderstanding of when to use "sourceRoot" which appears to only be when you're placing the .map, .d.ts, etc. files in a completely separate location. I can't think of a reason to do that except to host the files on a web-site.

IMO - the only issue here is documentation.

mistic commented 3 years ago

@raijinsetsu thanks for the inputs here. I actually know what you mentioned and confirm this. However our use case is different. When using Bazel, the compilation happens in a sandbox in a temporary location. The build contents are then located outside of the repo and symlinks are created. I really need the option to control the 'sourceRoot' content, which we have at the moment, but the relative path is not correctly calculated for nested files/folders under the package root. We need to patch that behaviour on sourceRoot or introduce the new param the sourceRootDir.

raijinsetsu commented 3 years ago

@raijinsetsu thanks for the inputs here. I actually know what you mentioned and confirm this. However our use case is different. When using Bazel, the compilation happens in a sandbox in a temporary location. The build contents are then located outside of the repo and symlinks are created. I really need the option to control the 'sourceRoot' content, which we have at the moment, but the relative path is not correctly calculated for nested files/folders under the package root. We need to patch that behaviour on sourceRoot or introduce the new param the sourceRootDir.

Would that not be fixed by putting the symlink in the same relative location to the src folder as it is within the sandbox?

Sandbox: /src /lib

Repo: /src /lib -> SANDBOX/lib

mistic commented 3 years ago

@raijinsetsu we don't have that level of control on Bazel and honestly I dont want to create a new symlink for each package target folder. Developing on windows is already slower and symlinks/junctions there are always painful. What we really need is that sourceRoot behaves like rootDir and calculates the nested relative paths correctly accordingly the value passed on the setting. In alternative a new sourceRootDir param will also work okay! 😃

RyanCavanaugh commented 2 years ago

This one's been around a long while and we haven't yet seen the quantity of feedback that would justify further complicating the file resolution process.

tinganho commented 1 year ago

@mistic just curious if you found any workaround? Also in need for relative workspace source root paths.

mistic commented 1 year ago

@tinganho I endup implementing a custom Typescript types summariser with sourcemaps support that runs over the d.ts files and produces a single output. Then the sourcemaps correctly generated for this summary will allow the IDE to correctly map the point and click. However I'm now thinking about pivoting from this approach and just run the typecheck on the IDE over the sources and thats it. Only CI will run the typecheck step out from the sources

tinganho commented 1 year ago

Thanks @mistic for your answer.

@RyanCavanaugh do you accept PR on this or still awaiting feedback? I think this blocks Bazel IDE debugging adoption for TypeScript atm.

Also on the last point on @rbuckton proposal:

  • Cannot be used in combination with the sourceRoot option (they are mutually exclusive)

First, I have hard time seeing a use case for verbatim sourceRoot alone? Unless, you have a single folder with source files, on your entire project? Second, isn't there a use case of combining sourceRoot + sourceRootDir, for instance to create http://localhost/sourcemaps/[workspace_path_to_source_file], where sourceRoot is http://localhost/sourcemaps/ and sourceRootDir is .(project root)?

tinganho commented 1 year ago

fwiw, my initial thought was that sourceRoot was behaving as the proposed sourceRootDir.

BenceSzalai commented 1 year ago

This would be really needed when working with a monorepo utilising workspaces. Consider the scenario where root/packages/a contains an src and build folder. But root/packages/a is symlinked into root/node_modules/@my_org/a.

Whenever any other package uses @my_org/a the files will be loaded from root/node_modules/@my_org/a/build/*.

Without sourceRootDir a debugger has no idea that for root/node_modules/@my_org/a/build/* the sources should be located in root/packages/a/src/*.

Right now the only solution is to set sourceRoot to the absolute path of each package's src folder.

However, how such configuration is supposed to be manageable? We cannot commit local absolute paths into a repo, it would collide with everyone else's setup. Another approach could be to use the --sourceRoot CLI parameter and use pwd to set the correct absolute path for builds. It can be rather convenient if configured in package.json scripts.

... As long as you don't use references, because than this setup will be broken for all the dependent packages (even if the --sourceRoot CLI param is taken into account or not).

So if someone is using some external monorepo tools, like Nx or so, they can manually configure appropriate scripts for each packages and rely on the tool to trigger rebuild of dependent packages.

But with references it is not suitable.

Right now as a workaround we have a root level script in place that auto-generates a tsconfig.build.local.json for every package, extending tsconfig.build.json, but overriding sourceRoot to the actual absolute path. We have tsconfig.build.local.json on .gitignore, so the setup works for everyone regardless of the local path on their machine. But it is not elegant...

We need a solution that is not copied verbatim, like compilerOptions.sourceRoot, but instead would be processed by tsc and adjusted for each sub-folder level in the package. So setting that setting to "../../../../packages/a/src/" would mean:

Indeed we have to acknowledge that in this case the sourcemaps inside root/packages/a/build are pointing to a non-existent location, but since files would never run from this location it is fine.