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
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ const validLoaderOptions: ValidLoaderOptions[] = [
'experimentalWatchApi',
'allowTsInNodeModules',
'experimentalFileCaching',
'projectReferences'
'projectReferences',
'resolveModuleName'
];

/**
Expand Down
16 changes: 16 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,21 @@ export interface ReverseDependencyGraph {

export type LogLevel = 'INFO' | 'WARN' | 'ERROR';

export type ResolveModuleName = (
moduleName: string,
containingFile: string,
compilerOptions: typescript.CompilerOptions,
moduleResolutionHost: typescript.ModuleResolutionHost,
) => typescript.ResolvedModuleWithFailedLookupLocations;

export type CustomResolveModuleName = (
moduleName: string,
containingFile: string,
compilerOptions: typescript.CompilerOptions,
moduleResolutionHost: typescript.ModuleResolutionHost,
parentResolver: ResolveModuleName
) => typescript.ResolvedModuleWithFailedLookupLocations;

export interface LoaderOptions {
silent: boolean;
logLevel: LogLevel;
Expand All @@ -320,6 +335,7 @@ export interface LoaderOptions {
allowTsInNodeModules: boolean;
experimentalFileCaching: boolean;
projectReferences: boolean;
resolveModuleName?: CustomResolveModuleName;
}

export interface TSFile {
Expand Down
37 changes: 26 additions & 11 deletions src/servicesHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as typescript from 'typescript';

import * as constants from './constants';
import {
CustomResolveModuleName,
ModuleResolutionHost,
ResolvedModule,
ResolveSync,
Expand Down Expand Up @@ -36,7 +37,11 @@ export function makeServicesHost(
compiler,
compilerOptions,
files,
loaderOptions: { appendTsSuffixTo, appendTsxSuffixTo }
loaderOptions: {
appendTsSuffixTo,
appendTsxSuffixTo,
resolveModuleName: customResolveModuleName
}
} = instance;

const newLine =
Expand Down Expand Up @@ -148,7 +153,8 @@ export function makeServicesHost(
instance,
moduleNames,
containingFile,
getResolutionStrategy
getResolutionStrategy,
customResolveModuleName
),

getCustomTransformers: () => instance.transformers
Expand Down Expand Up @@ -426,7 +432,8 @@ function resolveModuleNames(
instance: TSInstance,
moduleNames: string[],
containingFile: string,
resolutionStrategy: ResolutionStrategy
resolutionStrategy: ResolutionStrategy,
customResolveModuleName?: CustomResolveModuleName
) {
const resolvedModules = moduleNames.map(moduleName =>
resolveModuleName(
Expand All @@ -438,7 +445,8 @@ function resolveModuleNames(
instance,
moduleName,
containingFile,
resolutionStrategy
resolutionStrategy,
customResolveModuleName
)
);

Expand All @@ -457,6 +465,15 @@ function isJsImplementationOfTypings(
);
}

function applyTsResolver(compiler: typeof typescript, moduleName: string, containingFile: string, compilerOptions: typescript.CompilerOptions, moduleResolutionHost: typescript.ModuleResolutionHost) {
return compiler.resolveModuleName(
moduleName,
containingFile,
compilerOptions,
moduleResolutionHost
);
}

function resolveModuleName(
resolveSync: ResolveSync,
moduleResolutionHost: ModuleResolutionHost,
Expand All @@ -466,7 +483,8 @@ function resolveModuleName(
instance: TSInstance,
moduleName: string,
containingFile: string,
resolutionStrategy: ResolutionStrategy
resolutionStrategy: ResolutionStrategy,
customResolveModuleName?: CustomResolveModuleName
) {
const { compiler, compilerOptions } = instance;

Expand Down Expand Up @@ -496,12 +514,9 @@ function resolveModuleName(
// tslint:disable-next-line:no-empty
} catch (e) {}

const tsResolution = compiler.resolveModuleName(
moduleName,
containingFile,
compilerOptions,
moduleResolutionHost
);
const tsResolution = customResolveModuleName
? 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)

: applyTsResolver(compiler, moduleName, containingFile, compilerOptions, moduleResolutionHost);

if (tsResolution.resolvedModule !== undefined) {
const resolvedFileName = path.normalize(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {HelloWorld} from 'foo';

export type HelloBuilder = (hello: number, world: number) => HelloWorld;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "bar",
"version": "1.0.0",
"typings": "./index.d.ts"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type HelloWorld = {
hello: number,
world: number,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "foo",
"version": "1.0.0",
"typings": "./index.d.ts"
}
47 changes: 47 additions & 0 deletions test/execution-tests/3.0.1_resolveModuleName/karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable no-var, strict */
'use strict';
var path = require('path');
var webpack = require('webpack');
var webpackConfig = require('./webpack.config.js');
var reporterOptions = require('../../reporterOptions');

