Vue3 doesn't seem to be loaded in Rails 7 + shakapacker app - ruby-on-rails

I created a new project for a shopify app with rails 7 and shakapacker. I want to use Vue components in my .slim files. The problem is that Vue doesn't seem to be loaded in my app, although I don't get any errors.
Here is what I did:
// config/webpack/rules/vue.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
extensions: [
'.vue'
]
}
}
// config/webpack/webpack.config.js
const { webpackConfig, merge } = require('shakapacker')
const vueConfig = require('./rules/vue')
module.exports = merge(vueConfig, webpackConfig)
// app/javascript/packs/application.js
import HelloWorld from '../components/HelloWorld'
import { createApp } from 'vue'
const app = createApp({
el: '#app'
})
app.component('helloworld', HelloWorld)
document.addEventListener('DOMContentLoaded', () => {
app
})
// app/javascript/components/HelloWorld.vue
<template>
<h1>Hello world</h1>
</template>
<script>
export default {
name: 'HelloWorld'
}
</script>
/ app/views/layouts/embedded_app.slim
doctype html
html[lang="en"]
head
meta[charset="utf-8"]
- application_name = ShopifyApp.configuration.application_name
title
= application_name
= stylesheet_link_tag "application", "data-turbo-track": "reload"
= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
= csrf_meta_tags
body
#app
.wrapper
main[role="main"]
= yield
= content_tag(:div, nil, id: 'shopify-app-init', data: { api_key: ShopifyApp.configuration.api_key,
shop_origin: #shop_origin || (#current_shopify_session.shop if #current_shopify_session),
host: #host,
debug: Rails.env.development? })
And finally, the view where I just want to display the HelloWorld.vue component:
/ app/views/home/index.slim
helloworld
However, nothing is displayed and I have no errors. I tried to modify the creation of the app in this way, to see if the log appears:
// app/javascript/packs/application.js
import HelloWorld from '../components/HelloWorld'
import { createApp } from 'vue'
const app = createApp({
el: '#app',
created () {
console.log('ok')
}
})
app.component('helloworld', HelloWorld)
document.addEventListener('DOMContentLoaded', () => {
app
})
but then again, I have nothing in console, so I'm not even sure that the app is well rendered. On the other hand, I checked that the DOMContentLoaded event is indeed triggered and it is.
I'm not very comfortable with webpack so I don't know if something is wrong with my configuration, I followed shakapacker's README.
I don't think this is related, but the app is rendered in a Shopify test store via an Ngrok tunnel.
I don't know where to look anymore... Does anyone have an idea?
Thanks in advance

I haven't written any VueJS in a long time, but this is usually what I do in my application.js using React & Shopify Polaris components.
function initialize() {
const rootElement = document.getElementById('app')
const root = createRoot(rootElement);
/* some app bridge code I removed here */
/* react 18 */
root.render(
<BrowserRouter>
/* ... */
</BrowserRouter>
)
}
document.readyState !== 'loading' ? initialize() : document.addEventListener('DOMContentLoaded', () => initialize())
If your <div id="app"> is EMPTY when inspected with browser tools, my first guess would be you're creating an instance, but not actually rendering it in the end.
An application instance won't render anything until its .mount() method is called.
https://vuejs.org/guide/essentials/application.html#mounting-the-app
I would've commented to ask first, but I don't have enough reputation points to do so

Related

vuejs component inside legacy rails modal

