All Projects → hapinessjs → ng-universal-module

hapinessjs / ng-universal-module

Licence: MIT license
This is a Hapiness Engine for running Angular Apps on the server for server side rendering.

Programming Languages

typescript
32286 projects
Makefile
30231 projects
javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to ng-universal-module

Universal
Seed project for Angular Universal apps featuring Server-Side Rendering (SSR), Webpack, CLI scaffolding, dev/prod modes, AoT compilation, HMR, SCSS compilation, lazy loading, config, cache, i18n, SEO, and TSLint/codelyzer
Stars: ✭ 669 (+3616.67%)
Mutual labels:  universal, server-side-rendering, angular-cli
React Prepare
Prepare you app state for async server-side rendering and more!
Stars: ✭ 100 (+455.56%)
Mutual labels:  universal, server-side-rendering
React Redux Saucepan
A minimal and universal react redux starter project. With hot reloading, linting and server-side rendering
Stars: ✭ 86 (+377.78%)
Mutual labels:  universal, server-side-rendering
Reactql
Universal React+GraphQL starter kit: React 16, Apollo 2, MobX, Emotion, Webpack 4, GraphQL Code Generator, React Router 4, PostCSS, SSR
Stars: ✭ 1,833 (+10083.33%)
Mutual labels:  universal, server-side-rendering
Awesome Nextjs
📔 📚 A curated list of awesome resources : books, videos, articles about using Next.js (A minimalistic framework for universal server-rendered React applications)
Stars: ✭ 6,812 (+37744.44%)
Mutual labels:  universal, server-side-rendering
Universal React Demo
ES6 demo of a simple but scalable React app with react-router, code splitting, server side rendering, and tree shaking.
Stars: ✭ 50 (+177.78%)
Mutual labels:  universal, server-side-rendering
Razzle Material Ui Styled Example
Razzle Material-UI example with Styled Components using Express with compression
Stars: ✭ 117 (+550%)
Mutual labels:  universal, server-side-rendering
Go Starter Kit
[abandoned] Golang isomorphic react/hot reloadable/redux/css-modules/SSR starter kit
Stars: ✭ 2,855 (+15761.11%)
Mutual labels:  universal, server-side-rendering
Beidou
🌌 Isomorphic framework for server-rendered React apps
Stars: ✭ 2,726 (+15044.44%)
Mutual labels:  universal, server-side-rendering
universal
A counterpart to common package to be used with Angular Universal
Stars: ✭ 115 (+538.89%)
Mutual labels:  universal, server-side-rendering
universal-react-relay-starter-kit
A starter kit for React in combination with Relay including a GraphQL server, server side rendering, code splitting, i18n, SEO.
Stars: ✭ 14 (-22.22%)
Mutual labels:  universal, server-side-rendering
Universal Starter
Angular 9 Universal repo with many features
Stars: ✭ 518 (+2777.78%)
Mutual labels:  universal, server-side-rendering
Cra Universal
🌏 Create React App companion for universal app. No eject, auto SSR, zero config, full HMR, and more (inactive project)
Stars: ✭ 419 (+2227.78%)
Mutual labels:  universal, server-side-rendering
Ngx Meta
Dynamic page title & meta tags utility for Angular (w/server-side rendering)
Stars: ✭ 331 (+1738.89%)
Mutual labels:  universal, server-side-rendering
react-ssr-hydration
Example of React Server Side Rendering with Styled Components and Client Side Hydration
Stars: ✭ 15 (-16.67%)
Mutual labels:  universal, server-side-rendering
Sambell
wake me when it's quitting time
Stars: ✭ 101 (+461.11%)
Mutual labels:  universal, server-side-rendering
kwerri-oss
Kwerri OSS: samvloeberghs.be + jsonld + seo service + scully-plugin-minify-html + scully-plugin-disable-angular
Stars: ✭ 60 (+233.33%)
Mutual labels:  universal, angular-cli
Vuesion
Vuesion is a boilerplate that helps product teams build faster than ever with fewer headaches and modern best practices across engineering & design.
Stars: ✭ 2,510 (+13844.44%)
Mutual labels:  universal, server-side-rendering
Universal React
A universal react starter, with routing, meta, title, and data features
Stars: ✭ 247 (+1272.22%)
Mutual labels:  universal, server-side-rendering
fastify-vite
This plugin lets you load a Vite client application and set it up for Server-Side Rendering (SSR) with Fastify.
Stars: ✭ 497 (+2661.11%)
Mutual labels:  universal, server-side-rendering

