翻译进度
15
分块数量
2
参与人数

使用 Flask 和 Vue.js 来构建全栈单页应用

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。

file

In this tutorial I would like to show you how to connect Vue.js single page application with Flask back-end.

Basically, there is no problem if you want to just use Vue.js library with Flask templates. Well, actually the one obvious problem is that Jinja (template engine) uses double curly braces for rendering stuff as well as Vue.js, but there is a nice workaround explained here.

I wanted a bit different case. What if I need a single page application built with Vue.js (using single page components, vue-router in HTML5 history mode and other good features) and served over Flask web server? In few words this should works as follows:

  • Flask serves my index.html which contains my Vue.js app
  • during front-end development I use Webpack with all the cool features it provides
  • Flask has API endpoints I can access from my SPA
  • I can access API endpoints even while I run Node.js for front-end development

Sounds interesting? Let's do this.

Full source code you can find here:

https://github.com/oleg-agapov/flask-vue-spa

客户端

为了生成基本的Vue.js文件结构,我将使用vue-cli。 如果你没有安装它,请运行下边的命令:

$ npm install -g vue-cli

客户端和后端代码将会被拆分到不同的文件夹中, 请运行下边命令初始化前端部分:

$ mkdir flaskvue
$ cd flaskvue
$ vue init webpack frontend

下边是安装过程中我的设置:

  • Vue build --- Runtime only
  • Install vue-router? --- Yes
  • Use ESLint to lint your code? --- Yes
  • Pick an ESLint preset --- Standard
  • Setup unit tests with Karma + Mocha? --- No
  • Setup e2e tests with Nightwatch? --- No
leamen 翻译于 1周前

下一步:

$ cd frontend
$ npm install
# 安装完成后运行下边命令
$ npm run dev

到这里,你应该安装好Vue.js了吧!那就让我们添加一些页面。

 在frontend/src/components 文件夹中添加Home.vue 和 About.vue两个文件。 并添加如下内容到对应的文件中:

// Home.vue文件的内容
<template>
  <div>
    <p>主页</p>
  </div>
</template>

// About.vue文件的内容
<template>
  <div>
    <p>关于</p>
  </div>
</template>
leamen 翻译于 1周前

We will use them to correctly recognize our current location (according to address bar). Now we need to change frontend/src/router/index.js file in order to render our new components:

import Vue from 'vue'
import Router from 'vue-router'
const routerOptions = [
  { path: '/', component: 'Home' },
  { path: '/about', component: 'About' }
]
const routes = routerOptions.map(route => {
  return {
    ...route,
    component: () => import(`@/components/${route.component}.vue`)
  }
})
Vue.use(Router)
export default new Router({
  routes,
  mode: 'history'
})

If you try to enter localhost:8080 and localhost:8080/about you should see corresponding pages.

file

We are almost ready to build a project in order to create a bundle with static assets. Before that let's redefine output directory for them. In frontend/config/index.js find next settings

index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),

and change them to

index: path.resolve(__dirname, '../../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../../dist'),

So /dist folder with html/css/js bundle will have the same level as /frontend. Now you can run $ npm run build to create a bundle.

gIwSvo62o6.png!large

Back-end

For Flask server I'll use python version 3.6. Inside root /flaskvue folder create new sub-folder for back-end code and initialize virtual environment there:

$ mkdir backend
$ cd backend
$ virtualenv -p python3 venv

To enable virtual environment run (on macOs):

$ source venv/bin/activate

For activation in Windows use this docs.

Under virtual environment install Flask with:

(venv) pip install Flask

Now let's write code for Flask server. Create a file run.py in root directory:

(venv) cd ..
(venv) touch run.py

Add next code to this file:

from flask import Flask, render_template
app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")
@app.route('/')
def index():
    return render_template("index.html")

This code is slightly differs from Flask starter "Hello world" code. The major difference is that we specify static and templates folder to point to /distfolder with our front-end bundle. To run Flask server run in root folder:

(venv) FLASK_APP=run.py FLASK_DEBUG=1 flask run

This will start a web server on localhost:5000FLASK_APP points to server startup file, FLASK_DEBUG=1 will run it in debug mode. If everything is correct you'll see familiar Home page you've done on in Vue.

与此同时,如果你试图添加一个 /about 页面。 Flask 将抛出一个页面未找到的错误。 确实如此,因为我们在 vue-router 中使用了 HTML5 历史模式,我们需要去 配置我们的服务器 让所有路由跳转到 index.html. 这个在 Flask 中很容易做到 。将现有的路由修改为如下内容:

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    return render_template("index.html")

新的 URL 链接 localhost:5000/about 将会跳转到 index.html ,并且 vue-router 将会自己处理其余的事情。

leamen 翻译于 1周前

添加 404 页面

因为我们定义了一个将所有请求跳转到 index.html 的路由,因此 Flask 将无法捕获到 404 错误(以及不存在的页面),将一些找不到页面的请求也跳转到 index.html。所以我们需要在 Vue.js 的路由文件中设置一条路由规则去处理这种情况。

在 frontend/src/router/index.js 中添加一行:

const routerOptions = [
  { path: '/', component: 'Home' },
  { path: '/about', component: 'About' },
  { path: '*', component: 'NotFound' }
]

