Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/@angular/cli/models/webpack-configs/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) {
replaceExport: appConfig.platform === 'server',
missingTranslation: buildOptions.missingTranslation,
hostReplacementPaths,
sourceMap: buildOptions.sourcemaps,
// If we don't explicitely list excludes, it will default to `['**/*.spec.ts']`.
exclude: []
}, options));
Expand Down
1 change: 1 addition & 0 deletions packages/@ngtools/webpack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ The loader works with the webpack plugin to compile your TypeScript. It's import
* `skipCodeGeneration`. Optional, defaults to false. Disable code generation and do not refactor the code to bootstrap. This replaces `templateUrl: "string"` with `template: require("string")` (and similar for styles) to allow for webpack to properly link the resources.
* `typeChecking`. Optional, defaults to true. Enable type checking through your application. This will slow down compilation, but show syntactic and semantic errors in webpack.
* `exclude`. Optional. Extra files to exclude from TypeScript compilation.
* `sourceMap`. Optional. Include sourcemaps.
* `compilerOptions`. Optional. Override options in `tsconfig.json`.

## Features
Expand Down
8 changes: 6 additions & 2 deletions packages/@ngtools/webpack/src/compiler_host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,12 @@ export class WebpackCompilerHost implements ts.CompilerHost {
this._changedFiles = Object.create(null);
this._changedDirs = Object.create(null);
}
getChangedFilePaths(): string[] {
return Object.keys(this._changedFiles);

getChangedFilePaths(tsOnly = true): string[] {
// Only get changed ts files by default.
// That's what we mostly care about and want to transpile, but all kinds of files are on this
// list, like package.json and .ngsummary.json files.
return Object.keys(this._changedFiles).filter(k => !tsOnly || k.endsWith('.ts'));
}

invalidate(fileName: string): void {
Expand Down
31 changes: 17 additions & 14 deletions packages/@ngtools/webpack/src/entry_resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import {join} from 'path';
import * as ts from 'typescript';

import {TypeScriptFileRefactor} from './refactor';
import {ProgramManager} from './program_manager';


function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
symbolName: string,
host: ts.CompilerHost,
program: ts.Program): string | null {
programManager: ProgramManager): string | null {
// Check this file.
const hasSymbol = refactor.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
.some((cd: ts.ClassDeclaration) => {
Expand All @@ -29,15 +30,16 @@ function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,

const modulePath = (decl.moduleSpecifier as ts.StringLiteral).text;
const resolvedModule = ts.resolveModuleName(
modulePath, refactor.fileName, program.getCompilerOptions(), host);
modulePath, refactor.fileName, programManager.program.getCompilerOptions(), host);
if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
return null;
}

const module = resolvedModule.resolvedModule.resolvedFileName;
if (!decl.exportClause) {
const moduleRefactor = new TypeScriptFileRefactor(module, host, program);
const maybeModule = _recursiveSymbolExportLookup(moduleRefactor, symbolName, host, program);
const moduleRefactor = new TypeScriptFileRefactor(module, host, programManager);
const maybeModule = _recursiveSymbolExportLookup(
moduleRefactor, symbolName, host, programManager);
if (maybeModule) {
return maybeModule;
}
Expand All @@ -51,17 +53,17 @@ function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
if (fs.statSync(module).isDirectory()) {
const indexModule = join(module, 'index.ts');
if (fs.existsSync(indexModule)) {
const indexRefactor = new TypeScriptFileRefactor(indexModule, host, program);
const indexRefactor = new TypeScriptFileRefactor(indexModule, host, programManager);
const maybeModule = _recursiveSymbolExportLookup(
indexRefactor, symbolName, host, program);
indexRefactor, symbolName, host, programManager);
if (maybeModule) {
return maybeModule;
}
}
}

// Create the source and verify that the symbol is at least a class.
const source = new TypeScriptFileRefactor(module, host, program);
const source = new TypeScriptFileRefactor(module, host, programManager);
const hasSymbol = source.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
.some((cd: ts.ClassDeclaration) => {
return cd.name != undefined && cd.name.text == symbolName;
Expand All @@ -80,7 +82,7 @@ function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
function _symbolImportLookup(refactor: TypeScriptFileRefactor,
symbolName: string,
host: ts.CompilerHost,
program: ts.Program): string | null {
programManager: ProgramManager): string | null {
// We found the bootstrap variable, now we just need to get where it's imported.
const imports = refactor.findAstNodes(null, ts.SyntaxKind.ImportDeclaration)
.map(node => node as ts.ImportDeclaration);
Expand All @@ -95,7 +97,7 @@ function _symbolImportLookup(refactor: TypeScriptFileRefactor,

const resolvedModule = ts.resolveModuleName(
(decl.moduleSpecifier as ts.StringLiteral).text,
refactor.fileName, program.getCompilerOptions(), host);
refactor.fileName, programManager.program.getCompilerOptions(), host);
if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
continue;
}
Expand All @@ -114,8 +116,9 @@ function _symbolImportLookup(refactor: TypeScriptFileRefactor,
for (const specifier of binding.elements) {
if (specifier.name.text == symbolName) {
// Create the source and recursively lookup the import.
const source = new TypeScriptFileRefactor(module, host, program);
const maybeModule = _recursiveSymbolExportLookup(source, symbolName, host, program);
const source = new TypeScriptFileRefactor(module, host, programManager);
const maybeModule = _recursiveSymbolExportLookup(
source, symbolName, host, programManager);
if (maybeModule) {
return maybeModule;
}
Expand All @@ -129,8 +132,8 @@ function _symbolImportLookup(refactor: TypeScriptFileRefactor,

export function resolveEntryModuleFromMain(mainPath: string,
host: ts.CompilerHost,
program: ts.Program) {
const source = new TypeScriptFileRefactor(mainPath, host, program);
programManager: ProgramManager) {
const source = new TypeScriptFileRefactor(mainPath, host, programManager);

const bootstrap = source.findAstNodes(source.sourceFile, ts.SyntaxKind.CallExpression, true)
.map(node => node as ts.CallExpression)
Expand All @@ -150,7 +153,7 @@ export function resolveEntryModuleFromMain(mainPath: string,
+ 'to the plugins options.');
}
const bootstrapSymbolName = bootstrap[0].text;
const module = _symbolImportLookup(source, bootstrapSymbolName, host, program);
const module = _symbolImportLookup(source, bootstrapSymbolName, host, programManager);
if (module) {
return `${module.replace(/\.ts$/, '')}#${bootstrapSymbolName}`;
}
Expand Down
8 changes: 5 additions & 3 deletions packages/@ngtools/webpack/src/lazy_routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {dirname, join} from 'path';
import * as ts from 'typescript';

import {TypeScriptFileRefactor} from './refactor';
import {ProgramManager} from './program_manager';


function _getContentOfKeyLiteral(_source: ts.SourceFile, node: ts.Node): string | null {
Expand All @@ -21,9 +22,9 @@ export interface LazyRouteMap {


export function findLazyRoutes(filePath: string,
program: ts.Program,
programManager: ProgramManager,
host: ts.CompilerHost): LazyRouteMap {
const refactor = new TypeScriptFileRefactor(filePath, host, program);
const refactor = new TypeScriptFileRefactor(filePath, host, programManager);

return refactor
// Find all object literals in the file.
Expand All @@ -50,7 +51,8 @@ export function findLazyRoutes(filePath: string,
? ({
resolvedModule: { resolvedFileName: join(dirname(filePath), moduleName) + '.ts' }
} as any)
: ts.resolveModuleName(moduleName, filePath, program.getCompilerOptions(), host);
: ts.resolveModuleName(
moduleName, filePath, programManager.program.getCompilerOptions(), host);
if (resolvedModuleName.resolvedModule
&& resolvedModuleName.resolvedModule.resolvedFileName
&& host.fileExists(resolvedModuleName.resolvedModule.resolvedFileName)) {
Expand Down
38 changes: 21 additions & 17 deletions packages/@ngtools/webpack/src/loader.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as ts from 'typescript';
import {removeModuleIdOnlyForTesting} from './loader';
import {WebpackCompilerHost} from './compiler_host';
import {TypeScriptFileRefactor} from './refactor';
import {ProgramManager} from './program_manager';

describe('@ngtools/webpack', () => {
describe('loader', () => {
Expand All @@ -20,25 +20,28 @@ describe('@ngtools/webpack', () => {
@SomeDecorator({ otherValue4: 4, moduleId: 123 }) class CLS4 {}
`, false);

const program = ts.createProgram(['/file.ts', '/file2.ts'], {}, host);
const programManager = new ProgramManager(['/file.ts', '/file2.ts'], {}, host);

const refactor = new TypeScriptFileRefactor('/file.ts', host, program);
const refactor = new TypeScriptFileRefactor('/file.ts', host, programManager);
removeModuleIdOnlyForTesting(refactor);
expect(refactor.sourceText).not.toMatch(/obj = \{\s+};/);
expect(refactor.sourceText).not.toMatch(/\{\s*otherValue: 1\s*};/);

const refactor2 = new TypeScriptFileRefactor('/file2.ts', host, program);
const outputText = refactor.transpile().outputText;
expect(outputText).not.toMatch(/obj = \{\s+};/);
expect(outputText).not.toMatch(/\{\s*otherValue: 1\s*};/);

const refactor2 = new TypeScriptFileRefactor('/file2.ts', host, programManager);
removeModuleIdOnlyForTesting(refactor2);
expect(refactor2.sourceText).toMatch(/\(\{\s+}\)/);
expect(refactor2.sourceText).toMatch(/\(\{\s*otherValue1: 1\s*}\)/);
expect(refactor2.sourceText).toMatch(/\(\{\s*otherValue2: 2\s*,\s*otherValue3: 3\s*}\)/);
expect(refactor2.sourceText).toMatch(/\(\{\s*otherValue4: 4\s*}\)/);
const outputText2 = refactor2.transpile().outputText;
expect(outputText2).toMatch(/\(\{\s*}\)/);
expect(outputText2).toMatch(/\(\{\s*otherValue1: 1\s*}\)/);
expect(outputText2).toMatch(/\(\{\s*otherValue2: 2\s*,\s*otherValue3: 3\s*}\)/);
expect(outputText2).toMatch(/\(\{\s*otherValue4: 4\s*}\)/);
});

it('should work without a root name', () => {
const host = new WebpackCompilerHost({}, '');
host.writeFile('/file.ts', `
import './file2.ts';
import './file2';
`, false);
host.writeFile('/file2.ts', `
@SomeDecorator({ moduleId: 123 }) class CLS {}
Expand All @@ -47,13 +50,14 @@ describe('@ngtools/webpack', () => {
@SomeDecorator({ otherValue4: 4, moduleId: 123 }) class CLS4 {}
`, false);

const program = ts.createProgram(['/file.ts'], {}, host);
const refactor = new TypeScriptFileRefactor('/file2.ts', host, program);
const programManager = new ProgramManager(['/file.ts'], {}, host);
const refactor = new TypeScriptFileRefactor('/file2.ts', host, programManager);
removeModuleIdOnlyForTesting(refactor);
expect(refactor.sourceText).toMatch(/\(\{\s+}\)/);
expect(refactor.sourceText).toMatch(/\(\{\s*otherValue1: 1\s*}\)/);
expect(refactor.sourceText).toMatch(/\(\{\s*otherValue2: 2\s*,\s*otherValue3: 3\s*}\)/);
expect(refactor.sourceText).toMatch(/\(\{\s*otherValue4: 4\s*}\)/);
const outputText = refactor.transpile().outputText;
expect(outputText).toMatch(/\(\{\s*}\)/);
expect(outputText).toMatch(/\(\{\s*otherValue1: 1\s*}\)/);
expect(outputText).toMatch(/\(\{\s*otherValue2: 2\s*,\s*otherValue3: 3\s*}\)/);
expect(outputText).toMatch(/\(\{\s*otherValue4: 4\s*}\)/);
});
});
});
Expand Down
31 changes: 18 additions & 13 deletions packages/@ngtools/webpack/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ function _addCtorParameters(classNode: ts.ClassDeclaration,
});

const ctorParametersDecl = `static ctorParameters() { return [ ${params.join(', ')} ]; }`;
refactor.prependBefore(classNode.getLastToken(refactor.sourceFile), ctorParametersDecl);
refactor.prependNode(classNode.getLastToken(refactor.sourceFile), ctorParametersDecl);
}


Expand Down Expand Up @@ -347,6 +347,7 @@ function _getResourceRequest(element: ts.Expression, sourceFile: ts.SourceFile)

function _replaceResources(refactor: TypeScriptFileRefactor): void {
const sourceFile = refactor.sourceFile;
let refactored = false;

_getResourceNodes(refactor)
// Get the full text of the initializer.
Expand All @@ -356,9 +357,11 @@ function _replaceResources(refactor: TypeScriptFileRefactor): void {
if (key == 'templateUrl') {
refactor.replaceNode(node,
`template: require(${_getResourceRequest(node.initializer, sourceFile)})`);
refactored = true;
} else if (key == 'styleUrls') {
const arr = <ts.ArrayLiteralExpression[]>(
refactor.findAstNodes(node, ts.SyntaxKind.ArrayLiteralExpression, false));
refactored = true;
if (!arr || arr.length == 0 || arr[0].elements.length == 0) {
return;
}
Expand All @@ -367,8 +370,17 @@ function _replaceResources(refactor: TypeScriptFileRefactor): void {
return _getResourceRequest(element, sourceFile);
});
refactor.replaceNode(node, `styles: [require(${initializer.join('), require(')})]`);
refactored = true;
}
});

if (refactored) {
// If we added a require call, we need to also add typings for it.
// The typings need to be compatible with node typings, but also work by themselves.
refactor.prependNode(refactor.getFirstNode(),
'declare var require: NodeRequire;interface NodeRequire {(id: string): any;}'
);
}
}


Expand Down Expand Up @@ -465,7 +477,7 @@ export function _replaceExport(plugin: AotPlugin, refactor: TypeScriptFileRefact
const factoryPath = _getNgFactoryPath(plugin, refactor);
const factoryClassName = plugin.entryModule.className + 'NgFactory';
const exportStatement = `export \{ ${factoryClassName} \} from '${factoryPath}'`;
refactor.appendAfter(node, exportStatement);
refactor.appendNode(node, exportStatement);
});
}

Expand Down Expand Up @@ -502,15 +514,15 @@ export function _exportModuleMap(plugin: AotPlugin, refactor: TypeScriptFileRefa

modules.forEach((module, index) => {
const relativePath = path.relative(dirName, module.modulePath!).replace(/\\/g, '/');
refactor.prependBefore(node, `import * as __lazy_${index}__ from './${relativePath}'`);
refactor.prependNode(node, `import * as __lazy_${index}__ from './${relativePath}'`);
});

const jsonContent: string = modules
.map((module, index) =>
`"${module.loadChildrenString}": __lazy_${index}__.${module.moduleName}`)
.join();

refactor.appendAfter(node, `export const LAZY_MODULE_MAP = {${jsonContent}};`);
refactor.appendNode(node, `export const LAZY_MODULE_MAP = {${jsonContent}};`);
});
}

Expand All @@ -537,7 +549,7 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
source = null;
}
const refactor = new TypeScriptFileRefactor(
sourceFileName, plugin.compilerHost, plugin.program, source);
sourceFileName, plugin.compilerHost, plugin.programManager, source);

Promise.resolve()
.then(() => {
Expand Down Expand Up @@ -594,14 +606,7 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
}
}

// Force a few compiler options to make sure we get the result we want.
const compilerOptions: ts.CompilerOptions = Object.assign({}, plugin.compilerOptions, {
inlineSources: true,
inlineSourceMap: false,
sourceRoot: plugin.basePath
});

const result = refactor.transpile(compilerOptions);
const result = refactor.transpile();
cb(null, result.outputText, result.sourceMap);
})
.catch(err => cb(err));
Expand Down
Loading