Hapiness

NG-Universal

This is a Hapiness Engine for running Angular Apps on the server for server side rendering.


Integrating NG-Universal into existing CLI Applications

This story will show you how to set up Universal bundling for an existing @angular/cli.

We support actually @angular @8.1.0 and next so you must upgrade all packages inside your project.

We use yarn as package manager.

Table of contents


Install Dependencies

Install @angular/platform-server into your project. Make sure you use the same version as the other @angular packages in your project.

Install Hapiness modules into your project: @hapiness/core, @hapiness/ng-universal and @hapiness/ng-universal-transfer-http.

You also need :

  • ts-loader and webpack, webpack-cli for your webpack build we'll show later and it's only in devDependencies.
  • @nguniversal/module-map-ngfactory-loader, as it's used to handle lazy-loading in the context of a server-render. (by loading the chunks right away)
$ yarn add --dev ts-loader webpack webpack-cli
$ yarn add @angular/platform-server @nguniversal/module-map-ngfactory-loader @hapiness/core @hapiness/ng-universal @hapiness/ng-universal-transfer-http

Step 1: Prepare your App for Universal rendering

The first thing you need to do is make your AppModule compatible with Universal by adding .withServerTransition() and an application ID to your BrowserModule import.

TransferHttpCacheModule installs a Http interceptor that avoids duplicate HttpClient requests on the client, for requests that were already made when the application was rendered on the server side.

When the module is installed in the application NgModule, it will intercept HttpClient requests on the server and store the response in the TransferState key-value store. This is transferred to the client, which then uses it to respond to the same HttpClient requests on the client.

To use the TransferHttpCacheModule just install it as part of the top-level App module.

src/app/app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { TransferHttpCacheModule } from '@hapiness/ng-universal-transfer-http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    // Add .withServerTransition() to support Universal rendering.
    // The application ID can be any identifier which is unique on
    // the page.
    BrowserModule.withServerTransition({ appId: 'ng-universal-example' }),
    // Add TransferHttpCacheModule to install a Http interceptor
    TransferHttpCacheModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Next, create a module specifically for your application when running on the server. It's recommended to call this module AppServerModule.

This example places it alongside app.module.ts in a file named app.server.module.ts:

src/app/app.server.module.ts:

import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    // The AppServerModule should import your AppModule followed
    // by the ServerModule from @angular/platform-server.
    AppModule,
    ServerModule,
    ModuleMapLoaderModule,
    ServerTransferStateModule
  ],
  // Since the bootstrapped component is not inherited from your
  // imported AppModule, it needs to be repeated here.
  bootstrap: [AppComponent]
})
export class AppServerModule {
}

Then, you must set an event on DOMContentLoaded to be sure TransferState will be passed between server and client.

src/main.ts:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

document.addEventListener('DOMContentLoaded', () => {
  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.log(err));
});

back to top


Step 2: Create a server "main" file and tsconfig to build it

Create a main file for your Universal bundle. This file only needs to export your AppServerModule. It can go in src. This example calls this file main.server.ts:

src/main.server.ts:

import { enableProdMode } from '@angular/core';

import { environment } from './environments/environment';

if (environment.production) {
    enableProdMode();
}

export { AppServerModule } from './app/app.server.module';

export { NgUniversalModule } from '@hapiness/ng-universal';

Copy tsconfig.app.json to tsconfig.server.json and change it to build with a "module" target of "commonjs".

Add a section for "angularCompilerOptions" and set "entryModule" to your AppServerModule, specified as a path to the import with a hash (#) containing the symbol name. In this example, this would be src/app/app.server.module#AppServerModule.

src/tsconfig.server.json:

{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "outDir": "./out-tsc/app",
        "baseUrl": "./",
        "module": "commonjs",
        "types": []
    },
    "include": [
        "src/**/*.ts"
    ],
    "exclude": [
        "test.ts",
        "**/*.spec.ts"
    ],
    "angularCompilerOptions": {
        "entryModule": "src/app/app.server.module#AppServerModule"
    }
}

back to top


Step 3: Create a new target in angular.json

In angular.json locate the architect property inside your project, and add a new server target.

In build target, adapt options.outputPath to dist/browser.

angular.json:

