第5章 运维和发布Vue.js项目

一般传统公司特别喜欢一个角色用一个人,如在生产流水线上,一个工人只负责拧螺丝,另一个工人只负责喷漆。很多软件公司也是这样,有人专门负责编写代码,有人专门负责运维。但是这样的弊端是:出了问题,程序员接触不到服务器,不知道问题出在哪里,运维可以接触服务器,却看不懂代码,也没有办法解决问题。所以传统公司往往怕出问题,因为解决起来特别慢。

现在,越来越多的人意识到,让程序员懂得运维知识特别重要。最好的运维就是程序员自己。在2011年,国外就开始流行一个词汇:DevOps(Developer + Operations),也叫作敏捷运维,就是对既懂编程又懂运维的人的称呼。

我们要有追求,做一个会运维的编程高手,做一个DevOps。

5.1 打包和部署

我们平时都是在本地做开发,每次打开的都是http://localhost:8080/#/,而在真实的项目中,需要把项目部署到某个地方,对项目进行打包和编译。

另外,在产品正式上线之前,也要在测试服务器上进行发布,这样才能发现一些平时在localhost上看不到的问题。

5.1.1 打包

直接使用下面的命令就可以把vue项目打包。

$ npm run build

该命令的运行过程如下:

第5章 运维和发布Vue.js项目

可以看到:

(1)整个过程耗时18.658s。

(2)使用的Webpack版本是2.6.1。

(3)对CSS文件进行了优化(优化的比率是52.11%)。

(5)所有的.vue文件都被打包编译成了下面的文件。

$ find ./dist
.
./static
./static/css
./static/css/app.32ddfe6eea5926f8e3c760d764fef3fa.css
./static/css/app.32ddfe6eea5926f8e3c760d764fef3fa.css.map
./static/js
./static/js/vendor.33c767135f1684f458a7.js.map
./static/js/app.d8b9f437c302a7070fe7.js.map
./static/js/manifest.75e2ba037e0bc6934514.js
./static/js/manifest.75e2ba037e0bc6934514.js.map
./static/js/app.d8b9f437c302a7070fe7.js
./static/js/vendor.33c767135f1684f458a7.js
./index.html

其中包括js、css、map和index.html。

我们需要将其放到HTTP服务器上,如nginx、apache。

5.1.2 部署

1. 上传代码到远程服务器

使用scp或ftp的方式,可以把代码上传到服务器。假设服务器是linux:

  • 路径是/opt/app/test_vue;
  • 服务器IP是123.255.255.33;
  • 服务器ssh端口是6666;
  • 服务器用户名是root。
$ scp -P 6666 -r dist [email protected]:/opt/app
index.html                                      100%  528  0.5KB/s  00:00
app.32ddfe6eea5926f8e3c760d764fef3fa.css        100%   74  0.1KB/s  00:00
app.32ddfe6eea5926f8e3c760d764fef3fa.css.map    100%  623  0.6KB/s  00:00
vendor.33c767135f1684f458a7.js.map    100%  927KB 927.3KB/s  00:00
app.d8b9f437c302a7070fe7.js.map       100%   63KB  62.6KB/s  00:00
manifest.75e2ba037e0bc6934514.js      100% 1511     1.5KB/s  00:00
manifest.75e2ba037e0bc6934514.js.map  100%   14KB  14.3KB/s  00:00
app.d8b9f437c302a7070fe7.js           100% 9323     9.1KB/s  00:00
vendor.33c767135f1684f458a7.js        100%  119KB 118.7KB/s  00:00

这样就把本地的dist目录,上传到了远程的/opt/app目录上。

2. 配置远程服务器

(1)登录远程服务器。

$ ssh [email protected] -p 6666
(输入密码,回车)

Welcome to Ubuntu 14.04.4 LTS (GNU/Linux 3.13.0-86-generic x86_64)
[email protected]_server:~#

(2)把刚才上传的文件夹重命名为vue_demo。

# mv /opt/app/dist /opt/app/vue_demo

