Skip to content
This repository has been archived by the owner on May 1, 2020. It is now read-only.

Commit

Permalink
fix(AoT): use in-memory data store instead of .tmp directory for AoT …
Browse files Browse the repository at this point in the history
…codegen

use in-memory data store isntead of .tmp directory for AoT codegen
  • Loading branch information
danbucholtz committed Dec 1, 2016
1 parent 3eb8d0e commit 93106ff
Show file tree
Hide file tree
Showing 26 changed files with 764 additions and 397 deletions.
10 changes: 0 additions & 10 deletions config/ngc.config.js

This file was deleted.

41 changes: 9 additions & 32 deletions config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,13 @@ var ionicWebpackFactory = require(ionicWebpackFactoryPath);

function getEntryPoint() {
if (process.env.IONIC_ENV === 'prod') {
return '{{TMP}}/app/main.prod.js';
return '{{SRC}}/app/main.ts';
}
return '{{SRC}}/app/main.dev.ts';
return '{{SRC}}/app/main.ts';
}

function getPlugins() {
if (process.env.IONIC_ENV === 'prod') {
return [
// This helps ensure the builds are consistent if source hasn't changed:
new webpack.optimize.OccurrenceOrderPlugin(),

// Try to dedupe duplicated modules, if any:
// Add this back in when Angular fixes the issue: https://github.com/angular/angular-cli/issues/1587
//new DedupePlugin()
];
}

// for dev builds, use our custom environment
return [
ionicWebpackFactory.getIonicEnvironmentPlugin()
];
}

function getSourcemapLoader() {
if (process.env.IONIC_ENV === 'prod') {
// TODO figure out the disk loader, it's not working yet
return [];
}

return [
{
test: /\.ts$/,
loader: path.join(process.env.IONIC_APP_SCRIPTS_DIR, 'dist', 'webpack', 'typescript-sourcemap-loader-memory.js')
}
];
return [ionicWebpackFactory.getIonicEnvironmentPlugin()];
}

function getDevtool() {
Expand All @@ -53,6 +25,7 @@ function getDevtool() {
}

module.exports = {
bail: true,
entry: getEntryPoint(),
output: {
path: '{{BUILD}}',
Expand All @@ -71,8 +44,12 @@ module.exports = {
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.(ts|ngfactory.js)$/,
loader: path.join(process.env.IONIC_APP_SCRIPTS_DIR, 'dist', 'webpack', 'typescript-sourcemap-loader-memory.js')
}
].concat(getSourcemapLoader())
]
},

plugins: getPlugins(),
Expand Down
151 changes: 151 additions & 0 deletions src/aot/aot-compiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { readFileSync } from 'fs';
import { extname } from 'path';

import 'reflect-metadata';
import { CompilerOptions, createProgram, ParsedCommandLine, Program, transpileModule, TranspileOptions, TranspileOutput } from 'typescript';
import { CodeGenerator, NgcCliOptions, NodeReflectorHostContext, ReflectorHost, StaticReflector }from '@angular/compiler-cli';
import { tsc } from '@angular/tsc-wrapped/src/tsc';
import AngularCompilerOptions from '@angular/tsc-wrapped/src/options';

import { HybridFileSystem } from '../util/hybrid-file-system';
import { getInstance as getHybridFileSystem } from '../util/hybrid-file-system-factory';
import { getInstance } from '../aot/compiler-host-factory';
import { NgcCompilerHost } from '../aot/compiler-host';
import { patchReflectorHost } from '../aot/reflector-host';
import { removeDecorators } from '../util/typescript-utils';
import { getMainFileTypescriptContentForAotBuild } from './utils';
import { printDiagnostics, clearDiagnostics, DiagnosticsType } from '../logger/logger-diagnostics';
import { runTypeScriptDiagnostics } from '../logger/logger-typescript';
import { BuildError } from '../util/errors';
import { changeExtension } from '../util/helpers';
import { BuildContext } from '../util/interfaces';

