Skip to content
This repository was archived by the owner on Aug 7, 2021. It is now read-only.

fix(angular): support angular lazy routes in preview #753

Merged
merged 5 commits into from
Dec 21, 2018
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
10 changes: 7 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ plugins/NativeScriptAngularCompilerPlugin.d.ts
plugins/NativeScriptAngularCompilerPlugin.js
plugins/NativeScriptAngularCompilerPlugin.js.map

transformers/ns-replace-bootstrap.d.ts
transformers/ns-replace-bootstrap.js
transformers/ns-replace-bootstrap.js.map
transformers/*.d.ts
transformers/*.js
transformers/*.js.map

utils/*.d.ts
utils/*.js
utils/*.js.map

plugins/PlatformFSPlugin.d.ts
plugins/PlatformFSPlugin.js
Expand Down
21 changes: 10 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const path = require("path");
const { existsSync } = require("fs");

const { ANDROID_APP_PATH } = require("./androidProjectHelpers");
const {
getPackageJson,
Expand All @@ -12,30 +11,30 @@ Object.assign(exports, require("./plugins"));
Object.assign(exports, require("./host/resolver"));

exports.getAotEntryModule = function (appDirectory) {
verifyEntryModuleDirectory(appDirectory);
verifyEntryModuleDirectory(appDirectory);

const entry = getPackageJsonEntry(appDirectory);
const aotEntry = `${entry}.aot.ts`;

const aotEntryPath = path.resolve(appDirectory, aotEntry);
if (!existsSync(aotEntryPath)) {
throw new Error(`For ahead-of-time compilation you need to have an entry module ` +
`at ${aotEntryPath} that bootstraps the app with a static platform instead of dynamic one!`)
`at ${aotEntryPath} that bootstraps the app with a static platform instead of dynamic one!`)
}

return aotEntry;
}

exports.getEntryModule = function (appDirectory) {
verifyEntryModuleDirectory(appDirectory);
verifyEntryModuleDirectory(appDirectory);

const entry = getPackageJsonEntry(appDirectory);

const tsEntryPath = path.resolve(appDirectory, `${entry}.ts`);
const jsEntryPath = path.resolve(appDirectory, `${entry}.js`);
if (!existsSync(tsEntryPath) && !existsSync(jsEntryPath)) {
throw new Error(`The entry module ${entry} specified in ` +
`${appDirectory}/package.json doesn't exist!`)
`${appDirectory}/package.json doesn't exist!`)
}

return entry;
Expand Down Expand Up @@ -72,10 +71,10 @@ function getPackageJsonEntry(appDirectory) {

function verifyEntryModuleDirectory(appDirectory) {
if (!appDirectory) {
throw new Error("Path to app directory is not specified. Unable to find entry module.");
}
throw new Error("Path to app directory is not specified. Unable to find entry module.");
}

if (!existsSync(appDirectory)) {
throw new Error(`The specified path to app directory ${appDirectory} does not exist. Unable to find entry module.`);
}
if (!existsSync(appDirectory)) {
throw new Error(`The specified path to app directory ${appDirectory} does not exist. Unable to find entry module.`);
}
}
29 changes: 26 additions & 3 deletions templates/webpack.angular.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const { join, relative, resolve, sep } = require("path");
const { join, relative, resolve, sep, dirname } = require("path");

const webpack = require("webpack");
const nsWebpack = require("nativescript-dev-webpack");
const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target");
const { nsReplaceBootstrap } = require("nativescript-dev-webpack/transformers/ns-replace-bootstrap");
const { nsReplaceLazyLoader } = require("nativescript-dev-webpack/transformers/ns-replace-lazy-loader");
const { getMainModulePath } = require("nativescript-dev-webpack/utils/ast-utils");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
Expand Down Expand Up @@ -44,7 +46,8 @@ module.exports = env => {
sourceMap, // --env.sourceMap
hmr, // --env.hmr,
} = env;
const externals = (env.externals || []).map((e) => { // --env.externals
env.externals = env.externals || [];
const externals = (env.externals).map((e) => { // --env.externals
return new RegExp(e + ".*");
});

Expand All @@ -53,14 +56,34 @@ module.exports = env => {

const entryModule = `${nsWebpack.getEntryModule(appFullPath)}.ts`;
const entryPath = `.${sep}${entryModule}`;
const ngCompilerTransformers = [];
const additionalLazyModuleResources = [];
if (aot) {
ngCompilerTransformers.push(nsReplaceBootstrap);
}

// when "@angular/core" is external, it's not included in the bundles. In this way, it will be used
// directly from node_modules and the Angular modules loader won't be able to resolve the lazy routes
// fixes https://github.com/NativeScript/nativescript-cli/issues/4024
if (env.externals.indexOf("@angular/core") > -1) {
const appModuleRelativePath = getMainModulePath(resolve(appFullPath, entryModule));
if (appModuleRelativePath) {
const appModuleFolderPath = dirname(resolve(appFullPath, appModuleRelativePath));
// include the lazy loader inside app module
ngCompilerTransformers.push(nsReplaceLazyLoader);
// include the new lazy loader path in the allowed ones
additionalLazyModuleResources.push(appModuleFolderPath);
}
}

const ngCompilerPlugin = new AngularCompilerPlugin({
hostReplacementPaths: nsWebpack.getResolver([platform, "tns"]),
platformTransformers: aot ? [nsReplaceBootstrap(() => ngCompilerPlugin)] : null,
platformTransformers: ngCompilerTransformers.map(t => t(() => ngCompilerPlugin)),
mainPath: resolve(appPath, entryModule),
tsConfigPath: join(__dirname, "tsconfig.tns.json"),
skipCodeGeneration: !aot,
sourceMap: !!sourceMap,
additionalLazyModuleResources: additionalLazyModuleResources
});

const config = {
Expand Down
3 changes: 0 additions & 3 deletions transformers/ns-replace-bootstrap.d.ts

This file was deleted.

9 changes: 3 additions & 6 deletions transformers/ns-replace-bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,17 @@ import {
makeTransform,
getFirstNode
} from "@ngtools/webpack/src/transformers";
import { workaroundResolve } from '@ngtools/webpack/src/compiler_host';
import { AngularCompilerPlugin } from '@ngtools/webpack';
import { getResolvedEntryModule } from "../utils/transformers-utils";


export function nsReplaceBootstrap(getNgCompiler: () => AngularCompilerPlugin): ts.TransformerFactory<ts.SourceFile> {
const shouldTransform = (fileName) => !fileName.endsWith('.ngfactory.ts') && !fileName.endsWith('.ngstyle.ts');
const getTypeChecker = () => getNgCompiler().typeChecker;

const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) {
const ops: TransformOperation[] = [];
const ngCompiler = getNgCompiler();

const entryModule = ngCompiler.entryModule
? { path: workaroundResolve(ngCompiler.entryModule.path), className: getNgCompiler().entryModule.className }
: ngCompiler.entryModule;
const entryModule = getResolvedEntryModule(getNgCompiler());

if (!shouldTransform(sourceFile.fileName) || !entryModule) {
return ops;
Expand Down
228 changes: 228 additions & 0 deletions transformers/ns-replace-lazy-loader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { tags } from "@angular-devkit/core";
import { createTypescriptContext, transformTypescript } from "@ngtools/webpack/src/transformers";
import { nsReplaceLazyLoader, NgLazyLoaderCode, getConfigObjectSetupCode } from "./ns-replace-lazy-loader";
import { AngularCompilerPlugin } from "@ngtools/webpack";

describe("@ngtools/webpack transformers", () => {
describe("ns-replace-lazy-loader", () => {
const configObjectName = "testIdentifier";
const configObjectSetupCode = getConfigObjectSetupCode(configObjectName, "providers", "NgModuleFactoryLoader", "{ provide: nsNgCoreImport_Generated.NgModuleFactoryLoader, useClass: NSLazyModulesLoader_Generated }");
const testCases = [
{
name: "should add providers and NgModuleFactoryLoader when providers is missing",
rawAppModule: `
import { NgModule } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppComponent } from "./app.component";

@NgModule({
bootstrap: [
AppComponent
],
imports: [
NativeScriptModule
],
declarations: [
AppComponent,
]
})
export class AppModule { }
`,
transformedAppModule: `
import * as tslib_1 from "tslib"; import { NgModule } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppComponent } from "./app.component";
${NgLazyLoaderCode}
let AppModule = class AppModule { };
AppModule = tslib_1.__decorate([ NgModule({
bootstrap: [ AppComponent ],
imports: [ NativeScriptModule ],
declarations: [ AppComponent, ],
providers: [{ provide: nsNgCoreImport_Generated.NgModuleFactoryLoader, useClass: NSLazyModulesLoader_Generated }] })
],
AppModule);
export { AppModule };`
},
{
name: "should add NgModuleFactoryLoader when the providers array is empty",
rawAppModule: `
import { NgModule } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppComponent } from "./app.component";

@NgModule({
bootstrap: [
AppComponent
],
imports: [
NativeScriptModule
],
declarations: [
AppComponent,
],
providers: []
})
export class AppModule { }
`,
transformedAppModule: `
import * as tslib_1 from "tslib"; import { NgModule } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppComponent } from "./app.component";
${NgLazyLoaderCode}
let AppModule = class AppModule { };
AppModule = tslib_1.__decorate([ NgModule({
bootstrap: [ AppComponent ],
imports: [ NativeScriptModule ],
declarations: [ AppComponent, ],
providers: [{ provide: nsNgCoreImport_Generated.NgModuleFactoryLoader, useClass: NSLazyModulesLoader_Generated }] })
],
AppModule);
export { AppModule };`
},
{
name: "should add NgModuleFactoryLoader at the end when the providers array is containing other providers",
rawAppModule: `
import { NgModule } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppComponent } from "./app.component";
@NgModule({
bootstrap: [
AppComponent
],
imports: [
NativeScriptModule
],
declarations: [
AppComponent,
],
providers: [MyCoolProvider]
})
export class AppModule { }
`,
transformedAppModule: `
import * as tslib_1 from "tslib"; import { NgModule } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppComponent } from "./app.component";
${NgLazyLoaderCode}
let AppModule = class AppModule { };
AppModule = tslib_1.__decorate([ NgModule({
bootstrap: [ AppComponent ],
imports: [ NativeScriptModule ],
declarations: [ AppComponent, ],
providers: [MyCoolProvider, { provide: nsNgCoreImport_Generated.NgModuleFactoryLoader, useClass: NSLazyModulesLoader_Generated }] })
],
AppModule);
export { AppModule };`
},
{
name: "should NOT add NgModuleFactoryLoader when it's already defined",
rawAppModule: `
import { NgModule } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppComponent } from "./app.component";

@NgModule({
bootstrap: [
AppComponent
],
imports: [
NativeScriptModule
],
declarations: [
AppComponent,
],
providers: [{ provide: NgModuleFactoryLoader, useClass: CustomLoader }]
})
export class AppModule { }
`,
transformedAppModule: `
import * as tslib_1 from "tslib"; import { NgModule } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppComponent } from "./app.component";
let AppModule = class AppModule { };
AppModule = tslib_1.__decorate([ NgModule({
bootstrap: [ AppComponent ],
imports: [ NativeScriptModule ],
declarations: [ AppComponent, ],
providers: [{ provide: NgModuleFactoryLoader, useClass: CustomLoader }] })
],
AppModule);
export { AppModule };`
},
{
name: "should setup the object when an object is passed to the NgModule",
rawAppModule: `
import { NgModule } from "@angular/core";
import { ${configObjectName} } from "somewhere";

@NgModule(${configObjectName})
export class AppModule { }
`,
transformedAppModule: `
import * as tslib_1 from "tslib";
import { NgModule } from "@angular/core";
import { ${configObjectName} } from "somewhere";

${NgLazyLoaderCode}
${configObjectSetupCode}
let AppModule = class AppModule { };
AppModule = tslib_1.__decorate([ NgModule(${configObjectName}) ], AppModule);

export { AppModule };
`
},
{
name: "should setup the object after its initialization when a local object is passed to the NgModule",
rawAppModule: `
import { NgModule } from "@angular/core";
const ${configObjectName} = {
bootstrap: [
AppComponent
],
declarations: [
AppComponent
]
};

@NgModule(${configObjectName})
export class AppModule { }
`,
transformedAppModule: `
import * as tslib_1 from "tslib";
import { NgModule } from "@angular/core";
${NgLazyLoaderCode}
const ${configObjectName} = {
bootstrap: [
AppComponent
],
declarations: [
AppComponent
]
};
${configObjectSetupCode}
let AppModule = class AppModule { };
AppModule = tslib_1.__decorate([ NgModule(${configObjectName}) ], AppModule);
export { AppModule };
`
}
];
testCases.forEach((testCase: any) => {
it(`${testCase.name}`, async () => {
const input = tags.stripIndent`${testCase.rawAppModule}`;
const output = tags.stripIndent`${testCase.transformedAppModule}`;
const { program, compilerHost } = createTypescriptContext(input);
const ngCompiler = <AngularCompilerPlugin>{
typeChecker: program.getTypeChecker(),
entryModule: {
path: "/project/src/test-file",
className: "AppModule",
},
};
const transformer = nsReplaceLazyLoader(() => ngCompiler);
const result = transformTypescript(undefined, [transformer], program, compilerHost);

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