Vue CLI and Server-Side Rendering - javascript

I'm using Vue CLI which uses main.js to mount the app versus Server-Side Rendering (according to the tutorial I follow here) which uses app.js, entry-client.js and entry-server.js.
I tried to bypass main.js, but I'm having an error since its seems its required some how.
How can I work to keep main.js with app.js, entry-client.js and entry-server.js.
It might be a very basic question and I could maybe use the content of app.js in main.js, be I want to do things right.
main.js :
import Vue from 'vue'
import bootstrap from 'bootstrap'
import App from './App.vue'
import router from './router'
import store from './store'
import VueResource from 'vue-resource'
import BootstrapVue from 'bootstrap-vue'
import './registerServiceWorker'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.config.productionTip = false
Vue.use(VueResource)
Vue.use(BootstrapVue)
new Vue({
bootstrap,
router,
store,
render: h => h(App)
}).$mount('#app')
app.js :
import Vue from 'vue'
import App from './App.vue'
import bootstrap from 'bootstrap'
import { createRouter } from './router'
import store from './store'
import VueResource from 'vue-resource'
import BootstrapVue from 'bootstrap-vue'
import './registerServiceWorker'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.config.productionTip = false
Vue.use(VueResource)
Vue.use(BootstrapVue)
export function createApp () {
// create router instance
const router = createRouter()
const app = new Vue({
bootstrap,
router,
store,
render: h => h(App)
})
return { app, bootstrap, router, store }
}
server-client.js
import { createApp } from './app'
const { app, router } = createApp()
router.onReady(() => {
app.$mount('#app')
})
entry-server.js
import { createApp } from './app'
export default context => {
// since there could potentially be asynchronous route hooks or components,
// we will be returning a Promise so that the server can wait until
// everything is ready before rendering.
return new Promise((resolve, reject) => {
const { app, router } = createApp()
// set server-side router's location
router.push(context.url)
// wait until router has resolved possible async components and hooks
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject({ code: 404 })
}
// the Promise should resolve to the app instance so it can be rendered
resolve(app)
}, reject)
})
}
Any help is greatly appreciated.

I just found how. By default, Vue CLI 3 comes with no vue.config.js. I had to create one at the root of the folder to override the entry which was set to /src/main.js.
I used the vue.config.js found on this github and onverwrittes the entry to ./src/entry-${target}
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const nodeExternals = require('webpack-node-externals')
const merge = require('lodash.merge')
const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
const createApiFile = TARGET_NODE
? './create-api-server.js'
: './create-api-client.js'
const target = TARGET_NODE
? 'server'
: 'client'
module.exports = {
configureWebpack: () => ({
entry: `./src/entry-${target}`,
target: TARGET_NODE ? 'node' : 'web',
node: TARGET_NODE ? undefined : false,
plugins: [
TARGET_NODE
? new VueSSRServerPlugin()
: new VueSSRClientPlugin()
],
externals: TARGET_NODE ? nodeExternals({
whitelist: /\.css$/
}) : undefined,
output: {
libraryTarget: TARGET_NODE
? 'commonjs2'
: undefined
},
optimization: {
splitChunks: undefined
},
resolve:{
alias: {
'create-api': createApiFile
}
}
}),
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options =>
merge(options, {
optimizeSSR: false
})
)
}
}

Related

How to use i18n in the Vuex store

I have a project where I need to do translations inside the Vuex store. But I keep on getting an error when trying to translate using i18n inside the store.
I have tried to import and instance of i18n inside the store using the following import statement. But I then I get an error Uncaught TypeError: _i18n__WEBPACK_IMPORTED_MODULE_3__.default.t is not a function
import i18n from '#/i18n';
In the main.js file of my Vue project I import and use the i18n file:
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { store } from './store';
import i18n from './i18n';
createApp(App).use(i18n).use(store).use(router).mount('#app');
This is my i18n.js file that is located inside the src folder:
import { createI18n } from 'vue-i18n';
function loadLocaleMessages() {
const locales = require.context(
'./locales',
true,
/[A-Za-z0-9-_,\s]+\.json$/i
);
const messages = {};
locales.keys().forEach((key) => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
if (matched && matched.length > 1) {
const locale = matched[1];
messages[locale] = locales(key);
}
});
return messages;
}
export default createI18n({
legacy: false,
locale: localStorage.locale ?? 'nl',
globalInjection: true,
messages: loadLocaleMessages(),
});
For Vue 3 guys out there struggling with usage of i18n in the Vuex store, I was able to achieve it like this:
translations/index.js
with basic setup
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
fallbackLocale: 'en',
globalInjection: true,
messages: messages
})
export default i18n
main.js
Import store and i18n and use them in Vue app instance
import i18n from './translations'
import store from './store'
const app = createApp(App)
app.use(store)
app.use(i18n)
app.mount('#app')
Vuex store module file with getter example:
import i18n from './translations'
const getters = {
getNotification: (state) => {
...
notification.title = i18n.global.t('notification.title')
...
}
}
I used vue-i18n in Vuex. Maybe it helps to you.
Create vue-i18n.js file like this;
import Vue from "vue";
import VueI18n from "vue-i18n";
// Localisation language list
import { locale as en } from "#/core/config/i18n/en.js";
import { locale as ch } from "#/core/config/i18n/ch.js";
Vue.use(VueI18n);
let messages = {};
messages = { ...messages, en, ch };
// get current selected language
const lang = localStorage.getItem("language") || "en";
// Create VueI18n instance with options
const i18n = new VueI18n({
locale: lang, // set locale
messages // set locale messages
});
export default i18n;
and import it to Vue in main.js file;
import i18n from "#/core/plugins/vue-i18n";
new Vue({
router,
store,
i18n,
render: h => h(App),
}).$mount('#app')
import it inside your store or modules ( i imported in my vuex module);
import i18n from "#/core/plugins/vue-i18n";
then use it wherever you want (action, mutation, setter or getter);
const sample = i18n.t('ERRORS.NETWORKERROR');
en.js file;
export const locale = {
LOGIN: {
OPERATORID: "Operator ID",
SIGNIN:"Sign In",
SCANCARD: "Scan Card"
},
ERRORS: {
NETWORKERROR: "Network error occurred!",
UNAUTHUSERERROR: "Unauthorized user!",
}
};

Change I18n locale don't work for Vee-Validate - VueJS

I am using Vee-Validate plugin for form validation in my VueJS Application. So, my app has more than 1 language, for that, I am using I18n. All the plugins I am using are in separate files under plugins folder and then I am getting all files and registering all plugins in main.js, so in my Vee-Validate.js I have written:
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import VeeValidate from 'vee-validate';
import enMessages from "./../locales/validation/en";
import urMessages from "./../locales/validation/ur";
Vue.use(VueI18n);
const i18n = new VueI18n();
i18n.locale = "en";
Vue.use(VeeValidate, {
errorBagName: 'vErrors',
i18nRootKey: 'validations',
i18n,
dictionary: {
en: {
messages: enMessages
},
ur: {
messages: urMessages
}
}
});
But on clicking the change locale button don't change this file locale,
My change locale function:
changeLocale () {
this.$i18n.locale == 'en' ? this.$i18n.locale = 'ur' : this.$i18n.locale = 'en'
this.$vuetify.rtl = this.$i18n.locale == 'ur' ? true : false;
}
Well, I'm not saying your configuration is wrong. I'm going just share mine that is working just fine.
1- vue.config.js
module.exports = {
transpileDependencies: [
'vuetify',
],
pluginOptions: {
i18n: {
locale: 'en',
fallbackLocale: 'en',
localeDir: 'locales',
enableInSFC: true,
},
},
};
2- i18n.ts
import Vue from 'vue';
import VueI18n, { LocaleMessages } from 'vue-i18n';
Vue.use(VueI18n);
function loadLocaleMessages(): LocaleMessages {
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i);
const messages: LocaleMessages = {};
locales.keys().forEach((key) => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
if (matched && matched.length > 1) {
const locale = matched[1];
messages[locale] = locales(key);
}
});
return messages;
}
export default new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages(),
});
3- main.ts
import Vue from 'vue';
import './registerServiceWorker';
import { sync } from 'vuex-router-sync';
import VueLodash from 'vue-lodash';
import Storage from 'vue-ls';
import vuetify from './plugins/vuetify';
import './utils/vee-validate';
// Components
import './components';
// Application imports
import App from './App.vue';
import router from '#/router';
import store from '#/store';
import i18n from './i18n';
// Sync store with router
sync(store, router);
const options = {
name: 'ls', // name variable Vue.[ls] or this.[$ls],
storage: 'local', // storage name session, local, memory
};
Vue.use(Storage, options);
Vue.config.productionTip = false;
Vue.use(VueLodash, { name: 'lodash' });
new Vue({
router,
store,
vuetify,
i18n,
render: h => h(App),
}).$mount('#app');
4- src/plugins/vuetify.ts
import Vue from 'vue';
import Vuetify from 'vuetify/lib';
// import VueI18n from 'vue-i18n';
// import i18n from '#/i18n';
import en from '#/locales/en.json';
import jp from '#/locales/jp.json';
Vue.use(Vuetify);
export default new Vuetify({
lang: {
locales: { en, jp },
current: 'jp',
},
});
5- src/utils/vee-validate.js
/* eslint-disable no-underscore-dangle */
import { extend } from 'vee-validate';
import { required, email, confirmed } from 'vee-validate/dist/rules';
import i18n from '#/i18n';
extend('required', {
...required,
message: (_, values) => i18n.t('GENERAL_VALIDATION_MESSAGES_REQUIRED', values),
});
extend('email', {
...email,
message: (_, values) => i18n.t('LOGIN_FORM_EMAIL_VALID_MESSAGE', values),
});
extend('confirmed', {
...confirmed,
message: (_, values) => i18n.t('CHANGE_PASSWORD_FORM_CONFIRMATION_VALID_MESSAGE', values),
});
6- I use vuex so from my language store
import Vue from 'vue';
import { localize } from 'vee-validate';
import Vuetify from 'vuetify/lib';
import i18n from '#/i18n';
import en from '#/locales/en.json';
import jp from '#/locales/jp.json';
...
const mutations = {
SET_LANG(state, data) {
state.lang = data;
i18n.locale = data;
localize(data, jp);
},
SET_LANG_ERROR() {
window.$messageGlobal('Error switching languages');
},
};
Hope it helps

Netlify Vue Build Failing To Find Export Modules

So I have a couple of modules that I am importing in to my main.js and store.js file that are not being found when building in Netlify and I cannot understand why. When I run my build locally there is no issues.
So the 2 files are ability.js and storage.js
This is the alarm from the build in netlify
12:48:10 PM: These relative modules were not found:
12:48:10 PM: * ./utils/ability.js in ./src/main.js, ./src/store.js
12:48:10 PM: * ./utils/storage.js in ./src/store.js
12:48:10 PM: ERROR Build failed with errors.
Here is the ability.js file
import { Ability } from '#casl/ability'
export const ability = new Ability()
export const abilityPlugin = (store) => {
ability.update(store.state.rules)
const rules = store.subscribe((mutation) => {
switch (mutation.type) {
case 'createSession':
ability.update(mutation.payload[0])
break
case 'destroySession':
ability.update([{ actions: '', subject: '' }])
break
}
})
return rules
}
here is the storage.js file
export default (options) => (store) => {
if (localStorage.state) {
const storedState = JSON.parse(localStorage.state)
store.replaceState(Object.assign(store.state, storedState))
}
return store.subscribe((mutation, state) => {
if (options.destroyOn && options.destroyOn.indexOf(mutation.type) !== -1) {
return localStorage.removeItem('state')
}
const newState = options.storedKeys.reduce((map, key) => {
map[key] = state[key]
return map
}, {})
localStorage.state = JSON.stringify(newState)
})
}
and here are the 2 files where I import these modules
main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import { abilitiesPlugin } from '#casl/vue';
import { ability } from './utils/ability.js';
Vue.use(abilitiesPlugin, ability);
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import { abilityPlugin, ability as appAbility } from './utils/ability.js'
import storage from './utils/storage.js'
export const ability = appAbility
Vue.use(Vuex)
axios.defaults.baseURL = 'https://aewcpa.traxit.pro/api'
axios.defaults.headers.common['header1'] = {
'X-Requested-With': 'XMLHttpRequest',
}
export default new Vuex.Store({
plugins: [
storage({
storedKeys: ['rules', 'token'],
destroyOn: ['destroySession']
}),
abilityPlugin
],
})
The directory structure with issues
The directory structure that currently works
Is Utils in the root of your project? Are you using the Vue Webpack Template?
If so the # resolver is configured for you (ES6 import using at ('#') sign in path in a vue.js project using Webpack)
If so change:
from './utils/ability.js' to from '#/utils/storage.js'
and
from './utils/storage.js' to from '#/utils/storage.js'

VueRouter is undefined in karma unit tests

