fix(@ngtools/webpack): fix resolution fallback of paths-plugin

If there is a package.json we should also verify that it has a main or fesm field
to see if webpack would actually resolve it properly. Otherwise use the JavaScript
resolution.

This is a temporary fix and still has obvious limitations and issues. Namely, this
code is never run if there is only one path mapping, but that falls outside the
scope of this fix. Also, some people might have valid packages but want to resolve
to the JS file itself (which is what TypeScript does by default). These should be
fixed with a refactoring of the path plugin.

Fixes https://github.com/nativescript/nativescript-dev-webpack/issues/607
This commit is contained in:
Hans 2018-07-11 16:10:22 -07:00
parent 91e7c3eb96
commit 0bef5aa4be
14 changed files with 227 additions and 8 deletions

View File

@ -160,15 +160,40 @@ export function resolveWithPaths(
const jsFilePath = `${pathNoExtension}.js`;
if (host.fileExists(pathNoExtension)) {
// This is mainly for secondary entry points
// ex: 'node_modules/@angular/core/testing.d.ts' -> 'node_modules/@angular/core/testing'
request.request = pathNoExtension;
} else if (host.fileExists(packageRootPath)) {
// Let webpack resolve the correct module format
request.request = pathDirName;
} else if (host.fileExists(jsFilePath)) {
// This is mainly for secondary entry points
// ex: 'node_modules/@angular/core/testing.d.ts' -> 'node_modules/@angular/core/testing'
request.request = pathNoExtension;
} else {
const packageJsonContent = host.readFile(packageRootPath);
let newRequest: string | undefined;
if (packageJsonContent) {
try {
const packageJson = JSON.parse(packageJsonContent);
// Let webpack resolve the correct module format IIF there is a module resolution field
// in the package.json. These are all official fields that Angular uses.
if (typeof packageJson.main == 'string'
|| typeof packageJson.browser == 'string'
|| typeof packageJson.module == 'string'
|| typeof packageJson.es2015 == 'string'
|| typeof packageJson.fesm5 == 'string'
|| typeof packageJson.fesm2015 == 'string') {
newRequest = pathDirName;
}
} catch {
// Ignore exceptions and let it fall through (ie. if package.json file is invalid).
}
}
if (newRequest === undefined && host.fileExists(jsFilePath)) {
// Otherwise, if there is a file with a .js extension use that
request.request = jsFilePath;
newRequest = jsFilePath;
}
if (newRequest !== undefined) {
request.request = newRequest;
}
}
callback(null, request);

View File

@ -0,0 +1,4 @@
{
"tsConfigPath": "./src/tsconfig.json",
"mainPath": "app/main.jit.ts"
}

View File

@ -0,0 +1,29 @@
{
"name": "test",
"license": "MIT",
"dependencies": {
"@angular/common": "^5.0.0-rc.8",
"@angular/compiler": "^5.0.0-rc.8",
"@angular/compiler-cli": "^5.0.0-rc.8",
"@angular/core": "^5.0.0-rc.8",
"@angular/http": "^5.0.0-rc.8",
"@angular/platform-browser": "^5.0.0-rc.8",
"@angular/platform-browser-dynamic": "^5.0.0-rc.8",
"@angular/platform-server": "^5.0.0-rc.8",
"@angular/router": "^5.0.0-rc.8",
"@ngtools/webpack": "0.0.0",
"core-js": "^2.4.1",
"rxjs": "^5.5.0",
"zone.js": "^0.8.14"
},
"devDependencies": {
"node-sass": "^4.7.0",
"performance-now": "^0.2.0",
"preprocess-loader": "^0.2.2",
"raw-loader": "^0.5.1",
"sass-loader": "^6.0.0",
"typescript": "~2.4.2",
"webpack": "~4.0.1",
"webpack-cli": "~2.0.9"
}
}

View File

@ -0,0 +1,29 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { Component, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Foo } from 'foo/foo';
console.log(Foo); // Make sure it's used.
@Component({
selector: 'home-view',
template: 'home!',
})
export class HomeView {}
@NgModule({
declarations: [
HomeView,
],
imports: [
BrowserModule,
],
bootstrap: [HomeView],
})
export class AppModule { }

View File

@ -0,0 +1,5 @@
import 'core-js/es7/reflect';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -0,0 +1,9 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export class Foo {}

View File

@ -0,0 +1,6 @@
console.log('NGTOOLS_WEBPACK_TEST_WRONG_FILE');
module.exports = {
Foo: () => {}, // Empty "class".
};

View File

@ -0,0 +1,3 @@
{
"main": "right/foo.js"
}

View File

@ -0,0 +1,6 @@
console.log('NGTOOLS_WEBPACK_TEST_RIGHT_FILE');
module.exports = {
Foo: () => {}, // Empty "class".
};

View File

@ -0,0 +1,12 @@
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<base href="">
</head>
<body>
<app-root></app-root>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="dist/app.main.js"></script>
</body>
</html>

View File

@ -0,0 +1,30 @@
{
"compilerOptions": {
"module": "es2015",
"moduleResolution": "node",
"target": "es5",
"baseUrl": ".",
"noImplicitAny": false,
"sourceMap": true,
"mapRoot": "",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2017",
"dom"
],
"outDir": "lib",
"skipLibCheck": true,
"rootDir": ".",
"paths": {
"foo/*": [
"./foo/*",
"*"
]
}
},
"angularCompilerOptions": {
"genDir": "app/generated/",
"entryModule": "app/app.module#AppModule"
}
}

View File

@ -0,0 +1,45 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const ngToolsWebpack = require('@ngtools/webpack');
const path = require('path');
const flags = require('./webpack.flags.json');
const preprocessLoader = 'preprocess-loader' + (flags.DEBUG ? '?+DEBUG' : '');
module.exports = {
resolve: {
extensions: ['.ts', '.js']
},
entry: './src/app/main.jit.ts',
output: {
path: path.resolve('./dist'),
publicPath: 'dist/',
filename: 'app.main.js'
},
plugins: [
new ngToolsWebpack.AngularCompilerPlugin(require('./aotplugin.config.json'))
],
module: {
rules: [
{ test: /\.scss$/, loaders: ['raw-loader', 'sass-loader', preprocessLoader] },
{ test: /\.css$/, loader: 'raw-loader' },
{ test: /\.html$/, loaders: ['raw-loader', preprocessLoader] },
// Use preprocess to remove DEBUG only code.
// @ngtools/webpack must be the first (right most) loader.
{ test: /\.ts$/, use: [
{ loader: preprocessLoader },
{ loader: '@ngtools/webpack' }
] }
]
},
devServer: {
historyApiFallback: true
}
};

View File

@ -0,0 +1,3 @@
{
"DEBUG": false
}

View File

@ -0,0 +1,13 @@
import {normalize} from 'path';
import {createProjectFromAsset} from '../../../utils/assets';
import {exec} from '../../../utils/process';
import {expectFileSizeToBeUnder, replaceInFile, expectFileToMatch} from '../../../utils/fs';
export default function(skipCleaning: () => void) {
return Promise.resolve()
.then(() => createProjectFromAsset('webpack/test-app-path-mapping'))
.then(() => exec(normalize('node_modules/.bin/webpack-cli')))
.then(() => expectFileToMatch('dist/app.main.js', 'NGTOOLS_WEBPACK_TEST_RIGHT_FILE'))
.then(() => skipCleaning());
}