Angular reload page when new version is deployed

在类似于angular程序的项目,部署到生产环境后,如果后续还有代码更新的版本升级,代码需要重新压缩,打包,之后在部署到生产环境。

注: 注意这个打包后的文件,类似于js,css等文件的文件名可以采取hash命名,就是每次打包的同一个js文件的文件名都是包含hash字符串的,这样的好处是,可以强制浏览强制从服务端读取最新的更改过的js文件,但是前提是用户的浏览器需要主动刷新。但是对于有些页面或者说单页面的应用来说,各个页面之间的迁移,可以不必刷新即可完成,换句话说浏览器不会主动去apache服务器读新版本的js文件。 换句话说这个hash文件名的打包方式解决浏览器主动刷新就可以取新版本文件的问题。

但是对于浏览器不主动刷新的情况:

假设这样一种情况,用户在使用应用的某一页面,因为前端的代码最后都是html,js,css等,并且已经加载到或者说cache到浏览器里,只要用户的浏览器没有主动刷新,当有新版本的代码放到apache server上时,用户用的js等文件仍然是旧版本的文件内容。

  1. 要解决上面的问题,下面的文章是一种思路,大概的思路是,当应用打包时,用webpack的插件把整个工程的文件内容hash出一个字符串(When any code file changes, that hash will change),以静态文件存储到包的某一个位置。
  2. 之后在angular程序里,写个server可以读取这个文件的内容。之后在某个主组件比如app-component.ts里每隔一段时间去调用这个server返回这个hash串的内容。读取后把这个值在放到这个component的以全局变量里,之后每隔一个一段时间在去调用这个server返回hash的值,如果两次的hash值不同,即为有新版本的代码产生。设置一个需要刷新的标志位。
  3. 这时在主AppRoutingModule里读#2中的需要刷新的标志位,如果标志位的值满足条件,即可取用户当前的url在重新reload网页。location.href = val.url;

这个思路很不错。

其实我理解像这样的跟业务无关的功能,Angular本身完全可以提供一个相应的解决方案,写程序时,只要配置一下,是否需要当服务端有新版本产生时,自动刷新页面取新版代码。这是很实用的功能

April 12, 2017

When developing a Single Page Application we want to deploy frequent changes, and do not want a user stuck at a stale version. We also do not want to interrupt the user if they are in the middle of a process to refresh the entire website. Here is my current solution to solve this problem:
 

System Specs/Configuration

  1. Angular 4.0.1
  2. Webpack 2.3.3
  3. Webpack builds go into "/dist" folder
  4. webpack.config.json is in the "/config" folder

Step 1: Use Webpack to generate a hash.json after every build

My webpack config adds different plugins based on environment. It always adds the following plugin to grab the hash:

plugins.push(
    function() {
        this.plugin("done", function(stats) {
            require("fs").writeFileSync(
                path.join(__dirname, "../dist", "hash.json"),
                JSON.stringify(stats.toJson().hash));
        });
    }
);

/dist/hash.json is my version. When any code file changes, that hash will change. The contents of the file look like this: "9363b1ba4e6a8ec5f47c"
 

Step 2:Write an Angular Service to check current version

We will do a GET to hash.json, and break any caching using current time-stamp as a cache buster.

ServerResponse is a reusable object I use for all http requests so I can handle errors.

common.ts:

export class ErrorItem {
    constructor(public msg: string, public param: string = "") {

    }
}

export class ServerResponse {
    public data: T;
    public errors: ErrorItem[];
    public status: number;
}

version.service.ts:

import { Injectable } from '@angular/core';
import { Http, Headers, Response  } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { ServerResponse } from '../models/common'
@Injectable()

export class VersionService {

    public needsRefresh : boolean = false;

    constructor(private http: Http) {

    }
    getVersion() : Observable> {

        let headers = new Headers();
        headers.append('Content-Type', 'application/json');

        let output = new ServerResponse();

        return this.http
            .get(
                'hash.json?v=' + (new Date()).getTime(),
                { headers }
            )
            .map((res : Response) => {
                output.status = res.status;
                var response = res.json();
                output.data = response;
                output.errors = [];

                return output;
            }).catch(err => {
                output.status = err.status;
                output.data = null;
                return Observable.of(output)
            });

    }
}

Step 3: Periodically check for version update

In my main app component, I call the VersionService and store the latest hash value. If the new value does not match the current value, a refresh is needed. The needsRefresh boolean is stored in the shared singleton service and it can be accessed by any module to get the latest value.

app.component.ts: 

import { Component, OnInit, ViewEncapsulation} from '@angular/core';
import { VersionService } from './shared/services/version.service';

@Component({
    selector: 'app',    
})
export class AppComponent implements OnInit{

    version : string = "";

    constructor(private versionService: VersionService) {

    }

    checkVersion() {
        this.versionService.getVersion().subscribe((result) => {
            if (result.data) {

                if (this.version && this.version != result.data) {
                    this.versionService.needsRefresh = true;
                }
                this.version = result.data;
            }
        });

        setTimeout(() => {
            this.checkVersion();
        }, 15000);
    }

    ngOnInit() {
        this.checkVersion();
    }
}

Step 4: On route change reload the entire page to new route if needsRefresh

I use a a routing module to handle all my routing needs. One of its jobs is to do a full page reload and not just change routes when a new version is deployed. The redirect takes the user to the the page they were trying to go to so the entire process is very smooth for the user. 

app.routing.ts:


import { NgModule } from '@angular/core';
import { SharedGlobalModule} from './shared/modules/shared.global.module'
import { Routes, RouterModule, Router, NavigationEnd, ActivatedRouteSnapshot, NavigationStart } from '@angular/router';
import { NgIdleKeepaliveModule } from '@ng-idle/keepalive';

import { PublicComponent} from './public/public.component'
import { PublicRoutes } from './public/public.routing'
import { PublicAuthGuard } from './public/public.guard'
import { SecureComponent} from './secure/secure.component'
import { SecureRoutes } from './secure/secure.routing'
import { SecureAuthGuard } from './secure/secure.guard'
import { VersionService } from './shared/services/version.service'

const routes: Routes = [
    { path: '', redirectTo: '/public/login', pathMatch: 'full' },
    { path: 'public', component: PublicComponent, children: PublicRoutes, canActivate: [PublicAuthGuard] },
    { path: 'secure', component: SecureComponent, children: SecureRoutes, canActivate: [SecureAuthGuard] },
    { path: 'p/:token', redirectTo: '/public/reset/:token', pathMatch: 'full' },
    { path: '**', redirectTo: '/public/login' },
];

@NgModule({
    imports: [RouterModule.forRoot(routes),SharedGlobalModule, NgIdleKeepaliveModule.forRoot()],
    exports: [RouterModule],
    declarations: [PublicComponent,SecureComponent],
    providers: [SecureAuthGuard,PublicAuthGuard,VersionService]
})
export class AppRoutingModule {
    watchRouteChanges() {
        this.router.events.subscribe((val) => {
            if (val instanceof NavigationStart && this.versionService.needsRefresh === true) {
             location.href = val.url;
            }
        });
    }

    constructor (private router : Router, private versionService: VersionService) {
        this.watchRouteChanges();
    }

}

Notes:

  • I am a little hesitant polling every 15 seconds, but it is a tiny static file, so I am currently OK with it.
  • Some code was omitted from the actual files I use in production, to focus on the topic of this post.

Angular NodeJs Single Page Application Webpack

猜你喜欢

转载自blog.csdn.net/liangxw1/article/details/84374000
今日推荐