enten / udk

Universal Development Kit: Webpack extension which improves universal application development. - THE UDK PROJECT SUPPORT IS CURRENTLY SUSPENDED.
MIT License
29 stars 7 forks source link

udk 0.3.13, 0.3.14 can't use with nestjs/nest but 0.3.12 can. #4

Closed Jimmysh closed 6 years ago

Jimmysh commented 6 years ago

you can use https://github.com/patrickhousley/nest-angular-universal to test.

patrickhousley commented 6 years ago

Look like this change is what caused the issue. If I restore that removed code, everything works.

enten commented 6 years ago

@Jimmysh
As @patrickhousley notify: udk@0.3.13 remove the temporary entry point ngmodule.

I was thinking that webpack externalizes entry point imports: but by default (with angular-cli webpack configs) the code in src/main.server.ts will be dupplicate in src/server/main.ts (you can check that by opening dist/server/main.js, it contains the code of dist/server/ngmodule.js). I did that trick to avoid injection of NgFactory and LAZY_MODULE_MAP in the server entry point.

Today, I think that ngmodule trick is a bad pattern and increase the bundle server size (and compilation time). An issue with Angular 6 i18n make me realize that it was a bad pattern to add an additional entry point.

From udk@0.3.13 (sorry for the versioning, its still in experimentation but I planned to split udk under npm's namespace @udk for the v1), the serverTarget options main is the main entry.

So you need the adjust your server entry point to bootstrap your http listener by using the AppServerModuleNgFactory and LAZY_MODULE_MAP dynamically exported by ngc during webpack's compilation stage.

The most important thing is to start your main instruction with process.nextTick(): because the dynamic LAZY_MODULE_MAP is concatenated at the end of the entry point. In that way, you ensure that the LAZY_MODULE_MAP export instruction is executed. You can use exports var to reference exports.AppServerModuleNgFactory and exports.LAZY_MODULE_MAP and avoid typescript compiler's errors.

I write the patch below (apply after patrickhousley/nest-angular-universal/commit/c62a5d00) which make your code works with udk@0.3.14.

diff --git a/angular.json b/angular.json
index a2bd954..c3cb96e 100644
--- a/angular.json
+++ b/angular.json
@@ -70,7 +70,7 @@
           "builder": "@angular-devkit/build-angular:server",
           "options": {
             "outputPath": "dist/server",
-            "main": "src/main.server.ts",
+            "main": "src/server/main.ts",
             "tsConfig": "src/tsconfig.server.json"
           },
           "configurations": {
diff --git a/package.json b/package.json
index 2172440..d62344f 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
     "ts-node": "~5.0.1",
     "tslint": "~5.9.1",
     "typescript": "~2.7.2",
-    "udk": "0.3.12"
+    "udk": "^0.3.14"
   },
   "repository": {}
 }
diff --git a/src/server/app.module.ts b/src/server/app.module.ts
index 69c24f4..784cc7e 100644
--- a/src/server/app.module.ts
+++ b/src/server/app.module.ts
@@ -5,7 +5,19 @@ import { ClientModule } from './client/client.module';
 @Module({
   imports: [
     HeroesModule,
-    ClientModule.forRoot()
-  ],
+    // ClientModule
+  ]
 })
-export class ApplicationModule {}
+export class ApplicationModule {
+  static bootstrap(bundle: any) {
+    const nextModules = [
+      ...Reflect.getOwnMetadata('modules', ApplicationModule),
+      ClientModule.forRoot(bundle)
+    ];
+    Reflect.defineMetadata('modules', nextModules, ApplicationModule);
+    const providers = Reflect.getOwnMetadata('modules', ApplicationModule);
+    console.log('__CP__ main.ts', providers)
+
+    return ApplicationModule;
+  }
+}
diff --git a/src/server/client/client.module.ts b/src/server/client/client.module.ts
index c30bb02..4bc8c28 100644
--- a/src/server/client/client.module.ts
+++ b/src/server/client/client.module.ts
@@ -24,10 +24,10 @@ export class ClientModule implements NestModule {
     @Inject(HTTP_SERVER_REF) private readonly app: NestApplication
   ) {}

-  static forRoot(): DynamicModule {
+  static forRoot(bundle: any): DynamicModule {
     const options: AngularUniversalOptions = {
       viewsPath: environment.clientPaths.client,
-      bundle: require('../../main.server')
+      bundle
     };

     return {
diff --git a/src/server/main.ts b/src/server/main.ts
index c05b401..80262f1 100644
--- a/src/server/main.ts
+++ b/src/server/main.ts
@@ -5,21 +5,24 @@ import { ValidationPipe, INestApplication, INestExpressApplication } from '@nest
 import { ApplicationModule } from './app.module';
 import { environment } from './environments/environment';

+export { AppServerModule } from '../app/app.server.module';
+
 let app: INestApplication & INestExpressApplication;

-async function bootstrap() {
+export async function bootstrap(bundle: any) {
   if (environment.production) {
     enableProdMode();
   }

-  app = await NestFactory.create(ApplicationModule);
+  app = await NestFactory.create(ApplicationModule.bootstrap(bundle));
   app.useGlobalPipes(new ValidationPipe());
   await app.listen(environment.port);
 }
-bootstrap();

 export async function close() {
   if (app) {
     await app.close();
   }
 }
+
+process.nextTick(() => bootstrap(exports));
Jimmysh commented 6 years ago

Thanks @enten , @patrickhousley.

patrickhousley commented 6 years ago

Thanks @enten for the update and the work you have put into this project. My goal with using this project was to have a single command that would run and watch both the server and client for changes and rebuild for development. I also didn't want to have to have a webpack config inside the project when using the Angular CLI. However, this change feels a little bit hackish with the need of process.tick and the use of the exports prop. The point that the AOT front-end code is duplicated in the server bundle is, in my opinion, not that big of a deal since it is server-side code. Maybe I am not accounting for something here. That said, if this is the path going forward, I believe the main is potentially unnecessary in the udk portion of the angular.json config. I would love to find a way to do this without the process.tick and exports prop use. Maybe it would be best to start exploring bazel.

enten commented 6 years ago

@patrickhousley
Thanks for you feedback and details.

I'm sharing your opinion.

But currently, udk schematic builder is in experimentation step like udk is (sadly I haven't lot of time to write the first stable release, but it's still planned). You can see that udk schematic builder haven't it's own package yet. More information in

I haven't yet all knowledge about every operations/configurations supported by Angular compiler.

The point that the AOT front-end code is duplicated in the server bundle is, in my opinion, not that big of a deal since it is server-side code.

Currently, I think not. As like I said, I haven't all required information about the Angular compiler (need to read and understand some of the angular-devkit packages). But I know that when we switch the angular server entry point into ngmodule entry, and a new main entry which import the ngmodule entry: that changes compilation behavior and some things will be broken (like i18n).

I believe the main is potentially unnecessary in the udk portion of the angular.json config.

You spotted it! I don't remove that option to not break previous setup. But right: main is currently useless. But not obsolete: I realy want to have that kind of option.

I need to design an API for the udk schematic builder which suits well with universal angular application development.

Feel free to share your details about udk schematic builder options.