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

Commit 05b981d

Browse files
authored
refactor(preprocessing): basic code splitting support
* feat(webpack): create a bundle of known ionic dependencies, one of unknown 3rd party dependencies, a create a bundle of known ionic dependencies, one of unknown 3rd party dependencies, and one of the a pps code * refactor(ngc): write ngc files to disk when in debug mode write ngc files to disk when in debug mode * WIP WIP * all the hax * refactor(config): make App Ng Module Path and Class configurable * refactor(deep-linking): Change 'path' and 'namedExport' to single 'loadChildren' property * refactor(config): access app NgModule data from config instead of manually resolving it
1 parent e6f9722 commit 05b981d

23 files changed

+545
-269
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ npm run build --rollup ./config/rollup.config.js
117117
| generate source map | `ionic_generate_source_map` | `--generateSourceMap` | `true` | Determines whether to generate a source map or not |
118118
| tsconfig path | `ionic_ts_config` | `--tsconfig` | `{{rootDir}}/tsconfig.json` | absolute path to tsconfig.json |
119119
| app entry point | `ionic_app_entry_point` | `--appEntryPoint` | `{{srcDir}}/app/main.ts` | absolute path to app's entrypoint bootstrap file |
120+
| app ng module path | `ionic_app_ng_module_path` | `--appNgModulePath` | `{{srcDir}}/app/app.module.ts` | absolute path to app's primary `NgModule` |
121+
| app ng module class | `ionic_app_ng_module_class` | `--appNgModuleClass` | `AppModule` | Exported class name for app's primary `NgModule` |
120122
| clean before copy | `ionic_clean_before_copy` | `--cleanBeforeCopy` | `false` | clean out existing files before copy task runs |
121123
| output js file | `ionic_output_js_file_name` | `--outputJsFileName` | `main.js` | name of js file generated in `buildDir` |
122124
| output js map file | `ionic_output_js_map_file_name` | `--outputJsMapFileName` | `main.js.map` | name of js source map file generated in `buildDir` |
@@ -144,6 +146,8 @@ These environment variables are automatically set to [Node's `process.env`](http
144146
| `IONIC_GENERATE_SOURCE_MAP`| Determines whether to generate a sourcemap or not. |
145147
| `IONIC_TS_CONFIG` | The absolute path to the project's `tsconfig.json` file |
146148
| `IONIC_APP_ENTRY_POINT` | The absolute path to the project's `main.ts` entry point file |
149+
| `IONIC_APP_NG_MODULE_PATH` | The absolute path to app's primary `NgModule` |
150+
| `IONIC_APP_NG_MODULE_CLASS` | The exported class name for app's primary `NgModule` |
147151
| `IONIC_GLOB_UTIL` | The path to Ionic's `glob-util` script. Used within configs. |
148152
| `IONIC_CLEAN_BEFORE_COPY` | Attempt to clean existing directories before copying files. |
149153
| `IONIC_CLOSURE_JAR` | The absolute path ot the closure compiler jar file |

config/webpack.config.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ module.exports = {
66
entry: process.env.IONIC_APP_ENTRY_POINT,
77
output: {
88
path: '{{BUILD}}',
9-
filename: process.env.IONIC_OUTPUT_JS_FILE_NAME,
9+
publicPath: 'build/',
10+
filename: '[name].js',
1011
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
1112
},
1213
devtool: process.env.IONIC_GENERATE_SOURCE_MAP ? process.env.IONIC_SOURCE_MAP_TYPE : '',
@@ -31,7 +32,9 @@ module.exports = {
3132
},
3233

3334
plugins: [
34-
ionicWebpackFactory.getIonicEnvironmentPlugin()
35+
ionicWebpackFactory.getIonicEnvironmentPlugin(),
36+
ionicWebpackFactory.getNonIonicCommonChunksPlugin(),
37+
ionicWebpackFactory.getIonicCommonChunksPlugin()
3538
],
3639

3740
// Some libraries import Node modules but don't use them in the browser.

src/aot/aot-compiler.ts

Lines changed: 85 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { readFileSync } from 'fs';
2-
import { extname, normalize, resolve } from 'path';
1+
import { mkdirpSync, readFileSync, writeFileSync } from 'fs-extra';
2+
import { basename, dirname, extname, join, normalize, relative, resolve } from 'path';
33

44
import 'reflect-metadata';
5-
import { CompilerOptions, createProgram, ParsedCommandLine, Program, ScriptTarget, transpileModule, TranspileOptions, TranspileOutput } from 'typescript';
5+
import { CallExpression, CompilerOptions, createProgram, createSourceFile, Decorator, Identifier, ParsedCommandLine, Program, ScriptTarget, SyntaxKind, transpileModule, TranspileOptions, TranspileOutput } from 'typescript';
66
import { CodeGenerator, NgcCliOptions, NodeReflectorHostContext, ReflectorHost, StaticReflector }from '@angular/compiler-cli';
77
import { tsc } from '@angular/tsc-wrapped/src/tsc';
88
import AngularCompilerOptions from '@angular/tsc-wrapped/src/options';
@@ -11,13 +11,14 @@ import { HybridFileSystem } from '../util/hybrid-file-system';
1111
import { getInstance as getHybridFileSystem } from '../util/hybrid-file-system-factory';
1212
import { getInstance } from './compiler-host-factory';
1313
import { NgcCompilerHost } from './compiler-host';
14-
import { resolveAppNgModuleFromMain } from './app-module-resolver';
1514
import { patchReflectorHost } from './reflector-host';
16-
import { getTypescriptSourceFile, removeDecorators } from '../util/typescript-utils';
15+
import { findNodes, getNodeStringContent, getTypescriptSourceFile, removeDecorators } from '../util/typescript-utils';
1716
import { getFallbackMainContent, replaceBootstrap } from './utils';
1817
import { Logger } from '../logger/logger';
1918
import { printDiagnostics, clearDiagnostics, DiagnosticsType } from '../logger/logger-diagnostics';
2019
import { runTypeScriptDiagnostics } from '../logger/logger-typescript';
20+
import { isDebugMode } from '../util/config';
21+
import * as Constants from '../util/constants';
2122
import { BuildError } from '../util/errors';
2223
import { changeExtension } from '../util/helpers';
2324
import { BuildContext } from '../util/interfaces';
@@ -31,6 +32,8 @@ export class AotCompiler {
3132
private reflectorHost: ReflectorHost;
3233
private compilerHost: NgcCompilerHost;
3334
private fileSystem: HybridFileSystem;
35+
private appLevelNgModuleFilePath: string;
36+
private lazyLoadedModuleDictionary: any;
3437

3538
constructor(private context: BuildContext, private options: AotOptions) {
3639
this.tsConfig = getNgcConfig(this.context, this.options.tsConfigPath);
@@ -47,7 +50,7 @@ export class AotCompiler {
4750
this.reflector = new StaticReflector(this.reflectorHost);
4851
}
4952

50-
compile() {
53+
compile(): Promise<void> {
5154
return Promise.resolve().then(() => {
5255
}).then(() => {
5356
clearDiagnostics(this.context, DiagnosticsType.TypeScript);
@@ -75,50 +78,31 @@ export class AotCompiler {
7578
}).then(() => {
7679
Logger.debug('[AotCompiler] compile: starting codegen ... DONE');
7780
Logger.debug('[AotCompiler] compile: Creating and validating new TypeScript Program ...');
78-
// Create a new Program, based on the old one. This will trigger a resolution of all
79-
// transitive modules, which include files that might just have been generated.
80-
this.program = createProgram(this.tsConfig.parsed.fileNames, this.tsConfig.parsed.options, this.compilerHost, this.program);
81-
const globalDiagnostics = this.program.getGlobalDiagnostics();
82-
const tsDiagnostics = this.program.getSyntacticDiagnostics()
83-
.concat(this.program.getSemanticDiagnostics())
84-
.concat(this.program.getOptionsDiagnostics());
85-
86-
if (globalDiagnostics.length) {
87-
const diagnostics = runTypeScriptDiagnostics(this.context, globalDiagnostics);
88-
printDiagnostics(this.context, DiagnosticsType.TypeScript, diagnostics, true, false);
89-
throw new BuildError(new Error('Failed to transpile TypeScript'));
90-
}
91-
if (tsDiagnostics.length) {
92-
const diagnostics = runTypeScriptDiagnostics(this.context, tsDiagnostics);
93-
printDiagnostics(this.context, DiagnosticsType.TypeScript, diagnostics, true, false);
94-
throw new BuildError(new Error('Failed to transpile TypeScript'));
95-
}
81+
this.program = errorCheckProgram(this.context, this.tsConfig, this.compilerHost, this.program);
82+
Logger.debug('[AotCompiler] compile: Creating and validating new TypeScript Program ... DONE');
9683
})
9784
.then(() => {
98-
Logger.debug('[AotCompiler] compile: Creating and validating new TypeScript Program ... DONE');
85+
9986
Logger.debug('[AotCompiler] compile: The following files are included in the program: ');
10087
for ( const fileName of this.tsConfig.parsed.fileNames) {
10188
Logger.debug(`[AotCompiler] compile: ${fileName}`);
10289
const cleanedFileName = normalize(resolve(fileName));
10390
const content = readFileSync(cleanedFileName).toString();
10491
this.context.fileCache.set(cleanedFileName, { path: cleanedFileName, content: content});
10592
}
106-
})
107-
.then(() => {
93+
}).then(() => {
10894
Logger.debug('[AotCompiler] compile: Starting to process and modify entry point ...');
10995
const mainFile = this.context.fileCache.get(this.options.entryPoint);
11096
if (!mainFile) {
11197
throw new BuildError(new Error(`Could not find entry point (bootstrap file) ${this.options.entryPoint}`));
11298
}
11399
const mainSourceFile = getTypescriptSourceFile(mainFile.path, mainFile.content, ScriptTarget.Latest, false);
114100
Logger.debug('[AotCompiler] compile: Resolving NgModule from entry point');
115-
const AppNgModuleStringAndClassName = resolveAppNgModuleFromMain(mainSourceFile, this.context.fileCache, this.compilerHost, this.program);
116-
const AppNgModuleTokens = AppNgModuleStringAndClassName.split('#');
117-
101+
this.appLevelNgModuleFilePath = this.options.appNgModulePath
118102
let modifiedFileContent: string = null;
119103
try {
120104
Logger.debug('[AotCompiler] compile: Dynamically changing entry point content to AOT mode content');
121-
modifiedFileContent = replaceBootstrap(mainFile.path, mainFile.content, AppNgModuleTokens[0], AppNgModuleTokens[1]);
105+
modifiedFileContent = replaceBootstrap(mainFile.path, mainFile.content, this.options.appNgModulePath, this.options.appNgModuleClass);
122106
} catch (ex) {
123107
Logger.debug(`Failed to parse bootstrap: `, ex.message);
124108
Logger.warn(`Failed to parse and update ${this.options.entryPoint} content for AoT compilation.
@@ -128,36 +112,68 @@ export class AotCompiler {
128112
modifiedFileContent = getFallbackMainContent();
129113
}
130114

115+
131116
Logger.debug(`[AotCompiler] compile: Modified File Content: ${modifiedFileContent}`);
132117
this.context.fileCache.set(this.options.entryPoint, { path: this.options.entryPoint, content: modifiedFileContent});
118+
Logger.debug('[AotCompiler] compile: Starting to process and modify entry point ... DONE');
133119
})
134120
.then(() => {
135-
Logger.debug('[AotCompiler] compile: Starting to process and modify entry point ... DONE');
136121
Logger.debug('[AotCompiler] compile: Removing decorators from program files ...');
137-
const tsFiles = this.context.fileCache.getAll().filter(file => extname(file.path) === '.ts' && file.path.indexOf('.d.ts') === -1);
138-
for (const tsFile of tsFiles) {
139-
// Temporary fix to keep custom decorators until a
140-
// proper resolution can be found.
141-
/*const cleanedFileContent = removeDecorators(tsFile.path, tsFile.content);
142-
tsFile.content = cleanedFileContent;*/
143-
const cleanedFileContent = tsFile.content;
144-
const transpileOutput = this.transpileFileContent(tsFile.path, cleanedFileContent, this.tsConfig.parsed.options);
145-
const diagnostics = runTypeScriptDiagnostics(this.context, transpileOutput.diagnostics);
146-
if (diagnostics.length) {
147-
// darn, we've got some things wrong, transpile failed :(
148-
printDiagnostics(this.context, DiagnosticsType.TypeScript, diagnostics, true, true);
149-
throw new BuildError();
150-
}
151-
152-
const jsFilePath = changeExtension(tsFile.path, '.js');
153-
this.fileSystem.addVirtualFile(jsFilePath, transpileOutput.outputText);
154-
this.fileSystem.addVirtualFile(jsFilePath + '.map', transpileOutput.sourceMapText);
155-
}
122+
transpileFiles(this.context, this.tsConfig, this.fileSystem);
156123
Logger.debug('[AotCompiler] compile: Removing decorators from program files ... DONE');
124+
}).then(() => {
125+
return {
126+
lazyLoadedModuleDictionary: this.lazyLoadedModuleDictionary
127+
};
157128
});
158129
}
130+
}
159131

160-
transpileFileContent(fileName: string, sourceText: string, options: CompilerOptions): TranspileOutput {
132+
function errorCheckProgram(context: BuildContext, tsConfig: ParsedTsConfig, compilerHost: NgcCompilerHost, cachedProgram: Program) {
133+
// Create a new Program, based on the old one. This will trigger a resolution of all
134+
// transitive modules, which include files that might just have been generated.
135+
const program = createProgram(tsConfig.parsed.fileNames, tsConfig.parsed.options, compilerHost, cachedProgram);
136+
const globalDiagnostics = program.getGlobalDiagnostics();
137+
const tsDiagnostics = program.getSyntacticDiagnostics()
138+
.concat(program.getSemanticDiagnostics())
139+
.concat(program.getOptionsDiagnostics());
140+
141+
if (globalDiagnostics.length) {
142+
const diagnostics = runTypeScriptDiagnostics(context, globalDiagnostics);
143+
printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, false);
144+
throw new BuildError(new Error('Failed to transpile TypeScript'));
145+
}
146+
if (tsDiagnostics.length) {
147+
const diagnostics = runTypeScriptDiagnostics(context, tsDiagnostics);
148+
printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, false);
149+
throw new BuildError(new Error('Failed to transpile TypeScript'));
150+
}
151+
return program;
152+
}
153+
154+
function transpileFiles(context: BuildContext, tsConfig: ParsedTsConfig, fileSystem: HybridFileSystem) {
155+
const tsFiles = context.fileCache.getAll().filter(file => extname(file.path) === '.ts' && file.path.indexOf('.d.ts') === -1);
156+
for (const tsFile of tsFiles) {
157+
const transpileOutput = transpileFileContent(tsFile.path, tsFile.content, tsConfig.parsed.options);
158+
const diagnostics = runTypeScriptDiagnostics(context, transpileOutput.diagnostics);
159+
if (diagnostics.length) {
160+
// darn, we've got some things wrong, transpile failed :(
161+
printDiagnostics(context, DiagnosticsType.TypeScript, diagnostics, true, true);
162+
throw new BuildError();
163+
}
164+
165+
const jsFilePath = changeExtension(tsFile.path, '.js');
166+
fileSystem.addVirtualFile(jsFilePath, transpileOutput.outputText);
167+
fileSystem.addVirtualFile(jsFilePath + '.map', transpileOutput.sourceMapText);
168+
169+
// write files to disk here if debug is enabled
170+
if (isDebugMode() || true) {
171+
writeNgcFilesToDisk(context, tsFile.path, tsFile.content, transpileOutput.outputText, transpileOutput.sourceMapText);
172+
}
173+
}
174+
}
175+
176+
function transpileFileContent(fileName: string, sourceText: string, options: CompilerOptions): TranspileOutput {
161177
const transpileOptions: TranspileOptions = {
162178
compilerOptions: options,
163179
fileName: fileName,
@@ -166,12 +182,27 @@ export class AotCompiler {
166182

167183
return transpileModule(sourceText, transpileOptions);
168184
}
169-
}
185+
186+
function writeNgcFilesToDisk(context: BuildContext, typescriptFilePath: string, typescriptFileContent: string, transpiledFileContent: string, sourcemapContent: string) {
187+
const dirName = dirname(typescriptFilePath);
188+
const relativePath = relative(process.cwd(), dirName);
189+
const tmpPath = join(context.tmpDir, relativePath);
190+
const fileName = basename(typescriptFilePath);
191+
const fileToWrite = join(tmpPath, fileName);
192+
const jsFileToWrite = changeExtension(fileToWrite, '.js');
193+
194+
mkdirpSync(tmpPath);
195+
writeFileSync(fileToWrite, typescriptFileContent);
196+
writeFileSync(jsFileToWrite, transpiledFileContent);
197+
writeFileSync(jsFileToWrite + '.map', sourcemapContent);
198+
}
170199

171200
export interface AotOptions {
172201
tsConfigPath: string;
173202
rootDir: string;
174203
entryPoint: string;
204+
appNgModulePath: string;
205+
appNgModuleClass: string;
175206
}
176207

177208
export function getNgcConfig(context: BuildContext, tsConfigPath?: string): ParsedTsConfig {
@@ -188,3 +219,4 @@ export interface ParsedTsConfig {
188219
parsed: ParsedCommandLine;
189220
ngOptions: AngularCompilerOptions;
190221
}
222+

0 commit comments

Comments
 (0)