这里的 '*' 是 vue-router 中的通配符,用以代表任何除了我们已经定义好的路由之外的其他情况。 接下来我们在 /components 文件夹中创建一个 NotFound.vue 文件,并写几行简单的代码:

// NotFound.vue
<template>
  <div>
    <p>404 - Not Found</p>
  </div>
</template>

现在通过运行 npm run dev 来重新运行前端服务器,并尝试一些不存在的 URL 链接,例如 localhost:8080/gljhewrgoh 。你就可以看到“Not Found”的消息提示了.

leamen 翻译于 1周前

Adding API endpoint

The very last example of my Vue.js/Flask tutorial will be creation of API on server side and dispatching it on client-side. I'll create a simple endpoint which will return a random number from 1 to 100.

Open run.py and add:

from flask import Flask, render_template, jsonify
from random import *
app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")
@app.route('/api/random')
def random_number():
    response = {
        'randomNumber': randint(1, 100)
    }
    return jsonify(response)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    return render_template("index.html")

First I imported random library and jsonify function from Flask library. Then I added new route /api/random to return JSON like this:

{
  "randomNumber": 36
}

You can test this route by navigating to localhost:5000/api/random.

At this point server-side work is done. Time to show this on client-side. I'll change Home.vue component to show my random number:

<template>
  <div>
    <p>Home page</p>
    <p>Random number from backend: {{ randomNumber }}</p>
    <button @click="getRandom">New random number</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      randomNumber: 0
    }
  },
  methods: {
    getRandomInt (min, max) {
      min = Math.ceil(min)
      max = Math.floor(max)
      return Math.floor(Math.random() * (max - min + 1)) + min
    },
    getRandom () {
      this.randomNumber = this.getRandomInt(1, 100)
    }
  },
  created () {
    this.getRandom()
  }
}
</script>

At this stage I just emulate random number generation process on client-side. So, this component works like this:

  • on initialization variable randomNumber is equal to 0
  • in methods section we have getRandomInt(min, max) function which will return a number from specified range, getRandom function will dispatch previous function and assign it value to randomNumber
  • after creation of component method getRandom will be invoked to initialize randomNumber
  • on button click event we will dispatch getRandom method to get new number

Now on home page you should see our random number generated by front-end. Let's connect it to back-end.

For that purpose I will use axios library. It allows us to make HTTP requests and return JavaScript Promise with JSON answer. Let's install it:

(venv) cd frontend
(venv) npm install --save axios

Open Home.vue again and add a few changes to <script> section:

import axios from 'axios'
methods: {
  getRandom () {
    // this.randomNumber = this.getRandomInt(1, 100)
    this.randomNumber = this.getRandomFromBackend()
  },
  getRandomFromBackend () {
    const path = `http://localhost:5000/api/random`
    axios.get(path)
    .then(response => {
      this.randomNumber = response.data.randomNumber
    })
    .catch(error => {
      console.log(error)
    })
  }
}

At the top we need to import axios library. Then there is a new method getRandomFromBackend which will use axios to asynchronously reach API and retrieve the result. And finally, method getRandom now should use getRandomFromBackend function to get a random value.

Save a file, go to browser, run a dev server again, refresh localhost:8080and... You should see an error in console and no random value. But don't worry, everything is working. We got CORS error which means that our Flask server API by default is closed to other web-servers (in our case it's Node.js server running our Vue.js app). If you create a bundle with npm run buildand open localhost:5000 (so Flask server) you will see working application. But it's not very convenient to create a bundle every time you made some changes to client-side application.

Let's use CORS plugin for Flask which will allow us to create a rules for API accesses. Plugin is called flask-cors , let's install it:

(venv) pip install -U flask-cors

You can read documentation on better explanation of what ways you have to enable CORS on your server. I'll use resource specific method and apply {"origins": "*"} to all /api/* routes (so everyone can use my /apiendpoints). In run.py:

from flask_cors import CORS
app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

With that change in place you can call Flask APIs right from front-end development server.

Update:

Actually, there is no need for CORS extension if you will serve static files through Flask. Thanks to Carson Gee for this trick.

The idea is next. If the application is in debug mode it will just proxying our front-end server. Otherwise (in production) serve the static files. Here is how we can do that:

import requests

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    if app.debug:
        return requests.get('http://localhost:8080/{}'.format(path)).text
    return render_template("index.html")

Simple and elegant. Magic ✨!

Now you have a full-stack application built with your favorite technologies.

Q9mPWiSqp9.png!largeid7NjI9Gdd.png!large

Afterword

In the end I want to say a few words on how you can improve in this solution.

First of all get use CORS extension only if you want to give access to your API endpoints for external servers. Otherwise just use a trick with proxying front-end development server.

Another improvement will avoiding hard coded API routes on client side. Maybe you need to think of some dictionary with API endpoints. So when you'll change you API route all you need to do is just to refresh a dictionary. Front-end will still have a valid endpoint.

Usually during development you will have at least two terminal windows: one for Flask and another for Vue.js. In production you'll get rid of running separate Node.js server for Vue.

Source code: https://github.com/oleg-agapov/flask-vue-spa

Thank you for reading!

本文章首发在 LearnKu.com 网站上。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://codeburst.io/full-stack-single-p...

译文地址:https://learnku.com/python/t/24985

参与译者:2
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!