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

Commit 93106ff

Browse files
committed
fix(AoT): use in-memory data store instead of .tmp directory for AoT codegen
use in-memory data store isntead of .tmp directory for AoT codegen
1 parent 3eb8d0e commit 93106ff

26 files changed

+764
-397
lines changed

config/ngc.config.js

-10
This file was deleted.

config/webpack.config.js

+9-32
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,13 @@ var ionicWebpackFactory = require(ionicWebpackFactoryPath);
66

77
function getEntryPoint() {
88
if (process.env.IONIC_ENV === 'prod') {
9-
return '{{TMP}}/app/main.prod.js';
9+
return '{{SRC}}/app/main.ts';
1010
}
11-
return '{{SRC}}/app/main.dev.ts';
11+
return '{{SRC}}/app/main.ts';
1212
}
1313

1414
function getPlugins() {
15-
if (process.env.IONIC_ENV === 'prod') {
16-
return [
17-
// This helps ensure the builds are consistent if source hasn't changed:
18-
new webpack.optimize.OccurrenceOrderPlugin(),
19-
20-
// Try to dedupe duplicated modules, if any:
21-
// Add this back in when Angular fixes the issue: https://github.com/angular/angular-cli/issues/1587
22-
//new DedupePlugin()
23-
];
24-
}
25-
26-
// for dev builds, use our custom environment
27-
return [
28-
ionicWebpackFactory.getIonicEnvironmentPlugin()
29-
];
30-
}
31-
32-
function getSourcemapLoader() {
33-
if (process.env.IONIC_ENV === 'prod') {
34-
// TODO figure out the disk loader, it's not working yet
35-
return [];
36-
}
37-
38-
return [
39-
{
40-
test: /\.ts$/,
41-
loader: path.join(process.env.IONIC_APP_SCRIPTS_DIR, 'dist', 'webpack', 'typescript-sourcemap-loader-memory.js')
42-
}
43-
];
15+
return [ionicWebpackFactory.getIonicEnvironmentPlugin()];
4416
}
4517