export class AotCompiler {

private tsConfig: ParsedTsConfig;
private angularCompilerOptions: AngularCompilerOptions;
private program: Program;
private reflector: StaticReflector;
private reflectorHost: ReflectorHost;
private compilerHost: NgcCompilerHost;
private fileSystem: HybridFileSystem;

constructor(private context: BuildContext, private options: AotOptions) {
this.tsConfig = getNgcConfig(this.context, this.options.tsConfigPath);

this.angularCompilerOptions = Object.assign({}, this.tsConfig.ngOptions, {
basePath: this.options.rootDir,
entryPoint: this.options.entryPoint
});

this.fileSystem = getHybridFileSystem();
this.compilerHost = getInstance(this.tsConfig.parsed.options);
this.program = createProgram(this.tsConfig.parsed.fileNames, this.tsConfig.parsed.options, this.compilerHost);
this.reflectorHost = new ReflectorHost(this.program, this.compilerHost, this.angularCompilerOptions);
this.reflector = new StaticReflector(this.reflectorHost);
}

compile() {
clearDiagnostics(this.context, DiagnosticsType.TypeScript);
const i18nOptions: NgcCliOptions = {
i18nFile: undefined,
i18nFormat: undefined,
locale: undefined,
basePath: this.options.rootDir
};

// Create the Code Generator.
const codeGenerator = CodeGenerator.create(
this.angularCompilerOptions,
i18nOptions,
this.program,
this.compilerHost,
new NodeReflectorHostContext(this.compilerHost)
);

// We need to temporarily patch the CodeGenerator until either it's patched or allows us
// to pass in our own ReflectorHost.
patchReflectorHost(codeGenerator);
return codeGenerator.codegen({transitiveModules: true})
.then(() => {
// Create a new Program, based on the old one. This will trigger a resolution of all
// transitive modules, which include files that might just have been generated.
this.program = createProgram(this.tsConfig.parsed.fileNames, this.tsConfig.parsed.options, this.compilerHost, this.program);

const tsDiagnostics = this.program.getSyntacticDiagnostics()
.concat(this.program.getSemanticDiagnostics())
.concat(this.program.getOptionsDiagnostics());

if (tsDiagnostics.length) {
throw tsDiagnostics;
}
})
.then(() => {
for ( const fileName of this.tsConfig.parsed.fileNames) {
const content = readFileSync(fileName).toString();
this.context.fileCache.set(fileName, { path: fileName, content: content});
}
})
.then(() => {
const mainContent = getMainFileTypescriptContentForAotBuild();
this.context.fileCache.set(this.options.entryPoint, { path: this.options.entryPoint, content: mainContent});
})
.then(() => {
const tsFiles = this.context.fileCache.getAll().filter(file => extname(file.path) === '.ts' && file.path.indexOf('.d.ts') === -1);
for (const tsFile of tsFiles) {
const cleanedFileContent = removeDecorators(tsFile.path, tsFile.content);
tsFile.content = cleanedFileContent;
const transpileOutput = this.transpileFileContent(tsFile.path, cleanedFileContent, this.tsConfig.parsed.options);
const diagnostics = runTypeScriptDiagnostics(this.context, transpileOutput.diagnostics);
if (diagnostics.length) {
// darn, we've got some things wrong, transpile failed :(
printDiagnostics(this.context, DiagnosticsType.TypeScript, diagnostics, true, true);
throw new BuildError();
}

const jsFilePath = changeExtension(tsFile.path, '.js');
this.fileSystem.addVirtualFile(jsFilePath, transpileOutput.outputText);
this.fileSystem.addVirtualFile(jsFilePath + '.map', transpileOutput.sourceMapText);
}
});
}

transpileFileContent(fileName: string, sourceText: string, options: CompilerOptions): TranspileOutput {
const sourceFile = this.program.getSourceFile(fileName);
const diagnostics = this.program.getSyntacticDiagnostics(sourceFile)
.concat(this.program.getSemanticDiagnostics(sourceFile))
.concat(this.program.getDeclarationDiagnostics(sourceFile));
if (diagnostics.length) {
throw diagnostics;
}

const transpileOptions: TranspileOptions = {
compilerOptions: options,
fileName: fileName,
reportDiagnostics: true
};

return transpileModule(sourceText, transpileOptions);
}
}

export interface AotOptions {
tsConfigPath: string;
rootDir: string;
entryPoint: string;
}

export function getNgcConfig(context: BuildContext, tsConfigPath?: string): ParsedTsConfig {

const tsConfigFile = tsc.readConfiguration(tsConfigPath, process.cwd());
if (!tsConfigFile) {
throw new BuildError(`tsconfig: invalid tsconfig file, "${tsConfigPath}"`);

}
return tsConfigFile;
}

export interface ParsedTsConfig {
parsed: ParsedCommandLine;
ngOptions: AngularCompilerOptions;
}
12 changes: 12 additions & 0 deletions src/aot/compiler-host-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CompilerOptions } from 'typescript';
import { NgcCompilerHost } from './compiler-host';
import { getInstance as getFileSystemInstance } from '../util/hybrid-file-system-factory';

let instance: NgcCompilerHost = null;

export function getInstance(options: CompilerOptions) {
if (!instance) {
instance = new NgcCompilerHost(options, getFileSystemInstance());
}
return instance;
}
103 changes: 103 additions & 0 deletions src/aot/compiler-host.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { CancellationToken, CompilerHost, CompilerOptions, createCompilerHost, ScriptTarget, SourceFile } from 'typescript';
import { VirtualFileSystem } from '../util/interfaces';
import { getTypescriptSourceFile } from '../util/typescript-utils';

export interface OnErrorFn {
(message: string): void;
}

