Skip to content
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

Implements a custom resolveModuleName option #862

Merged
merged 11 commits into from
Oct 31, 2018

Conversation

arcanis
Copy link
Contributor

@arcanis arcanis commented Oct 26, 2018

Solves #861 - This PR adds a new resolveModuleName option that can be used to help TypeScript figure out the location of the files on the disk. It mimics the TypeScript API.

I published a first implementation as part of ts-pnp, and it can be tested using the pnp-webpack-plugin which provides an integration.

I'm not too sure how and where I should test this - any advice?

export type CustomResolveModuleName = (
moduleName: string,
containingFile: string,
options: typescript.CompilerOptions,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be called compilerOptions also please?

compilerOptions,
moduleResolutionHost
);
const tsResolver = (moduleName: string, containingFile: string, compilerOptions: typescript.CompilerOptions, moduleResolutionHost: typescript.ModuleResolutionHost) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given tsResolver is a pure function could we break this out in to a separate function please? That saves creating a new function each time this runs.

@johnnyreilly
Copy link
Member

Thanks for this! Could you address the comments and fix the linting errors that are showing up in CI please?

@johnnyreilly
Copy link
Member

johnnyreilly commented Oct 27, 2018

Regarding testing, it would be good to implement an execution test to cover this. You can find out more about these here: https://github.com/TypeStrong/ts-loader/blob/master/test/execution-tests/README.md

A good example to clone as a basis for your test might be this one:

https://github.com/TypeStrong/ts-loader/tree/master/test/execution-tests/3.0.1_projectReferences

or

https://github.com/TypeStrong/ts-loader/tree/master/test/execution-tests/3.0.1_resolveJsonModule

