Últimamente he estado utilizando Vuejs para diferentes proyectos y en ocasiones me he encontrado con la necesidad de reutilizar ciertos componentes en otros proyectos.
¿Cómo podemos utilizar estos componentes en una biblioteca que pueda ser utilizada por otros proyectos? Afortunadamente, las herramientas de Vue nos permiten configurar la construcción en forma de biblioteca para exportar este tipo de componentes.
Empezaremos por tener dos proyectos: uno que servirá como biblioteca de componentes y otro que dependerá de esta y utilizará los componentes.
Para empezar, utilizaremos el generador de Vue para estos proyectos
yarn global add @vue/cli
# biblioteca
vue create dummylib
# app
vue create testapp
Si corremos el proyecto utilizando yarn serve
podremos ver el despliegue de la aplicación demo en ambos proyectos.
Cuando se hace la construcción de un proyecto en Vue, por debajo se utiliza webpack para construir el sitio con los assets y dependencias necesarias.
Supongamos que tenemos en dummylib
un componente que queremos utilizar en testapp
<!-- TodayDateComponent -->
<template>
<div>
<p>Today is </p>
</div>
</template>
<script>
import * as moment from "moment";
export default {
computed: {
todayDate() {
return moment().format("LL");
}
}
};
</script>
Este componente tiene como función solo desplegar la fecha actual, utilizando una dependencia externa como moment.js
Construir como biblioteca
Ahora, necesitamos declarar el archivo main-lib.js
donde se especifique qué componentes vamos a exportar.
// main-lib.js
import TodayDateComponent from "./components/TodayDateComponent.vue";
export default TodayDateComponent;
Si construimos normalmente el proyecto dummylib
nos generará el sitio demo de Vue, necesitamos cambiar la configuración de construcción del proyecto a una biblioteca que exporte el contenido de main-lib.js
Afortunadamente, podemos usar el servicio de construcción de Vue para generar nuestro proyecto como una biblioteca en vez de un sitio web.
yarn global add @vue/cli-service-global
Para esto, utilizaremos vue-cli-service
con el parámetro --target lib
y un nombre para la biblioteca (--name dummylib
) junto con la dirección del archivo en que se especifica los componentes exportados (src/main-lib.js
).
El comando quedaría estructurado de la siguiente forma.
vue-cli-service build --target lib --name dummylib src/main-lib.js
Este comando podemos agregarlo a los comandos del archivo package.json
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build-lib": "vue-cli-service build --target lib --name dummylib src/main-lib.js",
"lint": "vue-cli-service lint"
},
Para que otro proyecto pueda usar nuestra biblioteca construida, necesitamos decirle cuál de los archivos utilizará como principal al importarla. Para esto, debemos agregar la ruta main
al package.json
"main": "./dist/dummylib.common.js"
Dependencias externas
Ahora, si solo la construimos así sin más, la configuración nos hará incluir todas las dependencias y archivos importados dentro de nuestro paquete.
Para evitar esto, debemos modificar la configuración de webpack a través del archivo vue.config.js
. Este archivo, nos permite incluir plugins de Vue para desarrollo (por ejemplo, pre-procesadores de HTML como Pug, o de CSS como SASS) y extender la configuración actual de webpack.
Para más información sobre las diferentes formas de configurar dependencias externas checa la Documentación de Webpack: Externals
/** vue.config.js */
module.exports = {
// options...
/** chainWebpack permite extender la config actual de webpack **/
chainWebpack: config => {
if (process.env.VUE_CLI_BUILD_TARGET === "lib") {
/** Utilizamos solo esta configuración para el paquete de la biblioteca */
config.externals({
moment: "moment"
});
}
}
};
Al decirle que obtenga moment
de dependencias externas, ya no lo incorporará en el paquete.
Utilizar en el proyecto de prueba
Ahora, en el proyecto testapp
podemos utilizar este componente añadiendo dummylib
como dependencia.
yarn add ../dummylib
Pro-tip: Puedes usar
yarn link
para añadir la dependencia en modo de desarrollo en vez de usar la ruta relativa.# ~/.../dummylib $ yarn link cd ../testapp yarn link "dummylib" yarn add "dummylib"
Después de añadir la dependencia, obtendríamos el componente dentro de App.vue
<!-- App.vue -->
<template>
<!-- ... -->
<today-date-component />
<!-- ... -->
</template>
<script>
import TodayDateComponent from "dummylib";
export default {
name: "App",
components: {
// ...
TodayDateComponent
// ...
}
// ...
};
</script>
¿Qué pasa si mi componente usa Vuex store?
En este caso, la biblioteca se tendrá que exportar como un plugin de Vue para poder utilizar la instancia de vuex store de la otra aplicación.
Suponiendo que tengamos un componente como este:
<!-- ./components/DummyButton.vue -->
<template>
<div>
<button @click="increment"></button>
</div>
</template>
<script>
export default {
computed: {
times() {
return this.$store.getters.counter !== 1 ? "times" : "time";
},
text() {
return `I have been clicked ${this.$store.getters.counter} ${this.times}`;
}
},
methods: {
increment() {
this.$store.commit("increment");
}
}
};
</script>
Que utiliza un módulo de store como este:
/** ./store/module.js */
const store = {
state: {
counter: 0
},
getters: {
counter: state => state.counter
},
mutations: {
increment(state) {
state.counter += 1;
}
}
};
export default store;
Necesitamos modificar nuestro archivo main-lib.js
para que en vez de simplemente exportar los componentes, exporte un plugin de Vue que requiere un store de Vuex.
/** main-lib.js **/
import DummyButton from "./components/DummyButton.vue";
import TodayDateComponent from "./components/TodayDateComponent.vue";
import store from "./store/module";
export default {
install(Vue, options) {
if (!options || !options.store) {
throw new Error("Please initialise plugin with a Vuex store.");
}
options.store.registerModule("dummylib", store);
Vue.component("DummyButton", DummyButton);
Vue.component("TodayDateComponent", TodayDateComponent);
}
};
Y simplemente podemos reconstruir la biblioteca
yarn build-lib
Utilizar en el app
En nuestra app, tendremos que utilizar vuex
e inicializar nuestro store.
yarn add vuex
/** ./store/index.js */
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({});
En nuestro archivo main.js
de la app, debemos utilizar este store en la instancia de Vue, pero antes, inicializar nuestra biblioteca dummylib
como plugin, pasándole el store.
/** main.js **/
import Vue from "vue";
import App from "./App.vue";
import store from "./store";
Vue.config.productionTip = false;
/** Add dummylib as plugin */
import dummylib from "dummylib";
Vue.use(dummylib, { store }); // store required
new Vue({
store,
render: h => h(App)
}).$mount("#app");
Al inicializar nuestra biblioteca como plugin, ya no es necesario hacer el import de los componentes en App.vue
por lo que podemos solo utilizarlos en el template.
<!-- App.vue -->
<template>
<div id="app">
<today-date-component />
<dummy-button />
<!-- ... -->
</div>
</template>
Y voilá, ahora podemos hacer uso de ambos componentes dentro de testapp
y utilizando la extensión de herramientas de Vue en el navegador podemos ver el estado del store.