(3)配置nginx,使域名:vue_demo.siwei.me指向该位置。把下面的代码加入到nginx的配置文件中(/etc/nginx/nginx.conf):

server {
        listen       80;
        server_name  vue_demo.siwei.me;
        client_max_body_size       500m;
        charset utf-8;
        root /opt/app/vue_demo;
}

(4)重启nginx之前测试一下刚才加入的代码是否有问题。

# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

(5)可以看到代码没问题,然后重启nginx:

# nginx -s stop
# nginx
3. 修改域名配置

nginx运行之后,接下来就是配置域名,否则无法访问。我们需要增加一个二级域名:vue_demo.siwei.me。

不同的域名服务商提供的操作界面也不一样,根本思路就是增加A地址。我的域名服务商是dnspod,登录后,可以看到我的域名列表,如图5-1所示的域名为siwei.me的记录管理页面。

第5章 运维和发布Vue.js项目

图5-1 域名为siwei.me的记录管理页面

单击“添加记录”按钮,就可以看到该域名的编辑页面,如图5-2所示的增加vue_demo二级域名的A记录。

第5章 运维和发布Vue.js项目

图5-2 增加vue_demo二级域名的A记录

输入对应的二级域名(vue_demo),选择记录类型为A,记录值为siwei.me的主机IP地址,然后单击“保存”按钮。再回到命令行,输入ping命令。

$ ping vue_demo.siwei.me
PING vue_demo.siwei.me (123.57.235.33) 56(84) bytes of data.
64 bytes from 123.57.235.33: icmp_seq=1 ttl=54 time=5.79 ms
64 bytes from 123.57.235.33: icmp_seq=2 ttl=54 time=6.38 ms
64 bytes from 123.57.235.33: icmp_seq=3 ttl=54 time=9.25 ms

上面的结果说明二级域名vue_demo.siwei.me已经可以正常指向到服务器123.57.235.33(这个就是我们本次demo的后端服务器的IP地址)了。

4. 完成部署

打开浏览器,访问http://vue_demo.siwei.me就可以看到结果了,如图5-3所示。

第5章 运维和发布Vue.js项目

图5-3 项目可以通过vue_demo.siwei.me访问

5.2 解决域名问题与跨域问题

我们在部署之后会发现Vue.js遇到js的经典问题:远程服务器地址不对,或者跨域问题。还是以本书中的“4.6发送http请求”(显示博客的列表)为例。

真正的后台接口是http://siwei.me/interface/blogs/all,如图5-4所示。

第5章 运维和发布Vue.js项目

图5-4 后台接口

5.2.1 域名404问题

(1)使用浏览器打开http://vue_demo.siwei.me/#/blogs页面,提示页面出错,如图5-5所示。

第5章 运维和发布Vue.js项目

图5-5 页面出错

(2)可以看到出错的原因是404,打开http://vue_demo.siwei.me/api/interface/blogs/all页面,如图5-6所示接口地址返回404错误。

第5章 运维和发布Vue.js项目

图5-6 接口地址返回404错误

(3)这个问题是由于源代码中访问/interface/blogs/all接口引起的。

在文件src/components/BlogList.vue中的第41行,定义了远程访问的URL,如图5-7所示。

this.$http.get('/api/interface/blogs/all')...
第5章 运维和发布Vue.js项目

图5-7 代码中访问URL

这是因为在开发时,Vue.js会通过$npm run dev命令来运行“本地开发服务器”。这个服务器中有一个代理,可以把所有的以'/api'开头的请求,如:

localhost:8080/api/interface/blogs/all

转发到:

siwei.me/interface/blogs/all

“本地开发服务器”的配置如下:

proxyTable: {
  '/api': {
    target: 'http://siwei.me',
    changeOrigin: true,
    pathRewrite: {
      '^/api': ''
    }
  }
},

所以,在开发环境下一切正常。但是在生产环境中发起请求时,就不存在“代理服务器”“开发服务器”(dev server)了,因此会出错。

5.2.2 跨域问题

跨域是js的经典问题。比如,有的读者在解决上面的问题时,会问:我们直接把图5-7中41行的:

