I deployed a Django+VueJS app that uses django webpack loader in order to render Vue apps in my Django templates. I used Nginx and Gunicorn to deploy the app to a DigitalOcean VPS, everything works without any problem but i have some doubts on how to edit my components in production, since i'm fairly new to Vue
Here is my vue.config:
const BundleTracker = require("webpack-bundle-tracker");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const pages = {
'main': {
entry: './src/main.js',
chunks: ['chunk-vendors']
},
}
module.exports = {
pages: pages,
runtimeCompiler: true,
filenameHashing: false,
productionSourceMap: false,
publicPath: process.env.NODE_ENV === 'production'
? 'static/vue'
: 'http://localhost:8080/',
outputDir: '../django_vue_mpa/static/vue/',
chainWebpack: config => {
config.optimization
.splitChunks({
cacheGroups: {
moment: {
test: /[\\/]node_modules[\\/]moment/,
name: "chunk-moment",
chunks: "all",
priority: 5
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-vendors",
chunks: "all",
priority: 1
},
},
});
Object.keys(pages).forEach(page => {
config.plugins.delete(`html-${page}`);
config.plugins.delete(`preload-${page}`);
config.plugins.delete(`prefetch-${page}`);
})
config
.plugin('BundleTracker')
.use(BundleTracker, [{filename: '../vue_frontend/webpack-stats.json'}]);
// Uncomment below to analyze bundle sizes
// config.plugin("BundleAnalyzerPlugin").use(BundleAnalyzerPlugin);
config.resolve.alias
.set('__STATIC__', 'static')
config.devServer
.public('http://localhost:8080')
.host('localhost')
.port(8080)
.hotOnly(true)
.watchOptions({poll: 1000})
.https(false)
.headers({"Access-Control-Allow-Origin": ["*"]})
}
};
So in order to deploy the Vue part i did npm run build and npm created a bunch of files in my static directory. Now, every time i edit a component, in order to see the changes on the web, i do npm run build every time, which takes some time. Is this how am i supposed to do it? Or is there a shorter way?
I don't know about django, But I know about vue..
is this how am I supposed to do it?
For me, I don't suggest it, you can use your django as a backend for your frontend
that should mean you would have 2 servers running. 1 for your django and 1 for your vue app. use XHR request to access your django App, remember to handle CORS. IMHO I don't want vue to be used as a component based framework.
is there a shorter way.
YES, and this is how you do it.
add to package.json
{
...,
scripts: {
...,
'watch' : 'vue-cli-service build --watch --inline-vue',
...,
}
}
while using the following settings in vue.config.js
module.exports = {
'publicPath': '/django/path/to/public/folder',
'outputDir': '../dist',
'filenameHashing': false,
runtimeCompiler: true,
'css': {
extract: true,
},
}
i forgot about how publicPath and outputDir works..
but you can check it out here https://cli.vuejs.org/config/#publicpath
regarding the code on the package.json file..
you can check it here
https://github.com/vuejs/vue-cli/issues/1120#issuecomment-380902334
Related
Can some one help me with an example to load remote Angular micro frontend application into React Shell using webpack's Module federation concept?
I have checked https://www.angulararchitects.io/en/aktuelles/multi-framework-and-version-micro-frontends-with-module-federation-the-good-the-bad-the-ugly/ where React is loaded in angular. But I am looking for other way.
With webpack, you can put the bootstrapping of angular app in a mount method and export this method. This must be done in another file to avoid conflicts for angular to run independently.
(Make sure to have the bootstrap.js file imported in main.ts, as used by module federation)
remote/src/load.ts
const mount = ()=>{
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
export{mount}
expose this new file with webpack from angular
remote/webpack.config.ts
output: {
scriptType: 'text/javascript',
},
plugins: [
new ModuleFederationPlugin({
name: "remoteMfe",
filename: "remoteEntry.js",
exposes: {
'./webcomponent':'./src/loadApp.ts',
},
})
load the exposed module in react app using webpack
host/webpack.config.js
plugins: [
new ModuleFederationPlugin(
{
name: 'host',
filename:
'remoteEntry.js',
remotes: {
RemoteMFE:
'remoteMfe#http://localhost:3000/remoteEntry.js',
},
}
),
import the mount method that was exposed from angular remote and get the root element(i.e. ) from angular mfe. This can be used as a regular DOM element
host/src/Example.js
const ExampleComponent = () => {
useEffect(() => {
mount();
}, []);
return <div className="left-sidebar-module"><app-root></app-root></div>;
};
I've deployed a simple web application named "ogagnage" with Flask and Heroku (gunicorn). It works perfectly in production and I try now to run it as a Progressive Web Application. Following Heroku instructions, I've created manifest.json and service workerfile (sw.js). Manifest file is recognized by my browsers but not service worker and i unfortunately don't understand why...
Manifest.json OK
sw not recognized
sw error message
Here is the structure of my project:
Directory tree
And here are the code added:
In views.py :
#app.route('/sw.js')
def sw():
return app.send_static_file('sw.js')
#app.route('/manifest.json')
def manifest():
return app.send_static_file('manifest.json')
#app.route('/app/static/app.js')
def app_js():
return app.send_static_file('app.js')
In sw.js :
console.log('Hello from sw.js');
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.2.0/workbox-sw.js');
if (workbox) {
console.log("Yay! Workbox is loaded 🎉");
workbox.precaching.precacheAndRoute([
{
"url": "/",
"revision": "1"
}
]);
workbox.routing.registerRoute(
/\.(?:js|css)$/,
workbox.strategies.staleWhileRevalidate({
cacheName: 'static-resources',
}),
);
workbox.routing.registerRoute(
/\.(?:png|gif|jpg|jpeg|svg)$/,
workbox.strategies.cacheFirst({
cacheName: 'images',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
}),
],
}),
);
workbox.routing.registerRoute(
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
workbox.strategies.cacheFirst({
cacheName: 'googleapis',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 30,
}),
],
}),
);
} else {
console.log("Boo! Workbox didn't load 😬");
}
In app.js:
(function() {
if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('sw.js')
.then(function(registration) {
console.log('Service Worker Registered');
return registration;
})
.catch(function(err) {
console.error('Unable to register service worker.', err);
});
navigator.serviceWorker.ready.then(function(registration) {
console.log('Service Worker Ready');
});
});
}
})();
In my base template:
<script type="text/javascript" src="../static/js/app.js"></script>
And my git:
Github project
Hope someone could help me, it would be a beautiful Christmas present.
Tx
You also need to send the manifest.json file
#app.route('/manifest.json')
def manifest():
return app.send_from_directory('static', 'manifest.json')
If you'd like a complete example, I have created a Flask PWA before. Here is the repository: https://github.com/MurphyAdam/Flask-chatbot
I've solved the problem after changing #app routes in views.py and relative path to go to sw.js in app.js (cf code updated)
Now , my service worker is working:
Service Worker Ok
After two days I failed to setup (any form of) webpack working with django3 in the back and angular10 in the front, so I decided to just use gulp to start ng serve for frontend and python manage.py runserver for backend. I am new to this, so this is probably very stupid but really two days is a lot of time to give on setup and get nothing back ..
Currently I am trying to call an API on the django server that is on http://127.0.0.1:8000 while ng serve is running on http://127.0.0.0:4200
#Injectable()
export class EchoService {
constructor(private httpClient: HttpClient) {}
public makeCall(): Observable<any> {
return this.httpClient.get<any>(
'http://127.0.0.1:8000/my-api/'
);
}
}
'''
Is there a better way how to do this in Angular without using "http://127.0.0.1:8000" in every component call I do? How can I make it as close as possible to relative paths, that will be used in the prod version of this (for prod I will just put the bundles manually in the html, but I can not do that manually for dev)
Angular allows defining an environment. Here is what I did:
in your src folder, find the environments folder.
create the following files and adjust the content as needed (there should already be a file named environment.ts there, I'll get back to that later. For now:
environment.dev.ts
export const environment = {
production: true,
apiURL: "http://127.0.0.1:8000/my-api"
};
environment.prod.ts
export const environment = {
production: true,
apiURL: "https://api.yoururl.com/"
};
modify your angular.json:
...
"configurations": {
...
"production": {
"outputPath": "dist-prod/",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
}
"dev": {
"outputPath": "dist-dev/",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
],
}
...
For ng serve, the standard environment.ts file is used, so the contents should probably match those of environment.dev.ts.
You can create builds with this setup by just calling:
ng build --configuration dev
ng build --configuration prod
Use in your service/component:
import { environment } from 'your/path/environments/environment';
#Injectable()
export class EchoService {
constructor(private httpClient: HttpClient) {}
public makeCall(): Observable<any> {
return this.httpClient.get<any>(
environment.apiURL + 'your/endpoint/
);
}
}
Now, depending on the configuration, environment.apiURL might be 127.0.0.1:8000 or https://.....
I have one for development, one for staging, and one for production.
In case I`ve missed something, you can read about it here.
When I build (netflify build) my Gridsome personal website, tailwind CSS classes doesn't work and the website look's like without CSS.
I have already tried to build without git, reinstall tailwind...
I show my gridsome config if that's the problem:
const tailwind = require('tailwindcss');
const purgecss = require('#fullhuman/postcss-purgecss');
const postcssPlugins = [
tailwind(),
]
if (process.env.NODE_ENV === 'production') postcssPlugins.push(purgecss());
module.exports = {
siteName: 'Zolder | Works',
plugins: [],
css: {
loaderOptions: {
postcss: {
plugins: postcssPlugins,
},
},
},
}
I had this issue as well. I used the Tailwind Plugin for Gridsome and it worked locally but when deploying to Netlify, the Tailwind css wasn't getting added.
Referencing this starter file: https://github.com/drehimself/gridsome-portfolio-starter/blob/master/src/layouts/Default.vue
I added the main.css with all the Tailwind imports file to the end of the Default Layout template instead, and this worked for me.
You can add Tailwind to your Gridsome project with these steps:
edit gridsome.config.js
module.exports = {
siteName: "Zolder",
plugins: [],
chainWebpack: config => {
config.module
.rule("postcss-loader")
.test(/.css$/)
.use(["tailwindcss", "autoprefixer"])
.loader("postcss-loader");
}
};
create a global.css file in ./src/styles
#tailwind base;
#tailwind components;
#tailwind utilities;
import global.css in main.js
import './styles/global.css'
I have the following file: deposit-form.js.
With the following code:
new Vue({
el: '#app',
data: {
title: 'title',
depositForm: {
chosenMethod: 'online',
payMethods: [
{ text: 'Already paid via Venmo', value: 'venmo' },
{ text: 'Pay online', value: 'online' },
{ text: 'In-person payment', value: 'person' }
],
},
},
methods: {
submitDeposit: function() {
$.ajax({
url: 'http://localhost:8000/api/v1/deposit/',
type:'post',
data: $('#deposit-form').serialize(),
success: function() {
$('#content').fadeOut('slow', function() {
// Animation complete.
$('#msg-success').addClass('d-block');
});
},
error: function(e) {
console.log(e.responseText);
},
});
},
showFileName: function(event) {
var fileData = event.target.files[0];
var fileName = fileData.name;
$('#file-name').text('selected file: ' + fileName);
},
},
});
I'm having problems on how to setup Jest, how to import the VueJs functions inside 'methods' to make the tests with Jest.
How should be my code on the deposit-form.test.js ?
The first thing you need to do is export Vue app instance.
// deposit-form.js
import Vue from 'vue/dist/vue.common';
export default new Vue({
el: '#app',
data: {...},
...
});
Now you can use this code in your spec file. But now you need to have #app element before running tests. This can be done using the jest setup file. I will explain why it's needed. When you import your main file (deposit-form.js) into a test, an instance of Vue is created in your main file with new. Vue is trying to mount the application into #app element. But this element is not in your DOM. That is why you need to add this element just before running the tests.
In this file you also can import jQuery globally to use it in your tests without import separately.
// jest-env.js
import $ from 'jquery';
global.$ = $;
global.jQuery = $;
const mainAppElement = document.createElement('div');
mainAppElement.id = 'app';
document.body.appendChild(mainAppElement);
Jest setup file must be specified in the jest configuration section in package.json.
// package.json
{
...,
"dependencies": {
"jquery": "^3.3.1",
"vue": "^2.6.7"
},
"devDependencies": {
"#babel/core": "^7.0.0",
"#babel/plugin-transform-modules-commonjs": "^7.2.0",
"#babel/preset-env": "^7.3.4",
"#vue/test-utils": "^1.0.0-beta.29",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.1.0",
"babel-loader": "^8.0.5",
"babel-preset-vue": "^2.0.2",
"jest": "^24.1.0",
"vue-jest": "^3.0.3",
"vue-template-compiler": "^2.6.7",
"webpack": "^4.29.5",
"webpack-cli": "^3.2.3"
},
"scripts": {
"test": "./node_modules/.bin/jest --passWithNoTests",
"dev": "webpack --mode development --module-bind js=babel-loader",
"build": "webpack --mode production --module-bind js=babel-loader"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
},
"setupFiles": [
"<rootDir>/jest-env.js"
]
}
}
Also, you probably need to configure Babel to use the features of ES6 in your projects and tests. This is not necessary if you follow the commonjs-style in your code. Basic .babelrc file contains next code:
// .babelrc
{
"presets": [
[
"#babel/preset-env",
{
"useBuiltIns": "entry",
"targets": {
"browsers": [
"last 2 versions"
]
}
}
],
"vue",
],
"plugins": [
"#babel/plugin-transform-modules-commonjs",
]
}
Now you can write your tests.
// deposit-form.test.js
import App from './deposit-form';
describe('Vue test sample.', () => {
afterEach(() => {
const mainElement = document.getElementById('app');
if (mainElement) {
mainElement.innerHTML = '';
}
});
it('Should mount to DOM.', () => {
// Next line is bad practice =)
expect(App._isMounted).toBeTruthy();
// You have access to your methods
App.submitDeposit();
});
});
My recommendation is to learn Vue Test Utils Guides and start to divide your code into components. With the current approach, you lose all the power of components and the ability to test vue-applications.
I updated my answer a bit. As I understood from the comment to the answer, you connect the libraries on the page as separate files. Here is my mistake. I didn't ask if the build system is being used. Code in my examples is written in the ECMA-2015 standard. But, unfortunately, browsers do not fully support it. You need an transpiler that converts our files into a format that is understandable for browsers. It sounds hard. But it's not a problem. I updated the contents of the file package.json in response. Now it only remains to create an input file for the assembly and run the assembly itself.
The input file is simple.
// index.js
import './deposit-form';
The build is started with the following commands from terminal.
# for development mode
$ yarn run dev
# or
$ npm run dev
# for production mode
$ yarn run build
# or
$ npm run build
The output file will be placed in the directory ./dist/. Now instead of separate files you need to connect only one. It contains all the necessary for the library and your code.
I used webpack to build. More information about it can be found in the documentation. Good example you can find in this article.