-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Package scopes #4913
Package scopes #4913
Conversation
I think this is a huge step in the right direction! Thanks a ton for working on this. I haven't yet had a chance to actually try it out but some comments just based on your description. Things that directly relate to this PR:
Some items that might not directly pertain to this PR but are related:
|
And yes, it would be easier to deal with the other two issues with seperate proposals/PRs, IMO. |
👍 Very nice! I like the idea of being able to transition smoothly to 'proper external' modules (from current DT typings), by defining these two-liner files. It's basically my 'mixed-mode' idea in #4673, but then defined explicitly. Still have to play with the actual code. |
Created a test repository: https://github.com/basarat/typescript-node/tree/master/poelstra4 Short conclusion: it works! 😃
Any ideas on moving the typings out of |
I always thought it was odd that the organization you have there works, but it's because our module resolution logic mimics node's, so a loose file is resolved before the real dependency in the |
My thought was (is) that we shouldn't need any We no longer need them when modules provide their own typings, so it would be great if we also don't need them anymore for packages that don't provide their own. In my examples, only BTW: I'm wondering, would it be possible to apply your scoping concept to every single |
@@ -180,7 +180,7 @@ module ts { | |||
const actual = file.resolvedModules[id]; | |||
assert.isTrue(actual !== undefined); | |||
assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); | |||
assert.isTrue(expected.isExternalLibraryImport === actual.isExternalLibraryImport, `'shouldBeProperExternalModule': expected '${expected.isExternalLibraryImport}' to be equal to '${actual.isExternalLibraryImport}'`); | |||
assert.isTrue(expected.packageRoot === actual.packageRoot, `'shouldBeProperExternalModule': expected '${expected.packageRoot}' to be equal to '${actual.packageRoot}'`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like these should use assert.equals
Thanks for the pointer here, Wes. I see there was a lot of good discussion about this problem in early September. Are you and the team still actively working on it? |
@@ -1233,6 +1233,10 @@ namespace ts { | |||
isBracketed: boolean; | |||
} | |||
|
|||
export interface PackageDescriptor extends Scope { | |||
packageFile: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment? Specifically mention that this can be a package.json
or index.d.ts
.
New work items for this PR:
|
/cc @blakeembrey although I am pretty sure he knows 🌹 |
@mhegazy This PR has been synced with master and had the design changes we discussed (listed above) integrated. Do we want to keep looking at it? |
…dependent TS projects from building When a package has Shopify-Prime as a dependency, and has its own node.d.ts definition installed, the two definitions will clash and break the dependent's build because globals aren't scoped to their own packages. There is some work being done by MS at (microsoft/TypeScript#4913) to fix the problem.
This does not seem to be needed now with the move to |
After reading and commenting in #4665 and #4673, I felt like I'd gotten a pretty good idea for what we wanted to accomplish regarding package typings - and I believe our main concern was the pulling needs between properly representing the isolation that modules have from one another and the desire to enable consumer to reuse typings between "flat" or browser projects and commonjs based projects. I, personally, believed that a variety of package scoping was the correct solution if implementable and so looked into it and wrapped it up today - package scopes are very possible with our current compiler. The difficulty of adding a package scope was exaggerated.
While I haven't specified the exact behaviors of the package scope environment prior to my implementation, what I've changed internally is that any
SourceFile
can be a member of a package - a program puts a source file into a package if it find it via node module resolution and it was found transitively through either thenode_modules
folder or apackage.json
file and accompanyingtypings
field. The entrypoint.d.ts
file to that package (eitherindex
, a named.d.ts
directly inside thenode_modules
folder, or atypings
field reference) is considered its package "root" and uniquely identifies the package in the system. Please note - this is slightly different from the existing consideration for what an "external package" is for our errors - we neglected to consider finding a "package.json" outside anode_modules
folder as a package (despite it clearly being a package since it has a package file) with the justification that it shouldn't be "external" in that case. Limiting the package-scoping feature tonode_modules
-located packages seems silly, though, so the definition of where a package begins was updated to include anything found via apackage.json
.Continuing my explanation of the changes, after files are bound in the binder, normally file-locals for non-external-modules get merged into a single, global scope. With these changes, instead if a source file is a member of a package, those locals get merged into a package scope. Once the global environment has been setup and bound, the global scope is then merged into each package scope (I'll have to check out how this plays with interface merging and other global state). To pair with this newly created scope concept, at any point where previously the global scope may have been consulted, the package scope for the file containing that node is consulted first. Since the global scope is merged into the package scope, this implies that the global scope aught never to get meaningfully consulted if a package is present... but I don't enforce that right now, and I may have to create some scenarios to check for unexpected leakage.
TL;DR:
The end result? This is a valid configuration:
This lets people write their "isomorphic" typings (like
ref.d.ts
above) and then adapt them to a node package with a quick two-liner and without polluting the global scope. I think we can mostly agree that the above configuration can be a very desirable one for typescript users.The only issue with this approach that I can readily think of is that
stdlib
expectation errors will still be reported - but that may actually be fine. This means I can target ES5, include something which relies on ES6 promises (and expects to use the es6 lib), and includees6-promise
myself to polyfill it, or retarget against es6, depending on my needs.@mhegazy @jbrantly @poelstra you all seemed invested/interested in improving the node module typing scenario - what are your opinions on this approach?