diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbcda8b4..25fad9fe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 5.3.0 + +* [feat: Exposes a `resolveNodeModule` option](https://github.com/TypeStrong/ts-loader/pull/862) - thanks @arcanis! + ## 5.2.2 * [feat: Micro-optimizations](https://github.com/TypeStrong/ts-loader/pull/855) - thanks @johnnyreilly @@ -14,7 +18,7 @@ ## 5.1.1 -* [fix(getTranspilationEmit): pass the raw path to transpileModule](https://github.com/TypeStrong/ts-loader/pull/835) - thanks @Brooooooklyn +* [fix(getTranspilationEmit): pass the raw path to transpileModule](https://github.com/TypeStrong/ts-loader/pull/835) - thanks @Brooooooklyn ## 5.1.0 diff --git a/package.json b/package.json index 460c16bbb..e0c866d4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ts-loader", - "version": "5.2.2", + "version": "5.3.0", "description": "TypeScript loader for webpack", "main": "index.js", "types": "dist/types/index.d.ts", diff --git a/src/index.ts b/src/index.ts index 80272b0ac..2fe667793 100644 --- a/src/index.ts +++ b/src/index.ts @@ -254,7 +254,8 @@ const validLoaderOptions: ValidLoaderOptions[] = [ 'experimentalWatchApi', 'allowTsInNodeModules', 'experimentalFileCaching', - 'projectReferences' + 'projectReferences', + 'resolveModuleName' ]; /** diff --git a/src/interfaces.ts b/src/interfaces.ts index 49fdf34bc..738d523a7 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -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; @@ -320,6 +335,7 @@ export interface LoaderOptions { allowTsInNodeModules: boolean; experimentalFileCaching: boolean; projectReferences: boolean; + resolveModuleName?: CustomResolveModuleName; } export interface TSFile { diff --git a/src/servicesHost.ts b/src/servicesHost.ts index 2b1cfda7c..fcae7366b 100644 --- a/src/servicesHost.ts +++ b/src/servicesHost.ts @@ -3,6 +3,7 @@ import * as typescript from 'typescript'; import * as constants from './constants'; import { + CustomResolveModuleName, ModuleResolutionHost, ResolvedModule, ResolveSync, @@ -36,7 +37,11 @@ export function makeServicesHost( compiler, compilerOptions, files, - loaderOptions: { appendTsSuffixTo, appendTsxSuffixTo } + loaderOptions: { + appendTsSuffixTo, + appendTsxSuffixTo, + resolveModuleName: customResolveModuleName + } } = instance; const newLine = @@ -148,7 +153,8 @@ export function makeServicesHost( instance, moduleNames, containingFile, - getResolutionStrategy + getResolutionStrategy, + customResolveModuleName ), getCustomTransformers: () => instance.transformers @@ -426,7 +432,8 @@ function resolveModuleNames( instance: TSInstance, moduleNames: string[], containingFile: string, - resolutionStrategy: ResolutionStrategy + resolutionStrategy: ResolutionStrategy, + customResolveModuleName?: CustomResolveModuleName ) { const resolvedModules = moduleNames.map(moduleName => resolveModuleName( @@ -438,7 +445,8 @@ function resolveModuleNames( instance, moduleName, containingFile, - resolutionStrategy + resolutionStrategy, + customResolveModuleName ) ); @@ -457,6 +465,21 @@ 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, @@ -466,7 +489,8 @@ function resolveModuleName( instance: TSInstance, moduleName: string, containingFile: string, - resolutionStrategy: ResolutionStrategy + resolutionStrategy: ResolutionStrategy, + customResolveModuleName?: CustomResolveModuleName ) { const { compiler, compilerOptions } = instance; @@ -496,12 +520,34 @@ function resolveModuleName( // tslint:disable-next-line:no-empty } catch (e) {} - const tsResolution = compiler.resolveModuleName( - moduleName, - containingFile, - compilerOptions, - moduleResolutionHost - ); + const tsResolution = + customResolveModuleName !== undefined + ? customResolveModuleName( + moduleName, + containingFile, + compilerOptions, + moduleResolutionHost, + ( + moduleNameFromCustomFn: string, + containingFileFromCustomFn: string, + compilerOptionsFromCustomFn: typescript.CompilerOptions, + moduleResolutionHostFromCustomFn: typescript.ModuleResolutionHost + ) => + applyTsResolver( + compiler, + moduleNameFromCustomFn, + containingFileFromCustomFn, + compilerOptionsFromCustomFn, + moduleResolutionHostFromCustomFn + ) + ) + : applyTsResolver( + compiler, + moduleName, + containingFile, + compilerOptions, + moduleResolutionHost + ); if (tsResolution.resolvedModule !== undefined) { const resolvedFileName = path.normalize( diff --git a/test/comparison-tests/validateLoaderOptionNames/expectedOutput-3.1/bundle.js b/test/comparison-tests/validateLoaderOptionNames/expectedOutput-3.1/bundle.js index 07e172697..f469245b1 100644 --- a/test/comparison-tests/validateLoaderOptionNames/expectedOutput-3.1/bundle.js +++ b/test/comparison-tests/validateLoaderOptionNames/expectedOutput-3.1/bundle.js @@ -93,7 +93,7 @@ /*! no static exports found */ /***/ (function(module, exports) { -eval("throw new Error(\"Module build failed (from C:/source/ts-loader/index.js):/nError: ts-loader was supplied with an unexpected loader option: notRealOption/n/nPlease take a look at the options you are supplying; the following are valid options:/nsilent / logLevel / logInfoToStdOut / instance / compiler / context / configFile / transpileOnly / ignoreDiagnostics / errorFormatter / colors / compilerOptions / appendTsSuffixTo / appendTsxSuffixTo / onlyCompileBundledFiles / happyPackMode / getCustomTransformers / reportFiles / experimentalWatchApi / allowTsInNodeModules / experimentalFileCaching / projectReferences/n/n at validateLoaderOptions (C://source//ts-loader//dist//index.js:151:19)/n at getLoaderOptions (C://source//ts-loader//dist//index.js:110:5)/n at Object.loader (C://source//ts-loader//dist//index.js:16:21)\");\n\n//# sourceURL=webpack:///./app.ts?"); +eval("throw new Error(\"Module build failed (from C:/source/ts-loader/index.js):/nError: ts-loader was supplied with an unexpected loader option: notRealOption/n/nPlease take a look at the options you are supplying; the following are valid options:/nsilent / logLevel / logInfoToStdOut / instance / compiler / context / configFile / transpileOnly / ignoreDiagnostics / errorFormatter / colors / compilerOptions / appendTsSuffixTo / appendTsxSuffixTo / onlyCompileBundledFiles / happyPackMode / getCustomTransformers / reportFiles / experimentalWatchApi / allowTsInNodeModules / experimentalFileCaching / projectReferences / resolveModuleName/n/n at validateLoaderOptions (C://source//ts-loader//dist//index.js:152:19)/n at getLoaderOptions (C://source//ts-loader//dist//index.js:110:5)/n at Object.loader (C://source//ts-loader//dist//index.js:16:21)\");\n\n//# sourceURL=webpack:///./app.ts?"); /***/ }) diff --git a/test/comparison-tests/validateLoaderOptionNames/expectedOutput-3.1/output.txt b/test/comparison-tests/validateLoaderOptionNames/expectedOutput-3.1/output.txt index cb1108e95..6f71f590f 100644 --- a/test/comparison-tests/validateLoaderOptionNames/expectedOutput-3.1/output.txt +++ b/test/comparison-tests/validateLoaderOptionNames/expectedOutput-3.1/output.txt @@ -1,15 +1,15 @@ - Asset Size Chunks Chunk Names -bundle.js 4.58 KiB main [emitted] main + Asset Size Chunks Chunk Names +bundle.js 4.6 KiB main [emitted] main Entrypoint main = bundle.js -[./app.ts] 835 bytes {main} [built] [failed] [1 error] +[./app.ts] 855 bytes {main} [built] [failed] [1 error] ERROR in ./app.ts Module build failed (from /index.js): Error: ts-loader was supplied with an unexpected loader option: notRealOption Please take a look at the options you are supplying; the following are valid options: -silent / logLevel / logInfoToStdOut / instance / compiler / context / configFile / transpileOnly / ignoreDiagnostics / errorFormatter / colors / compilerOptions / appendTsSuffixTo / appendTsxSuffixTo / onlyCompileBundledFiles / happyPackMode / getCustomTransformers / reportFiles / experimentalWatchApi / allowTsInNodeModules / experimentalFileCaching / projectReferences +silent / logLevel / logInfoToStdOut / instance / compiler / context / configFile / transpileOnly / ignoreDiagnostics / errorFormatter / colors / compilerOptions / appendTsSuffixTo / appendTsxSuffixTo / onlyCompileBundledFiles / happyPackMode / getCustomTransformers / reportFiles / experimentalWatchApi / allowTsInNodeModules / experimentalFileCaching / projectReferences / resolveModuleName - at validateLoaderOptions (dist\index.js:151:19) + at validateLoaderOptions (dist\index.js:152:19) at getLoaderOptions (dist\index.js:110:5) at Object.loader (dist\index.js:16:21) \ No newline at end of file diff --git a/test/execution-tests/3.0.1_resolveModuleName/bar-pkg/index.d.ts b/test/execution-tests/3.0.1_resolveModuleName/bar-pkg/index.d.ts new file mode 100644 index 000000000..0f0896fa3 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/bar-pkg/index.d.ts @@ -0,0 +1,3 @@ +import {HelloWorld} from 'foo'; + +export type HelloBuilder = (hello: number, world: number) => HelloWorld; diff --git a/test/execution-tests/3.0.1_resolveModuleName/bar-pkg/package.json b/test/execution-tests/3.0.1_resolveModuleName/bar-pkg/package.json new file mode 100644 index 000000000..0a9d633ad --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/bar-pkg/package.json @@ -0,0 +1,5 @@ +{ + "name": "bar", + "version": "1.0.0", + "typings": "./index.d.ts" +} diff --git a/test/execution-tests/3.0.1_resolveModuleName/baz-pkg/index.d.ts b/test/execution-tests/3.0.1_resolveModuleName/baz-pkg/index.d.ts new file mode 100644 index 000000000..169529593 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/baz-pkg/index.d.ts @@ -0,0 +1,3 @@ +import {HelloBuilder} from 'bar'; + +export declare const makeHello: HelloBuilder; diff --git a/test/execution-tests/3.0.1_resolveModuleName/baz-pkg/index.js b/test/execution-tests/3.0.1_resolveModuleName/baz-pkg/index.js new file mode 100644 index 000000000..02a917ea2 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/baz-pkg/index.js @@ -0,0 +1,4 @@ +export const makeHello = (hello, world) => ({ + hello, + world, +}); diff --git a/test/execution-tests/3.0.1_resolveModuleName/baz-pkg/package.json b/test/execution-tests/3.0.1_resolveModuleName/baz-pkg/package.json new file mode 100644 index 000000000..58ebd538c --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/baz-pkg/package.json @@ -0,0 +1,5 @@ +{ + "name": "baz", + "version": "1.0.0", + "main": "index.ts" +} diff --git a/test/execution-tests/3.0.1_resolveModuleName/foo-pkg/index.d.ts b/test/execution-tests/3.0.1_resolveModuleName/foo-pkg/index.d.ts new file mode 100644 index 000000000..ca025da24 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/foo-pkg/index.d.ts @@ -0,0 +1,4 @@ +export type HelloWorld = { + hello: number, + world: number, +}; diff --git a/test/execution-tests/3.0.1_resolveModuleName/foo-pkg/package.json b/test/execution-tests/3.0.1_resolveModuleName/foo-pkg/package.json new file mode 100644 index 000000000..d20a6c558 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/foo-pkg/package.json @@ -0,0 +1,5 @@ +{ + "name": "foo", + "version": "1.0.0", + "typings": "./index.d.ts" +} diff --git a/test/execution-tests/3.0.1_resolveModuleName/karma.conf.js b/test/execution-tests/3.0.1_resolveModuleName/karma.conf.js new file mode 100644 index 000000000..f0cb98bc7 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/karma.conf.js @@ -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 + }); +}; diff --git a/test/execution-tests/3.0.1_resolveModuleName/main.js b/test/execution-tests/3.0.1_resolveModuleName/main.js new file mode 100644 index 000000000..118f90112 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/main.js @@ -0,0 +1,2 @@ +const testsContext = require.context('./', true, /\.tests\.ts(x?)$/); +testsContext.keys().forEach(testsContext); diff --git a/test/execution-tests/3.0.1_resolveModuleName/package.json b/test/execution-tests/3.0.1_resolveModuleName/package.json new file mode 100644 index 000000000..12bc6974a --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/package.json @@ -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" + } +} diff --git a/test/execution-tests/3.0.1_resolveModuleName/src/app.ts b/test/execution-tests/3.0.1_resolveModuleName/src/app.ts new file mode 100644 index 000000000..91c04e831 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/src/app.ts @@ -0,0 +1,4 @@ +import {HelloWorld} from 'foo'; +import {makeHello} from 'baz'; + +export const def: HelloWorld = makeHello(1, 2); diff --git a/test/execution-tests/3.0.1_resolveModuleName/test/app.tests.ts b/test/execution-tests/3.0.1_resolveModuleName/test/app.tests.ts new file mode 100644 index 000000000..e223dcb42 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/test/app.tests.ts @@ -0,0 +1,8 @@ +import * as app from '../src/app'; + +describe("app", () => { + it("app.def to have been setup", () => { + expect(app.def.hello).toEqual(1); + expect(app.def.world).toEqual(2); + }); +}); diff --git a/test/execution-tests/3.0.1_resolveModuleName/tsconfig.json b/test/execution-tests/3.0.1_resolveModuleName/tsconfig.json new file mode 100644 index 000000000..0f0acf626 --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "noEmitOnError": true + } +} diff --git a/test/execution-tests/3.0.1_resolveModuleName/webpack.config.js b/test/execution-tests/3.0.1_resolveModuleName/webpack.config.js new file mode 100644 index 000000000..00da136ae --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/webpack.config.js @@ -0,0 +1,30 @@ +var path = require('path') + +module.exports = { + mode: 'development', + entry: './app.ts', + output: { + filename: 'bundle.js' + }, + resolve: { + extensions: ['.ts', '.js'], + alias: { baz: path.join(__dirname, 'baz-pkg') }, + }, + 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); + case 'baz': return parentResolver(path.join(__dirname, 'baz-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") } } diff --git a/test/execution-tests/3.0.1_resolveModuleName/yarn.lock b/test/execution-tests/3.0.1_resolveModuleName/yarn.lock new file mode 100644 index 000000000..2bf33ec6c --- /dev/null +++ b/test/execution-tests/3.0.1_resolveModuleName/yarn.lock @@ -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"