项目管理的作业是完成一个图书管理管理系统。这是我第一次写前端界面,就且不说写得好不好看,确实遇到了很多问题,比如说之前的一篇博客Angular跨域访问豆瓣API。在昨天晚上基本上是把所有的功能都完成了,但是在部署的时候,发现有一个文件大小好像不是特别对。

情况简介

我们使用的是 Angular-cli 来新建和管理Angular项目,它会使用webpack将我们的项目进行打包以便部署。

至于部署的方式中,我们使用的是angular-cli-ghpages这个工具。当然我们首先需要设置好github pages上的分支。

angular github pages设置

通过npm 可以进行安装

1
npm i -g angular-cli-ghpages

我们需要将我们的项目进行打包,指定我们给出的域名和子域名:

1
ng build --prod --base-href "http://bibliosoft.ciaran.cn/"

之后使用angular-cli-ghpages提交到github上项目的一个分支里。因为在angular.json中设置的"outputPath": "dist/frontend",所以--dir的参数为dist\frontend\

1
ngh --dir dist\frontend\ --cname bibliosoft.ciaran.cn

然后经队友反应,我们的网页在部署上去之后几乎打不开,这令我非常惊讶。然后通过查看浏览器Dev Tools中的网络情况,我发现有个文件的大小,竟然高达4M,而就是这个文件,花了50+秒的时间来进行加载,给了一种几乎打不开网页的绝望。

在运行了ng build --prod之后,出现了下面这个输出。

1
2
3
4
5
6
7
8
9
10
C:\Users\ciaran\Desktop\Projects\BibliosoftFrontend>ng build --prod --base-href "http://bibliosoft.ciaran.cn/"

Date: 2018-11-05T12:04:53.287Z
Hash: 9a6e25332ddfeb9c55dd
Time: 294347ms
chunk {scripts} scripts.ee7fed27c36eaa5fa8a9.js (scripts) 137 kB [rendered]
chunk {0} runtime.ec2944dd8b20ec099bf3.js (runtime) 1.44 kB [entry] [rendered]
chunk {1} main.9eb5eb35c7698cd3671d.js (main) 4.34 MB [initial] [rendered]
chunk {2} polyfills.11f7482fa4a251158466.js (polyfills) 59.6 kB [initial] [rendered]
chunk {3} styles.dde26916a8c71097a81f.css (styles) 224 kB [initial] [rendered]

可以看到的是,main.js这个文件足有4MB。在之前的十几次部署中,基本都是800k左右的大小,这说明我们需要删减一些东西了。

分析问题

在Angular的 ng build --prod 中,main.js 这个文件是存放程序的代码的文件。这样的增长肯定是我引入了一些新的依赖导致的。在这个期间我们新加了一个绘图的功能尝试使用了plotly,以及为了尝试更好的API而使用了material-angular,当然我确定就是这两个东西让我的main.js膨胀了起来。于是我使用了webpack-bundle-analyzer来对我们webpack打包的文件进行分析。

首先安装它:

1
npm install webpack-bundle-analyzer -g

我们需要在编译的时候添加一个--stats-json的参数,这样webpack在打包的时候会输出一个stats.js文件作为这些文件中文件来源的信息。

1
ng build --prod --base-href "http://bibliosoft.ciaran.cn/" --stats-json

然后我们可以对这个文件使用webpack-bundle-analyzer进行分析,这样我们就能得到一个产物文件夹的内容来源的分析报告。

1
webpack-bundle-analyzer dist\frontend\stats.json

这条命令会在8888端口启动一个分析报告的服务器并直接帮你打开这个网址。这样我们能看到这样的一个页面:

Webpack analyze before

虽然在左上角那里勾选Show content of concatenated modules (inaccurate)能显示更多的信息,但是单看图片就已经能发现:最大的部分来自于plotly.min.js

细看之下 material-angular 的部分占了700k,但是 plotly.min.js 的大小为2.73M。

这确实令人难以容忍。基本上去掉了plotly之后就是原来大小了,但是我又确实不希望整个plotly的部分都砍掉。

减小Plotly

在我们的项目中,使用了 angular-plotly.js 库来使用Plot.ly,据称这是Plot.ly绘图库的官方Angular wrapper,直接提供了一个plotly-plot的组件,然后直接使用即可,这样确实非常舒服,但是很明显对我们来说这太沉重了。