4618
function getDevtool() {
@@ -53,6 +25,7 @@ function getDevtool() {
5325
}
5426

5527
module.exports = {
28+
bail: true,
5629
entry: getEntryPoint(),
5730
output: {
5831
path: '{{BUILD}}',
@@ -71,8 +44,12 @@ module.exports = {
7144
{
7245
test: /\.json$/,
7346
loader: 'json-loader'
47+
},
48+
{
49+
test: /\.(ts|ngfactory.js)$/,
50+
loader: path.join(process.env.IONIC_APP_SCRIPTS_DIR, 'dist', 'webpack', 'typescript-sourcemap-loader-memory.js')
7451
}
75-
].concat(getSourcemapLoader())
52+
]
7653
},
7754

7855
plugins: getPlugins(),

src/aot/aot-compiler.ts

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { readFileSync } from 'fs';
2+
import { extname } from 'path';
3+
4+
import 'reflect-metadata';
5+
import { CompilerOptions, createProgram, ParsedCommandLine, Program, transpileModule, TranspileOptions, TranspileOutput } from 'typescript';
6+
import { CodeGenerator, NgcCliOptions, NodeReflectorHostContext, ReflectorHost, StaticReflector }from '@angular/compiler-cli';
7+
import { tsc } from '@angular/tsc-wrapped/src/tsc';
8+
import AngularCompilerOptions from '@angular/tsc-wrapped/src/options';
9+
10+
import { HybridFileSystem } from '../util/hybrid-file-system';
11+
import { getInstance as getHybridFileSystem } from '../util/hybrid-file-system-factory';
12+
import { getInstance } from '../aot/compiler-host-factory';
13+
import { NgcCompilerHost } from '../aot/compiler-host';
14+
import { patchReflectorHost } from '../aot/reflector-host';
15+
import { removeDecorators } from '../util/typescript-utils';
16+
import { getMainFileTypescriptContentForAotBuild } from './utils';
17+
import { printDiagnostics, clearDiagnostics, DiagnosticsType } from '../logger/logger-diagnostics';
18+
import { runTypeScriptDiagnostics } from '../logger/logger-typescript';
19+
import { BuildError } from '../util/errors';
20+
import { changeExtension } from '../util/helpers';
21+
import { BuildContext } from '../util/interfaces';
22+
23+
export class AotCompiler {
24+
25+
private tsConfig: ParsedTsConfig;
26+
private angularCompilerOptions: AngularCompilerOptions;
27+
private program: Program;
28+
private reflector: StaticReflector;
29+
private reflectorHost: ReflectorHost;
30+
private compilerHost: NgcCompilerHost;
31+
private fileSystem: HybridFileSystem;
32+
33+
constructor(private context: BuildContext, private options: AotOptions) {
34+
this.tsConfig = getNgcConfig(this.context, this.options.tsConfigPath);
35+
36+
this.angularCompilerOptions = Object.assign({}, this.tsConfig.ngOptions, {
37+
basePath: this.options.rootDir,
38+
entryPoint: this.options.entryPoint
39+
});
40+
41+
this.fileSystem = getHybridFileSystem();
42+
this.compilerHost = getInstance(this.tsConfig.parsed.options);
43+
this.program = createProgram(this.tsConfig.parsed.fileNames, this.tsConfig.parsed.options, this.compilerHost);
44+
this.reflectorHost = new ReflectorHost(this.program, this.compilerHost, this.angularCompilerOptions);
45+
this.reflector = new StaticReflector(this.reflectorHost);
46+
}
47+
48+
compile() {
49+
clearDiagnostics(this.context, DiagnosticsType.TypeScript);
50+
const i18nOptions: NgcCliOptions = {
51+
i18nFile: undefined,
52+
i18nFormat: undefined,
53+
locale: undefined,
54+
basePath: this.options.rootDir
55+
};
56+
57+
// Create the Code Generator.
58+
const codeGenerator = CodeGenerator.create(
59+
this.angularCompilerOptions,
60+
i18nOptions,
61+
this.program,
62+
this.compilerHost,
63+
new NodeReflectorHostContext(this.compilerHost)
64+
);
65+
66+
// We need to temporarily patch the CodeGenerator until either it's patched or allows us
67+
// to pass in our own ReflectorHost.
68+
patchReflectorHost(codeGenerator);
69+
return codeGenerator.codegen({transitiveModules: true})
70+
.then(() => {
71+
// Create a new Program, based on the old one. This will trigger a resolution of all
72+
// transitive modules, which include files that might just have been generated.
73+
this.program = createProgram(this.tsConfig.parsed.fileNames, this.tsConfig.parsed.options, this.compilerHost, this.program);
74+
75+
const tsDiagnostics = this.program.getSyntacticDiagnostics()
76+
.concat(this.program.getSemanticDiagnostics())
77+
.concat(this.program.getOptionsDiagnostics());
78+
79+
if (tsDiagnostics.length) {
80+
throw tsDiagnostics;
81+
}
82+
})
83+
.then(() => {
84+
for ( const fileName of this.tsConfig.parsed.fileNames) {
85+
const content = readFileSync(fileName).toString();
86+
this.context.fileCache.set(fileName, { path: fileName, content: content});
87+
}
88+
})
89+
.then(() => {
90+
const mainContent = getMainFileTypescriptContentForAotBuild();
91+
this.context.fileCache.set(this.options.entryPoint, { path: this.options.entryPoint, content: mainContent});
92+
})
93+
.then(() => {
94+
const tsFiles = this.context.fileCache.getAll().filter(file => extname(file.path) === '.ts' && file.path.indexOf('.d.ts') === -1);
95+
for (const tsFile of tsFiles) {
96+
const cleanedFileContent = removeDecorators(tsFile.path, tsFile.content);
97+
tsFile.content = cleanedFileContent;
98+
const transpileOutput = this.transpileFileContent(tsFile.path, cleanedFileContent, this.tsConfig.parsed.options);
99+
const diagnostics = runTypeScriptDiagnostics(this.context, transpileOutput.diagnostics);
100+
if (diagnostics.length) {
101+
// darn, we've got some things wrong, transpile failed :(
102+
printDiagnostics(this.context, DiagnosticsType.TypeScript, diagnostics, true, true);
103+
throw new BuildError();
104+
}
105+
106+
const jsFilePath = changeExtension(tsFile.path, '.js');
107+
this.fileSystem.addVirtualFile(jsFilePath, transpileOutput.outputText);
108+
this.fileSystem.addVirtualFile(jsFilePath + '.map', transpileOutput.sourceMapText);
109+
}
110+
});
111+
}
112+
113+
transpileFileContent(fileName: string, sourceText: string, options: CompilerOptions): TranspileOutput {
114+
const sourceFile = this.program.getSourceFile(fileName);
115+
const diagnostics = this.program.getSyntacticDiagnostics(sourceFile)
116+
.concat(this.program.getSemanticDiagnostics(sourceFile))
117+
.concat(this.program.getDeclarationDiagnostics(sourceFile));
118+
if (diagnostics.length) {
119+
throw diagnostics;
120+
}
121+
122+
const transpileOptions: TranspileOptions = {
123+
compilerOptions: options,
124+
fileName: fileName,
125+
reportDiagnostics: true
126+
};
127+
128+
return transpileModule(sourceText, transpileOptions);
129+
}
130+
}
131+
132+
export interface AotOptions {
133+
tsConfigPath: string;
134+
rootDir: string;
135+
entryPoint: string;
136+
}
137+
138+
export function getNgcConfig(context: BuildContext, tsConfigPath?: string): ParsedTsConfig {
139+
140+
const tsConfigFile = tsc.readConfiguration(tsConfigPath, process.cwd());
141+
if (!tsConfigFile) {
142+
throw new BuildError(`tsconfig: invalid tsconfig file, "${tsConfigPath}"`);
143+
144+
}
145+
return tsConfigFile;
146+
}
147+
148+
export interface ParsedTsConfig {
149+
parsed: ParsedCommandLine;
150+
ngOptions: AngularCompilerOptions;
151+
}

src/aot/compiler-host-factory.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CompilerOptions } from 'typescript';
2+
import { NgcCompilerHost } from './compiler-host';
3+
import { getInstance as getFileSystemInstance } from '../util/hybrid-file-system-factory';
4+
5+
let instance: NgcCompilerHost = null;
6+
7+
export function getInstance(options: CompilerOptions) {
8+
if (!instance) {
9+
instance = new NgcCompilerHost(options, getFileSystemInstance());
10+
}
11+
return instance;
12+
}

