Skip to content

Commit

Permalink
feat(aot): adding README and type checking.
Browse files Browse the repository at this point in the history
Close #2527
  • Loading branch information
hansl authored and filipesilva committed Oct 7, 2016
1 parent afb36e8 commit 8a5b265
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 153 deletions.
10 changes: 4 additions & 6 deletions packages/angular-cli/models/webpack-build-typescript.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as path from 'path';
import * as webpack from 'webpack';
import {findLazyModules} from './find-lazy-modules';
import {NgcWebpackPlugin} from '@ngtools/webpack';
import {AotPlugin} from '@ngtools/webpack';

const atl = require('awesome-typescript-loader');

Expand Down Expand Up @@ -59,11 +59,9 @@ export const getWebpackAotConfigPartial = function(projectRoot: string, appConfi
]
},
plugins: [
new NgcWebpackPlugin({
project: path.resolve(projectRoot, appConfig.root, appConfig.tsconfig),
baseDir: path.resolve(projectRoot, appConfig.root),
main: path.join(projectRoot, appConfig.root, appConfig.main),
genDir: path.resolve(projectRoot, appConfig.root)
new AotPlugin({
tsConfigPath: path.resolve(projectRoot, appConfig.root, appConfig.tsconfig),
mainPath: path.join(projectRoot, appConfig.root, appConfig.main)
}),
]
};
Expand Down
39 changes: 39 additions & 0 deletions packages/webpack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Angular Ahead-of-Time Webpack Plugin

Webpack plugin that AoT compiles your Angular components and modules.

## Usage
In your webpack config, add the following plugin and loader:

```typescript
import {AotPlugin} from '@ngtools/webpack'

exports = { /* ... */
module: {
rules: [
{
test: /\.ts$/,
loader: '@ngtools/webpack',
}
]
},

plugins: [
new AotPlugin({
tsConfigPath: 'path/to/tsconfig.json',
entryModule: 'path/to/app.module#AppModule'
})
]
}
```

The loader works with the webpack plugin to compile your TypeScript. It's important to include both, and to not include any other TypeScript compiler loader.

## Options

* `tsConfigPath`. The path to the `tsconfig.json` file. This is required. In your `tsconfig.json`, you can pass options to the Angular Compiler with `angularCompilerOptions`.
* `basePath`. Optional. The root to use by the compiler to resolve file paths. By default, use the `tsConfigPath` root.
* `entryModule`. Optional if specified in `angularCompilerOptions`. The path and classname of the main application module. This follows the format `path/to/file#ClassName`.
* `mainPath`. Optional if `entryModule` is specified. The `main.ts` file containing the bootstrap code. The plugin will use AST to determine the `entryModule`.
* `genDir`. Optional. The output directory of the offline compiler. The files created by the offline compiler will be in a virtual file system, but the import paths might change. This can also be specified in `angularCompilerOptions`, and by default will be the same as `basePath`.
* `typeChecking`. Optional, defaults to true. Enable type checking through your application. This will slow down compilation, but show syntactic and semantic errors in webpack.
67 changes: 42 additions & 25 deletions packages/webpack/src/loader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as path from 'path';
import * as ts from 'typescript';
import {NgcWebpackPlugin} from './plugin';
import {AotPlugin} from './plugin';
import {MultiChange, ReplaceChange, insertImport} from '@angular-cli/ast-tools';

// TODO: move all this to ast-tools.
Expand Down Expand Up @@ -31,7 +31,7 @@ function _removeDecorators(fileName: string, source: string): string {

function _replaceBootstrap(fileName: string,
source: string,
plugin: NgcWebpackPlugin): Promise<string> {
plugin: AotPlugin): Promise<string> {
// If bootstrapModule can't be found, bail out early.
if (!source.match(/\bbootstrapModule\b/)) {
return Promise.resolve(source);
Expand All @@ -40,11 +40,11 @@ function _replaceBootstrap(fileName: string,
let changes = new MultiChange();

// Calculate the base path.
const basePath = path.normalize(plugin.angularCompilerOptions.basePath);
const basePath = path.normalize(plugin.basePath);
const genDir = path.normalize(plugin.genDir);
const dirName = path.normalize(path.dirname(fileName));
const [entryModulePath, entryModuleName] = plugin.entryModule.split('#');
const entryModuleFileName = path.normalize(entryModulePath + '.ngfactory');
const entryModule = plugin.entryModule;
const entryModuleFileName = path.normalize(entryModule.path + '.ngfactory');
const relativeEntryModulePath = path.relative(basePath, entryModuleFileName);
const fullEntryModulePath = path.resolve(genDir, relativeEntryModulePath);
const relativeNgFactoryPath = path.relative(dirName, fullEntryModulePath);
Expand Down Expand Up @@ -82,7 +82,7 @@ function _replaceBootstrap(fileName: string,
.filter(call => bootstraps.some(bs => bs == call.expression))
.forEach((call: ts.CallExpression) => {
changes.appendChange(new ReplaceChange(fileName, call.arguments[0].getStart(sourceFile),
entryModuleName, entryModuleName + 'NgFactory'));
entryModule.className, entryModule.className + 'NgFactory'));
});

calls
Expand All @@ -98,7 +98,7 @@ function _replaceBootstrap(fileName: string,
'bootstrapModule', 'bootstrapModuleFactory'));
});
changes.appendChange(insertImport(fileName, 'platformBrowser', '@angular/platform-browser'));
changes.appendChange(insertImport(fileName, entryModuleName + 'NgFactory', ngFactoryPath));
changes.appendChange(insertImport(fileName, entryModule.className + 'NgFactory', ngFactoryPath));

let sourceText = source;
return changes.apply({
Expand All @@ -107,35 +107,52 @@ function _replaceBootstrap(fileName: string,
}).then(() => sourceText);
}

function _transpile(plugin: AotPlugin, filePath: string, sourceText: string) {
const program = plugin.program;
if (plugin.typeCheck) {
const sourceFile = program.getSourceFile(filePath);
const diagnostics = program.getSyntacticDiagnostics(sourceFile)
.concat(program.getSemanticDiagnostics(sourceFile))
.concat(program.getDeclarationDiagnostics(sourceFile));

if (diagnostics.length > 0) {
const message = diagnostics
.map(diagnostic => {
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`;
})
.join('\n');
throw new Error(message);
}
}

const result = ts.transpileModule(sourceText, {
compilerOptions: plugin.compilerOptions,
fileName: filePath
});

return {
outputText: result.outputText,
sourceMap: JSON.parse(result.sourceMapText)
};
}

// Super simple TS transpiler loader for testing / isolated usage. does not type check!
export function ngcLoader(source: string) {
this.cacheable();

const plugin = this._compilation._ngToolsWebpackPluginInstance as NgcWebpackPlugin;
if (plugin && plugin instanceof NgcWebpackPlugin) {
const plugin = this._compilation._ngToolsWebpackPluginInstance as AotPlugin;
// We must verify that AotPlugin is an instance of the right class.
if (plugin && plugin instanceof AotPlugin) {
const cb: any = this.async();

plugin.done
.then(() => _removeDecorators(this.resource, source))
.then(sourceText => _replaceBootstrap(this.resource, sourceText, plugin))
.then(sourceText => {
const result = ts.transpileModule(sourceText, {
compilerOptions: {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.ES2015,
}
});

if (result.diagnostics && result.diagnostics.length) {
let message = '';
result.diagnostics.forEach(d => {
message += d.messageText + '\n';
});
cb(new Error(message));
}

cb(null, result.outputText, result.sourceMapText ? JSON.parse(result.sourceMapText) : null);
const result = _transpile(plugin, this.resource, sourceText);
cb(null, result.outputText, result.sourceMap);
})
.catch(err => cb(err));
} else {
Expand Down
Loading

0 comments on commit 8a5b265

Please sign in to comment.