Angular6 服务端渲染

栏目: 编程语言 · AngularJS · 发布时间: 5年前

内容简介:服务端渲染即在服务端渲染产生页面之后直接返回到客户端查看第一次请求网页地址的时候,返回已经在服务端渲染好的静态html文件,上面没有点击事件,键盘事件,和交互js,这段页面用一个ID标注,然后开始在客户端渲染页面,渲染好之后,根据ID替换在服务端渲染的页面,填补了main.js(有可能较大)的下载时间+页面渲染事件的空窗期,使页面在slow3G的情况下依然流畅此时 ng run

服务端渲染即在服务端渲染产生页面之后直接返回到客户端查看

第一次请求网页地址的时候,返回已经在服务端渲染好的静态html文件,上面没有点击事件,键盘事件,和交互js,这段页面用一个ID标注,然后开始在客户端渲染页面,渲染好之后,根据ID替换在服务端渲染的页面,填补了main.js(有可能较大)的下载时间+页面渲染事件的空窗期,使页面在slow3G的情况下依然流畅

优势

  1. 帮助网络爬虫(SEO)
  2. 提升在手机和低功耗设备上的性能
  3. 迅速显示出第一个页面

开发流程

安装依赖

$ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader webpack-cli
复制代码

在app.module.ts中添加

@NgModule({
  bootstrap: [AppComponent],
  imports: [
	// 加上下面这句,appId就是上面提到用于替换的唯一标识
    BrowserModule.withServerTransition({appId: 'my-app'}),
    ...
  ],

})
export class AppModule {}
复制代码

同目录下创建app.server.module.ts

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

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

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ModuleMapLoaderModule // 非常重要,用来支持惰性加载的
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}
复制代码

src下创建main.js

export { AppServerModule } from './app/app.server.module';
复制代码

复制ts.app.json为ts.server.json并修改

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    // 重要
    "module": "commonjs",
    "types": []
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ],
  // 指向上面建立的AppServerModule
  "angularCompilerOptions": {
    "entryModule": "app/app.server.module#AppServerModule"
  }
复制代码

在angular.json中修改配置,打包server

"architect": {
  "build": { ... }
  "server": {
    "builder": "@angular-devkit/build-angular:server",
    "options": {
      "outputPath": "dist/my-project-server",
      "main": "src/main.server.ts",
      "tsConfig": "src/tsconfig.server.json"
    }
  }
}
复制代码

此时 ng run projectName :server应该可以得到下面结果

$ ng run my-project:server

Date: 2017-07-24T22:42:09.739Z
Hash: 9cac7d8e9434007fd8da
Time: 4933ms
chunk {0} main.js (main) 9.49 kB [entry] [rendered]
chunk {1} styles.css (styles) 0 bytes [entry] [rendered]
复制代码

注意!坑1:在服务器渲染的时候路径和编译的时候不同,如果在这部报错找不到'src/app/.....'的时候,是你使用了src/的绝对路径,需要全部改为../../的相对位置

设置服务器环境

在根目录下,新建server.ts,并往里面写入

// 这些必须在最前面引入
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';

//坑3:报错document not defined,通过引入domino来解决
const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync('./dist/browser/index.html').toString();
const win = domino.createWindow(template);
const files = fs.readdirSync(`${process.cwd()}/dist/server`);
global['navigator'] = win.navigator;
global['window'] = win;
Object.defineProperty(win.document.body.style, 'transform', {
  value: () => {
    return {
      enumerable: true,
      configurable: true
    };
  },
});
global['document'] = win.document;
global['CSS'] = null;

enableProdMode();

const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

// 这里要根据我们自己的目录来,指向的是浏览器端编译的index.html
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();

const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./server/main');


app.engine('html', (_, options, callback) => {
  renderModuleFactory(AppServerModuleNgFactory, {
    document: template,
    url: options.req.url,
	// 依赖注入,这里是我们实现懒加载的一点
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  }).then(html => {
    callback(null, html);
  });
});

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

// 静态文件
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

// angular路由
app.get('*', (req, res) => {
  res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});

// api的话写在中间,可以作为一个mock服务器

// 启动
app.listen(PORT, () => {
  console.log(`Node server listening on http://localhost:${PORT}`);
});
复制代码

打包并在服务器上使用

设置 webpack 配置,以处理 Node Express 的 server.ts 文件,并启动应用服务器。

在应用的根目录下,创建一个 Webpack 配置文件 webpack.server.config.js,它会把 server.ts 及其依赖编译到 dist/server.js 中。

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

// 坑2:用webpack引入后台的nodemodule的时候注意某些server端专用的npm包是要加上commonjs前缀的
var fs = require('fs');
var nodeModules = {};
fs.readdirSync('node_modules')
  .filter(function(x) {
    return ['.bin'].indexOf(x) === -1;
  })
  .forEach(function(mod) {
    if (mod=='redis'||mod=='express'){
      nodeModules[mod] = 'commonjs ' + mod;
    }
  });

module.exports = {
  entry: {  server: './server.ts' },
  resolve: { extensions: ['.js', '.ts'] },
  target: 'node',
  // this makes sure we include node_modules and other 3rd party libraries
  externals: nodeModules,
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      { test: /\.ts$/, loader: 'ts-loader' }
    ]
  },
  plugins: [
    new webpack.ContextReplacementPlugin(
      /(.+)?angular(\\|\/)core(.+)?/,
      path.join(__dirname, 'src'), 
      {} 
    ),
    new webpack.ContextReplacementPlugin(
      /(.+)?express(\\|\/)(.+)?/,
      path.join(__dirname, 'src'),
      {}
    )
  ]
}
复制代码

现在我们使用 node dist/server.js 应该是可以启动服务的,进入localhost:4000就可以访问到工程

脚本

"scripts": {
  "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
  "serve:ssr": "node dist/server.js",
  "build:client-and-server-bundles": "ng build --prod && ng run my-project:server:production",
  "webpack:server": "webpack --config webpack.server.config.js --progress --colors",
  ...
}
复制代码

执行 npm run build:ssr 之后执行 npm run serve:srr 即可

坑3:报错NotYetImplemented 这个其实是因为你引用了Cookie或者什么之类在server上访问不到的模块,这些模块需要你自己在工程里面进行排查和debug,目前没有更好的解决方法

坑4:报错_angular_common_http__WEBPACK_IMPORTED_MODULE_5__.ɵHttpInterceptingHandler is not a constructor 这个是angular/core版本的问题,需要在package.json中升级angular/core就可以解决,参考: stackoverflow.com/questions/5…


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Inside Larry's and Sergey's Brain

Inside Larry's and Sergey's Brain

Richard Brandt / Portfolio / 17 Sep 2009 / USD 24.95

You’ve used their products. You’ve heard about their skyrocketing wealth and “don’t be evil” business motto. But how much do you really know about Google’s founders, Larry Page and Sergey Brin? Inside......一起来看看 《Inside Larry's and Sergey's Brain》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具