src/aot/compiler-host.ts

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { CancellationToken, CompilerHost, CompilerOptions, createCompilerHost, ScriptTarget, SourceFile } from 'typescript';
2+
import { VirtualFileSystem } from '../util/interfaces';
3+
import { getTypescriptSourceFile } from '../util/typescript-utils';
4+
5+
export interface OnErrorFn {
6+
(message: string): void;
7+
}
8+
9+
export class NgcCompilerHost implements CompilerHost {
10+
private sourceFileMap: Map<string, SourceFile>;
11+
private diskCompilerHost: CompilerHost;
12+
13+
constructor(private options: CompilerOptions, private fileSystem: VirtualFileSystem, private setParentNodes = true) {
14+
this.diskCompilerHost = createCompilerHost(this.options, this.setParentNodes);
15+
this.sourceFileMap = new Map<string, SourceFile>();
16+
}
17+
18+
fileExists(filePath: string): boolean {
19+
const fileContent = this.fileSystem.getFileContent(filePath);
20+
if (fileContent) {
21+
return true;
22+
}
23+
return this.diskCompilerHost.fileExists(filePath);
24+
}
25+
26+
readFile(filePath: string): string {
27+
const fileContent = this.fileSystem.getFileContent(filePath);
28+
if (fileContent) {
29+
return fileContent;
30+
}
31+
return this.diskCompilerHost.readFile(filePath);
32+
}
33+
34+
directoryExists(directoryPath: string): boolean {
35+
const stats = this.fileSystem.getDirectoryStats(directoryPath);
36+
if (stats) {
37+
return true;
38+
}
39+
return this.diskCompilerHost.directoryExists(directoryPath);
40+
}
41+
42+
getFiles(directoryPath: string): string[] {
43+
return this.fileSystem.getFileNamesInDirectory(directoryPath);
44+
}
45+
46+
getDirectories(directoryPath: string): string[] {
47+
const subdirs = this.fileSystem.getSubDirs(directoryPath);
48+
49+
let delegated: string[];
50+
try {
51+
delegated = this.diskCompilerHost.getDirectories(directoryPath);
52+
} catch (e) {
53+
delegated = [];
54+
}
55+
return delegated.concat(subdirs);
56+
}
57+
58+
getSourceFile(filePath: string, languageVersion: ScriptTarget, onError?: OnErrorFn) {
59+
const existingSourceFile = this.sourceFileMap.get(filePath);
60+
if (existingSourceFile) {
61+
return existingSourceFile;
62+
}
63+
// we haven't created a source file for this yet, so try to use what's in memory
64+
const fileContentFromMemory = this.fileSystem.getFileContent(filePath);
65+
if (fileContentFromMemory) {
66+
const typescriptSourceFile = getTypescriptSourceFile(filePath, fileContentFromMemory, languageVersion, this.setParentNodes);
67+
this.sourceFileMap.set(filePath, typescriptSourceFile);
68+
return typescriptSourceFile;
69+
}
70+
// dang, it's not in memory, load it from disk and cache it
71+
const diskSourceFile = this.diskCompilerHost.getSourceFile(filePath, languageVersion, onError);
72+
this.sourceFileMap.set(filePath, diskSourceFile);
73+
return diskSourceFile;
74+
}
75+
76+
getCancellationToken(): CancellationToken {
77+
return this.diskCompilerHost.getCancellationToken();
78+
}
79+
80+
getDefaultLibFileName(options: CompilerOptions) {
81+
return this.diskCompilerHost.getDefaultLibFileName(options);
82+
}
83+
84+
writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: OnErrorFn) {
85+
this.fileSystem.addVirtualFile(fileName, data);
86+
}
87+
88+
getCurrentDirectory(): string {
89+
return this.diskCompilerHost.getCurrentDirectory();
90+
}
91+
92+
getCanonicalFileName(fileName: string): string {
93+
return this.diskCompilerHost.getCanonicalFileName(fileName);
94+
}
95+
96+
useCaseSensitiveFileNames(): boolean {
97+
return this.diskCompilerHost.useCaseSensitiveFileNames();
98+
}
99+
100+
getNewLine(): string {
101+
return this.diskCompilerHost.getNewLine();
102+
}
103+
}

