3

i'm currently trying to create a library out of an existing project, that should export a component with a "flow" inside it (eg. Imagine the library as an iframe replace, so importing the library should give the possibility to import a component that handle it's own flow, data, etc..).

So, the pinia instance is shared across the Host and the Library.

The Host has also a Vuex instance installed on it, we will see it later on.

Library

Node: v16.17.0

npm: v8.15.0

Library exports a default method install that manage to apply all the plugins that needs in order to execute correctly.

export default {
    install(Vue) {
        // Vue.use(...) Vue custom plugins that install only prototypes
        Vue.use(CustomRouterPlugin); // install a plugin that uses store inside it, it replace the VueRouter functionality with our state based management
        Vue.component("LibraryCore", LibraryCore);
    },
};

It also exports all the stores used by the Library and LibraryCore component:

import * as Stores from '@/stores'

// install method exports

export { Stores, LibraryCore };

index.js

import { useAStore } from "./astore";
import { useBStore } from "./bstore";
import { useCStore } from "./cstore";
// continue ...
export {
    useAStore,
    useBStore,
    useCStore
};

stores/index.js

All the stores in Library are pretty the same:

import { defineStore } from "pinia";

import { routes } from "@/router";


export const useRouterStore = defineStore("router", {
    state: () => ({
        routeName: "home",
        routes: routes, // {'home': { name: 'home',component: Object | VueComponent, etc..} }
    }),
    getters: {
        currentRoute: function () {
            return this.routes[this.routeName];
        },
    },
    actions: {
        setCurrentRoute: function (routeName) {

            if (!this.routes[routeName]) {
                throw new Error(`Route ${routeName} does not exist`);
            }
           this.routeName = routeName;
        },
    },
});

stores/namestore.js

This is the Library vite config file:

import { fileURLToPath } from "url";
import { defineConfig, loadEnv } from "vite";
import { createVuePlugin as vue2 } from "vite-plugin-vue2";
import path from "path";
/** @type {import('vite').UserConfig} */

export default ({ mode }) => {
    let buildConfigs = {
        brotliSize: true,
    };

    if (mode.includes("library")) {
        buildConfigs.lib = {
            entry: path.resolve(__dirname, "src/index.js"),
            name: "cf-components",
            formats: ["es"],
            fileName: (format) => `cf-components.${format}.js`,
        };
        buildConfigs.rollupOptions = {
            external: [
                'vue',
                'pinia',
                'vue-router'
            ],

            output: {
                dir: "dist",
                globals: {
                    vue: "Vue",
                },
            },
        };
    }

    return defineConfig({
        plugins: [
            vue2({
                jsx: true,
            }),
        ],
        resolve: {
            alias: {
                "@": fileURLToPath(new URL("./src", import.meta.url)),
                vue: "vue/dist/vue.esm.js",
            },
            dedupe: ["vue"],
        },
        build: buildConfigs,
    });
};

vite.config.js

And the Library package.json:

{
    "name": "@company/cf-components",
    "version": "0.2.10",
    "private": false,
    "repository": {
        "type": "git",
        "url": "repo url"
    },
    "files": [
        "dist"
    ],
    "main": "dist/cf-components.cjs.js",
    "module": "dist/cf-components.es.js",
    "exports": {
        ".": {
            "require": "./dist/cf-components.cjs.js",
            "import": "./dist/cf-components.es.js"
        },
        "./css": "./dist/style.css"
    },
    "sideEffects": false,
    "scripts": {
        "dev": "vite",
        "build-staging": "vite build --mode staging",
        "build-library": "vite build --mode library",
        "build": "vite build",
        "serve": "vite preview"
    },
    "dependencies": {
        "@formkit/auto-animate": "^1.0.0-beta.3",
        "@lottiefiles/vue-lottie-player": "^1.0.9",
        "@tailwindcss/line-clamp": "^0.4.2",
        "chart.js": "^3.9.1",
        "dompurify": "^2.4.0",
        "lodash": "^4.17.21",
        "marked": "^4.1.0",
        "tippy.js": "^6.3.7",
        "vue-i18n": "^8.28.2",
        "axios": "^0.27.2",
        "js-cookie": "^3.0.1"
    },
    "peerDependencies": {
        "vue": "^2.7.10",
        "vue-router": "^3.5.1",
        "pinia": "^2.0.22",
    },
    "devDependencies": {
        "@types/markdown-it": "^12.2.3",
        "@vitejs/plugin-basic-ssl": "^0.1.2",
        "autoprefixer": "^10.4.8",
        "postcss": "^8.4.16",
        "prettier": "^2.7.1",
        "prettier-plugin-tailwindcss": "^0.1.13",
        "tailwindcss": "^3.1.8",
        "vite": "^3.2.0",
        "vite-plugin-vue2": "^2.0.2",
        "vue-template-compiler": "^2.7.10"
    }
}

package.json

All components inside Library use mapState,mapAction and mapStores from pinia.

Running the Library as a normal Vue app using npm run dev works fine.

Host

Node: v16.17.0

npm: v8.15.0

Host is a laravel application using laravel mix: 8.15.0

The Library package is imported using npm link for local development.

The Host imports Library and install it's plugin:

import style from '@company/cf-components/css';
import { createPinia, PiniaVuePlugin } from 'pinia';
import LibraryPlugin, { AxiosPiniaPlugin, CfStores } from '@company/cf-components';

const pinia = createPinia();
Vue.use(PiniaVuePlugin); // imported from pinia
pinia.use(AxiosPiniaPlugin); // custom pinia plugin that inject a custom axios instance
Vue.use(LibraryPlugin);


const app = new Vue({
    el: '#app',
    router: router,
    store: store, // the host's vuex store
    i18n: i18n,
    pinia: pinia,
    render: (h) => h(App),
});

app.js

And use the Library component inside a Test component:

<template lang="">
    <LibraryCore class="mt-32" :isPluginValue="true"></LibraryCore>
    <!-- :isPluginValue tell the library component to act as a Plugin, so it doesn't use the VueRouter but our custom made Router based on state -->
</template>
<script>
import style from '@company/cf-components/css';
import { mapStores } from 'pinia';

export default {
 name: "Test"
};
</script>

In Host the LibraryCore is rendered correctly.

Also the Vue DevTools shows pinia's stores correctly with also the Vuex's store.

When an action that should mutate the Library's store is called, the values inside the devtools pinia's store change correctly, but the changes are not reflected inside the components.

Example:

mapping the "setCurrentRoute" action inside the component, and calling it, actually change the state value from the default 'home' to the one given as parameter.

Also the getter "currentRoute", inside dev tools, change accordingly the state "routeName" and returns the expected element.

But it looks like that this changes are not reactive, and doesn't reflect in the component computed proprieties.

...mapState(useRouterStore, ['currentRoute'])

What did i tried

  • I've tried to create and instantiate a different pinia store inside Host and use it from an Host's component. And it looks like that when using it inside an Host component it's reactivity works.
  • I've instantiate a Library's exported store inside an Host's component and tried to show a state value inside the component, and modifying that state value inside the dev tools seems to not be reactive also.

What i expect

The desired behavior is that when a Library's store value is mutated, the mutation should be reflected to the Library's component making it reactive and changing accordingly to the state values.

Ceereals
  • 46
  • 5

0 Answers0