Flask 和 Vue.js 开发及整合部署实例
想做些简单的 Web 工具,首先想到的是 Flask + Vue.js, 当然可以完全用 Flask 自己的页面模板 Jinja2, 但一个网站项目不能享受到像 Vue.js, React 类似框架的灵活性真是太可惜了。于是 Flask 只专注于 API, 页面逻辑全用 Vue.js 的组合就成了我的首选,Flask 方面还能进一步选择 FlaskRESTful 框架。还需做得更漂亮的话,CSS 框架可选择 Bootstrap 或与 Vue 紧密集成的 BootstrapVue, 这是后话。
本文主要参考 Flask和Vue.js构建全栈单页面web应用【通过Flask开发RESTful API】的前部分,英文原文在这里 Developing a Single Page App with Flask and Vue.js。
开发过程中我们可以保持 Flask 和 Vue.js 为单独的两个项目,并启动各自的服务,比如 Flask 是 http://localhost:5000, Vue.js 项目通过
但部署时需进一步整合,最终只需要启动 Flask 服务,而无须两个,方便部署。如果是以 Docker 容器的方式发布,使用 docker-compose 来编排两个容器来发布也还算不错。更专业的部署方式应该是 Vue.js 的静态内容放到专门的 Web 服务器,如 Apache/Nginx 中,Flask 也通过 wsgi 与 Web 服务器集成起来。
介于原文中所用的 Vue CLI 稍稍显老,所以实践中也有些区别,先注明本文写作时所依赖的各主要组件版本
现在在 flask-vue-app 下创建一个 backend 目录,并在其中创建文件 app.py, 文件目录结构是
app.py 的内容为
简单说明一下上面的代码
运行 Flask
正式创建项目 frontend,在
启动 Vue 服务
下面是如何在 Vue.js(8080) 中调用到 Flask(5000) 的
创建
编辑
对
浏览器中访问 http://localhost:8080/ping, "Hello!" 显示的还是
现在开始将 Ping.vue 与 Flask 的
编辑
高亮行为新加的代码, 保存后 http://localhost:8080/ping 窗口中的内容自动刷新为
访问 http://localhost:8080/ping_xyz 指向了同一个 Vue 组件,所以效果上与 http://localhost:8080/ping 是一样的。
首先用 npm 对 fronend 中的静态内容打包
dist 是由 npm run build 生成的
这时候只要在 backend 中创建一个符号链接


