Noah's Blog

Flask 和 Vuejs 的同步开发笔记

2018-01-21

前言

最近在完成一个私有项目,为了缩减项目部署的规模,想着用 Flask 开发后端 API,用 Vue.js 开发 SPA 前端应用,并把它们合并成一个项目开发和部署。

然而,Google 到了一些前辈的帖子,一种是在相对传统的模板开发中使用 Vue 作为渲染库,不符合实际的需求,前后端的耦合度会过高,另一种就是本文将探讨的,整合前端和后端两个项目的方法。

然又而,前端圈子的迅猛升级使得找到的几篇帖子都失去了意义,在使用了最新的 webpack 版本脚手架搭出项目后各种失败…最后还是填坑成功,现将方案整理如下。

正文

注意:以下均是以Vue脚手架模板为“vue init webpack ”为例

首先介绍一下我的项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.
├── api
│ ├── resources 资源文件夹
│ ├── __init__.py 后端包入口
│ └── routes.py API 路由模块
├── web
│ ├── build 构建脚本
│ ├── config 构建配置
│ ├── dist 发布目录
│ ├── src 前端源码
│ ├── static 静态资源
│ ├── test 测试源码
│ ├── node_modules
│ ├── package.json
│ ├── README.md
│ ├── index.html 前端入口
│ └── yarn.lock
├── venv 后端环境配置
├── requirements.txt
├── config.py 后端配置
├── manage.py 总入口
└── README.md

First Blood

首先,我们需要让后端开启的服务器 http://localhost:5000 上能够渲染出前端的入口页面 index.html

这一页面存在于 ./web/dist/(生产环境)和 ./web/(开发环境)。

它所需的静态资源则分别位于 ./web/dist/static/(生产环境)和 ./web/static/(开发环境)。

那么我们可以在后端的入口(创建 app)处,修改如下:

我的入口在 ./api/__init__.py

1
2
3
4
5
6
def create_app(config_name):
app = Flask(__name__,
static_folder = config[config_name].TEMPLATE_DIR + "/static",
template_folder = config[config_name].TEMPLATE_DIR) # Initialize app

return app

其中的 config[config_name].TEMPLATE_DIR 是根据环境判断,该变量值开发环境下为 ./web/,生产环境下为 ./web/dist/

完成这一步后,我们来规划一下路由:

在app创建后的位置(我这里是 ./manage.py

1
2
3
4
5
app = create_app(os.getenv('FLASK_CONFIG') or 'default')

@app.route('/')
def index():
return render_template("index.html")

现在我们在 ./web 目录下运行 npm run build 进行编译后,应该就可以在生产环境下启动后端(我的项目中就是运行 FLASK_CONFIG=production python3 manage.py),之后就可以在 localhost:5000 下看到自己的前端应用了

Double kill

完成上面的操作后,通过 构建->运行 的方式已经可以通过后端服务访问我们的前端应用了。

不过这种方式并不完美,我们不可能在开发时每次修改都编译,那不就丧失了js大法的本意咯。

所以我们继续,现在在开发环境下打开 localhost:5000 会发现是空白的,原因倒是很简单,因为压根就没引入任何脚本(笑

在运行 npm run dev 后,webpack 会加载 index.html,在内存中动态将脚本地址插入到 HTML 中,使用 node 完成的开发服务器提供给浏览器。

而我们现在只是渲染这个空的 HTML 文件,当然是空白页啦~那脚本在哪呢,当然在 webpack 的开发服务器上了,地址为 http://localhost:8080/app.js

所以我们在 ./web/index.html 中的body标签末尾添加:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
...
<div id="app"></div>
<!-- built files will be auto injected -->
<!-- only for development -->
<script type="text/javascript" src="{{ local_develop_script }}"></script>
</body>
</html>

并将 ./manage.py 中的路由部分修改为:

我的脚本中还加入了适配 vue-router 的history 模式有关配置

1
2
3
4
5
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):
return render_template("index.html",
local_develop_script = 'http://localhost:8080/app.js' if not app.config.get('PRODUCTION') else '')

现在,我们在 ./web 目录下运行 npm run dev,在 ./ 目录下运行 python3 ./manage.py

开发模式运行 Finished

Triple kill

在实际开发中,我们还会用到模块热替换特性来加速开发,而目前网上的实现方案多是使用和 app.js 一样的的方式映射 __webpack_hmr

但是实测中发现,使用最新脚手架引入的 webpack 已经不再采用这种方式进行热加载,而是异步的在修改文件后加载 [id].hot-update.json[id].hot-update.js

而在 localhost:5000 我们的后端服务器上并没有提供这两个文件,所以当然不能成功完成热更新。

所以,我们在后端入口 ./manage.py 中的路由后面加入:

1
2
3
4
5
6
7
@app.route('/<string:id>.hot-update.<string:suffix>')
def webpack_hot_update(id, suffix):
return redirect('http://localhost:8080/' + id + '.hot-update.' + suffix)

@app.route('/__webpack_hmr')
def webpack_hmr():
return redirect('http://localhost:8080/__webpack_hmr')

不过由此会产生一些跨域问题,所以我们还需要修改 ./web/build/webpack.dev.conf.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
const devWebpackConfig = merge(baseWebpackConfig, {
...
devServer: {
...
watchOptions: {
aggregateTimeout: 300,
poll: config.dev.poll,
},
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization'
}
},
...
})

后记

至此,我们已经可以在使用 npm run dev 运行开发服务器的情况下,使用后端建立的 localhost:5000 这一服务访问我们的前端页面,并且能在文件改动后完成热更新。

在需要部署时,只需要简单的使用 npm run build,后端配置为生产模式即可。

感谢

在本文编写过程中,参考了以下文章:

最后感谢 Flask、Vue.js、Webpack 的开发者和社区做出的巨大贡献。