src/aot/optimization.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { removeDecorators } from '../util/typescript-utils';
2+
3+
export function optimizeJavascript(filePath: string, fileContent: string) {
4+
fileContent = removeDecorators(filePath, fileContent);
5+
fileContent = purgeDecoratorStatements(filePath, fileContent, ['@angular']);
6+
fileContent = purgeCtorStatements(filePath, fileContent, ['@angular']);
7+
fileContent = purgeKnownContent(filePath, fileContent, ['@angular']);
8+
9+
return fileContent;
10+
}
11+
12+
export function purgeDecoratorStatements(filePath: string, fileContent: string, exclusions: string[]) {
13+
const exclude = shouldExclude(filePath, exclusions);
14+
if (exclude) {
15+
return fileContent.replace(DECORATORS_REGEX, '');
16+
}
17+
return fileContent;
18+
}
19+
20+
export function purgeCtorStatements(filePath: string, fileContent: string, exclusions: string[]) {
21+
const exclude = shouldExclude(filePath, exclusions);
22+
if (exclude) {
23+
return fileContent.replace(CTOR_PARAM_REGEX, '');
24+
}
25+
return fileContent;
26+
}
27+
28+
export function purgeKnownContent(filePath: string, fileContent: string, exclusions: string[]) {
29+
const exclude = shouldExclude(filePath, exclusions);
30+
if (exclude) {
31+
return fileContent.replace(TREE_SHAKEABLE_IMPORTS, '');
32+
}
33+
return fileContent;
34+
}
35+
36+
function shouldExclude(filePath: string, exclusions: string[]) {
37+
for (const exclusion in exclusions) {
38+
if (filePath.includes(exclusion)) {
39+
return true;
40+
}
41+
}
42+
return false;
43+
}
44+
45+
const DECORATORS_REGEX = /(.+)\.decorators[\s\S\n]*?([\s\S\n]*?)];/igm;
46+
const CTOR_PARAM_REGEX = /(.+).ctorParameters[\s\S\n]*?([\s\S\n]*?)];/igm;
47+
const TREE_SHAKEABLE_IMPORTS = /\/\* AoT Remove Start[\s\S\n]*?([\s\S\n]*?)AoT Remove End \*\//igm;

0 commit comments

Comments
 (0)