How to get Flask to see Vue build files without using webpack? - vue.js

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:
What's wrong with the path the Flask is using to look for the static and template files?
Is it possible to use Vue with Flask without webpack?
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!

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

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.

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.

Related

I want to use Vue 3 without build and without CDN

https://v3.vuejs.org/guide/installation.html#download-and-self-host
https://v3.vuejs.org/guide/installation.html#from-cdn-or-without-a-bundler
how do I import vue without CDN?
so what I care about is not having a build step. everything in pure human-legible js.
I found this https://github.com/maoberlehner/goodbye-webpack-building-vue-applications-without-webpack
I'm going to try and implement it inside unity Embedded browser https://assetstore.unity.com/packages/tools/gui/embedded-browser-55459
the challenge is that my interface cannot load things from the web and it can't be compiled.
Create index.html
index.html (using Vue 3 - important!)
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Minimalistic Vue JS</title>
<script type="text/javascript" src="./vue.global.prod.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>
</body>
<script>
var app = Vue.createApp({
data() {
return {
message: "Hello world"
}
}
})
app.mount("#app")
</script>
</html>
Download vue.global.prod.js from https://unpkg.com/browse/vue#3.0.11/dist/vue.global.prod.js and save it along index.html
Open index.html in browser
Works just fine in Chrome or Firefox.
Notes
for the record my code is the repo I linked plus the vue libraries I downloaded and added in the root
Note: following is related to the repo linked before question was changed
The code in repo is written for Vue 2 (just try to open https://unpkg.com/vue in the browser). So if you downloaded distros for Vue 3 (for example the link I'm using above) the code from repo will not work
Even if you download Vue 2 version, the code in the repo will not work when opened from file system as it is using native ES6 modules - problem I described in the previous version of my answer:
As described here and here ES6 modules are always loaded with CORS. So just opening the index.html in the browser (without using server) will not work (definitely does not work in Chrome). Maybe Unity Embeded Browser has this restrictions weakened (as it's purpose is to be embeded) but without possibility to use desktop browser to develop and test your app, your experience will be terrible. I would reconsider the decision not to use bundler...
Update 1
Building Vue.js Applications Without webpack (sample project) will not help you either as it is again using native ES6 modules
To use Vue as a module from a local installation, you don't want to explicitly include it in a script tag in your page. Instead, import it in the scripts that use it. The whole idea of modules is that you can import them which makes explicitly including them in your page obsolete.
In https://bitbucket.org/letsdebugit/minimalistic-vue/src/master/index.js, import Vue:
import * as Vue from "./local/path/to/vue.esm-browser.prod.js";

Bundling a plugin with Rollup but having duplicate Vue.js package imported in the client app's bundle (Nuxt)

Dear Stack Overflow / Vue.js / Rollup community
This could be a noob question for the master plugin developers working with Vue and Rollup. I will write the question very explicitly hoping that it could help other noobs like me in the future.
I have simple plugin that helps with form validation. One of the components in this plugin imports Vue in order to programatically create a component and append to DOM on mount like below:
import Vue from 'vue'
import Notification from './Notification.vue' /* a very simple Vue component */
...
mounted() {
const NotificationClass = Vue.extend(Notification)
const notificationInstance = new NotificationClass({ propsData: { name: 'ABC' } })
notificationInstance.$mount('#something')
}
This works as expected, and this plugin is bundled using Rollup with a config like this:
import vue from 'rollup-plugin-vue'
import babel from 'rollup-plugin-babel'
import { terser } from 'rollup-plugin-terser'
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
export default {
input: 'src/index.js',
output: {
name: 'forms',
globals: {
vue: 'Vue'
}
},
plugins: [
vue(),
babel(),
resolve(),
commonjs(),
terser()
],
external: ['vue']
}
As you can see, Vue.js is getting externalised in this bundle. The aim (and the assumption) is that the client app that imports this plugin will be running on Vue, therefore there's no need to bundle it here (assumption).
The very simple src/index.js that the bundler uses is below:
import Form from './Form.vue'
export default {
install(Vue, _) {
Vue.component('bs-form', Form)
}
}
Rollup creates 2 files (one esm and one umd) and references them in in the plugins package.json file like below:
"name": "bs-forms",
"main": "./dist/umd.js",
"module": "./dist/esm.js",
"files": [
"dist/*"
],
"scripts": {
"build": "npm run build:umd & npm run build:es",
"build:es": "rollup --config rollup.config.js --format es --file dist/esm.js",
"build:umd": "rollup --config rollup.config.js --format umd --file dist/umd.js"
}
Everything works as expected up to this point and the bundles are generated nicely.
The client app (Nuxt SSR) imports this plugin (using npm-link since it's in development) with a very simple import in a plugin file:
/* main.js*/
import Vue from 'vue'
import bsForms from 'bs-forms'
Vue.use(bsForms)
This plugin file (main.js) is added to nuxt.config.js as a plugin:
// Nuxt Plugins
...
plugins: [{src: '~/plugins/main'}]
...
Everything still works as expected but here comes the problem:
Since the clients is a Nuxt app, the Vue is imported by default of course but the externalised Vue module (by the forms plugin) is also imported in the client. Therefore there is a duplication of this package in the client bundle.
I guess the client app can configure its webpack config in order to remove this duplicated module. Perhaps by using something like a Dedupe plugin or something? Can someone suggests how to best handle situation like these?
But what I really want to learn, is the best practice of bundling the plugin at the first place, so that the client doesn't have to change anything in its config and simply imports this plugin and move on.
I know that importing the Vue.js in the plugin may not be a great thing to do at the first place. But there could be other reasons for an import like this as well, for example imagine that the plugin could be written in Typescript and Vue.js / Typescript is written by using Vue.extend statements (see below) which also imports Vue (in order to enable type interface):
import Vue from 'vue'
const Component = Vue.extend({
// type inference enabled
})
So here's the long question. Please masters of Rollup, help me and the community out by suggesting best practice approaches (or your approaches) to handle situations like these.
Thank you!!!!
I had the same problem and I found this answer of #vatson very helpful
Your problem is the combination of "npm link", the nature of nodejs module loading and the vue intolerance to multiple instances from different places.
Short introduction how import in nodejs works. If your script has some kind of library import, then nodejs initially looks in the local node_modules folder, if local node_modules doesn't contain required dependency then nodejs goes to the folder above to find node_modules and your imported dependency there.
You do not need to publish your package on NPM. It is enough if you generate your package locally using npm pack and then install it in your other project npm install /absolute_path_to_your_local_package/your_package_name.tgz. If you update something in your package, you can reinstall it in your other project and everything should work.
Here is the source about the difference between npm pack and npm link https://stackoverflow.com/a/50689049/6072503.
I have sorted this problem with an interesting caveat:
The duplicate Vue package doesn't get imported when the plugin is used via an NPM package (installed by npm install -save <plugin-name> )
However, during development, if you use the package vie npm link (like npm link <plugin-name>) then Vue gets imported twice, like shown in that image in the original question.
People who encounter similar problems in the future, please try to publish and import your package and see if it makes any difference.
Thank you!

Vue + SWPrecacheWebpackPlugin - app.js is not cached

I'm trying to implement a service worker for my website. I used SWPrecacheWebpackPlugin with vue and registering and so on works well. Somehow it doesn't cache all files, in this case I guess the most important app.js file.
When I'm online the file structure built by vue looks like the following:
But when I check the cache the app.js file is missing and in offline mode i just get a white page.
So obivously the service-worker is up and running and is even caching some stuff, but not the relevant app.js file.
My webpack config looks like the following:
new SWPrecacheWebpackPlugin({
cacheId: 'xxx',
filename: 'service-worker.js',
staticFileGlobs: ['dist/**/*.{js,html,css}'],
minify: true,
stripPrefix: 'dist/'
})
I have actually no idea what I am missing.
Update:
The app.js file was too big and wasn't cached by the PlugIn. No warning, no error, nothing...

Snap.svg with Vue.js

I'm trying to use Snap.svg in my Vue.js app, however I'm confuse with how to do so.
I used Vue CLI 3 command to initialized my project, and install the snapsvg dependency with yarn.
Also, I read this article.
But I can't find the webpack.base.conf.js file !
When I try to import the dependency into my main.js file or into any component I've got no error but my App become empty.
What did I miss to properly import Snap.svg in my Vue.js app ?
Instead of hacking webpack,
use snapsvg-cjs
This project simply unwraps the excellent SnapSVG from its published AMD format and hosts it on NPM as CommonJS in a package called snapsvg-cjs. This package then works out-of-the-box with Webpack, without needing any import-loader hax.
npm install snapsvg-cjs;
the only line of code you'll need:
import "snapsvg-cjs";
In Vue cli 3, webpack is configured in vue.config.js
To use snap place the vue.config.js with the following content in your root directory
module.exports = {
chainWebpack: config => {
config.module
.rule("snapsvg")
.test(require.resolve("snapsvg"))
.use("imports-loader?this=>window,fix=>module.exports=0")
.loader("imports-loader")
.end();
}
};
add this line to your main.js file:
const snap = require(`imports-loader?this=>window,fix=>module.exports=0!snapsvg/dist/snap.svg.js`);

Import npm module using es6 style imports without transpiling?

I would like to call an npm module from my code which is an es6 module. Is there a way to do this without transpiling or bundling my code? The reason I don't want to transpile is for simplicity and so I can see my code changes instantly in the browser when I'm debugging.
You can work with native ESM modules in the browser using the script type="module". It works only for browsers that support it.
index.html
<html>
<head></head>
<body>
<script type="module" src="my-script.js"></script>
</body>
</html>
my-script.js
import {stuff} from './module1.js';
import Stuff from './module2.js';
console.log(Stuff);
console.log(stuff);
module1.js
export const stuff = {b: 1};
module2.js
export default {a: 1};
Then setup a quick web server to see the page working:
python -m SimpleHTTPServer 7654
That said, if your problem is refreshing your code at every change and debug it in ES6 dev mode, I recommend sourceMaps as the solution. With sourceMaps you can see your code working compiled (or "transpiled" as you like) as in production while debugging the development version in ES6. Webpack (or alternatives) is very optimized right now and can do partial compiling very quickly reloading the browser at every save.