this.$http.get('/api/interface/blogs/all')

改成:

this.$http.get('http://siwei.me/interface/blogs/all')

不就可以了吗?答案是否定的。

动手试一下我们就会发现,如果vue_demo.siwei.me直接访问siwei.me域名下的资源,就会报错。因为它们是两个不同的域名。

代码如:

this.$http.get('http://siwei.me/api/interface/blogs/all')...

就会得到报错:

XMLHttpRequest cannot load http://siwei.me/api/interface/blogs/all.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://vue_demo.siwei.me' is therefore not allowed access.

如图5-8所示的跨域问题的报错信息。

第5章 运维和发布Vue.js项目

图5-8 跨域问题的报错信息

5.2.3 解决域名问题和跨域问题

其实,上面提到的两个问题的根源都是一个,也就是说,解决办法是相同的。

(1)在代码端,处理方式不变,访问/api+原接口url。(无变化)

this.$http.get('/api/interface/blogs/all')...

(2)在开发时继续保持Vue.js的代理存在。配置代码如下:(无变化)

proxyTable: {
  '/api': {
    target: 'http://siwei.me',
    changeOrigin: true,
    pathRewrite: {
      '^/api': ''
    }
  }
},

(3)在nginx的配置文件中加入代理(详细说明参见代码中的注释,这个是新增的)。

第5章 运维和发布Vue.js项目

也就是说,上面的配置把:

http://vue_demo.siwei.me/api/interface/blogs/all

在服务器端的nginx中做了变换,相当于访问了:

http://siwei.me/interface/blogs/all

重启nginx,就会发现已经生效了。恢复正常的文章列表页如图5-9所示。

第5章 运维和发布Vue.js项目

图5-9 恢复正常的文章列表页

5.3 如何Debug

浏览器环境下的JavaScript,实际上有两个天生的缺陷。

  • 不严谨。不同浏览器的js实现上会略有不同,这个问题在Android、iOS上也是一样的。
  • 不是严格意义上的计算编程语言,有语法漏洞,如"=="。

因此,我们要想驾驭好JS语言,就要知道如何有效的Debug。

5.3.1 时刻留意本地开发服务器

开发时的命令如下:

$ npm run dev

会开启“本地开发服务器”,要时刻留意该服务器的后台输出。有时候我们把代码编写错了,导致Vue.js无法编译,浏览器页面就会一片空白,并且没有任何出错提示。

其实浏览器页面的一片空白,是由于Vue.js的代码无法运行导致的。此时服务器会报错,如图5-10所示的Vue.js本地开发服务器报错。

第5章 运维和发布Vue.js项目

图5-10 Vue.js本地开发服务器报错

上面的错误提示很好理解,提示“编译时出现错误”,并给出了错误的详细位置。

5.3.2 看developer tools提出的日志

无论是chrome、safari还是firefox,以及IE 7+,都带有developer tools。任何时候页面空白了,都要先看它,而不是问别人:“页面怎么不动了?”

由于JS代码不是特别严谨,因此给出的错误提示也都很概括。我们可以做个对比:

  • JSP错误可以精确到某行。
  • PHP错误可以精确到某行。
  • Rails错误可以精确到某行。
  • Vue.js、Angular、Titanium等JS框架错误可以精确到某个文件。

这是由于所有的JS框架的表现层都是“框架怪胎”,是一种与js语言环境妥协的代码,出了问题很难定位到最底层的根源。而JSP、PHP、Rails ERB则是“正常框架”,出了问题可以直接找到最底层。

因此我们要有一定的经验Debug,来理解错误日志。如图5-11所示的Vue.js框架报错信息。

第5章 运维和发布Vue.js项目

图5-11 Vue.js框架报错信息

