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

Commit a6c23da

Browse files
author
Dimitar Tachev
authored
fix(angular): support angular lazy routes in preview (#753)
* feat: transform the main angular module in order to include the lazy loader in the bundle when angular/core is external * fix pr comments * refactor: remove blank lines * fix: move a typescript specific logic in ast utils in order to avoid invalid import in JS applications * fix: normalize the app module paths in order to avoid incorrect comparison on Windows
1 parent d0feea2 commit a6c23da

9 files changed

+732
-26
lines changed

Diff for: .gitignore

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ plugins/NativeScriptAngularCompilerPlugin.d.ts
77
plugins/NativeScriptAngularCompilerPlugin.js
88
plugins/NativeScriptAngularCompilerPlugin.js.map
99

10-
transformers/ns-replace-bootstrap.d.ts
11-
transformers/ns-replace-bootstrap.js
12-
transformers/ns-replace-bootstrap.js.map
10+
transformers/*.d.ts
11+
transformers/*.js
12+
transformers/*.js.map
13+
14+
utils/*.d.ts
15+
utils/*.js
16+
utils/*.js.map
1317

1418
plugins/PlatformFSPlugin.d.ts
1519
plugins/PlatformFSPlugin.js

Diff for: index.js

+10-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const path = require("path");
22
const { existsSync } = require("fs");
3-
43
const { ANDROID_APP_PATH } = require("./androidProjectHelpers");
54
const {
65
getPackageJson,
@@ -12,30 +11,30 @@ Object.assign(exports, require("./plugins"));
1211
Object.assign(exports, require("./host/resolver"));
1312

1413
exports.getAotEntryModule = function (appDirectory) {
15-
verifyEntryModuleDirectory(appDirectory);
16-
14+
verifyEntryModuleDirectory(appDirectory);
15+
1716
const entry = getPackageJsonEntry(appDirectory);
1817
const aotEntry = `${entry}.aot.ts`;
1918

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

2625
return aotEntry;
2726
}
2827

2928
exports.getEntryModule = function (appDirectory) {
30-
verifyEntryModuleDirectory(appDirectory);
29+
verifyEntryModuleDirectory(appDirectory);
3130

3231
const entry = getPackageJsonEntry(appDirectory);
3332

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

4140
return entry;
@@ -72,10 +71,10 @@ function getPackageJsonEntry(appDirectory) {
7271

7372
function verifyEntryModuleDirectory(appDirectory) {
7473
if (!appDirectory) {
75-
throw new Error("Path to app directory is not specified. Unable to find entry module.");
76-
}
74+
throw new Error("Path to app directory is not specified. Unable to find entry module.");
75+
}
7776

78-
if (!existsSync(appDirectory)) {
79-
throw new Error(`The specified path to app directory ${appDirectory} does not exist. Unable to find entry module.`);
80-
}
77+
if (!existsSync(appDirectory)) {
78+
throw new Error(`The specified path to app directory ${appDirectory} does not exist. Unable to find entry module.`);
79+
}
8180
}

Diff for: templates/webpack.angular.js

+26-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
const { join, relative, resolve, sep } = require("path");
1+
const { join, relative, resolve, sep, dirname } = require("path");
22

33
const webpack = require("webpack");
44
const nsWebpack = require("nativescript-dev-webpack");
55
const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target");
66
const { nsReplaceBootstrap } = require("nativescript-dev-webpack/transformers/ns-replace-bootstrap");
7+
const { nsReplaceLazyLoader } = require("nativescript-dev-webpack/transformers/ns-replace-lazy-loader");
8+
const { getMainModulePath } = require("nativescript-dev-webpack/utils/ast-utils");
79
const CleanWebpackPlugin = require("clean-webpack-plugin");
810
const CopyWebpackPlugin = require("copy-webpack-plugin");
911
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
@@ -44,7 +46,8 @@ module.exports = env => {
4446
sourceMap, // --env.sourceMap
4547
hmr, // --env.hmr,
4648
} = env;
47-
const externals = (env.externals || []).map((e) => { // --env.externals
49+
env.externals = env.externals || [];
50+
const externals = (env.externals).map((e) => { // --env.externals
4851
return new RegExp(e + ".*");
4952
});
5053

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

5457
const entryModule = `${nsWebpack.getEntryModule(appFullPath)}.ts`;
5558
const entryPath = `.${sep}${entryModule}`;
59+
const ngCompilerTransformers = [];
60+
const additionalLazyModuleResources = [];
61+
if (aot) {
62+
ngCompilerTransformers.push(nsReplaceBootstrap);
63+
}
64+
65+
// when "@angular/core" is external, it's not included in the bundles. In this way, it will be used
66+
// directly from node_modules and the Angular modules loader won't be able to resolve the lazy routes
67+
// fixes https://github.com/NativeScript/nativescript-cli/issues/4024
68+
if (env.externals.indexOf("@angular/core") > -1) {
69+
const appModuleRelativePath = getMainModulePath(resolve(appFullPath, entryModule));
70+
if (appModuleRelativePath) {
71+
const appModuleFolderPath = dirname(resolve(appFullPath, appModuleRelativePath));
72+
// include the lazy loader inside app module
73+
ngCompilerTransformers.push(nsReplaceLazyLoader);
74+
// include the new lazy loader path in the allowed ones
75+
additionalLazyModuleResources.push(appModuleFolderPath);
76+
}
77+
}
5678

5779
const ngCompilerPlugin = new AngularCompilerPlugin({
5880
hostReplacementPaths: nsWebpack.getResolver([platform, "tns"]),
59-
platformTransformers: aot ? [nsReplaceBootstrap(() => ngCompilerPlugin)] : null,
81+
platformTransformers: ngCompilerTransformers.map(t => t(() => ngCompilerPlugin)),
6082
mainPath: resolve(appPath, entryModule),
6183
tsConfigPath: join(__dirname, "tsconfig.tns.json"),
6284
skipCodeGeneration: !aot,
6385
sourceMap: !!sourceMap,
86+
additionalLazyModuleResources: additionalLazyModuleResources
6487
});
6588

6689
const config = {

Diff for: transformers/ns-replace-bootstrap.d.ts

-3
This file was deleted.

Diff for: transformers/ns-replace-bootstrap.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,17 @@ import {
99
makeTransform,
1010
getFirstNode
1111
} from "@ngtools/webpack/src/transformers";
12-
import { workaroundResolve } from '@ngtools/webpack/src/compiler_host';
1312
import { AngularCompilerPlugin } from '@ngtools/webpack';
13+
import { getResolvedEntryModule } from "../utils/transformers-utils";
14+
1415

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

1920
const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) {
2021
const ops: TransformOperation[] = [];
21-
const ngCompiler = getNgCompiler();
22-
23-
const entryModule = ngCompiler.entryModule
24-
? { path: workaroundResolve(ngCompiler.entryModule.path), className: getNgCompiler().entryModule.className }
25-
: ngCompiler.entryModule;
22+
const entryModule = getResolvedEntryModule(getNgCompiler());
2623

2724
if (!shouldTransform(sourceFile.fileName) || !entryModule) {
2825
return ops;

Diff for: transformers/ns-replace-lazy-loader.spec.ts

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import { tags } from "@angular-devkit/core";
2+
import { createTypescriptContext, transformTypescript } from "@ngtools/webpack/src/transformers";
3+
import { nsReplaceLazyLoader, NgLazyLoaderCode, getConfigObjectSetupCode } from "./ns-replace-lazy-loader";
4+
import { AngularCompilerPlugin } from "@ngtools/webpack";
5+
6+
describe("@ngtools/webpack transformers", () => {
7+
describe("ns-replace-lazy-loader", () => {
8+
const configObjectName = "testIdentifier";
9+
const configObjectSetupCode = getConfigObjectSetupCode(configObjectName, "providers", "NgModuleFactoryLoader", "{ provide: nsNgCoreImport_Generated.NgModuleFactoryLoader, useClass: NSLazyModulesLoader_Generated }");
10+
const testCases = [
11+
{
12+
name: "should add providers and NgModuleFactoryLoader when providers is missing",
13+
rawAppModule: `
14+
import { NgModule } from "@angular/core";
15+
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
16+
import { AppComponent } from "./app.component";
17+
18+
@NgModule({
19+
bootstrap: [
20+
AppComponent
21+
],
22+
imports: [
23+
NativeScriptModule
24+
],
25+
declarations: [
26+
AppComponent,
27+
]
28+
})
29+
export class AppModule { }
30+
`,
31+
transformedAppModule: `
32+
import * as tslib_1 from "tslib"; import { NgModule } from "@angular/core";
33+
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
34+
import { AppComponent } from "./app.component";
35+
${NgLazyLoaderCode}
36+
let AppModule = class AppModule { };
37+
AppModule = tslib_1.__decorate([ NgModule({
38+
bootstrap: [ AppComponent ],
39+
imports: [ NativeScriptModule ],
40+
declarations: [ AppComponent, ],
41+
providers: [{ provide: nsNgCoreImport_Generated.NgModuleFactoryLoader, useClass: NSLazyModulesLoader_Generated }] })
42+
],
43+
AppModule);
44+
export { AppModule };`
45+
},
46+
{
47+
name: "should add NgModuleFactoryLoader when the providers array is empty",
48+
rawAppModule: `
49+
import { NgModule } from "@angular/core";
50+
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
51+
import { AppComponent } from "./app.component";
52+
53+
@NgModule({
54+
bootstrap: [
55+
AppComponent
56+
],
57+
imports: [
58+
NativeScriptModule
59+
],
60+
declarations: [
61+
AppComponent,
62+
],
63+
providers: []
64+
})
65+
export class AppModule { }
66+
`,
67+
transformedAppModule: `
68+
import * as tslib_1 from "tslib"; import { NgModule } from "@angular/core";
69+
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
70+
import { AppComponent } from "./app.component";
71+
${NgLazyLoaderCode}
72+
let AppModule = class AppModule { };
73+
AppModule = tslib_1.__decorate([ NgModule({
74+
bootstrap: [ AppComponent ],
75+
imports: [ NativeScriptModule ],
76+
declarations: [ AppComponent, ],
77+
providers: [{ provide: nsNgCoreImport_Generated.NgModuleFactoryLoader, useClass: NSLazyModulesLoader_Generated }] })
78+
],
79+
AppModule);
80+
export { AppModule };`
81+
},
82+
{
83+
name: "should add NgModuleFactoryLoader at the end when the providers array is containing other providers",
84+
rawAppModule: `
85+
import { NgModule } from "@angular/core";
86+
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
87+
import { AppComponent } from "./app.component";
88+
@NgModule({
89+
bootstrap: [
90+
AppComponent
91+
],
92+
imports: [
93+
NativeScriptModule
94+
],
95+
declarations: [
96+
AppComponent,
97+
],
98+
providers: [MyCoolProvider]
99+
})
100+
export class AppModule { }
101+
`,
102+
transformedAppModule: `
103+
import * as tslib_1 from "tslib"; import { NgModule } from "@angular/core";
104+
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
105+
import { AppComponent } from "./app.component";
106+
${NgLazyLoaderCode}
107+
let AppModule = class AppModule { };
108+
AppModule = tslib_1.__decorate([ NgModule({
109+
bootstrap: [ AppComponent ],
110+
imports: [ NativeScriptModule ],
111+
declarations: [ AppComponent, ],
112+
providers: [MyCoolProvider, { provide: nsNgCoreImport_Generated.NgModuleFactoryLoader, useClass: NSLazyModulesLoader_Generated }] })
113+
],
114+
AppModule);
115+
export { AppModule };`
116+
},
117+
{
118+
name: "should NOT add NgModuleFactoryLoader when it's already defined",
119+
rawAppModule: `
120+
import { NgModule } from "@angular/core";
121+
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
122+
import { AppComponent } from "./app.component";
123+
124+
@NgModule({
125+
bootstrap: [
126+
AppComponent
127+
],
128+
imports: [
129+
NativeScriptModule
130+
],
131+
declarations: [
132+
AppComponent,
133+
],
134+
providers: [{ provide: NgModuleFactoryLoader, useClass: CustomLoader }]
135+
})
136+
export class AppModule { }
137+
`,
138+
transformedAppModule: `
139+
import * as tslib_1 from "tslib"; import { NgModule } from "@angular/core";
140+
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
141+
import { AppComponent } from "./app.component";
142+
let AppModule = class AppModule { };
143+
AppModule = tslib_1.__decorate([ NgModule({
144+
bootstrap: [ AppComponent ],
145+
imports: [ NativeScriptModule ],
146+
declarations: [ AppComponent, ],
147+
providers: [{ provide: NgModuleFactoryLoader, useClass: CustomLoader }] })
148+
],
149+
AppModule);
150+
export { AppModule };`
151+
},
152+
{
153+
name: "should setup the object when an object is passed to the NgModule",
154+
rawAppModule: `
155+
import { NgModule } from "@angular/core";
156+
import { ${configObjectName} } from "somewhere";
157+
158+
@NgModule(${configObjectName})
159+
export class AppModule { }
160+
`,
161+
transformedAppModule: `
162+
import * as tslib_1 from "tslib";
163+
import { NgModule } from "@angular/core";
164+
import { ${configObjectName} } from "somewhere";
165+
166+
${NgLazyLoaderCode}
167+
${configObjectSetupCode}
168+
let AppModule = class AppModule { };
169+
AppModule = tslib_1.__decorate([ NgModule(${configObjectName}) ], AppModule);
170+
171+
export { AppModule };
172+
`
173+
},
174+
{
175+
name: "should setup the object after its initialization when a local object is passed to the NgModule",
176+
rawAppModule: `
177+
import { NgModule } from "@angular/core";
178+
const ${configObjectName} = {
179+
bootstrap: [
180+
AppComponent
181+
],
182+
declarations: [
183+
AppComponent
184+
]
185+
};
186+
187+
@NgModule(${configObjectName})
188+
export class AppModule { }
189+
`,
190+
transformedAppModule: `
191+
import * as tslib_1 from "tslib";
192+
import { NgModule } from "@angular/core";
193+
${NgLazyLoaderCode}
194+
const ${configObjectName} = {
195+
bootstrap: [
196+
AppComponent
197+
],
198+
declarations: [
199+
AppComponent
200+
]
201+
};
202+
${configObjectSetupCode}
203+
let AppModule = class AppModule { };
204+
AppModule = tslib_1.__decorate([ NgModule(${configObjectName}) ], AppModule);
205+
export { AppModule };
206+
`
207+
}
208+
];
209+
testCases.forEach((testCase: any) => {
210+
it(`${testCase.name}`, async () => {
211+
const input = tags.stripIndent`${testCase.rawAppModule}`;
212+
const output = tags.stripIndent`${testCase.transformedAppModule}`;
213+
const { program, compilerHost } = createTypescriptContext(input);
214+
const ngCompiler = <AngularCompilerPlugin>{
215+
typeChecker: program.getTypeChecker(),
216+
entryModule: {
217+
path: "/project/src/test-file",
218+
className: "AppModule",
219+
},
220+
};
221+
const transformer = nsReplaceLazyLoader(() => ngCompiler);
222+
const result = transformTypescript(undefined, [transformer], program, compilerHost);
223+
224+
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
225+
});
226+
});
227+
});
228+
});

0 commit comments

Comments
 (0)