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

Commit b042c65

Browse files
committed
feat: transform the main angular module in order to include the lazy loader in the bundle when angular/core is external
1 parent fffcf66 commit b042c65

7 files changed

+704
-20
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

+19-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const path = require("path");
22
const { existsSync } = require("fs");
3-
3+
const { findBootstrapModulePath } = require("./utils/ast-utils")
44
const { ANDROID_APP_PATH } = require("./androidProjectHelpers");
55
const {
66
getPackageJson,
@@ -12,35 +12,43 @@ Object.assign(exports, require("./plugins"));
1212
Object.assign(exports, require("./host/resolver"));
1313

1414
exports.getAotEntryModule = function (appDirectory) {
15-
verifyEntryModuleDirectory(appDirectory);
16-
15+
verifyEntryModuleDirectory(appDirectory);
16+
1717
const entry = getPackageJsonEntry(appDirectory);
1818
const aotEntry = `${entry}.aot.ts`;
1919

2020
const aotEntryPath = path.resolve(appDirectory, aotEntry);
2121
if (!existsSync(aotEntryPath)) {
2222
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!`)
23+
`at ${aotEntryPath} that bootstraps the app with a static platform instead of dynamic one!`)
2424
}
2525

2626
return aotEntry;
2727
}
2828

2929
exports.getEntryModule = function (appDirectory) {
30-
verifyEntryModuleDirectory(appDirectory);
30+
verifyEntryModuleDirectory(appDirectory);
3131

3232
const entry = getPackageJsonEntry(appDirectory);
3333

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

4141
return entry;
4242
};
4343

44+
exports.getMainModulePath = function (entryFilePath) {
45+
try {
46+
return findBootstrapModulePath(entryFilePath);
47+
} catch (e) {
48+
return null;
49+
}
50+
}
51+
4452
exports.getAppPath = (platform, projectDir) => {
4553
if (isIos(platform)) {
4654
const appName = path.basename(projectDir);
@@ -72,10 +80,10 @@ function getPackageJsonEntry(appDirectory) {
7280

7381
function verifyEntryModuleDirectory(appDirectory) {
7482
if (!appDirectory) {
75-
throw new Error("Path to app directory is not specified. Unable to find entry module.");
76-
}
83+
throw new Error("Path to app directory is not specified. Unable to find entry module.");
84+
}
7785

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

Diff for: templates/webpack.angular.js

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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");
78
const CleanWebpackPlugin = require("clean-webpack-plugin");
89
const CopyWebpackPlugin = require("copy-webpack-plugin");
910
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
@@ -44,7 +45,8 @@ module.exports = env => {
4445
sourceMap, // --env.sourceMap
4546
hmr, // --env.hmr,
4647
} = env;
47-
const externals = (env.externals || []).map((e) => { // --env.externals
48+
env.externals = env.externals || [];
49+
const externals = (env.externals).map((e) => { // --env.externals
4850
return new RegExp(e + ".*");
4951
});
5052

@@ -53,14 +55,31 @@ module.exports = env => {
5355

5456
const entryModule = `${nsWebpack.getEntryModule(appFullPath)}.ts`;
5557
const entryPath = `.${sep}${entryModule}`;
58+
const ngCompilerTransformers = [];
59+
const additionalLazyModuleResources = [];
60+
if (aot) {
61+
ngCompilerTransformers.push(nsReplaceBootstrap);
62+
}
63+
64+
if (env.externals.indexOf("@angular/core") > -1) {
65+
const appModuleRelativePath = nsWebpack.getMainModulePath(resolve(appFullPath, entryModule));
66+
if (appModuleRelativePath) {
67+
const appModuleFolderPath = dirname(resolve(appFullPath, appModuleRelativePath));
68+
// include the lazy loader inside app module
69+
ngCompilerTransformers.push(nsReplaceLazyLoader);
70+
// include the new lazy loader path in the allowed ones
71+
additionalLazyModuleResources.push(appModuleFolderPath);
72+
}
73+
}
5674

5775
const ngCompilerPlugin = new AngularCompilerPlugin({
5876
hostReplacementPaths: nsWebpack.getResolver([platform, "tns"]),
59-
platformTransformers: aot ? [nsReplaceBootstrap(() => ngCompilerPlugin)] : null,
77+
platformTransformers: ngCompilerTransformers.map(t => t(() => ngCompilerPlugin)),
6078
mainPath: resolve(appPath, entryModule),
6179
tsConfigPath: join(__dirname, "tsconfig.tns.json"),
6280
skipCodeGeneration: !aot,
6381
sourceMap: !!sourceMap,
82+
additionalLazyModuleResources: additionalLazyModuleResources
6483
});
6584

6685
const config = {

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

-3
This file was deleted.

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 its 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)