vue.esm.js?65d7:434 [Vue warn]: Property or method "博客详情页" is not defined
on
the instance but referenced during render. Make sure to declare reactive data
properties in the data option.
found in
---> <Blog> at /workspace/test_vue_0613/src/components/Blog.vue
       <App> at /workspace/test_vue_0613/src/App.vue
         <Root>
  • vue.esm.js?65d7:434表示错误的来源。这个文件一般人不知道来自哪里,我们暂且认为它来自临时产生的文件或虚拟js机。
  • Property or method "博客详情页" is not defined ...这句话提示了错误的原因。
  • found in ... <Blog> at ...这里则是调用栈,可以看出文件是从下调用到最上面的,问题出在最上面的文件,但是并没有给出错误的行数。

5.3.3 查看页面给出的错误提示

页面给出的错误提示如图5-12所示。

第5章 运维和发布Vue.js项目

图5-12 本地开发服务器的报错信息

  Error compiling template:

<div class='logo'> <img src='http://siweitech.b0.upaiyun.com//image/570/siwei.me_header.png'/> </div> <template> <script> </script> - Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead. - Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as <script>, as they will not be parsed. - tag <template> has no matching end tag.

这里Error compiling template:给出了提示,错误是在模板被编译时产生的。

下面给出的HTML代码是出错的点。

@ ./src/components/Logo.vue 6:2-177
@ ./~/babel-loader/lib!./~/vue-
loader/lib/selector.js?type=script&index=0!./src/components/BlogList.vue
@ ./src/components/BlogList.vue
@ ./src/router/index.js
@ ./src/main.js
@ multi ./build/dev-client ./src/main.js

这里是调用栈,可以看到@./src/components/Logo.vue 6:2-177,错误在Logo.vue,第6行第2列。

5.4 基本命令

接下来要讲解的命令都是vue-cli提供的,可以认为是Webpack+Vue.js的结合。

5.4.1 建立新项目

$ vue init webpack my_blog

vue init命令会创建一个文件夹。具体的说明可参见3.6 Webpack下的Vue.js项目文件结构。

5.4.2 安装所有的第三方包

$ npm install

该命令是根据"package.json"文件中定义的内容来安装所有用到的第三方js库。所有的文件都会安装到"node_module"目录下。

还可以输入--verbose命令查看运行细节。

$ npm install --verbose

运行结果如下:

$ npm install --verbose
npm info it worked if it ends with ok
npm verb cli [ 'D:\\nodejs\\node.exe',
npm verb cli   'D:\\nodejs\\node_modules\\npm\\bin\\npm-cli.js',
npm verb cli   'install',
npm verb cli   '--verbose' ]
npm info using [email protected]
npm info using [email protected]
npm verb npm-session d1e752145cbb60ba
npm info lifecycle [email protected]~preinstall: [email protected]
npm timing stage:loadCurrentTree Completed in 1609ms
npm timing stage:loadIdealTree:cloneCurrentTree Completed in 12ms
npm timing stage:loadIdealTree:loadShrinkwrap Completed in 681ms
...

这样,出现问题时就很容易知道“卡”在哪里了。

5.4.3 在本地运行

使用下列命令:

$ npm run dev

默认会在localhost的8080端口启动一个小型的Web服务器,性能可以完全满足开发使用,还具备代理转发等功能。

源代码发生改变时,服务器也会自动重启(偶尔需要手动重启)。

代码如下所示:

[email protected] MINGW64 /d/workspace/vue_js_lesson_demo (master)
$ npm run dev
> [email protected] dev D:\workspace\vue_js_lesson_demo
> node build/dev-server.js
[HPM] Proxy created: /api  ->  http://siwei.me
[HPM] Proxy rewrite rule created: "^/api" ~> ""
> Starting dev server...
DONE  Compiled successfully in 2373ms10:55:18

> Listening at http://localhost:8080
WAIT Compiling...11:02:14
DONE Compiled successfully in 213ms11:02:14
WAIT Compiling...11:06:09
DONE Compiled successfully in 117ms11:06:09
WAIT Compiling...11:06:26
DONE Compiled successfully in 103ms11:06:26 ...

5.4.4 打包编译

$ npm run build

该命令用于把src目录下的所有文件打包成Webpack的标准文件,供部署使用。具体内容可参见5.1打包和部署。

相关文章: