Complete logic decoupling with Vuex - vue.js

I'm building a Vue web application with Vue Router and Vuex, using vue-cli for scaffolding. The majority of the project structure is very simple, with one main store and simple routing:
project/
├── src/
│ ├── components/
│ │ ├── Hosting/
│ │ └── Website/
│ ├── router/
│ ├── store/
│ ├── App.vue
│ └── main.js
└── index.html
The Hosting component, routed at /hosting, needs to be decoupled from the rest of the website. That URL will host a component that will be loaded on other websites using an <iframe>. What's the best way to accomplish this logic decoupling? I'd thought about including a store directory in the Hosting folder, but wasn't sure how to inject that into the Hosting root component. I looked into using modules, but this doesn't really accomplish what I want since all of the modules are accessible from every component.
Note that I don't actually need the /hosting endpoint to use the same routing as the rest of the website, since it will only ever be accessed as the src of an <iframe>. So if I need to do something with how webpack compiles the project (like creating a hosting.html target in addition to index.html), I could do that.

You can consider the answer that Joachim gave you, I mean split to 2 apps, and I am doing it on my application (providing the configuration on webpack)
webpack.config.js
...
entry: {
'./app-1.js': './src/app1/main.js',
'./app-2.js': './src/app2/main.js'
},
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/build/',
filename: '[name]'
},
...
If you need to access the same store from both apps, I think you should re-consider the modules solution in vuex. I am splitting my store into modules and load the wanted modules in each application (in App.vue in the beforeCreate lifecycle hook). Looks something like this:
main1.js
import Vue from 'vue'
import App from './App.vue'
import { store } from '../store/store';
new Vue({
el: '#app2',
store: store,
render: h => h(App)
});
main2.js
import Vue from 'vue'
import App from './App.vue'
import { store } from '../store/store';
new Vue({
el: '#app1',
store: store,
render: h => h(App)
});
(please note that both apps load same store)and then:
App1.vue
export default {
...
beforeCreate() {
// this is where we add dynamically specific modules used on this app.
const store = this.$store;
const modulesToBoDynamicallyRegistered = {
[Types.module1.TYPE_NAME]: module1,
[Types.module2.TYPE_NAME]: module2,
[Types.module3.TYPE_NAME]: module3,
[Types.module4.TYPE_NAME]: module4,
};
Object.keys(modulesToBoDynamicallyRegistered).forEach((moduleName) => {
utils.registerModuleIfNotExists(store, moduleName, modulesToBoDynamicallyRegistered[moduleName]);
});
},
...
}
App2.vue
export default {
...
beforeCreate() {
// this is where we add dynamically specific modules used on this app.
const store = this.$store;
const modulesToBoDynamicallyRegistered = {
[Types.module7.TYPE_NAME]: module7,
[Types.module9.TYPE_NAME]: module9,
};
Object.keys(modulesToBoDynamicallyRegistered).forEach((moduleName) => {
utils.registerModuleIfNotExists(store, moduleName, modulesToBoDynamicallyRegistered[moduleName]);
});
},
...
}
the store itself holds the common modules:
store.js
import Vue from 'vue';
import Vuex from 'vuex';
import module5 from './modules/module5/index';
import module6 from './modules/module6/index';
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
module5,
module6,
}
});
if you need the registerModuleIfNotExists function:
registerModuleIfNotExists: (store, moduleName, module) => {
if (!(store && store.state && store.state[moduleName])) {
store.registerModule(moduleName, module);
} else {
// re-use the already existing module
throw `reusing module: ${moduleName}`;
}
},

Consider the Hosting component as a completely separate app would give you the freedom to host it on other sites as well. So yes, create a hosting.html page to show in the iframe and in other sites.
That would make your own usage of the component align completely with how it is hosted on other sites and make finding bugs easier as well.
You did not mention if the hosting component requires any data or parameters from the containing structure, but if so, you should provide this in the query string of hosting.html.

Related

How to tell Vite to only build a specific component in library mode?

I have a Vue project that is able to load other Vue components bundled as .mjs files. I want to develop all those pluggable components inside a repository but instead of distributing all pluggable components in a single build I want to tell Vite which component to build. If that works I don't need to think about dealing with a Monorepo, Multirepo or something else.
After creating the library project I thought about organizing each plugin into a separate folder in the src directory
.
└── src
├── textWithBlueBackground
| ├── index.ts ( importing "TextWithBlueBackground" and exporting as "Renderer" )
| └── TextWithBlueBackground.vue
└── textWithRedBackground
├── index.ts ( importing "TextWithRedBackground" and exporting as "Renderer" )
└── TextWithRedBackground.vue
The problem is that I need to switch to library mode but I don't know what to pass in there
build: {
lib: {
entry: resolve(__dirname, "./src/index.ts"), // this is wrong
name: "Renderer",
fileName: "renderer",
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
},
},
},
After fixing that... Is it possible to tell Vite ( via CLI flag ) to only build a specific sub directory? E.g.
vite build ./src/{folderName}
If that's not possible I could create an index.ts in src
import { Renderer as TextWithBlueBackground } from "./textWithBlueBackground";
import { Renderer as TextWithRedBackground } from "./textWithRedBackground";
export { TextWithBlueBackground, TextWithRedBackground }
but how can I tell Vite to only build a specific component then?
The generated .mjs file should only contain the desired component, preferably as "Renderer" but I think the component name should be fine too.

Is there a way to import a folder structure in Vite

I have a bunch of sub-folders plus a few .vue components in the src/pages folder. With webpack I was able to get a list of the page paths and name with code like this:
export default require
.context("../pages", true, /^\.\/.*\.vue$/)
.keys()
.map(page => page.slice(2).replace(".vue", ""))
.filter(page => page !== "Index")
.map(page => ({
file: page,
title: createTitle(page),
path: slugify(kebabCase(page))
}));
Vite doesn't seem to support this. I tried const pages = import.meta.glob('../pages/*.vue') but this only works for files, not files inside sub-folders.
Any idea how I can achieve this with Vite?
I found a way. It's not perfect but not terrible either:
const pages = import.meta.glob('../pages/*.vue')
const folders = import.meta.glob('../pages/*/*.vue')
const both = {...pages, ...folders}
export default both
This is a refinement:
const pages = import.meta.glob('../pages/**/*.vue')
export default pages
I think you're looking for something like vite-plugin-pages :
installation :
npm install -D vite-plugin-pages
which requires vue-router to be installed :
npm install vue-router
Add to your vite.config.js:
import Pages from 'vite-plugin-pages'
export default {
plugins: [
// ...
Pages(),
],
}
Router config :
import { createRouter } from 'vue-router'
import routes from '~pages'
const router = createRouter({
// ...
routes,
})
There're also other plugin like unplugin-vue-components to resolve components and vite-plugin-vue-layouts to resolve layouts

Exporting Vue and Vuex in one custom npm package

I am trying to break out a Vue/Vuex project into an npm package hosted privately. I think I am getting there however I am unsure of my current layout, so far I have:
src/
├── BrokerProposal
│   ├── FormInputs
│ ├── index.js
│   └── modals
└── store
├── index.js
└── modules
└── proposal
├── actions
├── getters
├── helpers
├── mutations
└── states
└── validations
My aim is to make the BrokerProposal directory importable which is done via the first index.js file:
const BrokerProposal = require('./BrokerCalculator.vue');
function install(Vue) {
if (install.installed) return;
install.installed = true;
Vue.component("v-broker-proposal", BrokerProposal);
}
const plugin = {
install
};
let GlobalVue = null;
if (typeof window !== "undefined") {
GlobalVue = window.Vue;
} else if (typeof global !== "undefined") {
GlobalVue = global.vue;
}
if (GlobalVue) {
GlobalVue.use(plugin);
}
BrokerProposal.install = install;
export default BrokerProposal;
This project also uses vuex so I have broken out the mutators etc of the state into this package to go along with the BrokerProposal, the end user can then bind this store once importing, here is the index.js file:
import Vue from 'vue'
import Vuex from 'vuex'
// Vuex Modules
import tabs from './modules/tabs'
import proposal from './modules/proposal'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
tabs,
proposal,
},
})
export default store
I feel like I should be including another index.js file in on the same level as /src as there is a section 'main' in the package.json file that must point to something?
Side effects like Vue.use(Vuex) and GlobalVue.use(plugin) should be avoided because they may interfere with a project that consumes this package. It's the responsibility of a project to set up plugins with appropriate Vue instance.
All public exports can be named exports in entry point, e.g. src/index.js:
export { default as BrokerProposal } from './BrokerProposal';
export { default as store } from './store';
Also it's a good practice to export components, in case they need to be imported locally instead of relying on global registration with Vue.component.

Loading vuetify in a package that i use in a vuetify project