module.exports = function(config) {
config.set({
browsers: [ 'ChromeHeadless' ],

files: [
// This loads all the tests
'main.js'
],

port: 9876,

frameworks: [ 'jasmine' ],

logLevel: config.LOG_INFO, //config.LOG_DEBUG

preprocessors: {
'main.js': [ 'webpack', 'sourcemap' ]
},

webpack: {
devtool: 'inline-source-map',
mode: webpackConfig.mode,
module: webpackConfig.module,
resolve: webpackConfig.resolve,

// for test harness purposes only, you would not need this in a normal project
resolveLoader: webpackConfig.resolveLoader
},

webpackMiddleware: {
quiet: true,
stats: {
colors: true
}
},

// reporter options
mochaReporter: reporterOptions
});
};
2 changes: 2 additions & 0 deletions test/execution-tests/3.0.1_resolveModuleName/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const testsContext = require.context('./', true, /\.tests\.ts(x?)$/);
testsContext.keys().forEach(testsContext);
10 changes: 10 additions & 0 deletions test/execution-tests/3.0.1_resolveModuleName/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "basic",
"license": "MIT",
"version": "1.0.0",
"main": "index.js",
"devDependencies": {
"@types/jasmine": "^2.5.35",
"jasmine-core": "^2.3.4"
}
}
6 changes: 6 additions & 0 deletions test/execution-tests/3.0.1_resolveModuleName/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {HelloBuilder} from 'bar';

export const makeHello: HelloBuilder = (hello: number, world: number) => ({
hello,
world,
});
12 changes: 12 additions & 0 deletions test/execution-tests/3.0.1_resolveModuleName/test/app.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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...

// 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 👍

// allowing this code to compile without errors, hence this:
// "noEmitOnError": true
// in tsconfig.json
describe("app", () => {
it("makeHello to exist", () => {
expect(!!app.makeHello).toBe(true);
});
});
6 changes: 6 additions & 0 deletions test/execution-tests/3.0.1_resolveModuleName/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"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 👍

}
}
28 changes: 28 additions & 0 deletions test/execution-tests/3.0.1_resolveModuleName/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
var path = require('path')

module.exports = {
mode: 'development',
entry: './app.ts',
output: {
filename: 'bundle.js'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{ test: /\.ts$/, loader: 'ts-loader', options: {
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);
default: return parentResolver(moduleName, containingFile, compilerOptions, compilerHost);
}
},
} }
]
}
}

// for test harness purposes only, you would not need this in a normal project
module.exports.resolveLoader = { alias: { 'ts-loader': path.join(__dirname, "../../../index.js") } }
11 changes: 11 additions & 0 deletions test/execution-tests/3.0.1_resolveModuleName/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


"@types/jasmine@^2.5.35":
version "2.8.5"
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.5.tgz#96e58872583fa80c7ea0dd29024b180d5e133678"

jasmine-core@^2.3.4:
version "2.9.1"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.9.1.tgz#b6bbc1d8e65250d56f5888461705ebeeeb88f22f"