Nx: Angular Universal

Created on 18 Dec 2017  路  15Comments  路  Source: nrwl/nx

Angular CLI 1.6 added support for

ng generate universal

When can we expect support for that?

angular feature

Most helpful comment

@FrozenPandaz how to run SSR if more than one project? Will there be support for this?
way with server.ts very ugly and requires code support for node.js

All 15 comments

In one of the nx discussions on either angular air or ngHuston there was mention of merging schematics collections. Is that what this is about? Now that nx schematics overrides ng generate, it has no idea that universal exists in the "base"? There needs a way to merge the base with the overriding schematics?

You can run ng generate universal --collection=@angular/schematics.

@MarkPieszak has been working on making sure the universal schematic works properly with nx.

This functionality broke recently. After extending @ngrx/schematics.

This should be fixed in @ngrx/[email protected]? But we can add a fix to extend it.

Will this be fixed anytime soon...? I love nrwl nx but I really want to use universal

Is it fixed? ngrx/schematics already v6.0.1

Fixed by #650

@FrozenPandaz how to run SSR if more than one project? Will there be support for this?
way with server.ts very ugly and requires code support for node.js

I'm trying to get this to work, if I run:

ng add @nguniversal/express-engine --clientProject universal-project

...everything installs fine. However, my libs cannot be resolved when running npm run build:ssr:

ERROR in src/app/app.module.ts(33,5): Error during template compile of 'AppModule' Could not resolve @yves-workspace/workspace-forms relative to [object Object].. apps/yves-website/src/app/shared/services/user-calculator/user-calculator.service.ts(3,21): error TS2307: Cannot find module '@yves-workspace/utils'. apps/yves-website/src/app/components/site-components.module.ts(15,32): error TS2307: Cannot find module '@yves-workspace/shared-ui'. apps/yves-website/src/app/components/components.module.ts(10,32): error TS2307: Cannot find module '@yves-workspace/shared-ui'. apps/yves-website/src/app/components/components.module.ts(11,37): error TS2307: Cannot find module '@yves-workspace/workspace-forms'. apps/yves-website/src/app/components/components.module.ts(12,28): error TS2307: Cannot find module '@yves-workspace/data'.

Did anyone solve this particular issue?

@FrozenPandaz could you comment on this?

@YvesCandel
Not solving this particular issue but I managed to make it work with
ng add @nestjs/ng-universal --clientProject=ng-test-app --project=ng-test-app
I documented how to setup a demo including the command above; maybe this helps..

@YvesCandel as @servrox made a showcase you can make it, and I was able to reproduce it in my project too. However, in SSR paths must be relative, making it from @libs/any other path won't work and I had exactly the same problem.

Doesn't work:
import { SharedModule } from @libs/shared/src/index';

Does work:
import { SharedModule } from './../../../../libs/shared/src/index';

Plus then you have a problem with tslint, so you have to make it that way:

// tslint:disable-next-line:nx-enforce-module-boundaries
import { SharedModule } from './../../../../libs/shared/src/index';

Improvements in that area would be much appreciated, especially adding the way to work with defined paths. Now imagine how lazy loading routes looks like with the absolute paths workaround. It was opened 1.5 years ago, and it really should be a priority.

Removing the baseUrl from the tsconfig.server.json eventually fixed it for me...

Hi guys. I'm still looking for a way to properly setup Angular Universal on a per-app basis in a pretty large monorepo.
Is there anything planned to support this out-of-the-box with Nx or are we on our own for the foreseeable future? Thanks for a heads up :)

Hi @derdaani
Actually there is not a straight forward way to generate an univeral app like here described on the angular doc.

I created a repo. Take a look at it.

@vsavkin @FrozenPandaz Would you be interested a new schematic to do the all the plumbing work to make it possible to generate all the things needed for an angular ssr app out of the box?

Plumbing work would be:

  • npm install -SE @nguniversal/{express-engine,module-map-ngfactory-loader} express
  • npm install -DE @types/node tsconfig-paths-webpack-plugin
  • Generate new app,
  • generate new universal with clientProject set to previously generated app,
  • generate server.ts and webpack.server.config.js,
  • Update tsconfig.app.json, tsconfig.server.json to include and exclude server.ts, main.server.ts
  • Update Output paths in tsconfig.app.json, tsconfig.server.json
  • Update Output paths in angular.json for build and server targets
  • Add npm script to package.json

