diff --git a/package.json b/package.json index 22bd094c874d..b17784c22102 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "diff": "^3.1.0", "ember-cli-normalize-entity-name": "^1.0.0", "ember-cli-string-utils": "^1.0.0", + "enhanced-resolve": "^3.1.0", "exports-loader": "^0.6.3", "extract-text-webpack-plugin": "^2.1.0", "file-loader": "^0.10.0", diff --git a/packages/@ngtools/webpack/package.json b/packages/@ngtools/webpack/package.json index ec7b3e1399e8..e71a221fb2d1 100644 --- a/packages/@ngtools/webpack/package.json +++ b/packages/@ngtools/webpack/package.json @@ -25,6 +25,7 @@ "npm": ">= 3.0.0" }, "dependencies": { + "enhanced-resolve": "^3.1.0", "loader-utils": "^1.0.2", "magic-string": "^0.19.0", "source-map": "^0.5.6" diff --git a/packages/@ngtools/webpack/src/paths-plugin.ts b/packages/@ngtools/webpack/src/paths-plugin.ts index 46ddde71d6a2..305bd3aa071b 100644 --- a/packages/@ngtools/webpack/src/paths-plugin.ts +++ b/packages/@ngtools/webpack/src/paths-plugin.ts @@ -1,25 +1,46 @@ import * as path from 'path'; import * as ts from 'typescript'; -import {Callback, Tapable, NormalModuleFactory, NormalModuleFactoryRequest} from './webpack'; +import {Request, ResolverPlugin, Callback, Tapable} from './webpack'; + + +const ModulesInRootPlugin: new (a: string, b: string, c: string) => ResolverPlugin + = require('enhanced-resolve/lib/ModulesInRootPlugin'); + +interface CreateInnerCallback { + (callback: Callback, + options: Callback, + message?: string, + messageOptional?: string): Callback; +} + +const createInnerCallback: CreateInnerCallback + = require('enhanced-resolve/lib/createInnerCallback'); +const getInnerRequest: (resolver: ResolverPlugin, request: Request) => string + = require('enhanced-resolve/lib/getInnerRequest'); + function escapeRegExp(str: string): string { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); } + export interface PathsPluginOptions { - nmf: NormalModuleFactory; tsConfigPath: string; compilerOptions?: ts.CompilerOptions; compilerHost?: ts.CompilerHost; } export class PathsPlugin implements Tapable { - private _nmf: NormalModuleFactory; private _tsConfigPath: string; private _compilerOptions: ts.CompilerOptions; private _host: ts.CompilerHost; + + source: string; + target: string; + + private mappings: any; + private _absoluteBaseUrl: string; - private _mappings: any[] = []; private static _loadOptionsFromTsConfig(tsConfigPath: string, host?: ts.CompilerHost): ts.CompilerOptions { @@ -55,13 +76,15 @@ export class PathsPlugin implements Tapable { this._host = ts.createCompilerHost(this._compilerOptions, false); } + this.source = 'described-resolve'; + this.target = 'resolve'; + this._absoluteBaseUrl = path.resolve( path.dirname(this._tsConfigPath), this._compilerOptions.baseUrl || '.' ); - this._nmf = options.nmf; - + this.mappings = []; let paths = this._compilerOptions.paths || {}; Object.keys(paths).forEach(alias => { let onlyModule = alias.indexOf('*') === -1; @@ -76,7 +99,7 @@ export class PathsPlugin implements Tapable { aliasPattern = new RegExp(`^${withStarCapturing}`); } - this._mappings.push({ + this.mappings.push({ onlyModule, alias, aliasPattern, @@ -86,36 +109,66 @@ export class PathsPlugin implements Tapable { }); } - apply(): void { - this._nmf.plugin('before-resolve', (request: NormalModuleFactoryRequest, - callback: Callback) => { - for (let mapping of this._mappings) { - const match = request.request.match(mapping.aliasPattern); - if (!match) { continue; } + apply(resolver: ResolverPlugin): void { + let baseUrl = this._compilerOptions.baseUrl || '.'; - let newRequestStr = mapping.target; - if (!mapping.onlyModule) { - newRequestStr = newRequestStr.replace('*', match[1]); - } - - const moduleResolver: ts.ResolvedModuleWithFailedLookupLocations = - ts.nodeModuleNameResolver( - newRequestStr, - this._absoluteBaseUrl, - this._compilerOptions, - this._host - ); - const moduleFilePath = moduleResolver.resolvedModule ? - moduleResolver.resolvedModule.resolvedFileName : ''; - - if (moduleFilePath) { - return callback(null, Object.assign({}, request, { - request: moduleFilePath.includes('.d.ts') ? newRequestStr : moduleFilePath - })); - } - } + if (baseUrl) { + resolver.apply(new ModulesInRootPlugin('module', this._absoluteBaseUrl, 'resolve')); + } - return callback(null, request); + this.mappings.forEach((mapping: any) => { + resolver.plugin(this.source, this.createPlugin(resolver, mapping)); }); } + + resolve(resolver: ResolverPlugin, mapping: any, request: any, callback: Callback): any { + let innerRequest = getInnerRequest(resolver, request); + if (!innerRequest) { + return callback(); + } + + let match = innerRequest.match(mapping.aliasPattern); + if (!match) { + return callback(); + } + + let newRequestStr = mapping.target; + if (!mapping.onlyModule) { + newRequestStr = newRequestStr.replace('*', match[1]); + } + if (newRequestStr[0] === '.') { + newRequestStr = path.resolve(this._absoluteBaseUrl, newRequestStr); + } + + let newRequest = Object.assign({}, request, { + request: newRequestStr + }) as Request; + + return resolver.doResolve( + this.target, + newRequest, + `aliased with mapping '${innerRequest}': '${mapping.alias}' to '${newRequestStr}'`, + createInnerCallback( + function(err, result) { + if (arguments.length > 0) { + return callback(err, result); + } + + // don't allow other aliasing or raw request + callback(null, null); + }, + callback + ) + ); + } + + createPlugin(resolver: ResolverPlugin, mapping: any): any { + return (request: any, callback: Callback) => { + try { + this.resolve(resolver, mapping, request, callback); + } catch (err) { + callback(err); + } + }; + } } diff --git a/packages/@ngtools/webpack/src/plugin.ts b/packages/@ngtools/webpack/src/plugin.ts index eae12be1d53d..2d2eecf61f76 100644 --- a/packages/@ngtools/webpack/src/plugin.ts +++ b/packages/@ngtools/webpack/src/plugin.ts @@ -341,11 +341,7 @@ export class AotPlugin implements Tapable { cb(); } }); - }); - - compiler.plugin('normal-module-factory', (nmf: any) => { compiler.resolvers.normal.apply(new PathsPlugin({ - nmf, tsConfigPath: this._tsConfigPath, compilerOptions: this._compilerOptions, compilerHost: this._compilerHost diff --git a/packages/@ngtools/webpack/src/webpack.ts b/packages/@ngtools/webpack/src/webpack.ts index 2e88c4e43150..f04b5d7dc765 100644 --- a/packages/@ngtools/webpack/src/webpack.ts +++ b/packages/@ngtools/webpack/src/webpack.ts @@ -39,18 +39,6 @@ export interface NormalModule { resource: string; } -export interface NormalModuleFactory { - plugin( - event: string, - callback: (data: NormalModuleFactoryRequest, callback: Callback) => void - ): any; -} - -export interface NormalModuleFactoryRequest { - request: string; - contextInfo: { issuer: string }; -} - export interface LoaderContext { _module: NormalModule; diff --git a/tests/e2e/tests/build/ts-paths.ts b/tests/e2e/tests/build/ts-paths.ts index 46e7c2f55157..8a39a803a1e8 100644 --- a/tests/e2e/tests/build/ts-paths.ts +++ b/tests/e2e/tests/build/ts-paths.ts @@ -13,10 +13,6 @@ export default function() { ], '@shared/*': [ 'app/shared/*' - ], - '*': [ - '*', - 'app/shared/*' ] }; }) @@ -29,14 +25,12 @@ export default function() { import { meaning } from 'app/shared/meaning'; import { meaning as meaning2 } from '@shared'; import { meaning as meaning3 } from '@shared/meaning'; - import { meaning as meaning4 } from 'meaning'; // need to use imports otherwise they are ignored and // no error is outputted, even if baseUrl/paths don't work console.log(meaning) console.log(meaning2) console.log(meaning3) - console.log(meaning4) `)) .then(() => ng('build')); }