{
  ...
  "architect": {
    "build": {
      "builder": "@angular-devkit/build-angular:browser",
      "options: {
        "outputPath": "dist/browser",
        ...
      },
      ...
    }
    "server": {
        "builder": "@angular-devkit/build-angular:server",
        "options": {
            "outputPath": "dist/server",
            "main": "src/main.server.ts",
            "tsConfig": "tsconfig.server.json"
        },
        "configurations": {
            "production": {
                "fileReplacements": [
                    {
                        "replace": "src/environments/environment.ts",
                        "with": "src/environments/environment.prod.ts"
                    }
                ]
            }
        }
    }
  }
  ...
}

Building the bundle:

With these steps complete, you should be able to build a server bundle for your application:

# This builds the client application in dist/browser/
$ ng build --prod
...
# This builds the server bundle in dist/server/
$ ng run your-project-name:server

# outputs:
Date: 2017-10-21T21:54:49.240Z                                                       
Hash: 3034f2772435757f234a
Time: 3689ms
chunk {0} main.js (main) 9.2 kB [entry] [rendered]
chunk {1} styles.css (styles) 0 bytes [entry] [rendered]

back to top


Step 4: Setting up a Hapiness Application to run our Universal bundles

Now that we have everything set up to -make- the bundles, how we get everything running?

We'll use Hapiness application and @hapiness/ng-universal module.

Below we can see a TypeScript implementation of a -very- simple Hapiness application to fire everything up.

Note:

This is a very bare bones Hapiness application, and is just for demonstrations sake.

In a real production environment, you'd want to make sure you have other authentication and security things setup here as well.

This is just meant just to show the specific things needed that are relevant to Universal itself. The rest is up to you!

At the ROOT level of your project (where package.json / etc are), created a file named: server.ts

server.ts (root project level):

// This is important and needed before anything else
import 'zone.js/dist/zone-node';

import { Hapiness, Module } from '@hapiness/core';
import { HttpServer, HttpServerConfig } from '@hapiness/core/httpserver';
import { join } from 'path';

const BROWSER_FOLDER = join(process.cwd(), 'dist', 'browser');

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

// Create our Hapiness application
@Module({
    version: '1.0.0',
    imports: [
        NgUniversalModule.setConfig({
            bootstrap: AppServerModuleNgFactory,
            lazyModuleMap: LAZY_MODULE_MAP,
            staticContent: {
                indexFile: 'index.html',
                rootPath: BROWSER_FOLDER
            }
        })
    ]
})
class HapinessApplication {
    /**
     * OnStart process
     */
    onStart(): void {
        console.log(`SSR application is running`);
    }

    /**
     * OnError process
     */
    onError(error: Error): void {
        console.error(error);
    }
}


// Boostrap Hapiness application
Hapiness.bootstrap(HapinessApplication, [
    HttpServer.setConfig<HttpServerConfig>({
        host: '0.0.0.0',
        port: 4000
    })
]);

Extra Providers:

Extra Providers can be provided either on engine setup

NgUniversalModule.setConfig({
  bootstrap: AppServerModuleNgFactory,
  lazyModuleMap: LAZY_MODULE_MAP,
  staticContent: {
    indexFile: 'index.html',
    rootPath: BROWSER_FOLDER
  },
  providers: [
      ServerService
  ]
})

Using the Request, Reply and Utils:

The Request, Reply and Utils objects are injected into the app via injection tokens (REQUEST, REPLY and UTILS). You can access them by @Inject

import { Inject, Injectable } from '@angular/core';
import { HttpServerRequest, REQUEST } from '@hapiness/ng-universal';

@Injectable()
export class RequestService {
  constructor(@Inject(REQUEST) private _request: HttpServerRequest) {}
}

If your app runs on the client side too, you will have to provide your own versions of these in the client app.

  • REQUEST token will inject HttpServerRequest the current instance of Fastify Request.
  • REPLY token will inject HttpServerReply current instance provides:
    • header(key: string, value: string): HttpServerReply method to add new header in SSR response
    • redirect(url: string): HttpServerReply method to redirect the response with a 302 to the given URL.
  • UTILS token will inject HttpUtils current instance provides:
    • parseCookie(str: string, options?: any) method which is the same of original cookie library.
    • serializeCookie(name: string, value: string, options?: any) method which is the same of original cookie library.

back to top


Step 5: Setup a webpack config to handle this Node server.ts file and serve your application!

Now that we have our Hapiness application setup, we need to pack it and serve it!

Create a file named webpack.server.config.js at the ROOT of your application.

This file basically takes that server.ts file, and takes it and compiles it and every dependency it has into dist/server.js.

./webpack.server.config.js (root project level):

const path = require('path');
const webpack = require('webpack');

