forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(offline compiler): a replacement for tsc that compiles templates
see angular#7483.
- Loading branch information
Showing
17 changed files
with
743 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Angular Template Compiler | ||
|
||
Angular applications are built with templates, which may be `.html` or `.css` files, | ||
or may be inline `template` attributes on Decorators like `@Component`. | ||
|
||
These templates are compiled into executable JS at application runtime (except in `interpretation` mode). | ||
This compilation can occur on the client, but it results in slower bootstrap time, and also | ||
requires that the compiler be included in the code downloaded to the client. | ||
|
||
You can produce smaller, faster applications by running Angular's compiler as a build step, | ||
and then downloading only the executable JS to the client. | ||
|
||
## Configuration | ||
|
||
The `tsconfig.json` file is expected to contain an additional configuration block: | ||
``` | ||
"angularCompilerOptions": { | ||
"genDir": "." | ||
} | ||
``` | ||
the `genDir` option controls the path (relative to `tsconfig.json`) where the generated file tree | ||
will be written. More options may be added as we implement more features. | ||
|
||
We recommend you avoid checking generated files into version control. This permits a state where | ||
the generated files in the repository were created from sources that were never checked in, | ||
making it impossible to reproduce the current state. Also, your changes will effectively appear | ||
twice in code reviews, with the generated version inscrutible by the reviewer. | ||
|
||
In TypeScript 1.8, the generated sources will have to be written alongside your originals, | ||
so set `genDir` to the same location as your files (typicially the same as `rootDir`). | ||
Add `**/*.ngfactory.ts` to your `.gitignore` or other mechanism for your version control system. | ||
|
||
In TypeScript 1.9 and above, you can add a generated folder into your application, | ||
such as `codegen`. Using the `rootDirs` option, you can allow relative imports like | ||
`import {} from './foo.ngfactory'` even though the `src` and `codegen` trees are distinct. | ||
Add `**/codegen` to your `.gitignore` or similar. | ||
|
||
Note that in the second option, TypeScript will emit the code into two parallel directories | ||
as well. This is by design, see https://github.com/Microsoft/TypeScript/issues/8245. | ||
This makes the configuration of your runtime module loader more complex, so we don't recommend | ||
this option yet. | ||
|
||
See the example in the `test/` directory for a working example. | ||
|
||
## Compiler CLI | ||
|
||
This program mimics the TypeScript tsc command line. It accepts a `-p` flag which points to a | ||
`tsconfig.json` file, or a directory containing one. | ||
|
||
This CLI is intended for demos, prototyping, or for users with simple build systems | ||
that run bare `tsc`. | ||
|
||
Users with a build system should expect an Angular 2 template plugin. Such a plugin would be | ||
based on the `index.ts` in this directory, but should share the TypeScript compiler instance | ||
with the one already used in the plugin for TypeScript typechecking and emit. | ||
|
||
## Design | ||
At a high level, this program | ||
- collects static metadata about the sources using the `ts-metadata-collector` package in angular2 | ||
- uses the `OfflineCompiler` from `angular2/src/compiler/compiler` to codegen additional `.ts` files | ||
- these `.ts` files are written to the `genDir` path, then compiled together with the application. | ||
|
||
## For developers | ||
Run the compiler from source: | ||
``` | ||
# Build angular2 | ||
gulp build.js.cjs | ||
# Build the compiler | ||
./node_modules/.bin/tsc -p tools/compiler_cli/src | ||
# Run it on the test project | ||
node ./dist/js/cjs/compiler_cli -p tools/compiler_cli/test | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/** | ||
* Transform template html and css into executable code. | ||
* Intended to be used in a build step. | ||
*/ | ||
import * as ts from 'typescript'; | ||
import * as path from 'path'; | ||
|
||
import * as compiler from 'angular2/compiler'; | ||
import {StaticReflector} from 'angular2/src/compiler/static_reflector'; | ||
import {CompileMetadataResolver} from 'angular2/src/compiler/metadata_resolver'; | ||
import {HtmlParser} from 'angular2/src/compiler/html_parser'; | ||
import {DirectiveNormalizer} from 'angular2/src/compiler/directive_normalizer'; | ||
import {Lexer} from 'angular2/src/compiler/expression_parser/lexer'; | ||
import {Parser} from 'angular2/src/compiler/expression_parser/parser'; | ||
import {TemplateParser} from 'angular2/src/compiler/template_parser'; | ||
import {DomElementSchemaRegistry} from 'angular2/src/compiler/schema/dom_element_schema_registry'; | ||
import {StyleCompiler} from 'angular2/src/compiler/style_compiler'; | ||
import {ViewCompiler} from 'angular2/src/compiler/view_compiler/view_compiler'; | ||
import {TypeScriptEmitter} from 'angular2/src/compiler/output/ts_emitter'; | ||
import {RouterLinkTransform} from 'angular2/src/router/directives/router_link_transform'; | ||
import {Parse5DomAdapter} from 'angular2/platform/server'; | ||
|
||
import {MetadataCollector} from 'ts-metadata-collector'; | ||
import {NodeReflectorHost} from './reflector_host'; | ||
import {wrapCompilerHost, CodeGeneratorHost} from './compiler_host'; | ||
|
||
const SOURCE_EXTENSION = /\.[jt]s$/; | ||
const PREAMBLE = `/** | ||
* This file is generated by the Angular 2 template compiler. | ||
* Do not edit. | ||
*/ | ||
`; | ||
|
||
export interface AngularCompilerOptions { | ||
// Absolute path to a directory where generated file structure is written | ||
genDir: string; | ||
} | ||
|
||
export class CodeGenerator { | ||
constructor(private ngOptions: AngularCompilerOptions, private basePath: string, | ||
public program: ts.Program, public host: CodeGeneratorHost, | ||
private staticReflector: StaticReflector, private resolver: CompileMetadataResolver, | ||
private compiler: compiler.OfflineCompiler, | ||
private reflectorHost: NodeReflectorHost) {} | ||
|
||
private generateSource(metadatas: compiler.CompileDirectiveMetadata[]) { | ||
const normalize = (metadata: compiler.CompileDirectiveMetadata) => { | ||
const directiveType = metadata.type.runtime; | ||
const directives = this.resolver.getViewDirectivesMetadata(directiveType); | ||
const pipes = this.resolver.getViewPipesMetadata(directiveType); | ||
return new compiler.NormalizedComponentWithViewDirectives(metadata, directives, pipes); | ||
}; | ||
|
||
return this.compiler.compileTemplates(metadatas.map(normalize)); | ||
} | ||
|
||
private readComponents(absSourcePath: string) { | ||
const result: Promise<compiler.CompileDirectiveMetadata>[] = []; | ||
const metadata = this.staticReflector.getModuleMetadata(absSourcePath); | ||
if (!metadata) { | ||
console.log(`WARNING: no metadata found for ${absSourcePath}`); | ||
return result; | ||
} | ||
|
||
const symbols = Object.keys(metadata['metadata']); | ||
if (!symbols || !symbols.length) { | ||
return result; | ||
} | ||
for (const symbol of symbols) { | ||
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath); | ||
let directive: compiler.CompileDirectiveMetadata; | ||
directive = this.resolver.maybeGetDirectiveMetadata(<any>staticType); | ||
|
||
if (!directive || !directive.isComponent) { | ||
continue; | ||
} | ||
result.push(this.compiler.normalizeDirectiveMetadata(directive)); | ||
} | ||
return result; | ||
} | ||
|
||
codegen() { | ||
Parse5DomAdapter.makeCurrent(); | ||
const generateOneFile = (absSourcePath: string) => | ||
Promise.all(this.readComponents(absSourcePath)) | ||
.then((metadatas: compiler.CompileDirectiveMetadata[]) => { | ||
if (!metadatas || !metadatas.length) { | ||
return; | ||
} | ||
const generated = this.generateSource(metadatas); | ||
const sourceFile = this.program.getSourceFile(absSourcePath); | ||
|
||
// Write codegen in a directory structure matching the sources. | ||
// TODO(alexeagle): maybe use generated.moduleUrl instead of hardcoded ".ngfactory.ts" | ||
// TODO(alexeagle): relativize paths by the rootDirs option | ||
const emitPath = | ||
path.join(this.ngOptions.genDir, path.relative(this.basePath, absSourcePath)) | ||
.replace(SOURCE_EXTENSION, '.ngfactory.ts'); | ||
this.host.writeFile(emitPath, PREAMBLE + generated.source, false, () => {}, | ||
[sourceFile]); | ||
}) | ||
.catch((e) => { console.error(e.stack); }); | ||
|
||
return Promise.all(this.program.getRootFileNames() | ||
.filter(f => !/\.ngfactory\.ts$/.test(f)) | ||
.map(generateOneFile)); | ||
} | ||
|
||
static create(ngOptions: AngularCompilerOptions, parsed: ts.ParsedCommandLine, basePath: string, | ||
compilerHost: ts.CompilerHost): | ||
{errors?: ts.Diagnostic[], generator?: CodeGenerator} { | ||
const program = ts.createProgram(parsed.fileNames, parsed.options, compilerHost); | ||
const errors = program.getOptionsDiagnostics(); | ||
if (errors && errors.length) { | ||
return {errors}; | ||
} | ||
|
||
const metadataCollector = new MetadataCollector(); | ||
const reflectorHost = | ||
new NodeReflectorHost(program, metadataCollector, compilerHost, parsed.options); | ||
const xhr: compiler.XHR = {get: (s: string) => Promise.resolve(compilerHost.readFile(s))}; | ||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); | ||
const staticReflector = new StaticReflector(reflectorHost); | ||
const htmlParser = new HtmlParser(); | ||
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser); | ||
const parser = new Parser(new Lexer()); | ||
const tmplParser = new TemplateParser(parser, new DomElementSchemaRegistry(), htmlParser, | ||
/*console*/ null, [new RouterLinkTransform(parser)]); | ||
const offlineCompiler = new compiler.OfflineCompiler( | ||
normalizer, tmplParser, new StyleCompiler(urlResolver), | ||
new ViewCompiler(new compiler.CompilerConfig(true, true, true)), new TypeScriptEmitter()); | ||
const resolver = new CompileMetadataResolver( | ||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), | ||
new compiler.ViewResolver(staticReflector), null, null, staticReflector); | ||
|
||
return { | ||
generator: new CodeGenerator(ngOptions, basePath, program, | ||
wrapCompilerHost(compilerHost, parsed.options), staticReflector, | ||
resolver, offlineCompiler, reflectorHost) | ||
}; | ||
} | ||
} |
Oops, something went wrong.