Vuetifyに依存したVueコンポーネントをnpmパッケージとして共有する方法

こんにちは、AIサービス開発部の今枝と申します。
前回もTech BlogでVue.jsに関連した記事を書きましたが、今回もVue.jsに関連する内容です。

前回の記事

やりたいこと

Vuetifyのライブラリに依存しているVueコンポーネントをnpmパッケージとして共有することが目的です。(Vuetifyを使う関係上 Vue 2系(v2.6.11)での内容になります。)
加えて、公開したnpmパッケージのコンポーネントがVuetifyのSASS/SCSS 変数を上書きしてスタイルが変更されることも確認します。
今回、npmパッケージの公開方法などは詳しく扱いません。お好みのnpmレジストリを使用してパッケージ公開してください。

調査の背景

社内の共通の資産としてUIコンポーネントnpmパッケージを作成する場合、UIフレームワーク(Vuetify)に依存したVueコンポーネントが適切にnpmパッケージとして公開できるかを調査しました。

環境

  • Vue.js v2.6.12
  • vue-cli-plugin-vuetify: 2.3.1
  • node: 14.16.1
  • yarn: v1.22.5
  • @vue/cli 4.5.12

npmパッケージとして公開する方法

環境構築

VueCLIを用いて簡単に環境を構築します。 はじめに、Vueのセットアップをおこないます。

vue create tech-blog-demo
Vue CLI v4.5.12
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel,
TS, Linter
? Choose a version of Vue.js that you want to start the project with 2.x
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected
 polyfills, transpiling JSX)? Yes
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicate
d config files
? Save this as a preset for future projects? No

次に Vuetify を導入します。

cd tech-blog-demo
vue add vuetify

src/plugins/vuetify.ts が下記のように記載されていることを確認します。 異なっている場合は、下記のように修正してください。

import Vue from "vue";
import Vuetify from "vuetify/lib";

Vue.use(Vuetify);

export default new Vuetify({});

コンポーネント・ライブラリ作成用のファイルの追加

src/components/DemoDialog.vue に確認用のコンポーネントを配置します。

<template>
  <v-dialog v-model="dialog">
    <template v-slot:activator="{ on }">
      <v-btn v-on="on" color="primary"> Demo Dialog </v-btn>
    </template>
    <v-card>
      <v-card-title>Demo Dialog</v-card-title>
      <v-card-text> デモ用のダイアログです。 </v-card-text>
      <v-card-actions>
        <v-btn @click="dialog = false" color="primary">Close</v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>
<script>
export default {
  name: "DemoDialog",
  data: () => ({
    dialog: false,
  }),
};
</script>

src/build/build.lib.js にライブラリ作成用のファイルの追加します。

参考: Vue コンポーネントを npm パッケージ化する — Vue.js

// Vue.use() によって実行される install 関数を定義
const install = function (Vue) {
  if (install.installed) return;
  install.installed = true;

  // For each matching file name...
  requireComponent.keys().forEach((fileName) => {
    const componentConfig = requireComponent(fileName);
    const componentName = fileName
      .split("/")
      .pop()
      .replace(/\.\w+$/, "");
    Vue.component(componentName, componentConfig.default || componentConfig);
  });
  return;
};

// Vue.use() のためのモジュール定義を作成
// Create module definition for Vue.use()
const plugin = {
  install,
};

// vue が見つかった場合に自動インストールする (ブラウザで <script> タグを用いた場合等)
let GlobalVue = null;
if (typeof window !== "undefined") {
  GlobalVue = window.Vue;
} else if (typeof global !== "undefined") {
  GlobalVue = global.Vue;
}
if (GlobalVue) {
  GlobalVue.use(plugin);
}

const requireComponent = require.context(
  // コンポーネントフォルダの相対パス
  "../components",
  // サブフォルダ内を調べるかどうか
  false,
  // 基底コンポーネントのファイル名に一致させるのに使う正規表現
  /[\w-]+\.vue$/
);

export default plugin;