What is the correct way of loading vuetify into a package that i use in a vuetify project?
When serving projects it all seems to work fine but when i build the project i've got some issues with the css/sass
things i've tried:
With vuetify loader: the css is loaded twice so i can't overwrite sass variables
Without vuetify loader: the package doesn't have the vuetify css, so it looks horrible
Without vuetify loader with vuetify.min.css: the css is loaded twice so i can't overwrite sass variables, and the loaded css is all the css so it's huge
My package is called vuetify-resource, and this is the source code of the index.js (without the vuetify loader) At this point everything works on npm run serve But when i build the package doesn't have "access" to the vuetify css.
import Vue from 'vue';
import Vuetify from 'vuetify';
import VuetifyResourceComponent from './VuetifyResource.vue';
Vue.use(Vuetify);
const VuetifyResource = {
install(Vue, options) {
Vue.component('vuetify-resource', VuetifyResourceComponent);
},
};
export default VuetifyResource;
To solve my issue i had to do a couple of things.
Make peer dependencies of vuetify and vue
add vuetify to the webpack externals, so when someone uses the package, the package uses that projects vuetify
not longer import vue and vuetify in the index.js it's not needed, the project that uses the package imports that
import the specific components that you use in every .vue file
for example:
Vue.config.js
module.exports = {
configureWebpack: {
externals: {'vuetify/lib': 'vuetify/lib'},
},
};
index.js
import VuetifyResourceComponent from './VuetifyResource.vue';
const VuetifyResource = {
install(Vue, options) {
Vue.component('vuetify-resource', VuetifyResourceComponent);
},
};
export default VuetifyResource;
part of the component.vue
import { VDataTable } from 'vuetify/lib';
export default {
name: 'vuetify-resource',
components: {
VDataTable
},
Step 4 in Ricardo's answer is not needed if you use vuetify-loader, it will do the job for you.
And I would modify step 2 to also exclude Vuetify's styles/css from your bundle. If you don't exclude them you can run into styling issues when the Vuetify version differ between your library and your application.
Use a regular expression in vue.config.js like this: configureWebpack: { externals: /^vuetify\// }. That way, only your own styles are included in the library bundle.

vue-router doesn't load / display components?

import Vue from 'vue';
import Router from 'vue-router';
//
// Application routes
//
const appRoutes = []
//
// let's add 404 page
// in the case we have landed on non-processed URL
//
import PageNotFound from '#/lib/components/page-not-found';
appRoutes.push({
path: '*',
name: 'PageNotFound',
component: PageNotFound,
meta: {
layout: 'DefaultLayout',
},
});
Vue.use(Router);
export default new Router({
mode: 'history',
saveScrollPosition: true,
routes: appRoutes,
});
Such code does not display anything in the browser when I hit any URL (as router is configured for path '*'), however if I replace to async load
appRoutes.push({
path: '*',
name: 'PageNotFound',
component: () => import('#/lib/components/page-not-found'),
meta: {
layout: 'DefaultLayout',
},
});
then everything starts to work.
Chrome console displays NO errors
2865:├─┬ eslint-plugin-vue#4.5.0
2866:│ └─┬ vue-eslint-parser#2.0.3
3912:├─┬ jest-serializer-vue#1.0.0
5629:│ └── vue-resize#0.4.4
5631:├── vue#2.5.16
5632:├─┬ vue-alertify#1.0.5
5640:│ └── vue#2.5.16 deduped
5641:├── vue-autosuggest#1.4.1
5642:├── vue-axios#2.1.1
5643:├── vue-class-component#6.2.0
5644:├── vue-error-boundary#1.0.1
5645:├── vue-extend-layout#1.1.2
5646:├── vue-head#2.0.12
5647:├─┬ vue-highlightable-input#1.0.5
5652:├── vue-i18n#7.8.1
5653:├─┬ vue-jest#2.6.0
5675:│ └── vue-template-es2015-compiler#1.6.0
5676:├─┬ vue-loader#14.2.3
5693:│ ├── vue-hot-reload-api#2.3.0
5694:│ ├── vue-style-loader#4.1.0 deduped
5695:│ └── vue-template-es2015-compiler#1.6.0 deduped
5696:├─┬ vue-meta#1.5.0
5700:├── vue-momentjs#0.1.2
5701:├─┬ vue-multi-select#3.5.1
5702:│ └── vue#2.5.16 deduped
5703:├── vue-progress-path#0.0.2
5704:├─┬ vue-property-decorator#6.1.0
5706:│ └── vue-class-component#6.2.0 deduped
5707:├─┬ vue-resize-directive#1.0.1
5710:├─┬ vue-responsive-components#0.2.3
5713:├── vue-router#3.0.1
5714:├─┬ vue-routisan#2.1.1
5715:│ └── vue-router-multiguard#1.0.3
5716:├── vue-select#2.4.0
5717:├── vue-spinner#1.0.3
5718:├─┬ vue-style-loader#4.1.0
5721:├─┬ vue-styled-components#1.2.3
5746:├── vue-svg-sprite#1.2.3
5747:├── vue-table-component#1.9.1
5748:├─┬ vue-template-compiler#2.5.16
5751:├── vue-toasted#1.1.24
5752:├── vue-truncate-filter#1.1.7
5753:├── vue-upload-component#2.8.9
5754:├── vue2-filters#0.3.0
5755:├─┬ vue2-sentry#1.2.1
5757:│ └── vue#2.5.16 deduped
5758:├── vue2-transitions#0.2.3
5759:├─┬ vuejs-uploader#0.6.5
5760:│ └── vue#2.5.16 deduped
5761:├── vuex#3.0.1
5762:├── vuex-cache#1.1.1
5763:├── vuex-class#0.3.1
5764:├── vuex-loading#0.3.0
5765:├─┬ vuex-search#2.2.1
Any ideas?
So I had to run
vue init webpack
and then started moving the code from the project one folder by another, at the very end everything worked just fine without any code modifications
As I was copying the folders one by one I was checking if code compiles against simple HelloWorld component that vue init has generated, then started to import configs, stores etc and then finally everything worked with non-async import of the component in the router config.
Before I have started a new project by cloning the folder (cp -R) and maybe that was an issue.