const tsResolution = customResolveModuleName
? customResolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost, tsResolver)
: tsResolver(moduleName, containingFile, compilerOptions, moduleResolutionHost);
? customResolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost, applyTsResolver.bind(null, compiler))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't expected the .bind(null, compiler) would be necessary... I'm guessing it is? What happens without it? Presumably some kind of this issue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we simply need the compiler variable to be able to call compiler.resolveModuleName (and compiler is obtained from instance, which isn't a global)

{
"compilerOptions": {
"noEmitOnError": true,
"resolveJsonModule": true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we remove the "resolveJsonModule": true please? It doesn't do any harm but it might lead someone who was reading the test to believe that it was necessary for resolveModuleName to work. (Not least because of the similarity in naming 😄 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure 👍

import * as app from '../src/app';

// We don't actually care about the result of the following operations;
// what we care about is typescript resolving json as modules
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you amend the comment to reflect what the intention of the test is please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left the comment because it's actually the same intent - we don't care about what is being built, just about TS being able to make the resolution

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see a "json" is in the comment - will change it 👍

@johnnyreilly
Copy link
Member

johnnyreilly commented Oct 27, 2018

Good work on the changes @arcanis! I've a couple more comments that I've added just now.

Well done on the execution test. Looks good! Question though: it looks like it's only resolving types from foo-pkg / bar-pkg. There doesn't seem to be any actual JS being resolved. It might be good to include some JS as well so we know that as well as the compiler resolving types correctly we can see the actual code that we want to run coming through as well.

Does that sound sensible? Or have I misunderstood your intent?

@arcanis
Copy link
Contributor Author

arcanis commented Oct 27, 2018

@johnnyreilly I wasn't entirely sure if the generated code had to be tested as well, since I wasn't sure whether ts-loader was entirely relying on TypeScript for the resolution or not (versus only using it for the type resolution, leaving the regular Webpack resolver setup the links between the packages). What do you think?

@johnnyreilly
Copy link
Member

johnnyreilly commented Oct 27, 2018

@johnnyreilly I wasn't entirely sure if the generated code had to be tested as well,

Yes please!

since I wasn't sure whether ts-loader was entirely relying on TypeScript for the resolution or not (versus only using it for the type resolution, leaving the regular Webpack resolver setup the links between the packages).

It's a good question. ts-loader uses webpack for actual file resolution:

https://github.com/TypeStrong/ts-loader/blob/master/src/resolver.ts

Funnily enough, it's on my radar to try out using TypeScript for resolve module names resolution too, but as yet I haven't.

See: #757 (comment)

@arcanis
Copy link
Contributor Author

arcanis commented Oct 27, 2018

Let me know if that matches what you had in mind :)

containingFile,
compilerOptions,
moduleResolutionHost,
applyTsResolver.bind(null, compiler)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing a .bind my perf spider sense is tingling!

https://stackoverflow.com/questions/42117911/lambda-functions-vs-bind-memory-and-performance

I wonder if we could switch this to a lamba instead? It'll be slightly more verbose but hopefully should perform better...

@@ -0,0 +1,13 @@
import * as app from '../src/app';

// We don't actually care about the result of the following operations;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true actually; we do care about the result of the following operations! Can we amend the comment to make test intent clearer please? We care both about type resolution and that the code executes...

@johnnyreilly
Copy link
Member

johnnyreilly commented Oct 28, 2018

Thanks chap! I've put a few comments in and I've also fixed the failing comparison test on your branch.

I think the execution test is better but it's still not quite what I had in mind; let me explain:

As I understand it foo-pkg etc represent node modules? i.e. that which you would expect to find in the node_modules folder.

That being the case you'd likely not consume them with raw TypeScript files in; you'd either:

  1. Consume a node module with just JS and rely on external type definitions (whether defined by yourself or via an @types/ scoped package published to npm by Definitely Typed.

Or

  1. Consume a node module with JS and .d.ts definition files in the package too. (Eg mobx)

If my understanding is correct then ideally our tests should be covering those scenarios rather than exporting raw .ts.

It's possible my understanding of this feature is not quite right; feel free to set me straight! 😄

@arcanis
Copy link
Contributor Author

arcanis commented Oct 28, 2018

I think the first scenario you mention isn't relevant to ts-loader - it's the resolveModuleName implementation that handles the fact that @types is tried before other possible options (check my implementation).

I've updated baz-pkg to be the case you describe (a index.js + a index.d.ts), let me know if that works for you 🙂

@johnnyreilly
Copy link
Member

johnnyreilly commented Oct 29, 2018

Cool - thanks for that! I've just fixed up the linting errors; hopefully we should now see a clean CI 😄

Question: in the test you only make use of the moduleName and parentResolver parameter; the others are all pass through parameters:

resolveModuleName: (moduleName, containingFile, compilerOptions, compilerHost, parentResolver) => {
switch (moduleName) {
case 'foo': return parentResolver(path.join(__dirname, 'foo-pkg'), containingFile, compilerOptions, compilerHost);
case 'bar': return parentResolver(path.join(__dirname, 'bar-pkg'), containingFile, compilerOptions, compilerHost);
case 'baz': return parentResolver(path.join(__dirname, 'baz-pkg'), containingFile, compilerOptions, compilerHost);
default: return parentResolver(moduleName, containingFile, compilerOptions, compilerHost);
}
},

That being the case, does it make sense for the resolveModuleName function to have all those parameters? I'm assuming it does; but I wanted to double check they weren't being passed on for no reason 😄

All things being equal, I suspect we're pretty much there!

@johnnyreilly
Copy link
Member

Ping @arcanis 😉

@arcanis
Copy link
Contributor Author

arcanis commented Oct 30, 2018

Sorry, was flying yesterday 🙂

So it don't use those values yet (and I'm not too familiar with TS to know what they're used for, tbh), but since the TS resolveModuleName hook requires all four of them I figured it'd make sense to also accept them, since they're easily available to us while the hook doesn't have any way to know them otherwise l.

One particular advantage is that it will make it easier for different projects/loaders to use the same custom resolveModuleName implementation (which would be a bit more difficult if they were each expecting a different set of parameters).

@johnnyreilly
Copy link
Member

That sounds reasonable! Do you have anymore work to do on this or is this ready to merge from your perspective?

@arcanis
Copy link
Contributor Author

arcanis commented Oct 30, 2018

Nope, I think it's ready on my side!

@johnnyreilly
Copy link
Member

Nice! Thanks for your work!

Would you be able to update the package.json and the CHANGELOG.md (don't forget to thank yourself!)?

That'll help me as I look to merge / publish this.

@arcanis
Copy link
Contributor Author

arcanis commented Oct 30, 2018

Here you go! Minor, right? 👍

@johnnyreilly johnnyreilly merged commit 08cd5a3 into TypeStrong:master Oct 31, 2018
@johnnyreilly
Copy link
Member

Thanks! Published with v5.3.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants