VueJS With Multi-Page Rails 6 App With Turbolinks - Layout - ruby-on-rails

I've got a specific question that I'm struggling to find information online for. I've successfully installed VueJS into my Rails 6 app using webpacker and have a root component registering just fine on my application layout file:
<!-- application.html.erb -->
<!-- javascript pack tags loaded in head and my app component displays the template properly -->
<div id="vue-app-root">
<app></app>
</div>
I'm aware that in a single-page app, the VueJS app initializes a single time upon visiting the website and then async requests are made and view/template changes are initiated by VueJS - all makes sense. My question pertains to how exactly the vue app initialization should work with a multi-page app.
Does the app initialize completely on every reload/turbolink load? If I'm just using a few components here and there in various pages, am I stuck with having the entire VueJS app initialize on each page load and then, subsequently, the components? If so, should I have the Vue JS app initialize on the body tag (or a root element) within the application.html.erb or should I have the app initialize only on the pages that have components?

You need to find out what is responsible for your routing - Vue or Rails?
If it is Rails, then yes, Vue app will need to be remounted on every page load. This applies whether or not you use Turbo/Turbolinks, as long as Vue app is in the fetched partial. If the fetched partial is outside the Vue container and you use Turbo/Turbolinks, Vue app will not be reloaded. This is true even for a server rendered Vue which hydrates on client.
If it is Vue that does the routing, Rails may not do it, otherwise Vue app will always be reloaded.

To #sjaustirni's answer, I'd add that depending if you want to use Rails integrated routing or not, you may split the 2 codebases. What I mean: if you do only use Rails as an API and not it's routing, you should put the Vue project in it's own repo rather than mixing both.
Of course if you're using Rails' routing, it makes sense to keep both in the same place.
Also, I would like to share the existence of vue-turbolinks which may be useful in your case, especially if using Hotwire.
Here is a video explaining it: https://gorails.com/episodes/how-to-use-vuejs-and-turbolinks-together

Related

Best way to use Vue 3 components built elsewhere in an ASP.NET project's views?

I'm rewriting some templates and functionality previously developed using AngularJS 1.x which are currently managed and developed as static assets in an ASP.NET MVC application and are used alongside razor syntax (.cshtml). There are no components either. Imagine the AngularJS modules as a huge bunch of jQuery code linked and coupled with views.
This time, I'm implementing everything we need in a Vue 3 app in a separate git repository and I'm also using Vuex 4.
I'm hoping to be able to do the following:
Build the Vue app
Load the assets in BundleConfig.cs
Link the assets to my _layout.cshtml to have them on all my pages.
Use the components wherever I need them.
I'm going well on developing the components and functionalities within its standalone project, yet I'm facing several problems and/or ambiguities.
I have pages that are mostly if not entirely rendered by the client-side. These pages may or may not be handled by a client-side router such as vue-router.
I also have pages that are mostly rendered by the server and then stuff is added or dynamic contents are loaded by the client-side. These pages can't use a client-side router.
I'm not using a router and I'm having a hard time developing and testing those pages that are mostly rendered by Vue.
if I use a router I think I won't be able to do what I'm planning to do about those pages that are mostly rendered by the server. I really need all pages (whichever kind they are) to have access to my Vuex store.
What do you recommend I do to make it easier for myself both in development and production?
Should I create several static HTML files for each of my pages in Vue's public directory tweak Webpack's configuration in order to simulate what will happen in production (use within the ASP.NET project)?
Should I start having a router, put all pages that are mostly CSR under its control, and somehow configure it to have nothing to do with my other pages that are mostly SSR?
I need to be able to debug and test stuff when I run npm run serve and then do what I'm tasked to do. Unless the whole plan is a bad/wrong idea somehow.
I might also be able to build my Vue app as a library and then, in the ASP.NET project, init a small Vue app that imports that library and that itself is bundled with the back-end project. The whole reason I'm doing this is to make the client-side stuff reusable and easy to maintain. I don't want to take a GET SHIT DONE approach.
Thanks

Vue and .NET Core integration (+authentication) choice

As far as I understand that, there are two major options for Vue and .NET Core integration within single MVC/Razor project.
Option 1.
MVC/Razor-rendered non-reactive page is used for authentication with built-in ASP.NET Identity. Vue is not involved for authentication/authorization at all. As soon as users are authenticated, they are redirected to another MVC/Razor page that is used as a HTML template for Vue. It’s possible to combine MVC/Razor rendering and Vue. For example, user name on the top of the page can be rendered by MVC but button actions and data tables will be further processed by Vue. It’s possible to use many pages (so it will be MPA, not SPA), it comes naturally. Using *.vue files is not possible. MVC routing seems to be primary routing option (not sure, would be possible to combine with Vue routing and whether any needs for that). Vue JS files can reside anywhere in the project, for example, can be bound to the HTML pages similarly as CS files in Razor pages do (and it’s nice). Then, all these JS files along with Vue itself can be bundled to the wwwroot by Webpack. Vue CLI is not available but seems there is no need for that.
Option 2.
MVC/Razor is not used for rendering user pages at all. Authentication occurs by third-party solutions like IdentityServer and with Vue-managed pages. .NET Core is used exclusively as a WebAPI for Vue and to hold a project. Vue part is totally independent from the MVC/Razor part, they even render pages to the different HTTP ports so proxy is needed to convert Vue HTTP port to the MVC/Razor HTTP port to make Vue works in the single project. We can use either Microsoft.AspNetCore.SpaServices.Extensions or NuGet third-party VueCliMiddleware for that. All Vue files typically reside within a ClientApp folder and then are building to the wwwroot folder. Using *.vue files is possible. Vue CLI is available and recommended to scaffold new application to the ClientApp folder (but further CLI seems is not needed). Vue router seems to be the only option for routing. SPA seems to be the primary choice as a structure (not sure, whether MPA is readily available option). Webpack is still used for building Vue app from the ClientApp to the wwwroot.
I started mu Vue journey with Option 1 even without webpack and npm, just with CDN tag on the one of the Razor pages and it works very well. For me Option 1 seems less complex while more flexible. My primary concern is that Microsoft uses Option 2 as a built-in templates for Angular and React in Visual Studio, so I probably is missing something and soon I will be pushed to rewrite my app to the Option 2.
What you guys think which option is better and whether my understanding explained above, is correct?

How to exclude a chunk from VueJS PWA SW precache?

Currently, I have fresh installation of vuejs app with some test pages and components. All my routes and components are using dynamic imports to load js chunks only when user goes to a particular route or a component is rendered and it is working fine in SPA and SSR mode. The problem occurs in PWA mode when it prefetches all chunks at start.
I have tried 'exclude' function but it still pre-fetched the file.
Is there a way to exclude complete routes e.g. /admin to be pre-fetched? As this is only for internal use and not needed for offline usage.
We are using webpack + workbox bundler.
Any help would be highly appreciated.
I edited your question's title to match what you're actually asking. I hope I got it right :)
Yes, that's possible. You can exclude a lazy loaded chunk ("route" or "page" or whatever) from the asset list to be precached.
I assume you're using Vue CLI based project. Look for the PWA config options here https://cli.vuejs.org/config/#pwa. It will lead you to whole config of the Vue CLI PWA plugin here https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa. That will tell you how to pass any options to the underlying workbox-webpack-plugin. What you need is the excludeChunks option, described here https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin#full_generatesw_config.

Grails Project with React/Flux