Flask + Vue 对 http://localhost:5000/ping 和 http://localhost:5000/ping_xyz 的处理过程是
最后,Flask 与 Vue.js 这样整合后,Vue.js 路由中访问 Flask API 要与 Flask 实际启动的 IP 端口保持一致,因为只有一个服务也就不存在跨域访问的问题,允许跨域相关的 Python 代码也就可以移除掉了。
本文演示的是一个 Vue.js 多页面程序,如果是单页面程序(用 /#/abc) 导引的,在 Flask 中处理起来还稍微简单些,只要 "/" 请求交给 Vue.js 的入口
这时候打开
浏览时看到原来的
接下来将在 Vue.js 中试验 Bootstrap 和 BootstrapVue 的集成。
本实例代码已推送到了 github, 仓库地址为 https://github.com/yabqiu/flask-vue-app.git,姓没变,欢迎检阅
相关链接:
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
本文主要参考 Flask和Vue.js构建全栈单页面web应用【通过Flask开发RESTful API】的前部分,英文原文在这里 Developing a Single Page App with Flask and Vue.js。
开发过程中我们可以保持 Flask 和 Vue.js 为单独的两个项目,并启动各自的服务,比如 Flask 是 http://localhost:5000, Vue.js 项目通过
npm run serve 启动在 http://localhost:8080,借助于 node js 的功能,修改 Vue.js 项目的内容能够自动刷新网页。要是开发中把静态文件全放在 Flask 项目中,那么任何对静态文件的修改都必须重启 Flask 服务。虽然 Debug 模式启动的 Flask 在看到它的目录中有任何修改时也能自动重启,但对静态文件的修改重启 Flask 没这个必要性。 但部署时需进一步整合,最终只需要启动 Flask 服务,而无须两个,方便部署。如果是以 Docker 容器的方式发布,使用 docker-compose 来编排两个容器来发布也还算不错。更专业的部署方式应该是 Vue.js 的静态内容放到专门的 Web 服务器,如 Apache/Nginx 中,Flask 也通过 wsgi 与 Web 服务器集成起来。
介于原文中所用的 Vue CLI 稍稍显老,所以实践中也有些区别,先注明本文写作时所依赖的各主要组件版本
- Vue v2.6.11
- Vue CLI v4.6.6
- Node v14.4.0
- npm v6.14.4
- Flask v1.1.2
- Python v3.7
创建 Flask 项目
创建项目目录$ mkdir flask-vue-app接下来创建 Python 虚拟环境
$ cd flask-vue-app
$ python3.7 -m venv .venv安装 Flask 和 Flask-CORS 扩展,前面说过,由于开发中启动了两个服务,需要跨域访问服务,所以要用到 Flask-CORS
$ source .venv/bin/activate
(.venv) $ pip install flask-corsFlask 本身会被自动安装,当前日期为 2020-06-30, 所安装的 flask-cors 版本为 3.0.8, Fask 为 1.1.2。也可以锁定版本来安装扩展,如 pip install flask-cors==3.0.8。现在查看下所有的第三方依赖
$ pip freeze有需要的话,保存为 requirements.txt 放到版本服务器上
click==7.1.2
Flask==1.1.2
Flask-Cors==3.0.8
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
six==1.15.0
Werkzeug==1.0.1
现在在 flask-vue-app 下创建一个 backend 目录,并在其中创建文件 app.py, 文件目录结构是
1flask-vue-app
2└── backend
3 └── app.pyapp.py 的内容为
1from flask import Flask, jsonify
2from flask_cors import CORS
3
4DEBUG = True
5
6app = Flask(__name__)
7app.config.from_object(__name__)
8
9CORS(app, resources={r'/*': {'origins': '*'}})
10
11
12@app.route('/api/ping', methods=['GET'])
13def ping_pong():
14 return jsonify('pong!')
15
16
17@app.route('/')
18def index():
19 return app.send_static_file('index.html')
20
21
22@app.route('/<path:fallback>')
23def fallback(fallback): # Vue Router 的 mode 为 'hash' 时可移除该方法
24 if fallback.startswith('css/') or fallback.startswith('js/')\
25 or fallback.startswith('img/') or fallback == 'favicon.ico':
26 return app.send_static_file(fallback)
27 else:
28 return app.send_static_file('index.html')
29
30
31if __name__ == '__main__':
32 app.run()简单说明一下上面的代码
- CORS(app, resources={r'/*': {'origins': '*'}}) 允许来自于 Vue 的跨域访问请求
- 定义以
/api/* 开头的 Flask 的路由,由 Flask 来处理 /请求直接发送一个静态文件/index.html,由于不会用到 Flask 的模板系统,所以也就无需调用render_template()方法去渲染。- 后面会将到在
backend目录中会建立一个到 Vue.js 项目打包后的 dist 目录的符号链接static, 所以其中有index.html等 - @app.rout('/<path:fallback>') 里是个关键,凡是 Flask 未定义的路由都会落到这里来。如果访问的是
static(dis)中的 css, js, img 或 favicon.ico 文件,直接送出内容,其他的请求转到 Vue 的入口index.html, 最后将由 Vue 中定义的路由来处理 - 如果 Vue 的 Router 工作在 hash 模式的话,fallback 方法可以不要,因为
/#/home到/#/about的切换本身不产生 HTTP 请求,Flask 只需要/一个路由进入 Vue 入口页面
运行 Flask
(.venv) $ python backend/app.pyFlask 会在 localhost:5000 中启动服务,用 curl 命令验证
$ curl http://localhost:5000/api/ping
"pong!"
创建 Vue 项目
开始转到 Vue 项目来,将使用 Vue CLI 工具来生成它,首先是安装 Vue CLI$ npm install -g @vue/cli当前日期 2020-06-30, 安装后用 vue --version 看到的版本是 @vue/cli 4.4.6。安装时欲锁定版本用命令 npm install -g @vue/cli@4.4.6
正式创建项目 frontend,在
flask-vue-app 目录下运行$ vue create frontend # 选择 Manually select features, 接下回答几个问题最后 flask-vue-app 的目录结构为
1flask-vue-app
2├── backend
3│ └── app.py
4└── frontend
5 ├── README.md
6 ├── babel.config.js
7 ├── package-lock.json
8 ├── package.json
9 ├── public
10 │ ├── favicon.ico
11 │ └── index.html
12 └── src
13 ├── App.vue
14 ├── assets
15 │ └── logo.png
16 ├── components
17 │ └── HelloWorld.vue
18 ├── main.js
19 ├── router
20 │ └── index.js
21 └── views
22 ├── About.vue
23 └── Home.vue启动 Vue 服务
$ cd frontend打开浏览器访问 http://localhost:8080 会有一个 "Wellcome to Your Vue.js App" 的界面。后面对 frontend 项目的修改会自动刷新网页。
$ npm run serve
下面是如何在 Vue.js(8080) 中调用到 Flask(5000) 的
/api/ping 服务,当前在 frontend 目录中创建
src/components/Ping.vue 文件,内容为 1<template>
2 <div>
3 <p>{{ msg }}</p>
4 </div>
5</template><br/><br/>
6<script>
7export default {
8 name: 'Ping',
9 data() {
10 return {
11 msg: 'Hello!',
12 };
13 },
14};
15</script>编辑
src/router/index.js 文件,高亮行为新加的内容 1import Vue from 'vue';
2import VueRouter from 'vue-router';
3import Home from '../views/Home.vue';
4import Ping from '../components/Ping.vue';
5
6Vue.use(VueRouter);
7
8const routes = [
9 {
10 path: '/',
11 name: 'Home',
12 component: Home,
13 },
14 {
15 path: '/about',
16 name: 'About',
17 // route level code-splitting
18 // this generates a separate chunk (about.[hash].js) for this route
19 // which is lazy-loaded when the route is visited.
20 component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
21 },
22 {
23 path: '/ping', # 用来调用 Flask 的 "/api/ping" API
24 name: 'Ping',
25 component: Ping,
26 },
27 {
28 path: '/ping_xyz', # 这个用来测试,非 Flask 中定义的路由,可被 Vue 进行处理
29 name: 'Ping',
30 component: Ping,
31 },
32];
33
34const router = new VueRouter({
35 mode: 'history',
36 base: process.env.BASE_URL,
37 routes,
38});
39
40export default router;对
src/App.vue 的 <template> 中的导航部分删除,内容变为1<template>
2 <div id="app">
3 <router-view/>
4 </div>
5</template>浏览器中访问 http://localhost:8080/ping, "Hello!" 显示的还是
src/components/Ping.vue 中 data 的内容
现在开始将 Ping.vue 与 Flask 的 /api/ping API 进行连接,Vue 中要用 Ajax 来访问,先要安装 axios,命令如下$ npm install axios --save目前安装的是 axios@0.19.2, 安装后可在
package.json 里看到 dependencies 中的 "axios": "^0.19.2"编辑
src/components/Ping.vue 文件,修改为 1<script>
2import axios from 'axios';
3
4export default {
5 name: 'Ping',
6 data() {
7 return {
8 msg: '',
9 };
10 },
11 methods: {
12 getMessage() {
13 const path = 'http://localhost:5000/ping';
14 axios.get(path)
15 .then((res) => {
16 this.msg = res.data;
17 })
18 .catch((error) => {
19 // eslint-disable-next-line
20 console.error(error);
21 });
22 },
23 },
24 created() {
25 this.getMessage();
26 },
27};
28</script>高亮行为新加的代码, 保存后 http://localhost:8080/ping 窗口中的内容自动刷新为
pong! 消息是来自于 Flask 的 /api/ping API 的响应。由于我们前面是以 Debug 模式启动的 Flask backend 应用, 所以在控制台也能够看到一个对 /api/ping 的请求127.0.0.1 - - [01/Jul/2020 02:53:06] "GET /api/ping HTTP/1.1" 200 -
访问 http://localhost:8080/ping_xyz 指向了同一个 Vue 组件,所以效果上与 http://localhost:8080/ping 是一样的。
Flask 与 Vue.js 整合
开发的时候启动两个服务很方面,但我们希望在部署后只启动一个 Flask 服务,那么可以这样做首先用 npm 对 fronend 中的静态内容打包
$ npm run build将会在 frontend 下生成 dist 目录,其下内容为
css favicon.ico img index.html js绿色为目录, 文件目录树层次是
1flask-vue-app
2├── backend
3│ └── app.py
4├── frontend
5│ ├── README.md
6│ ├── babel.config.js
7│ ├── dist
8│ │ ├── css
9│ │ │ └── app.ee9fa358.css
10│ │ ├── favicon.ico
11│ │ ├── img
12│ │ │ └── logo.82b9c7a5.png
13│ │ ├── index.html
14│ │ └── js
15│ │ ├── about.838e43ea.js
16│ │ ├── about.838e43ea.js.map
17│ │ ├── app.38688cdc.js
18│ │ ├── app.38688cdc.js.map
19│ │ ├── chunk-vendors.6ddee4a6.js
20│ │ └── chunk-vendors.6ddee4a6.js.map
21│ ├── package-lock.json
22│ ├── package.json
23│ ├── public
24│ │ ├── favicon.ico
25│ │ └── index.html
26│ └── src
27│ ├── App.vue
28│ ├── assets
29│ │ └── logo.png
30│ ├── components
31│ │ ├── HelloWorld.vue
32│ │ └── Ping.vue
33│ ├── main.js
34│ ├── router
35│ │ └── index.js
36│ └── views
37│ ├── About.vue
38│ └── Home.vue
39└── requirements.txtdist 是由 npm run build 生成的
这时候只要在 backend 中创建一个符号链接
$ ln -s ../front/dist static创建后在 backend 目录中的内容为
-rw-r--r-- 1 yanbin root 690 Jul 1 01:01 app.py因为 Flask 是以 Debug 模式启动的,对 Flask 项目 backend 的改动也可能会触发 Flask 的重新启动,需要的话手动重启 Flask (CTRL+C 退出再重启)
lrwxr-xr-x 1 yanbin root 16 Jun 30 22:33 static -> ../frontend/dist
$ python backend/app.py现在 Vue.js 那个服务可以停止了,不管是 Flask 还是 Vue.js 的路由都能够通过 http://localhost:5000 来访问了

http://localhost:5000/ping

http://localhost:5000/ping_xyz
Flask + Vue 对 http://localhost:5000/ping 和 http://localhost:5000/ping_xyz 的处理过程是
- 对 localhost:5000 的请求发往 Flask, Flask 的
@app.route('/<path:fallback>')进行处理 - 不是 css/js/img 和 favicon.iso 的请求,交由 Vue.js 的入口
index.html处理 - Vue.js 在自己的路由表中找到了
/ping和/ping_xyz, 进它们进行渲染 - 如随意一个 http://localhost:5000/abc,也会转给 Vue.js 的入口
index.html,但 Vue.js 未定/abc路由,页面得不到渲染,一面空白
最后,Flask 与 Vue.js 这样整合后,Vue.js 路由中访问 Flask API 要与 Flask 实际启动的 IP 端口保持一致,因为只有一个服务也就不存在跨域访问的问题,允许跨域相关的 Python 代码也就可以移除掉了。
本文演示的是一个 Vue.js 多页面程序,如果是单页面程序(用 /#/abc) 导引的,在 Flask 中处理起来还稍微简单些,只要 "/" 请求交给 Vue.js 的入口
index.html, 其他全当是静态文件,Flask 的 API 还是最好约定为 /api/* 的形式。VueRouter 的 history 和 hash 模式
如果 VueRouter 使用 hash 模式,在服务端可以更简单的些,前面说过在app.py 中的 fallback() 方法可以不需要了。Vue 默认的模式是 hash, 只是用 vue 命令生成的项目设置成了 history 模式,重新启用 hash 模式的方法是修改 src/router/index.js 文件中,把 mode 值改为 hash 或去掉 mode 行1const router = new VueRouter({
2 // mode: 'history', 或改为 mode: 'hash', 默认为 'hash'
3 base: process.env.BASE_URL,
4 routes,
5});这时候打开
http://localhost:8080 会自动跳转到 http://localhost:8080/#/, 其他的路由也加上了 #, 如 /#/ping
浏览时看到原来的 localhost:8080/ping 变成了 localhost:8080/#/ping, 使用 hash 的好处是每次 Vue 的路由跳转其时是一个锚点链接(anchor),它相当于当前页的位置跳转,不会重新刷新整个页面,且本身不会产生与服务端的 HTTP 请求,所以可减少许多的因 Vue 跳转而产生的交互,虽然前也简单的跳转回 Vue 的入口文件 index.html,但怎么着也是省了不少来回。接下来将在 Vue.js 中试验 Bootstrap 和 BootstrapVue 的集成。
本实例代码已推送到了 github, 仓库地址为 https://github.com/yabqiu/flask-vue-app.git,姓没变,欢迎检阅
相关链接:
- Flask和Vue.js构建全栈单页面web应用【通过Flask开发RESTful API】
- Developing a Single Page App with Flask and Vue.js
- Vue SPA and Flask together
- Best practices to deploy a Flask and Vue app?
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。