Clayton Ramstedt
Clayton Ramstedt

Reputation: 125

How to get Flask to see Vue build files without using webpack?

I'm pretty new to Vue and modern web development in general, so I apologize in advance. Here's the situation.

I'm trying to make a simple Flask app that uses Vue and Vuetify on the frontend. When I look up tutorials for how to marry Flask to Vue (like this one), it relies on webpack, and I can get it working just fine. When I tried to add Vuetify to the project based on the instructions of their Quick start guide, I discovered that it requires you must follow the Existing Applications guide since the project was not initialized using vue-cli3 (see bug report here). I tried following those instructions but gave up after a few confusing hours with no results.

After reading up on webpack, I've decided that for my specific application, I can afford not to use it. So I went back to the Vuetify Quick Start guide and followed the New Applications instructions, fired up Flask and this is what I get:

127.0.0.1 - - [21/May/2019 18:31:20] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [21/May/2019 18:31:20] "GET /js/app.c12f4ec0.js HTTP/1.1" 404 -
127.0.0.1 - - [21/May/2019 18:31:20] "GET /css/chunk-vendors.b7bc2226.css HTTP/1.1" 404 -
127.0.0.1 - - [21/May/2019 18:31:20] "GET /js/chunk-vendors.2eb58769.js HTTP/1.1" 404 -
127.0.0.1 - - [21/May/2019 18:31:21] "GET /js/app.c12f4ec0.js HTTP/1.1" 404 -
127.0.0.1 - - [21/May/2019 18:38:22] "GET /index.html HTTP/1.1" 404 -

The code in question:

from flask import Flask, render_template

app = Flask(__name__,
            static_folder="./test/dist",
            template_folder="./test/dist")

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

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000, debug=True)

For the most part the Vue code is just a new project with Vuetify installed. The only change I made was in main.js where I changed what the delimiters would be so that they don't conflict with Jinja2.

import Vue from 'vue'
import './plugins/vuetify'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  delimiters: ["[[", "]]"],
}).$mount('#app')

My three main questions are:

  1. What's wrong with the path the Flask is using to look for the static and template files?
  2. Is it possible to use Vue with Flask without webpack?
  3. If it is not possible, would someone kindly do an eli5 of how to integrate Vuetify into a webpack based Vue project?

Thanks in advance!

Upvotes: 2

Views: 1865

Answers (3)

Nishant
Nishant

Reputation: 1

I was facing a similar issue. The answer provided by @clayton-ramstedt provided 200 status code but it was loading a blank page instead of the required static UI. Reason being that the index.html referred various other JS and CSS files and the above answer returned index.html for all of them.

The below code worked for me.

from flask import Flask, send_from_directory
import os

app = Flask(__name__,
            static_folder="./test/dist",
            template_folder="./test/dist")

@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def serve_frontend(path):
    
    if path != "" and os.path.exists(app.static_folder + '/' + path):
        return send_from_directory(app.static_folder, path)
    return send_from_directory(app.static_folder, 'index.html')

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000, debug=True)

This will render JS and CSS files properly and when we hit some other routes (like /login or /dashboard) those will be handled by index.html accordingly.

Make sure to provide path converter type because the default string type will not render the JS and CSS files which contain slashes.

Upvotes: 0

Clayton Ramstedt
Clayton Ramstedt

Reputation: 125

Okay so I found the answer, and it turned out to be way easier than I was making it out to be. To make it easier to replicate my steps, I'm going to start from the top.

app.py

from flask import Flask, render_template

app = Flask(__name__,
            static_folder="./test/dist", # place that webpack builds to
            template_folder="./test/dist")

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

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000, debug=True)

In the same directory as app.py run these commands (assuming you already have vue-cli installed):

$ vue init webpack vue-app-name
$ cd vue-app-name
$ yarn add vuetify # or use npm install vuetify --save

Now you basically just follow the instructions from the Existing Applications section of the Vuetify Quick Start page:

Top of main.js

import Vue from 'vue'
import App from './App'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css' // Ensure you are using css-loader


Vue.config.productionTip = false
Vue.use(Vuetify)

<head> section of index.html

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>frontend</title>
  <link href='https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons' rel="stylesheet">
</head>

And finally in the build section of index.js change the directory that web pack outputs its build to:

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

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

At this point, if you run

$ yarn build # or npm run build
$ cd ..
$ python app.py

and pop open your web browser, you should see the default Vue HelloWorld page, and you have Flask working with Vue, Vuetify and webpack all in one.

This is where I got confused. If you follow the instructions for a new Vuetify project and run the dev server, they have their own custom Hello World page. However because the Hello World page was still the default Vue page, I took it to mean that Vuetify was not installed. Really what's happening is that they have different App.vue and HelloWorld.vue files. If you replace the regular App.vue and HelloWorld.vue files with the Vuetify ones, copy the logo.svg file into the assets folder and rerun the dev server, you will get the Vuetify landing page.

Anyways, I feel like an idiot, but hopefully, this helps out another Flask/Vue/Vuetify/Webpack newbie.

This was done using python 3.7.3 and vue-cli 3.7.0

Upvotes: 3

Attack68
Attack68

Reputation: 4767

I have multiple projects with Vue and Flask and have found that webpack is the best approach. I disliked the Vue-CLI becuase it was too difficult to specify the paths of local files and bundled files on build. I know it is a learning curve at first but worth the day or two if you invest the time.

You should use Flask-Webpack to allow flask to locate assets which have been hashed and bundled. Along with a webpack plugin to create the manifest JSON that provides the link. When using flask webpack you also need to specify you folder paths directly as you have done.

I have this setup in my webpack config:

const ManifestPlugin = require('webpack-manifest-plugin')
...
plugins: [
    new ManifestPlugin({
      writeToFileEmit: true,
      seed: {
        publicPath: '/'
      },
      generate: (seed, files) => ({
        ...seed,
        'assets': files.reduce((manifest, { name, path }) => ({ ...manifest, 
        [name]: path }), {})
      })
   })
]

this creates a manifest.json file which looks like this:

{"assets":{"base_css.css":"static/css/base_css.1bf3ff18.css",
           "base_css.js":"static/js/base_css.1e6b291b.js",
           "chunk-vendors.js":"static/js/chunkvendors.f92da40c.js",},
 "publicPath":"/"}

Having setup your project I envision it is fairly straight forward to add Vuetify following the add to existing project using npm and imports.

Do note that when using Vue-Cli and/or webpack you cannot change the vue delimiters. From the API docs on delimiters: "Restrictions: This option is only available in the full build, with in-browser compilation."

But this last point shouldn't be a problem since your html templates and vue templates will be separately decoupled so the {{ }} crossovers wont matter, they are contextual.

Upvotes: 0

Related Questions