I am currently working on a project in which we are having a React/Flux UI being developed for us. I am being told that the UI code needs to be converted into GSPs and put into a Grails Project, to work with our backend. I feel like moving the UI into GSPs will nullify the use of our Flux implementation(Reflux).
My initial thought is that this is wrong and I have not found any use of Grails and Flux through searching.
I'm involved with a production Grails application which uses React as it's front-end. There's no need to "convert" the React/Flux code into GSP - in fact that would largely sacrifice the benefits of the React UI. There's no need- Grails is very well suited to provide a robust Restful backend to a React (or Angular, or any other JS framework) application.
Depending on your application needs, you will probably want to provide a restful API for the front-end to consume/post as needed. Use URLMappings.groovy to specify endpoints that the React app can access. You will likely choose to use JSON as the medium to send data to the React app - Grails' JSON views are a fast, flexible and straightforward means to render Grails domain objects or other data to a JSON payload.
There shouldn't be anything Grails-specifc regarding Flux - use it to manage and mutate your state in the React application, perhaps by making rest calls from your Flux dispatcher (or action creator) to the Grails backend to retrieve data and update your Flux store.
Regarding GSP, my recommendation is to simply have a barebones GSP view off your main controller, which simply loads the Javascript needed to run your React app. If you are using a module bundler like webpack, this can be as simple as linking your bundle.js file into your view and providing the root element specified in your top-level component:
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="app"></div>
<asset:javascript src="bundle.js"/>
</body>
</html>
Note that for this to work, you must be outputting your webpack bundle into grails-app/assets/javascripts, which I find to be the simplest way to load the React application in a Grails app. Using this approach, there's no need to load React, Flux or other related libraries into Grails directly - just build your project using the standard JS toolchain (using npm/package.json to manage your dependencies), and process/bundle the entire application into a plain JS bundle that can be loaded by the Grails asset-pipeline.
React makes a great choice as a view-focused Javascript library that doesn't make a lot of assumptions about your backend architecture. With a solid restful api based on Grails, and some intelligent choices about tooling and project structure, React is a great fit for a modern, single-page Javascript UI in a Grails app.
Why would the use of GSP nullify the use of React or Flux? GSP is just a server-side processing language that renders HTML. Last I checked React and Flux make good use of HTML for a great number of things in combination with Javascript.
You can use them together without any issues. How, is up to you.
You are being told wrong. GSPs are Java servlets. The only reason to mix React with a server-side technology is for an isomorphic application, where you would compile your JS prior to returning/pushing it to the client. In which case, you would need to create something akin to a Rhino-based servlet. Otherwise, treat it as you would treat any other static asset.

How to embed a React starterkit app into legacy Rails app?

I have a React app based on the starterkit of Erik Rasmussen from here.
The app works pretty well, using React Router and Redux.
Since my Rails app is pretty old, the idea to embed the application, is to have a separate controller where the index action renders an iFrame, which itself loads it's content from the same controllers show action.
Therefore I can stick with my Rails app's layout, and have the React apps markup rendered w/o any changes.
After I transpiled the app via
$> npm run build
I copy the main-.js and main-.css files into the Rails app, so they can be delivered from there.
Afterwards I start the React app via:
$> npm run start
and request the starting page via curl. The curl output goes into my show erb template. Inside I change the URL's where the main JS and CSS is loaded from.
When I request my index page now, the iFrame loads it's content, the app seems to initialize, but does not work at all. I see rendered content, but clicking on links leads to Rails routing errors. So the clicks seem not to be intercepted by React.
I tried to track execution down, and ended up debugging the code in client.js. It seems like, ReactDOM.render receives proper arguments, meaning, there is a proper looking component to render, a store, and even the destination element.
Currently I get the following error in the console:
Uncaught ReferenceError: _classCallCheck is not defined
I am using a slightly oder version of the starterkit, which embeds initial data into the markup, but I was not able to figure out, why the _classCallCheck works in the React app, but stops working, when embedded into Rails.
Even the most recent version of the starterkit does not work either. In that case I do not get any error at all, but have the same behavior.
Due to the old Rails version I can not use the React Gem, and I need some of the functionality the starterkit provides out of the box, so changing this one, is no option either.
Any hints?

Resources