module.exports = {
    mode: 'none',
    entry: { server: './server.ts' },
    target: 'node',
    resolve: {
        extensions: [ '.ts', '.js' ]
    },
    optimization: {
        minimize: false
    },
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name].js',
        libraryTarget: "commonjs"
    },
    module: {
        noParse: /polyfills-.*\.js/,
        rules: [
            { test: /\.ts$/, loader: 'ts-loader' },
            {
                // Mark files inside `@angular/core` as using SystemJS style dynamic imports.
                // Removing this will cause deprecation warnings to appear.
                test: /(\\|\/)@angular(\\|\/)core(\\|\/).+\.js$/,
                parser: { system: true },
            }
        ]
    },
    plugins: [
        // Temporary Fix for issue: https://github.com/angular/angular/issues/11580
        // for "WARNING Critical dependency: the request of a dependency is an expression"
        new webpack.ContextReplacementPlugin(
            /(.+)?angular(\\|\/)core(.+)?/,
            path.join(__dirname, 'src'), // location of your src
            {} // a map of your routes
        ),
        new webpack.ContextReplacementPlugin(
            /(.+)?hapiness(\\|\/)(.+)?/,
            path.join(__dirname, 'src'),
            {}
        )
    ],
    stats: {
        warnings: false
    }
};

You can add this config if you want to use @hapiness/config to have server config in ./config/default.yml instead of static data:

externals: [
    {
        // This is the only module you have to install with npm in your final packaging
        // npm i config
        config: {
            commonjs: 'config',
            root: 'config'
        }
    }
]

And replace the bootstrap in ./server.ts

import { Config } from '@hapiness/config';

// Boostrap Hapiness application
Hapiness.bootstrap(HapinessApplication, [
    HttpServer.setConfig<HttpServerConfig>(Config.get('server'))
]);

Now, you can build your server file:

$ webpack --config webpack.server.config.js --progress --colors

Almost there:

Now let's see what our resulting structure should look like, if we open up our /dist/ folder we should see:

/dist/
  /browser/
  /server/
  server.js

To fire up the application, in your terminal enter

$ node dist/server.js

Now lets create a few handy scripts to help us do all of this in the future.

"scripts": {

  // These will be your common scripts
  "build:dynamic": "yarn run build:client-and-server-bundles && yarn run webpack:server",
  "serve:dynamic": "node dist/server.js",

  // Helpers for the above scripts
  "build:client-and-server-bundles": "ng build --prod && ng run your-project-name:server:production",
  "webpack:server": "webpack --config webpack.server.config.js --progress --colors"
}

In the future when you want to see a Production build of your app with Universal (locally), you can simply run:

$ yarn run build:dynamic && yarn run serve:dynamic

Enjoy!

Once again to see a working version of everything, check out the universal-starter.

back to top


Contributing

To set up your development environment:

  1. clone the repo to your workspace,
  2. in the shell cd to the main folder,
  3. hit npm or yarn install,
  4. run npm or yarn run test.
    • It will lint the code and execute all tests.
    • The test coverage report can be viewed from ./coverage/lcov-report/index.html.

Back to top

Change History

  • v8.1.0 (2019-07-04)
    • Angular v8.1.0+
    • Documentation to allow dynamic import syntax directly to load lazy loaded chunks
  • v8.0.0 (2019-05-31)
    • Angular v8.0.0+
    • Migrate server to Hapiness v2 based on Fastify
    • Code refactoring
    • Adapt tests
    • Documentation
  • v7.0.0 (2018-10-31)
    • Angular v7.0.1+
    • Migrate tests to jest and ts-jest
    • Code refactoring
    • Documentation
  • v6.2.0 (2018-09-24)
    • Angular v6.1.8+
    • Latest packages' versions
    • Install automatically [email protected] to be compatible with all Hapiness extensions
    • Update doc of webpack.server.config.ts to match with latest version of Angular Universal story
    • Documentation
  • v6.1.0 (2018-07-26)
    • Angular v6.1.0+
    • Documentation
  • v6.0.1 (2018-05-25)
    • Angular v6.0.3+
    • RxJS v6.2.0+
    • Documentation
  • v6.0.0 (2018-05-11)
    • Angular v6.0.1+
    • RxJS v6.1.0+
    • Documentation

Back to top

Maintainers

tadaweb
Julien Fauville Sébastien Ritz Nicolas Jessel Mathieu Jeanmougin

Back to top

License

Copyright (c) 2018 Hapiness Licensed under the MIT license.

Back to top

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].