I want to implement a vuejs component inside an existing bootstrap slim modal.
In the modal I reference the container of the component like usually:
form.slim
= modal do
= javascript_pack_tag 'product_asset', 'data-turbolinks-track': 'reload'
#product_assets[
data-product-asset-routes=JsRoutes.generate(include: /product_asset/)
]
end
Outside of a modal it works properly. But in this action it doesn't.
The console output shows this:
Source map error: Error: request failed with status 404
Resource URL: null
Source Map URL: product_asset-ed5ee8937047520ba766.js.map
Anyone of you handled with this kind of problems?
I expected an event to fire. Something like turbolinks:load. But this is an XHR-Request where this kind of stuff doesnt happen.
From:
document.addEventListener(turbolinks:load => ({
const productAsset = new Vue({
el: '#product_asset_form',
store,
railsI18n,
productAssetRoutes,
render: h => h(ProductAsset, { props: { ...root_element.dataset } }),
}).$mount()
)}
To:
const productAsset = new Vue({
el: '#product_asset_form',
store,
// this is vue-i18n magic...
i18n: railsI18n,
productAssetRoutes,
render: h => h(ProductAsset, { props: { ...root_element.dataset } }),
}).$mount()
And now it fires immediatly

React + Rails with Redux Persist Gate

I am new to everything as specially to react_on_rails and I am trying to figure out how to use redux-persist with my project so that when I changing the page, I am not losing any redux store. I have figure out the redux setup but I cannot get Redux Persist to work right and it still fails to Uncaught Error: Could not find store registered with name 'rootStore'. Registered store names include [ ]. Maybe you forgot to register the store? I just wondering if anyone can help with it to fix a problem. I tried to go over the documentation several times and did really helped me with the option that I have.
my application view
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= notice %>
<%= alert %>
<%= redux_store('rootStore', props: {}) %>
<%= react_component('ProductDetailPage', props: {product: #product.id}) %>
<%= yield %>
<%= redux_store_hydration_data %>
</body>
</html>
My entry point to register ProductDetailPage
import ReactOnRails from 'react-on-rails';
import ProductDetailPage from '../pages/ProductDetailPage';
import { registerRootStore } from '../utils/ReactOnRails';
ReactOnRails.setOptions({
traceTurbolinks: true,
});
ReactOnRails.register({ProductDetailPage});
registerRootStore();
utils/ReactOnRails
import { configureStore } from '../store/rootStore';
export default function getReactOnRails() {
window.ReactOnRails = window.ReactOnRails || require('react-on-rails').default;
return window.ReactOnRails;
}
export const registerRootStore = function () {
if (getReactOnRails().storeGenerators().has('rootStore')) {
return;
}
getReactOnRails().registerStore({rootStore: configureStore });
};
store/rootStore
import { createStore } from 'redux';
import reducerIndex from '../reducers/index';
import { persistReducer} from 'redux-persist';
let _store;
let _persistor;
export const configureStore = function (props) {
if (_store) return _store;
const initialState = (props) ? {...props} : {};
_store = createStore(reducerIndex, initialState);
_persistor = persistReducer(_store);
return _store;
};
export const getPersistor = function () {
return _persistor;
};
reducers/index
import { combineReducers } from 'redux';
import { persistReducer} from 'redux-persist';
import cartReducer from './cartReducer';
const rootReducer = combineReducers({
cart: cartReducer,
});
const reducer = persistReducer(
{
key: 'root',
storage: require('localforage'),
},
rootReducer
);
export default reducer;
And the last file which handles all other components that will be later injected with.
// #flow
import * as React from 'react';
import { Provider } from 'react-redux';
import getReactOnRails from '../utils/ReactOnRails';
import { PersistGate } from 'redux-persist/es/integration/react';
import { getPersistor } from '../store/rootStore';
type Props = {
children: React.Node,
loading?: React.Node,
}
const rootStore = getReactOnRails.getStore('rootStore'); // another error that happening for me, it says that getStore is not a function.
export default class ProviderGate extends React.Component<Props> {
render() {
const persistor = getPersistor();
if (!persistor) return this.props.children;
return <Provider store={rootStore}>
<PersistGate persistor={persistor} loading={this.props.loading}>
{this.props.children}
</PersistGate>
</Provider>;
}
}
After a couple hours of debuging I actually find why my persistStore was not working. Here the things I did in my code.
store/rootStore
_persistor = persistReducer(_store); => _persistor = persistStore(_store);
Main Entry file
ReactOnRails.register({ProductDetailPage});
registerRootStore();
Should be
registerRootStore();
getReactOnRails().register({ProductDetailPage});
and finally a component that responds to PersistGate and redux Provider it should be rendered in the component, not outside of the class and it should be like this
const rootStore = getReactOnRails.getStore('rootStore');

Trying to integrate Azure media player in React JS. but Azure dependent JavaScript lib function doesn't found "amp"

This is my react js file code to render media player.
componentDidMount() I imported js file on a load of this page and at the time of render call 'amp' function with options parameter.
componentDidMount () {
const script = document.createElement("script");
script.src = "//amp.azure.net/libs/amp/2.1.5/azuremediaplayer.min.js";
script.async = true;
document.body.appendChild(script);
}
render() {
var myOptions = {
"nativeControlsForTouch": false,
controls: true,
autoplay: true,
width: "640",
height: "400",
}
var myPlayer = amp("azuremediaplayer", myOptions);
myPlayer.src([
{
"src": "//amssamples.streaming.mediaservices.windows.net/91492735-c523-432b-ba01-faba6c2206a2/AzureMediaServicesPromo.ism/manifest",
"type": "application/vnd.ms-sstr+xml"
}
]);
return (
<div className="form-horizontal">
<div className="form-group">
<div className="col-sm-4">Azure Media Player</div>
<div className="col-sm-6">
<video id="azuremediaplayer" class="azuremediaplayer amp-default-skin amp-big-play-centered" tabindex="0"></video>
</div>
</div>
</div>
);
}
}
export default AddItemForm;
And console gives this error
** Line 26: 'amp' is not defined no-undef**
I think if you load the player's script in a sync way like script.async = false;, it would work properly .. I know it would harm the performance, but unfortunately this is the way to go with loading this weirdly packaged player! .. I hate the fact that they didn't make an npm package for it!!
You could provide onload callback for your script like this:
script.onload = () => this.setState({libraryLoaded: true})
And then you can react on the state inside render method
render() {
if (!this.state.libraryLoaded) {
return <span>Loading...</span>
} else {
return ... // your component
}
}
Do not forget to initiate the state with libraryLoaded: false
You can also check my package which is basically doing this loading under the hood.

Value of Base tag appended to URL after component load

I have an ASP.NET MVC application that is using Angular 4. In my layout I have a base tag that looks like this:
<base href="/src/">
I am setting everything up and I just added Angular Routing. Now right after my base component loads my URL is appended with 'src'.
Here is my routes file:
import { Routes } from '#angular/router';
import { HomeComponent } from './Components/Home/home.component';
export const AppRouting: Routes = [
{ path: '', component: HomeComponent }
];
I did not see this prior to adding the routing.
The key purpose of the base tag is for routing. This is from the docs:
Most routing applications should add a element to the
index.html as the first child in the tag to tell the router how
to compose navigation URLs.
If the app folder is the application root, as it is for the sample
application, set the href value exactly as shown here.
https://angular.io/guide/router#base-href
At development time, it is most often set to "/" so the routes will run from root. At deployment, you change it to the folder on the server containing your application.
I was able to fix this. For reference, my app folder is under a src directory, not in the root of my project. Here is what I did.
Change the base tag to:
<base href="/">
Update my main.js call from:
<script>
System.import('main.js').catch(function (err) { console.error(err); });
</script>
to:
<script>
System.import('src/main.js').catch(function (err) { console.error(err); });
</script>
Then in my systemjs.config.js I had to change these lines:
map: {
//app is within the app folder
'app': 'app',
to:
map: {
//app is within the app folder
'app': 'src/app',
and I also had to change:
packages: {
app: {
defaultExtension: 'js',
meta: {
'./*.js': {
loader: 'systemjs-angular-loader.js'
}
}
},
to:
packages: {
app: {
defaultExtension: 'js',
meta: {
'./*.js': {
loader: 'src/systemjs-angular-loader.js'
}
}
},

How can I use the Vue.js code from a webpack bundle in a Razor page?

Here is my Razor page code:
#using System.Web.Optimization;
#{ BundleTable.Bundles.Add(
new ScriptBundle("~/Scripts/Vuejs")
.Include("~/Static/Scripts/Vuejs/vue.min.js")); }
#section Scripts {
#Scripts.Render("~/Static/vue/assets/bundle.js")
#Scripts.Render("~/Scripts/Vuejs")
}
<div id="app_container">
{{text}}
</div>
and here is the entry of the webpack javascript:
import Vue from 'vue';
const v = new Vue({
el: '#app_container',
data: { text: 'abcdefg' }
});
Webpack config:
export default {
entry: [
'babel-polyfill',
'./src/index.js'
],
output: {
filename: 'bundle.js',
path: 'C:/WebSandbox/Static/vue/assets',
publicPath: '/vue/assets/'
},
devtool: 'source-map',
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue' },
{ test: /\.js/, loader: 'babel', exclude: /node_modules/ },
{ test: /\.json$/, loader: 'json' },
{ test: /\.txt/, loader: 'raw' }
]
},
plugins: [
new webpack.optimize.DedupePlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production'),
APP_ENV: JSON.stringify('browser')
}
})
]
};
All the javascript files are in place and when open the page I can see the mapped code from Developer Tools of Chrome. And if I make a break point in the javascript code, it will be hit.
But the text displayed is "{{text}}", not "abcdefg".
If I added following code after the div:
<script>
const v = new Vue({ el: "#app_container", data: { text: "abcdefg" } });
</script>
or add following code and remove the javascript file from #section Scripts part
<script src='~/Static/vue/assets/bundle.js'></script>
It works.
So how can I make my webpack bundle work with the #Scripts.Render in Razor page?
OK, now I found why the bundle not working:
Because the #RenderSection("Scripts", false) was written in the header of _Layout.cshtml. So the bundle JavaScript file will be referenced in the header. But when I switch to the raw reference (script tag), it will be after my div tag where it should be.
When I change the bundle part to:
#section ScriptBlock {
#Scripts.Render("~/Static/vue/assets/bundle.js")
}
It works.
The #RenderSection("ScriptBlock", false) was written in the bottom of the _Layout.cshtml right after the closing tag of body.
I use vuejs with asp.net with browserify rather than webpack.
And the packing should be an independent build step.
I setup a gulp task to take my vue code and bundle it up and place it in the scripts folder of asp.net.
I think you need to do something similar here.

Resources