I'm getting warnings during tests running on my new Vue.js project. Wherever a component uses the router either in the template as a <router-link> or programatically as this.$router.push('/');
The tests are passing but logging these warnings:
ERROR LOG: '[Vue warn]: Error in render: "TypeError: Cannot read property 'resolve' of undefined"
found in
---> <RouterLink>
<Root>'
I'm using Vue2 and the project is based on the webpack project generated by the cli tool.
My unit test index.js:
import Vue from 'vue';
import VueNativeSock from 'vue-native-websocket';
import Router from 'vue-router';
Vue.use(Router);
Vue.use(VueNativeSock, process.env.WEBSOCKET_ADDR, { format: 'json', reconnection: true });
Vue.config.productionTip = false;
const testsContext = require.context('./specs', true, /\.spec$/);
testsContext.keys().forEach(testsContext);
const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/);
srcContext.keys().forEach(srcContext);
My main.js:
import Vue from 'vue';
import VueNativeSock from 'vue-native-websocket';
import VueHead from 'vue-head';
import App from './App';
import router from './router';
Vue.config.productionTip = process.env.NODE_ENV === 'production';
Vue.use(VueNativeSock, process.env.WEBSOCKET_ADDR, { format: 'json', reconnection: true });
Vue.use(VueHead);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App },
head: {
link: [
{ rel: 'icon', href: '/static/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
],
},
});
Any idea what I'm missing to get these warnings to go away?
A basic test might look like this:
import Vue from 'vue';
import ViewDTCs from '#/components/ViewDTCs';
describe('ViewDTCs.vue', () => {
const Constructor = Vue.extend(ViewDTCs);
const vm = new Constructor().$mount();
ViewDTCs.$socket = new WebSocket(process.env.WEBSOCKET_ADDR);
it('has a created hook', () => {
expect(typeof ViewDTCs.created).to.equal('function');
});
it('should render page', () => {
expect(vm.$el.textContent).to.contain('Retrieving DTCs');
});
});
It looks like your routes might not be set up anywhere in your test environment. If you're using Karma and Avoriaz or Vue Test Utils, I've been able to test components that contain routes like this:
import { mount } from 'vue-test-utils'
import router from 'src/router' // path to your router
import MyComponent from 'src/components/MyComponent'
describe('MyComponent.vue', () => {
let wrapper
beforeEach(() => {
wrapper = mount(MyComponent, {
router: router
})
})
it('has a created hook', () => {
expect(typeof wrapper.created).to.equal('function')
})
...
})

Correct way to install custom VueJs Plugin

Im creating a custom plugin that encapsulates a bunch of authentication functionality with vuex and vue-authenticate.
The problem im having is figuring out the correct way to load and install the module into VueJS, im not sure if its my webpack or vuejs knowledge that is lacking but so far I have the following
/node_modules/plugin/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import routes from './routes'
import store from './vuex/store'
import EventBus from './bus/eventBus'
import config from './config'
import ping from './vuex/modules/apiArchitect/ping/store'
import auth from './vuex/modules/apiArchitect/auth/store'
import user from './vuex/modules/apiArchitect/user/store'
Vue.use(Vuex)
Vue.use(EventBus)
const store = new Vuex.Store({
modules: {
ping,
user,
auth
},
strict: true
})
let apiArchitect = {}
apiArchitect.install = function (Vue, options) {
Vue.prototype.$apiArchitect = store,
Vue.prototype.$apiArchitect.$config = config
Vue.prototype.$apiArchitect.$routes = routes
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.use(apiArchitect)
}
}
export default apiArchitect
/src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import apiArchitect from 'vue-apiarchitect'
import addRouteGuards from 'vue-apiarchitect/src/addRouteGuards'
Vue.config.productionTip = false
Vue.config.env = process.env.NODE_ENV
Vue.use(router)
Vue.use(apiArchitect)
console.log(apiArchitect)
addRouteGuards(router)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
So far I am able to import the plugin and run the install hook with Vue.use(apiArchitect) I can access this.$apiArchitect in my App.vue.
The problem I have is that the plugin provides some auth routes stored in $apiArchitect.routes these routes need to be merged with the routes provided by router. If I try access $apiArchitect.routes in main.js I get an 'undefined' error I can only access them after vue has been instantiated. If I actually try console.log apiArchitect in main.js all I see is an object containing an install function none of the plugin i've provided which makes me belive its not installing correctly.
Does anyone know how i can access the apiArchitect.$routes property in main.js or a better way of achieving this?
Thanks
You can add routes dynamically with router.addRoutes() since 2.2.x.
The argument must be an Array using the same route config format with
the routes constructor option.
For example, you can use addRoutes in created hook of the root component:
// your plugin
const myPlugin = {
install: function(Vue, options) {
Vue.prototype.$myPlugin = {
routes: [{
path: '/myplugin', component: options.myComponent
}]
}
}
}
// components
const testComponent = { template: '<p>Component called by plugin route</p>' }
const Home = { template: '<p>Default component</p>' }
// router
const router = new VueRouter({
routes: [{
path: '/', component: Home
}]
})
Vue.use(VueRouter)
Vue.use(myPlugin, { myComponent: testComponent })
new Vue({
el: '#app',
router,
created() {
this.$router.addRoutes(this.$myPlugin.routes); // dinamically add routes
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<button #click="$router.push('/')">Home</button>
<button #click="$router.push('/myplugin')">Custom</button>
<router-view></router-view>
</div>

Categories

Resources