Общий модуль для нескольких vue js 3 приложений
Зачем?
На самом деле, если вы вдруг решили, что вам нужна локальная библиотека, подумайте еще раз. Скорее всего, она вам не нужна. На этом эту статью можно было бы и закончить.. но, увы, мир не совершенен. Зачастую, данные требования обусловлены желанием заказчика, не совсем корректным ТЗ, а также какими то другими специфическими обстоятельствами. Если это ваш случай, тогда добро пожаловать под кат.
Проблема
Представим ситуацию: имеем несколько проектов vue js, каждый из которых имеет общие зависимости (dumb ui компоненты, api вызовы, модели предметной области, утилиты и тд). Каким-то образом необходимо иметь доступ к общим компонентам vue js, а также раздельные скрипты и представления для сборки проектов.
Решение
Первым наиболее очевидным (и менее болезненным) является публикация приватного нпм пакета. Я не буду рассматривать этот метод детально, лишь этап сборки.
Локальный модуль. Данный подход в целом неплох, однако придется каждый раз перекомпилировать общий модуль, в случае активной разработки на начальном этапе это не совсем удобно, к тому же, в случае vue js, есть определенные проблемы с поддержкой типов во внешнем модулей.К тому же, в случае vue js, есть определенные проблемы с поддержкой типов во внешнем модулей (актуально на момент написания статьи).
Создание нескольких скриптов для сборки проектов. Данный метод позволяет использовать общие зависимоти с hot reload. К минусам можно отнести синхронизацию работы в больших командах. 1 репозиторий, жирные мерджи, конфликты и т.д. Для небольших же команд, минусов я не обнаружил.
Реализация через локальный пакет
Для начала разберем реализацию с локальным модулем. Наша структура будет выглядеть так (каждый из проектов может быть вынесет в отдельный git submodule).
- src
- dist
- components
- index.ts
...
project1
- src
- components
...
project2
- src
- components
...
Для начала ицнииализируем нашу будущее библиотеку, для этого вводим привычное `vue create shared, устанавливаем необходимые конфиги.
Для примера создадим общий HelloWorld.vue компонент, он у нас будет полезным, выводить Hello world мигающими буквами.
<template>
<h1 class="awesome-title">{{ msg }}</h1>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
props: {
msg: String
}
});
</script>
<style scoped lang="scss">
.awesome-title {
color: purple;
}
</style>
Внутри shared module будем использовать единый ентрипоинт index.ts
.
export { default as HelloWorld } from './components/HelloWorld.vue';
Для поддержки автокомплита, а также для работы с typescript нам необходимо задекларировать типы для экспортируемых компонентов. Для этого создадим директорию types в нашем общем модуле. Создадим файл index.d.ts с объявлением типов. Получим следующую стуркутру общего модуля:
shared_module
- src
- dist
- components
- index.ts
- types
- index.d.ts
...
import { VueConstructor } from 'vue';
export const HelloWorld: VueConstructor;
Теперь осталось собрать наш пакет и импортировать как зависимость в других vue приложениях.
Для этого создадим скрипт в package.json
. Также добавим сразу название файла для выходного бандла (в нашем случае shared.umd.min.js).
Также добавим файлы которые будут входить в состав пакета.
Отдельно объявим файл с типами.
{
"scripts": {
"build:lib": "vue-cli-service build --target lib --name shared ./src/index.ts && npm pack && mv shared0.1.0.tgz ../shared.tgz"
},
"main": "./dist/shared.umd.min.js",
"files": [
"dist/*",
"types/*"
],
"types": "./types/index.d.ts",
}
Если у вас есть дополнительные файлы, которые бы вы хотели экспортировать, используйте ключ “exports”
{
"exports": {
"./src/styles/index.scss": "./dist/styles/index.scss"
}
}
Для поддержки автокомплита, а также для работы с typescript нам необходимо задекларировать типы для экспортируемых компонентов. Для этого создадим директорию types в нашем общем модуле. Создадим файл index.d.ts с объявлением типов. Получим следующую стуркутру общего модуля:
shared_module
- src
- dist
- components
- index.ts
- types
- index.d.ts
...
import { VueConstructor } from 'vue';
export const HelloWorld: VueConstructor;
Теперь осталось собрать наш пакет и импортировать как зависимость в других vue приложениях.
Для этого создадим скрипт в package.json
. Также добавим сразу название файла для выходного бандла (в нашем случае shared.umd.min.js).
Также добавим файлы которые будут входить в состав пакета.
Отдельно объявим файл с типами.
{
"scripts": {
"build:lib": "vue-cli-service build --target lib --name shared ./src/index.ts && npm pack && mv shared0.1.0.tgz ../shared.tgz"
},
"main": "./dist/shared.umd.min.js",
"files": [
"dist/*",
"types/*"
],
"types": "./types/index.d.ts",
}
Если у вас есть дополнительные файлы, которые бы вы хотели экспортировать, используйте ключ "exports"
{
"exports": {
"./src/styles/index.scss": "./dist/styles/index.scss"
}
}
Корябаем npm run build:lib
(или yarn build:lib). На выходе получаем структуру:
shared
- src
- dist
- components
- index.ts
...
project1
- src
- components
...
project2
- src
- components
...
shared.tgz (наша скомпилированая библиотека)
Отлично, теперь можно импортировать скопилированную библиотеку как зависимость в нашем 2 приложении vue js.
shared
- src
- dist
- components
- index.ts
...
project1 (*)
- src
- components
...
project2
- src
- components
...
shared.tgz (наша скомпилированая библиотека)
Для этого пишем перейдем в project1 (не забудем сначала создать его через vue create project1) и установим наш модуль
npm i ../shared-0.1.0.tgz
В package.json должно появится
{
"dependencies": {
"core-js": "^3.6.5",
"shared": "file:../shared-0.1.0.tgz",
"vue": "^3.0.0",
"vue-class-component": "^8.0.0-0"
}
}
Теперь можно попробовать использовать наш компонент в project1, для этого в App.vue:
<template>
<HelloWorld msg="Our shared component" />
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { HelloWorld } from "shared";
import 'shared/dist/shared.css';
@Options({
components: {
HelloWorld
}
})
export default class App extends Vue {}
</script>
Запускаем проект и наслаждаемся.
Реализация через разные сборки
В данном подходе структура проекта будет выглядеть следующим образом:
project
- src
- shared
- project1
- view
- components
- main.ts
- project2
- view
- components
- main.ts
...
- dist
vue.config.js
...
В данном случае у нас будет лишь 1 node_modules, package.json и 2 2 dist. После создания проекта (привычным vue create) необходимо создать подпроекты в src. В каждом подпроекте есть main.ts, свои view, роуты и компоненты. Из магии остается только сборка проекта:
{
"scripts": {
"serve:project1": "vue-cli-service serve --open src/project1/main.ts",
"build:project1": "vue-cli-service build --dest dist/project1 --open src/project1/main.ts",
}
}
Проектов, разумеется, мб сколько угодно. Для более удобного доступа к shared модулю можно использовать аласи (tsconfig.json)
{
"paths": {
"@shared/*": ["src/shared/*"]
}
}
Создадим вышеупомянутый компонент HelloWorld.vue в нашем shared приложении.
project
- src
- shared
- components
- HelloWorld.vue
Теперь его можно использовать внутри project1 src/project1/view/App.vue
<template>
<HelloWorld msg="Another way to use shared dependencies" />
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import HelloWorld from "../shared/components/HelloWorld.vue";
@Options({
components: {
HelloWorld,
},
})
export default class App extends Vue {}
</script>
Запустим и убедимся что все работает npm run serve:project1
В заключение
Материал, согласно идеям Маркса, был частично позаимствован отсюда и отсюда. Возможны проблемы с относительными путями через @/ при использовании 2 подхода. На момент публикации статьи устранить их не удалось.