export class NgcCompilerHost implements CompilerHost {
private sourceFileMap: Map<string, SourceFile>;
private diskCompilerHost: CompilerHost;

constructor(private options: CompilerOptions, private fileSystem: VirtualFileSystem, private setParentNodes = true) {
this.diskCompilerHost = createCompilerHost(this.options, this.setParentNodes);
this.sourceFileMap = new Map<string, SourceFile>();
}

fileExists(filePath: string): boolean {
const fileContent = this.fileSystem.getFileContent(filePath);
if (fileContent) {
return true;
}
return this.diskCompilerHost.fileExists(filePath);
}

readFile(filePath: string): string {
const fileContent = this.fileSystem.getFileContent(filePath);
if (fileContent) {
return fileContent;
}
return this.diskCompilerHost.readFile(filePath);
}

directoryExists(directoryPath: string): boolean {
const stats = this.fileSystem.getDirectoryStats(directoryPath);
if (stats) {
return true;
}
return this.diskCompilerHost.directoryExists(directoryPath);
}

getFiles(directoryPath: string): string[] {
return this.fileSystem.getFileNamesInDirectory(directoryPath);
}

getDirectories(directoryPath: string): string[] {
const subdirs = this.fileSystem.getSubDirs(directoryPath);

let delegated: string[];
try {
delegated = this.diskCompilerHost.getDirectories(directoryPath);
} catch (e) {
delegated = [];
}
return delegated.concat(subdirs);
}

getSourceFile(filePath: string, languageVersion: ScriptTarget, onError?: OnErrorFn) {
const existingSourceFile = this.sourceFileMap.get(filePath);
if (existingSourceFile) {
return existingSourceFile;
}
// we haven't created a source file for this yet, so try to use what's in memory
const fileContentFromMemory = this.fileSystem.getFileContent(filePath);
if (fileContentFromMemory) {
const typescriptSourceFile = getTypescriptSourceFile(filePath, fileContentFromMemory, languageVersion, this.setParentNodes);
this.sourceFileMap.set(filePath, typescriptSourceFile);
return typescriptSourceFile;
}
// dang, it's not in memory, load it from disk and cache it
const diskSourceFile = this.diskCompilerHost.getSourceFile(filePath, languageVersion, onError);
this.sourceFileMap.set(filePath, diskSourceFile);
return diskSourceFile;
}

getCancellationToken(): CancellationToken {
return this.diskCompilerHost.getCancellationToken();
}

getDefaultLibFileName(options: CompilerOptions) {
return this.diskCompilerHost.getDefaultLibFileName(options);
}

writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: OnErrorFn) {
this.fileSystem.addVirtualFile(fileName, data);
}

getCurrentDirectory(): string {
return this.diskCompilerHost.getCurrentDirectory();
}

getCanonicalFileName(fileName: string): string {
return this.diskCompilerHost.getCanonicalFileName(fileName);
}

useCaseSensitiveFileNames(): boolean {
return this.diskCompilerHost.useCaseSensitiveFileNames();
}

getNewLine(): string {
return this.diskCompilerHost.getNewLine();
}
}
47 changes: 47 additions & 0 deletions src/aot/optimization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { removeDecorators } from '../util/typescript-utils';

export function optimizeJavascript(filePath: string, fileContent: string) {
fileContent = removeDecorators(filePath, fileContent);
fileContent = purgeDecoratorStatements(filePath, fileContent, ['@angular']);
fileContent = purgeCtorStatements(filePath, fileContent, ['@angular']);
fileContent = purgeKnownContent(filePath, fileContent, ['@angular']);

return fileContent;
}

export function purgeDecoratorStatements(filePath: string, fileContent: string, exclusions: string[]) {
const exclude = shouldExclude(filePath, exclusions);
if (exclude) {
return fileContent.replace(DECORATORS_REGEX, '');
}
return fileContent;
}

export function purgeCtorStatements(filePath: string, fileContent: string, exclusions: string[]) {
const exclude = shouldExclude(filePath, exclusions);
if (exclude) {
return fileContent.replace(CTOR_PARAM_REGEX, '');
}
return fileContent;
}

export function purgeKnownContent(filePath: string, fileContent: string, exclusions: string[]) {
const exclude = shouldExclude(filePath, exclusions);
if (exclude) {
return fileContent.replace(TREE_SHAKEABLE_IMPORTS, '');
}
return fileContent;
}

function shouldExclude(filePath: string, exclusions: string[]) {
for (const exclusion in exclusions) {
if (filePath.includes(exclusion)) {
return true;
}
}
return false;
}

const DECORATORS_REGEX = /(.+)\.decorators[\s\S\n]*?([\s\S\n]*?)];/igm;
const CTOR_PARAM_REGEX = /(.+).ctorParameters[\s\S\n]*?([\s\S\n]*?)];/igm;
const TREE_SHAKEABLE_IMPORTS = /\/\* AoT Remove Start[\s\S\n]*?([\s\S\n]*?)AoT Remove End \*\//igm;
Loading

0 comments on commit 93106ff

Please sign in to comment.