Server-side processing using Angular4 (Angular Universal)

综合技术 2018-01-01

I am working on an Angular4 webpack project to which I wanted to add AngularUniversal
to make server side rendering possible.But most tutorials are using angular cli.I want to integrate Universal with webpack.I tried following this tutorial
with no luck.Can someone please help.

This Angular Universal
is only for Angular 2. If you want to start from scratch you can use this Angular 4 Universal Seed
which has all features like:

  • Angular 4
  • WebPack
  • dev/prod modes
  • SCSS compilation
  • i18n, SEO, and TSLint/codelyzer
  • lazy loading, config, cache

Or if you already had Angular 4 project running you can Integrate Universal by doing following settings in your code:

Install these packages:

npm install @angular/{common,compiler,compiler-cli,core,forms,http,platform-browser,platform-browser-dynamic,platform-server,router,animations}@latest [email protected] --save

npm install express @types/express --save-dev

Add this in your app.module.ts
file

import { BrowserModule } from '@angular/platform-browser';
BrowserModule.withServerTransition({
  appId: 'my-app-id'   // withServerTransition is available only in Angular 4
}),

Create following files

src/uni/app.server.ts

import { NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { ServerModule } from '@angular/platform-server';
import { AppComponent } from '../app/app';
import { AppModule } from '../app/app.module';
import 'reflect-metadata';
import 'zone.js';
@NgModule({
  imports: [
    ServerModule,
    AppModule
  ],
  bootstrap: [
    AppComponent
  ],
  providers: [
    {provide: APP_BASE_HREF, useValue: '/'}
  ]
})
export class AppServerModule {
}

src/uni/server-uni.ts

import 'zone.js/dist/zone-node';
import 'zone.js';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import { AppServerModuleNgFactory } from  '../../aot/src/uni/app.server.ngfactory';
import * as express from 'express';
import { ngUniversalEngine } from './universal-engine';
enableProdMode();
const server = express();
// set our angular engine as the handler for html files, so it will be used to render them.
server.engine('html', ngUniversalEngine({
    bootstrap: [AppServerModuleNgFactory]
}));
// set default view directory
server.set('views', 'src');
// handle requests for routes in the app.  ngExpressEngine does the rendering.
server.get(['/', '/dashboard', '/heroes', '/detail/:id'], (req:any, res:any) => {
    res.render('index.html', {req});
});
// handle requests for static files
server.get(['/*.js', '/*.css'], (req:any, res:any, next:any) => {
    let fileName: string = req.originalUrl;
    console.log(fileName);
    let root = fileName.startsWith('/node_modules/') ? '.' : 'src';
    res.sendFile(fileName, { root: root }, function (err:any) {
        if (err) {
            next(err);
        }
    });
});
// start the server
server.listen(3200, () => {
    console.log('listening on port 3200...');
});

src/uni/universal-engine.ts

import * as fs from 'fs';
import { renderModuleFactory } from '@angular/platform-server';
const templateCache = {}; // cache for page templates
const outputCache = {};   // cache for rendered pages
export function ngUniversalEngine(setupOptions: any) {
  return function (filePath: string, options: { req: Request }, callback: (err: Error, html: string) => void) {
    let url: string = options.req.url;
    let html: string = outputCache[url];
    if (html) {
      // return already-built page for this url
      console.log('from cache: ' + url);
      callback(null, html);
      return;
    }
    console.log('building: ' + url);
    if (!templateCache[filePath]) {
      let file = fs.readFileSync(filePath);
      templateCache[filePath] = file.toString();
    }
    // render the page via angular platform-server
    let appModuleFactory = setupOptions.bootstrap[0];
    renderModuleFactory(appModuleFactory, {
      document: templateCache[filePath],
      url: url
    }).then(str => {
      outputCache[url] = str;
      callback(null, str);
    });
  };
}

Add below configuration in your tsconfig.ts
file which I assume located in root dir

{
    "compilerOptions": {
        "baseUrl": "",
        "declaration": false,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "lib": ["es2016", "dom"],
        "moduleResolution": "node",
        "outDir": "./dist/out-tsc",
        "sourceMap": true,
        "target": "es5",
        "module": "commonjs",
        "types": ["node"],
        "typeRoots": [
            "node_modules/@types"
        ]
    },
    "files": [
        "src/uni/app.server.ts",
        "src/uni/server-uni.ts"
    ],
    "angularCompilerOptions": {
        "genDir": "aot",
        "entryModule": "./src/app/app.module#AppModule",
        "skipMetadataEmit": true
    },
    "exclude": [
        "test.ts",
        "**/*.spec.ts"
    ]
}

Atlast your webpack.config.uni.js
in root dir

const ngtools = require('@ngtools/webpack');
const webpack = require('webpack');
const path = require('path');
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
module.exports = {
    devtool: 'source-map',
    entry: {
        main: ['./src/uni/app.server.ts', './src/uni/server-uni.ts']
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    target: 'node',
    output: {
        path: path.join(__dirname, "dist"),
        filename: 'server.js'
    },
    plugins: [
        new ngtools.AotPlugin({
            tsConfigPath: './tsconfig.json'
        })
    ],
    module: {
        rules: [
            {
                test: /.(scss|html|png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
                use: 'raw-loader'
            },
            { test: /.ts$/,  loader: require.resolve('@ngtools/webpack') },
            {
                test: /.(png|jpg|woff|woff2|eot|ttf|svg)(?v=[0-9].[0-9].[0-9])?$/,
                loader: 'url?limit=512&&name=[path][name].[ext]?[hash]'
            },
            { test: /.scss$/, use: [{
                loader: "style-loader" // creates style nodes from JS strings
            }, {
                loader: "css-loader" // translates CSS into CommonJS
            }, {
                loader: "sass-loader" // compiles Sass to CSS
            }] }
        ]
    }
}

Add below scripts in you package.json
file:

"ngc-build": "ngc -p ./tsconfig.json", // To generate ngFactory file
"build:uni": "webpack --config webpack.config.uni.js",
"serve:uni": "node dist/server.js",

There are certain things that we should keep in mind:

  • window
    , document
    , navigator
    , and other browser types - do not exist on the server - so using them, or any library that uses them (jQuery for example) will not work. You do have some options given in this link
    if you truly need some of this functionality.
Hello, buddy!

责编内容by:Hello, buddy! (源链)。感谢您的支持!

您可能感兴趣的

Getting Angular and Webpack Working Getting Angular and Webpack Working Together in the Fair Offer Project As part of scoping the ...
Build a new Web Application from scratch using Spr... To configure Bower, we simply have to add two files in the root folder of your project, a . ...
Advanced Angular Application Development Workshop Introduction After the great success of our introductory Angular workshop “The Future is Now wi...
Entrepreneurship Journal, 11/13/2017 In the last few months the following things have happened: I decided to put Angular on ...
Avoiding deeply nested component trees in React By passing child components down instead of data you can avoid passing data down through many lev...