In Laravel projects prior to 5.3 I've utilised Vue.js using the script tag like this:
<script type="text/javascript" src="../js/vue.js"></script>
I would then create a Vue instance specific for that page like this:
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
});
</script>
and then bind it to the relevant div#id in my HTML.
Now, in Laravel 5.3 Vue.js comes bundled and I am fully aware that I can use components as described in the docs by using gulp/elixir, however, my question is if I want to create a Vue.js instance like I just mentioned, i.e. where I create a Vue.js instance strictly for a given page (not a component) how do I do it?
Do I set it up like I used to by importing the vue.js library in a script tag or can I use generated app.js?
Am I not supposed to do it this way, should I be creating components for everything?
For me, it doesn't make sense to make a component for something I am only using once - I thought the purpose of components was that they are reusable - you can use it in more than one place. As mentioned in the Vue.js docs:
Components are one of the most powerful features of Vue.js. They help you extend basic HTML elements to encapsulate reusable code.
Any advice would be appreciated, thanks!
I'd leave Laravel the way it comes, with Webpack. This gives you the ability to add some good Webpack configuration. Plus gulp watch works inside the Homestead vagrant VM now since it will be using Webpack to watch the file changes. And also check out async components.
Now on to your question regarding separate Vue instances per page...let's start with app.js...
App.js
When you first install Laravel 5.3, you'll find an app.js entry point. Let's comment out the main Vue instance:
resources/assets/js/app.js
/**
* First we will load all of this project's JavaScript dependencies which
* include Vue and Vue Resource. This gives a great starting point for
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
Vue.component('example', require('./components/Example.vue'));
// Let's comment this out, each page will be its own main Vue instance.
//
// const app = new Vue({
// el: '#app'
// });
The app.js file still remains a place to for global stuff, so components added here are available (such as the example component seen above) to any page script that includes it.
Welcome Page Script
Now let's create a script that represents a Welcome Page:
resources/assets/js/pages/welcome.js
require('../app')
import Greeting from '../components/Greeting.vue'
var app = new Vue({
name: 'App',
el: '#app',
components: { Greeting },
data: {
test: 'This is from the welcome page component'
}
})
Blog Page Script
Now let's create another script that represents a Blog Page:
resources/assets/js/pages/blog.js
require('../app')
import Greeting from '../components/Greeting.vue'
var app = new Vue({
name: 'App',
el: '#app',
components: { Greeting },
data: {
test: 'This is from the blog page component'
}
})
Greeting Component
resources/assets/js/components/Greeting.vue
<template>
<div class="greeting">
{{ message }}
</div>
</template>
<script>
export default {
name: 'Greeting',
data: () => {
return {
message: 'This is greeting component'
}
}
}
</script>
Welcome Blade View
Let's update the welcome blade view that ships with Laravel:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
</head>
<body>
<div id="app">
<example></example>
#{{ pageMessage }}
<greeting></greeting>
</div>
<script src="/js/welcome.js"></script>
</body>
</html>
The idea would be the same for the blog view.
Elixir
Now bring it all together in your gulp file using Elixir's ability to merge Webpack config options with its own (read more about that here):
gulpfile.js
const elixir = require('laravel-elixir');
require('laravel-elixir-vue-2');
/*
|--------------------------------------------------------------------------
| Elixir Asset Management
|--------------------------------------------------------------------------
|
| Elixir provides a clean, fluent API for defining some basic Gulp tasks
| for your Laravel application. By default, we are compiling the Sass
| file for our application, as well as publishing vendor resources.
|
*/
elixir(mix => {
var config = elixir.webpack.mergeConfig({
entry: {
welcome: './resources/assets/js/pages/welcome.js',
blog: './resources/assets/js/pages/blog.js'
},
output: {
filename: '[name].js' // Template based on keys in entry above
}
});
mix.sass('app.scss')
.webpack('app.js', null, null, null, config);
});
Run gulp or gulp watch and you'll see both welcome.js and blog.js published.
Thoughts
I'm currently going the SPA route when it comes to "web apps" and just using Laravel as the backend API (or any other language/framework). I've seen some examples where Vue SPA is built in Laravel, but I really think it should be a completely seperate repo/project, independent of the backend. There's no Laravel/PHP templating views involved in an SPA, so build out the SPA separately. BTW, the SPA would have "page" components (which are usually called by VueRouter and of course would be made up of more nested components...see my example project link below).
However, for the "web site" I think Laravel is still a good choice for serving blade views and no need to go SPA for that. You can do what I've described in this answer. Also, you can connect your website to your webapp. On your website, you would have a "login" link that will take a user from the website to the webapp SPA to login. Your website remains SEO friendly (although there is good proof that Google is seeing content on SPA javascript sites as well).
For a look at an SPA approach, I've put up an example in Vue 2.0 here: https://github.com/prograhammer/example-vue-project (it works great, but still in progress).
Edit:
You may want to also checkout the Commons Chunk Plugin. This way browsers can cache some shared module dependencies separately. Webpack automatically can pull out shared imported dependencies and put them in a separate file. So that you have a both a common.js(shared stuff) and a welcome.js on a page. Then on another page you would again have common.js and blog.js and the browser can reuse the cached common.js.
If you want to incorporate vuejs into app.js using gulp then you can do it with elixir:
Firstly, you need laravel-elixir-browserify-official from npm:
npm install laravel-elixir-browserify-official
Then place the following in package.json:
"browserify": {
"transform": [
"vueify",
"babelify"
]
}
Your resources/assets/js/app.js file would then just need:
require('./bootstrap');
The bootstrap.js file should be in the "resources/assets/js" folder. I can't remember if this got installed with passport in my application, so if you don't have it then laravel provided the following code for "bootstrap.js":
window._ = require('lodash');
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
/**
* Vue is a modern JavaScript library for building interactive web interfaces
* using reactive data binding and reusable components. Vue's API is clean
* and simple, leaving you to focus on building your next great project.
*/
window.Vue = require('vue');
require('vue-resource');
/**
* We'll register a HTTP interceptor to attach the "CSRF" header to each of
* the outgoing requests issued by this application. The CSRF middleware
* included with Laravel will automatically verify the header's value.
*/
Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-TOKEN'] = Laravel.csrfToken;
next();
});
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from "laravel-echo"
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: 'your-pusher-key'
// });
Now in gulpfile.js you can use:
elixir(function(mix) {
mix.browserify('app.js');
});
And in your HTML you would have:
...
<div id="app">
#{{message}}
</div>
...
<script type="text/javascript">
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
});
</script>
Now just run gulp
If you are not using elixir then you should be able to do a similar thing with the browserify or webpack packages from npm.
Edit
To answer your updated question, you can of course use vue.js for a single page. I personally use knockout for this stuff (I'm using vue because laravel passport uses it), but architecturally they are the same - they are MVVM libraries.
The point in MVVM is to bind your view to an underlying data model, so when one updates the other is automatically updated (i.e. updates in the dom automatically update the model and vice verser). Vue components are a simple way to reuse blocks of code, which is really good for creating widgets or complex components, but if you are simply looking to render data from a view model on to your page, then you would not usually need to create a component for that.
As for generating app.js, this entirely depends on your project. You cannot bind more than one view model to a view, so if you plan on using multiple view models in your project you would need to find a way to include the specific view model for your page. To achieve that I would probably remove the view model from app.js and keep the bootstrap and registered components there, then create separate view models that would need to be included on each page.
If you are on Laravel 5.5 and beyond, here is the best solution if you want to utilize the power of Blade but still enjoy reactive of VueJS
https://stackoverflow.com/a/54349029/417899
Related
I want to bundle a vue app with the styles and everything into a single UMD javascript module using vue-cli-service so that I can import it into another Vue app via my component distribution server. I am able to do this with one component on the serve, but I don't know how I'll be able to bundle an entire app and load it remotely into a separate app. I use this article as a guide https://markus.oberlehner.net/blog/distributed-vue-applications-loading-components-via-http/
This is where I am importing it:
{
path: '/games',
component: GamesHome,
children: [
{
path: 'fun',
component: () =>
externalComponent(
'http://localhost:8200/game/Game.cd590421a6d6835e7ae2.umd.min.js'
),
name: 'Fun Game'
}
] }
So basically how do I create a Vue app then bundle it entirely with CSS and all using vue-cli-service
This is the problem which I have been trying to solve from day 1 ever since I started using VueJS. I will not consider a client side JS framework if it does not provide a solution for this problem.
I recently did a PoC in this and able to consume a VueJS application as module in another VueJS application. In my case I have a suite of VueJs applications where each of these applications is running in its own dedicated docker container. These applications have a lot of functionality which is common across all the applications. So I decided to move this common code (page layout, css frameworks etc) to a separate VueJS application and consume all existing VueJS applications as modules in this global application. I call this micro-app based architecture to differentiate it from micro-frontends based architecture because it does not use multiple client side JS frameworks and does not require another framework to achieve this. This is how the deployment architecture looks like in my case (you can ignore kubernetes specific stuff if your are not aware about it) -
Coming back to implementation part, you need to take a step wise approach to convert a VueJS application to a micro-app.
Lets say you project structure look as following (it shows only few files which require changes and NOT all the files) -
app-1
public
index.html
src
main.js
App.vue
router
index.js
store
index.js
Split your vuex state and routes files into global and application specific files -
app-1
public
index.html
src
main.js
App.vue
router
app1
index.js
index.js
store
app1
index.js
index.js
Make a copy of this project (global-app), remove global-app specific files from app-1 and app-1 from specific files from global-app. Also remove index.html and App.vue from app-1 project -
Add ROUTES and STORE_MODULES variables to index.html file of global-app -
<head>
....
....
<script type="text/javascript">
const ROUTES = []
const STORE_MODULES = {}
</script>
</head>
<body>
....
....
<div id="app"></div>
<script type="text/javascript" src="/app1/micro-app.umd.min.js"></script>
<!-- built files will be auto injected -->
</body>
Update router\index.js file of global-app for ROUTES variable -
const routes = [
....
....
]
routes.push(...ROUTES)
const router = new VueRouter({
Update store\index.js file of global-app for STORE_MODULES variable -
export default new Vuex.Store({
....
....
modules: STORE_MODULES
})
Clear content of app-1\src\main.js file and replace it with following content -
import routes from '#/router/app1'
import app1 from '#/store/app1'
ROUTES.push(...routes)
STORE_MODULES['app1'] = app1
Define build-app command under scripts block of package.json file of app-1 -
....
"scripts": {
"build-app": "vue-cli-service build --target lib --formats umd-min --no-clean --name micro-app src/main.js"
},
....
Now build and deploy these two applications in their dedicated containers and update nginx conf file of proxy container to forward requests to these containers as following -
location / {
proxy_pass http://global-app:80;
}
location /app1/ {
proxy_pass http://app1:80/;
}
You can access global app by using IP address and port of nginx container.
I hope I have included all the steps which are required to implement micro-app based architecture. You can refer following git repositories which were created as part of this PoC -
https://github.com/mechcloud/large-app-docker
https://github.com/mechcloud/large-app
https://github.com/mechcloud/large-app-plugin1
While I am not an expert in the internals of client side JS frameworks, I believe this approach will work for other JS frameworks (Angular, React etc) as well.
I'm currently creating a vue app and wondering how to integrate it into a template and passing vars (props) into it.
So I basically run npm run dev, coding the app and all its components and so far everything is fine.
When I run npm run build I'll get some js in my build folder, created by webpack.
I guess (as seen before before here on stackoverflow) I can just load these files in my template, create an html-element mit the id "App" and everything works and the app initializes itself automatically.
But now my problem is: the app fires some Ajax requests, and depending on the environment the targets are different (dev: api.local, test: api.testsystem.com, prod: api.livesystem.com, ...).
And therefore I need to pass the url from outside into the app.
We don't build the app at deployment, as it's on a different repository than the "websites" using it. (Our plans are wether to copy the build files manually in these projects or offer an cdn-like url where the other projects load it). And with other projects I mean a symfony based website, or a typo3 plugin, ...
So, from React I remember you can initiate an app like React.render('app.js', {props: 'api-url': 'http://api.local'}); (don't kill me, it was somehow like this...)
How do I do it in vue?
Like:
new Vue(
<template>
<App :api-url="api-url" />
</template>
<script>
import App from "path/to/app.js";
export default {
data() {
api-url: "inject url here"
}
}
</script>
);
or add an data attribute like <div id="App" data-api-url="http://url"> and try to access it inside the app?
I've also seen something like an env-loader - but I'm not sure if this helps in my case.
You talk about different environment mode. You should read this documentation about how to start vue app with different env mode.
You can define different .env files for each mode and populate process.env with this file, or you can start vue.js with NODE_ENV=development per example, and check for process.env.NODE_ENV where you need to pass appropriate variables.
Build systems like next.js, razzle and back-pack seem really helpful, but based on examples I've found and the default applications they generate, they seem to be geared toward Server Side Rendering. Are there similar build systems that can (or can the ones mentioned here) be used to build dynamic react applications w/ node.js back-ends?
I'm hoping to find one I can run out of the box that will watch back-end and front-end code and refresh on a change to either. I know I can find a webpack or gulp script to start with and customize it, but I like the idea of a tool like the above that is just one command.
Edit
I need to deploy my front-end to Google Cloud Storage, so I don't want any server-side rendering at all.
You can export your next.js app as long as you only use features supported by export.
From the documentation:
https://github.com/zeit/next.js/#static-html-export
This is a way to run your Next.js app as a standalone static app without any Node.js server. The export app supports almost every feature of Next.js including dynamic urls, prefetching, preloading and dynamic imports.
Simply develop your app as you normally do with Next.js. Then create a custom Next.js config as shown below:
// next.config.js
module.exports = {
exportPathMap: function() {
return {
'/': { page: '/' },
'/about': { page: '/about' },
'/readme.md': { page: '/readme' },
'/p/hello-nextjs': { page: '/post', query: { title: 'hello-nextjs' } },
'/p/learn-nextjs': { page: '/post', query: { title: 'learn-nextjs' } },
'/p/deploy-nextjs': { page: '/post', query: { title: 'deploy-nextjs' } }
}
}
}
In that, you specify what are the pages you need to export as static HTML.
Then simply run these commands:
next build
next export
On limitations:
Limitation
With next export, we build HTML version of your app when you run the command next export. In that time, we'll run the getInitialProps functions of your pages.
So, you could only use pathname, query and asPath fields of the context object passed to getInitialProps. You can't use req or res fields.
Basically, you won't be able to render HTML content dynamically as we pre-build HTML files. If you need that, you need run your app with next start.
If you don't need server side rendering you can go for create-react-app
The usual process of deploying a react application is simple when using webpack to handle the code transpilation and the final result is one .js app which contains all the css/js/html. But I have an application which needs some data from client's request to express server.
For example I want to get a userid from the request, and based on that provide some data to App component and then, provide a .js file (like what webpack provides) that contains the App and user specific data.
I've tried, to user babel-core/register and react-dom/server.renderToString, but it just provides to html and css and not the js code that is necessary to make the app work. I have to load those codes separately!
This is quite common problem.
It is better to solve it a little different then you tried (you don't want to force your user to wait ~10s to build bundle for him)
There is one universal build bundle for all users (or few - for example one for normal users and second for admin users)
Your server gets request from your user with special parameters for example userid
Your server render start html page as response to this request - for example index.html. This file should have <script src to bundle js for your user. Source path should be dynamically build by server to point exact js bundle (if there are more then one). It should also have javascript global object with parameters from server which should be visible by your components.
For example index.html could look like (you could do it differently, maybe without creating global object. I hope that you get general sense of this idea). :
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script>
__CONFIG__ = {
someObject1: '<!-- #echo someObject1 -->',
someObject2: '<!-- #echo someObject2 -->',
userid: '<!-- #echo userid -->',
}
</script>
<script src="<!-- #echo SOME_PATH -->/static/bundle.js"></script>
</body>
</html>
Now your component could check global variable CONFIG .
But I suggest not to use it in your normal components!
Create bootstrap component which loads your normal react app and which is something like bridge between your app and environment.
Read global config in this bootstrap component and then pass it as props to your app.
This bootstrap component could look like:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
const config = window.__CONFIG__;
ReactDOM.render(
<App
config={config}
/>, document.getElementById('root'));
Edit:
This is also good way if you have to set initial state based on db or to tell react to do something.
For this I add additional global variable __INIT__
I have these routes in my Ember.js app:
router.js
EmberRails.Router.map(function() {
this.resource('stories', function () {
this.resource('story', {path: '/:story_id'});
});
});
And this is what my Ember Inspector outputs in the routes tab:
Now, in the "Template" column (second from right), Ember tells me where it expects the templates should be for the different Story resources. As you can see, it wants certain templates directly in the /templates directory, and some in /templates/stories. I can see no reason for this and personally I find it confusing and ridiculous.
Is there any way in Ember to use a Rails-style convention, like this:
Show ('story') - /templates/stories/show
Index - /templates/stories/index
Edit - /templates/stories/edit
etc..
You can use Ember CLI and it's pods feature to do this. It was recommended by the Ember core team that all Ember applications move to Ember CLI as soon as possible.
If you aren't or don't want to use Ember CLI, you can overwrite the regular Ember.DefaultResolver methods to change the resolver's functionality. There is a short example in the Docs.