公開用スクリプトの追加

例としてgithub package の機能を利用してnpmパッケージを公開します。

今回、npm パッケージの公開方法に関しては説明しません。
社内npmレジストリや、GitLab Package Registryにデプロイする際は、適宜修正して利用してください。
package.jsonのScriptに下記のように修正します。

{
  "name": "[@github UserName]/tech-blog-demo",
  "version": "0.1.0",
  "license": "MIT",
  "main": "./dist/tech-blog-demo.common.js",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "build:package": "vue-cli-service build --target lib --name tech-blog-demo src/build/build.lib.js"
  },
  "files": [
    "dist"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/[@github UserName]/tech-blog-demo.git"
  },
  ...省略
}

VuetifyのTheme変更のAPIと、VuetifyのSASS/SCSS 変数変更が適応されていることを確認します。

npm パッケージの公開

yarn build:package
npm publish

上記のコマンドを入力することで公開完了です。 次にパッケージを読み込む場合の処理を行います。

公開したnpmパッケージの確認

公開したnpm パッケージの動作確認のために、VueCLIで新規にプロジェクトを作成して動作を確認します。

main.js or main.ts で下記のように読み込むことで公開したパッケージのコンポーネントを利用できるようになります。

import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';
import TechBlogDemo from '[@github UserName]/tech-blog-demo'

Vue.use(TechBlogDemo)
Vue.config.productionTip = false
new Vue({
  vuetify,
  render: h => h(App)
}).$mount('#app')

早速、アプリ内でコンポーネントを表示してみます。

<template>
  <v-app>
    <v-main>
      <v-container fill-height fluid>
        <v-row align="center" justify="center">
            <v-col class="text-center"><DemoDialog/></v-col>
        </v-row>
      </v-container>
    </v-main>
  </v-app>
</template>

<script>
export default {
  name: 'App',
};
</script>

DevServerで正常にコンポーネント読み込めていることができました!

f:id:optim-tech:20210413105504p:plain

f:id:optim-tech:20210413105511p:plain

最後に、VuetifyのAPIが公開パッケージで使用できることを確認します。

Vuetify Themeの変更

Vuetifyには、ThemeプロパティをVuetifyコンストラクタに渡すことで、コンポーネントの色などを簡単に変更可能です。
Theme プロパティを Vuetify コンストラクタに渡して公開したパッケージのコンポーネントの挙動を確認します。

src/plugins/vuetify.js を下記のように修正します。

import Vue from 'vue';
import Vuetify from 'vuetify/lib';

Vue.use(Vuetify);
import colors from 'vuetify/lib/util/colors'
export default new Vuetify({
  theme: {
    themes: {
      light: {
        primary: colors.purple,
      },
    },
  },
});

公開したパッケージで使用しているv-btnの色が変更されていることを確認できます。

f:id:optim-tech:20210413105528p:plain

Vuetify Sass変数の変更

VuetifyではSASS/SCSSカスタム変数を渡すことで、コンポーネントに設定されているデフォルトのSASS/SCSSを上書きすることができます。

SASS/SCSSカスタム変数を変更して公開したパッケージのコンポーネントの挙動を確認します。

src/scss/variables.scss を作成し、下記のように記載します。

// v-btn
$btn-font-sizes:
    (
      'x-small': 40px,
      'small': 40px,
      'default': 40px,
      'large': 40px,
      'x-large': 40px
    );

公開したパッケージで使用しているv-btnのフォントの大きさが変更されていることを確認できます。

f:id:optim-tech:20210413105519p:plain

おわりに

今回は、Vuetifyに依存したVueコンポーネントをnpmパッケージとして共有する方法について扱いました。社内でUIライブラリとして公開する際は、インターフェイスの設計、保守方法に関して導入前に議論しておく必要があると思います。 npmパッケージを公開して逆に開発工数が増加しないように注意して、ライブラリ化について議論したいと思います。

OPTiMでは、ものづくりが大好きなエンジニアのみなさんを募集しています!