I picked a few of the important things and made them bold.

///server.ts
import "zone.js/dist/zone-node";
import "reflect-metadata";
import { enableProdMode } from "@angular/core";
// Express Engine
import { ngExpressEngine } from "@nguniversal/express-engine";
// Import module map for lazy loading
import { provideModuleMap } from "@nguniversal/module-map-ngfactory-loader";

import * as express from "express";

import { join } from "path";

import * as compression from "compression";

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || 3000;
const DIST_FOLDER = join(process.cwd(), "dist", "apps", "universal-test");

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require("./../../dist/apps/universal-test/server/main");

// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine(
  "html",
  ngExpressEngine({
    bootstrap: AppServerModuleNgFactory,
    providers: [provideModuleMap(LAZY_MODULE_MAP)]
  })
);

app.disable("x-powered-by");
app.use(compression());

app.set("view engine", "html");
app.set("views", join(DIST_FOLDER, "browser"));

// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });

// Server static files from /browser
app.get(
  "*.*",
  express.static(join(DIST_FOLDER, "browser"), {
    maxAge: "1y"
  })
);

// All regular routes use the Universal engine
app.get("*", (req, res) => {
  res.render("index", { req, res });
});

// Start up the Node server
app.listen(PORT, () => {
  console.log(`Node Express server listening on http://localhost:${PORT}`);
});

///webpack.server.config.js
const path = require("path");
const webpack = require("webpack");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");

module.exports = {
  entry: {
    server: "./apps/universal-test/server.ts"
  },
  resolve: {
    extensions: [".ts", ".js", "json"],
    plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })]
  },
  target: "node",
  mode: "none",
  output: {
    path: path.resolve(__dirname, "..", "..", "dist", "apps", "universal-test"),
    filename: "[name].js"
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /\.spec\.ts$/,
        loader: "ts-loader"
      }
    ]
  },
  plugins: [
    new webpack.ContextReplacementPlugin(/(.+)?angular(\\|\/)core(.+)?/, path.join(__dirname, "apps", "universal-test"), {}),
    new webpack.ContextReplacementPlugin(/(.+)?express(\\|\/)(.+)?/, path.join(__dirname, "apps", "universal-test"), {})
  ]
};

///tsconfig.app.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/out-tsc/apps/universal-test",
    "types": []
  },
  "include": ["**/*.ts"],
  "exclude": ["src/test-setup.ts", 
    "**/*.spec.ts",
    "src/main.server.ts",
    "server.ts",]
}
///tsconfig.server.json
{
  "extends": **"./tsconfig.json"**,
  "compilerOptions": {
    "outDir": **"../../dist/out-tsc/apps/universal-test"**,
    "module": "commonjs",
    "types": ["node", "jest"]
  },
  "include": [
    "**/*.ts"
  ],
  "angularCompilerOptions": {
    "entryModule": "./src/app/app.server.module#AppServerModule"
  }
}
///angular.json builder
"build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": **"dist/apps/universal-test/browser",**
///angular.json server
"server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": **"dist/apps/universal-test/server"**,
            "main": "apps/universal-test/src/main.server.ts",
            "tsConfig": "apps/universal-test/tsconfig.server.json"
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "apps/universal-test/src/environments/environment.ts",
                  "with": "apps/universal-test/src/environments/environment.prod.ts"
                }
              ],
              "sourceMap": false,
              "optimization": {
                "scripts": true,
                "styles": true
              }
            }
          }
        }
///package.json
"build:universaltest:ssr": "ng build --configuration=production --project universal-test --stats-json && ng run universal-test:server:production && webpack --config \"./apps/universal-test/webpack.server.config.js\" --progress --colors"

It could ultimately be invoked via ng g @nrwl/angular:app universal-app --universal
What do guys say?

Folks, I'm going to close it.

The issue has been fixed in Angular 8. If you still have problems setting up universal, could you reopen an issue using the latest version of Nx and a repro.

Thank you!

Was this page helpful?
0 / 5 - 0 ratings