Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions packages/ngtools/webpack/src/angular_compiler_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,12 +406,12 @@ export class AngularCompilerPlugin {
}

// If there's still no entryModule try to resolve from mainPath.
if (!this._entryModule && this._mainPath && !this._compilerOptions.enableIvy) {
if (!this._entryModule && this._mainPath) {
time('AngularCompilerPlugin._make.resolveEntryModuleFromMain');
this._entryModule = resolveEntryModuleFromMain(
this._mainPath, this._compilerHost, this._getTsProgram() as ts.Program);

if (!this.entryModule) {
if (!this.entryModule && !this._compilerOptions.enableIvy) {
this._warnings.push('Lazy routes discovery is not enabled. '
+ 'Because there is neither an entryModule nor a '
+ 'statically analyzable bootstrap code in the main file.',
Expand Down Expand Up @@ -863,7 +863,12 @@ export class AngularCompilerPlugin {

if (!this._JitMode) {
// Replace bootstrap in browser AOT.
this._transformers.push(replaceBootstrap(isAppPath, getEntryModule, getTypeChecker));
this._transformers.push(replaceBootstrap(
isAppPath,
getEntryModule,
getTypeChecker,
!!this._compilerOptions.enableIvy,
));
}
} else if (this._platform === PLATFORM.Server) {
this._transformers.push(exportLazyModuleMap(isMainPath, getLazyRoutes));
Expand Down
23 changes: 15 additions & 8 deletions packages/ngtools/webpack/src/transformers/replace_bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export function replaceBootstrap(
shouldTransform: (fileName: string) => boolean,
getEntryModule: () => { path: string, className: string } | null,
getTypeChecker: () => ts.TypeChecker,
enableIvy?: boolean,
): ts.TransformerFactory<ts.SourceFile> {

const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) {
Expand All @@ -37,9 +38,6 @@ export function replaceBootstrap(
return [];
}

const relativeEntryModulePath = relative(dirname(sourceFile.fileName), entryModule.path);
const normalizedEntryModulePath = `./${relativeEntryModulePath}`.replace(/\\/g, '/');

// Find the bootstrap calls.
entryModuleIdentifiers.forEach(entryModuleIdentifier => {
// Figure out if it's a `platformBrowserDynamic().bootstrapModule(AppModule)` call.
Expand Down Expand Up @@ -79,19 +77,28 @@ export function replaceBootstrap(
const idNgFactory = ts.createUniqueName('__NgCli_bootstrap_');

// Add the transform operations.
const factoryClassName = entryModule.className + 'NgFactory';
const factoryModulePath = normalizedEntryModulePath + '.ngfactory';
const relativeEntryModulePath = relative(dirname(sourceFile.fileName), entryModule.path);
let className = entryModule.className;
let modulePath = `./${relativeEntryModulePath}`.replace(/\\/g, '/');
let bootstrapIdentifier = 'bootstrapModule';

if (!enableIvy) {
className += 'NgFactory';
modulePath += '.ngfactory';
bootstrapIdentifier = 'bootstrapModuleFactory';
}

ops.push(
// Replace the entry module import.
...insertStarImport(sourceFile, idNgFactory, factoryModulePath),
...insertStarImport(sourceFile, idNgFactory, modulePath),
new ReplaceNodeOperation(sourceFile, entryModuleIdentifier,
ts.createPropertyAccess(idNgFactory, ts.createIdentifier(factoryClassName))),
ts.createPropertyAccess(idNgFactory, ts.createIdentifier(className))),
// Replace the platformBrowserDynamic import.
...insertStarImport(sourceFile, idPlatformBrowser, '@angular/platform-browser'),
new ReplaceNodeOperation(sourceFile, platformBrowserDynamicIdentifier,
ts.createPropertyAccess(idPlatformBrowser, 'platformBrowser')),
new ReplaceNodeOperation(sourceFile, bootstrapModuleIdentifier,
ts.createIdentifier('bootstrapModuleFactory')),
ts.createIdentifier(bootstrapIdentifier)),
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,48 @@ describe('@ngtools/webpack transformers', () => {
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
});

it('should replace bootstrap for Ivy without referencing ngFactory', () => {
const input = tags.stripIndent`
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);
`;

// tslint:disable:max-line-length
const output = tags.stripIndent`
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';

import * as __NgCli_bootstrap_1 from "./app/app.module";
import * as __NgCli_bootstrap_2 from "@angular/platform-browser";

if (environment.production) {
enableProdMode();
}
__NgCli_bootstrap_2.platformBrowser().bootstrapModule(__NgCli_bootstrap_1.AppModule);
`;
// tslint:enable:max-line-length

const { program, compilerHost } = createTypescriptContext(input);
const transformer = replaceBootstrap(
() => true,
() => ({ path: '/project/src/app/app.module', className: 'AppModule' }),
() => program.getTypeChecker(),
true,
);
const result = transformTypescript(undefined, [transformer], program, compilerHost);

expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
});

it('should replace bootstrap when barrel files are used', () => {
const input = tags.stripIndent`
import { enableProdMode } from '@angular/core';
Expand Down
27 changes: 10 additions & 17 deletions packages/schematics/angular/application/files/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
<%= experimentalIvy ? '// ' : '' %>import { enableProdMode } from '@angular/core';
<%= experimentalIvy ? '// ' : '' %>import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
<%= experimentalIvy ? '// ' : '' %>
<%= experimentalIvy ? '// ' : '' %>import { AppModule } from './app/app.module';
<%= experimentalIvy ? '// ' : '' %>import { environment } from './environments/environment';
<%= experimentalIvy ? '// ' : '' %>
<%= experimentalIvy ? '// ' : '' %>if (environment.production) {
<%= experimentalIvy ? '// ' : '' %> enableProdMode();
<%= experimentalIvy ? '// ' : '' %>}
<%= experimentalIvy ? '// ' : '' %>
<%= experimentalIvy ? '// ' : '' %>platformBrowserDynamic().bootstrapModule(AppModule)
<%= experimentalIvy ? '// ' : '' %> .catch(err => console.error(err));
<% if (experimentalIvy) { %>
import { AppComponent } from './app/app.component';
import { ɵrenderComponent as renderComponent } from '@angular/core';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

renderComponent(AppComponent);
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

<% } %>
if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
46 changes: 45 additions & 1 deletion packages/schematics/angular/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { JsonObject, join, normalize, relative, strings } from '@angular-devkit/core';
import {
JsonObject,
JsonParseMode,
join,
normalize,
parseJsonAst,
relative,
strings,
} from '@angular-devkit/core';
import {
MergeStrategy,
Rule,
Expand All @@ -30,6 +38,7 @@ import {
getWorkspace,
} from '../utility/config';
import { NodeDependencyType, addPackageJsonDependency } from '../utility/dependencies';
import { findPropertyInAstObject, insertPropertyInAstObjectInOrder } from '../utility/json-utils';
import { latestVersions } from '../utility/latest-versions';
import { applyLintFix } from '../utility/lint-fix';
import { validateProjectName } from '../utility/validation';
Expand Down Expand Up @@ -94,6 +103,40 @@ function addDependenciesToPackageJson(options: ApplicationOptions) {
};
}

function addPostInstallScript() {
return (host: Tree) => {
const pkgJsonPath = '/package.json';
const buffer = host.read(pkgJsonPath);
if (!buffer) {
throw new SchematicsException('Could not read package.json.');
}

const packageJsonAst = parseJsonAst(buffer.toString(), JsonParseMode.Strict);
if (packageJsonAst.kind !== 'object') {
throw new SchematicsException('Invalid package.json. Was expecting an object.');
}

const scriptsNode = findPropertyInAstObject(packageJsonAst, 'scripts');
if (scriptsNode && scriptsNode.kind === 'object') {
const recorder = host.beginUpdate(pkgJsonPath);
const postInstall = findPropertyInAstObject(scriptsNode, 'postinstall');

if (!postInstall) {
// postinstall script not found, add it.
insertPropertyInAstObjectInOrder(
recorder,
scriptsNode,
'postinstall',
'ivy-ngcc',
4,
);
}

host.commitUpdate(recorder);
}
};
}

function addAppToWorkspaceFile(options: ApplicationOptions, workspace: WorkspaceSchema): Rule {
// TODO: use JsonAST
// const workspacePath = '/angular.json';
Expand Down Expand Up @@ -372,6 +415,7 @@ export default function (options: ApplicationOptions): Rule {
move(sourceDir),
]), MergeStrategy.Overwrite),
options.minimal ? noop() : schematic('e2e', e2eOptions),
options.experimentalIvy ? addPostInstallScript() : noop(),
options.skipPackageJson ? noop() : addDependenciesToPackageJson(options),
options.lintFix ? applyLintFix(sourceDir) : noop(),
]);
Expand Down
6 changes: 6 additions & 0 deletions packages/schematics/angular/application/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ describe('Application Schematic', () => {
expect(pkg.devDependencies['typescript']).toEqual(latestVersions.TypeScript);
});

it(`should add a postinstall in package.json when 'experimentalIvy'`, () => {
const tree = schematicRunner.runSchematic('application', { ...defaultOptions, experimentalIvy: true }, workspaceTree);
const pkg = JSON.parse(tree.readContent('/package.json'));
expect(pkg.scripts.postinstall).toEqual('ivy-ngcc');
});

it(`should not override existing users dependencies`, () => {
const oldPackageJson = workspaceTree.readContent('package.json');
workspaceTree.overwrite('package.json', oldPackageJson.replace(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<% if (!experimentalIvy) { %>import { BrowserModule } from '@angular/platform-browser';<% } %>
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
<% if (routing && !experimentalIvy) { %>
<% if (routing) { %>
import { AppRoutingModule } from './app-routing.module';<% } %>
import { AppComponent } from './app.component';

@NgModule({
declarations: [
AppComponent
],
imports: [<% if (!experimentalIvy) { %>
imports: [
BrowserModule<% if (routing) { %>,
AppRoutingModule<% } %>
<% } %>],
],
providers: [],
bootstrap: [AppComponent]
})
Expand Down