首先分析一下我们能做到什么程度,我们在项目中所画的图只有 一张折线图和几张饼图,查阅文档后发现它们大概都来自于Plot.lybasic chart的部分,那么我们可以只要plotly的basic部分就足够了。

那样我们需要重写一个Plot.ly的组件,不过这不算特别困难。首先是安装plotly.js,这是一个完全必要的部分,即使是 angular-plotly.js 也会依赖于这个plotly.js

1
npm install plotly.js --save

在安装了依赖后,在使用Plotlyjs的组件的ts文件中添加它。

1
import * as Plotly from 'plotly.js/dist/plotly-basic.min.js';

这样就能确保只引入了 plotly-basic 的部分。

在使用的时候,要画图我们需要调用:

1
Plotly.plot(element, data, layout)

其中 element 参数是 ElementRef<any>.nativeElement,对于如何得到这样的参数,我们可以写出一个组件的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {Component, OnInit, ViewChild} from '@angular/core';
import * as Plotly from 'plotly.js/dist/plotly-basic.min.js';

@Component({
selector: 'plotly-test',
template: `<div #chart></div>`,
providers: [],
})
export class PlotlyTestComponent implements OnInit {
@ViewChild('chart') el: ElementRef;

ngOnInit() {
const data = [{
x: [1,2,3],
y: [4,5,6]
}];
const layout = {
title: 'test image'
}
Plotly.plot(el.nativeElemrnt, data, layout);
}
}

当然有点让我觉得苦恼的是datalayout的类型,在使用angular-plotly.js的时候,我可以指定它们的类型,这样能避免一些类型的错误。这种苦恼的情感一直持续到我看到angualr-plotly这段源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export namespace Plotly {
export type Data = any;
export type Layout = any;
export type Config = any;

export interface Figure {
data: Data[];
layout: Partial<Layout>;
frames: Partial<Config>;
}

export interface PlotlyHTMLElement extends HTMLElement {
on(event: string, callback: Function): void;
removeListener(event: string, callback: Function): void;
}
}

居然是any…这样这段直接新建一个Service装下来就有了Plotly类型了。

最后我们将angular-plotly.js彻底从项目中移除,然后重新编译并运行webpack-bundle-analyzer

webpack analyzer after

可以见到,现在Plot.ly的部分已经只有700k了,而整个main.js的大小也减小到了2.26 MB。在通过github pages部署之后会使用gzip进行传输,这个大小会被压缩到500k,此时文件的加载时间已经是7秒左右了。

CDN 加速

但是这个速度依然不是非常顺畅,我们仍可以使用CDN对整个内容再进行一次加速。下面是加速前后的对比,来自于ping.chinaz.com

cdn加速前

cdn加速后

因为知道腾讯云的cdn有新用户的300G的流量赠送,所以就直接使用了腾讯云的服务。首先需要进行实名认证,和输入指定网站的类型和信息,这些略过不谈。

之后就是创建需要加速的域名了。在这里它就是 bibliosoft.ciaran.cn

之后是填写源站的信息,所谓的源站指的就是在没有CDN网络的情况下的源服务器。最好的方式当然是直接给出服务器的ip地址,这样就能避免有产生解析ip地址的消耗。但是我并不知道github pages的静态ip地址,即使是通过ping能获得ip地址,但是其ip地址也并非是静态的。所以还是填写这里的域名就好了,也就是ciaranchen.github.io

在完成了之后,经过一段时间的部署之后,腾讯云会返回一个cdn地址,我们要将它添加到域名的CNAME地址中,这样才是完成了整个CDN的过程。一般来说,腾讯云给出的都是以.cdn.dnsv1.com 结尾的一个地址,比如我所拿到的就是 bibliosoft.ciaran.cn.cdn.dnsv1.com,然后将它添加到域名解析中去:(也是腾讯云的)

cname setting

在腾讯云中我们可以设置境内与境外线路,在国外,访问github pages的话网络速度是很好的,而要是走国内的CDN的话,反而就是很愚蠢了,所以设置两条CNAME记录,一条直接走ciaranchen.github.io的;另一条是走腾讯云CDN的。

还需要等待一段时间让路由生效。在生效后我们就能看到我们的加载时间被极大的缩短了。

load time after CDN

现在我们的加载时间已经低到了一秒以下了,这是一个足以满意的结果。