のんラボ

自分的Storybook + Vue環境の作成方法(CLIなし)

2021/02/10 2021/02/10 自分的Storybook + Vue環境の作成方法(CLIなし)

自分的Storybook + Vue環境の作成方法

こんにちは。Nonです。
今回は

でTipsだけまとめているStorybookの環境構築や設定について自分なりに試行錯誤したものを書こうと思います。

弊社

では、デザイナーとフロントの協業というのがまだまだ弱く、フロント開発におけるテンプレートといいますか、明確なフローというのが確立していません。私はバックエンドの人間なので、デザイナーとどのように仕事をしていくかなどもわからなかったので、色々調べていった結果Storybookというものを発見しました。

なのでここでの目標は

  • デザイナーとコンポーネント単位のデザインレビューや、コードレビューを行うこと
  • Nuxtのようなフレームワークに依存せず、コンポーネント単位での開発ができること
  • 同様にNuxtのようなフレームワークに依存せず、コンポーネントをinstall or importできること

としています。

Storybook導入

storybookを使用するので、当然インストールしなければなりません。他のサイトやブログなどではNuxt + storybook のような構成を取っていますが、Nuxtに依存したくないと思いましたので、このようなフレームワークの導入は検討していません。

しかし、Vue.jsには依存しています。

また、storybook-cliも利用していません。使った方が簡単ですが、最小構成にしたい and cliのinstallしたくない などの理由があります。そのうちCLI版の記事も書くかも。

storybook インストール

copied.npm i @storybook/vue --save-dev

コマンドでstorybookを起動できるようにpackage.jsonに下記を追加。
この時点ではstorybookの設定をしていないので起動できません。

copied."scripts": {
  "storybook": "start-storybook",
  "build-storybook": "build-storybook",
},

Vue インストール

copied.npm i vue --save-dev

Vue loader インストール

copied.npm i vue-loader vue-template-compiler --save-dev

eslint インストール

copied.npm i eslint eslint-loader eslint-plugin-vue --save-dev

linterの設定はプロダクトに合わせて設定してください。

コマンドでlinterを起動できるように、package.jsonに下記を追加

copied."scripts": {
  "eslint": "eslint --ext .js,.vue ./",
  "eslint-fix": "eslint --fix --ext .js,.vue ./"
},

Storybook設定

{project_root}/.storybook/main.js に下記を追加

copied.module.exports = {
    stories: ['../src/**/*.stories.[tj]s'],
};

Storybook用のjsの場所はプロジェクト毎に決めていいと思います。

コンポーネント追加

{project_root}/src/components/atoms/Button/BaseButton.vue を配置。

copied.<template>
  <button>button</button>
</template>

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

<style scoped>

</style>

{project_root}/src/components/atoms/Button/Button.stories.js を配置。

copied.import {storiesOf} from '@storybook/vue';
import BaseButton from './BaseButton';

storiesOf('Button', module)
  .add('buttons', () => ({
    components: {BaseButton},
    template: '<div>' +
        '<base-button></base-button>' +
        '</div>',
  }));

外部でパッケージ利用できるように設定する

npm i でパッケージインストールしたときに使用できるようにコンポーネントを登録するスクリプトを記載。

これに関しては色々なリリース方法があるので、用途に応じて変更して下さい。(CDNとかplane jsとか)

詳細はこちら。

copied.import BaseButton from './src/components/atoms/Button/BaseButton';

const Components = {
  BaseButton,
};

export function install(Vue) {
  if (install.installed) return;
  install.installed = true;
  Object.keys(Components).forEach((component) => {
    Vue.component(component, Components[component]);
  });
}

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);
}

export default Components;

css / scss / sass 関連

2021/02現在、最新のwebpackでsass-loaderを動かすとバージョンの整合性チェックでエラーが発生するので、10系 の最新バージョンを指定してインストールする。

copied.npm i style-loader css-loader sass-loader@10.1.1 --save-dev

{project_root}/.storybook/main.js に下記を追加

copied.module.exports = {
    stories: ['../src/**/*.stories.[tj]s'],
    addons: [
        '@storybook/addon-actions',
        '@storybook/addon-knobs',
        '@storybook/addon-storysource',
        '@storybook/addon-viewport',
        'storybook-readme',
    ],
    webpackFinal: (config) => {
        config.module.rules.push({
            test: /\.scss$/,
            use: ['style-loader', 'css-loader', 'sass-loader']
        });
        return config;
    }
};
copied.npm run storybook

※↓エラーが発生する場合あり。

copied.Node Sass does not yet support your current environment: OS X 64-bit with Unsupported runtime (88)
For more information on which environments are supported please see:
https://github.com/sass/node-sass/releases/tag/v4.13.1

storybookを起動しようとしてエラーが出た場合、node-sassをインストールしてビルドしておくこと。

copied.npm i node-sass --save-dev

npm rebuild node-sass

npm run storybook

試しにscssファイルなどを作成してimportしてみる。

VueのScoped CSSで試しても大丈夫です。

{project_root}/src/sass/atoms/_button.scss

copied..btn {
  color: red;
}

{project_root}/src/sass/app.scss

copied.// My atom's scss.
@import 'atoms/button';

{project_root}/.storybook/preview.js でscssファイルをimportする

copied.import {addDecorator, addParameters} from "@storybook/vue";
import {INITIAL_VIEWPORTS} from "@storybook/addon-viewport";
import {addReadme} from 'storybook-readme/vue';
import '../src/sass/app.scss';

addParameters({
    viewport: {
        viewports: INITIAL_VIEWPORTS
    }
});

addDecorator(addReadme);

アドオンを追加

storybookを使うにあたって便利なアドオンを追加します。これも各プロダクトによって導入するかどうかを決めて大丈夫です。

@storybook/addon-actions

copied.npm i --save-dev @storybook/addon-actions

{project_root}/.storybook/main.js にアドオン登録

copied.module.exports = {
    stories: ['../src/**/*.stories.[tj]s'],
    addons: [
        '@storybook/addon-actions',
    ]
};

{project_root}/src/components/atoms/Button/BaseButton.vue にクリックイベント設置。

copied.<template>
  <button @click="$emit('click')">
    button
  </button>
</template>

{project_root}/src/components/atoms/Button/Button.stories.js を書き換え。

copied.import {storiesOf} from '@storybook/vue';
import BaseButton from './BaseButton';
import {action} from '@storybook/addon-actions';

storiesOf('Button', module)
  .add('buttons', () => ({
    components: {BaseButton},
    template: '<div>' +
        '<base-button @click="action"></base-button>' +
        '</div>',
    methods: {
      action: action('clicked')
    }
  }));

@storybook/addon-knobs

copied.npm i @storybook/addon-knobs --save-dev

{project_root}/.storybook/main.js にアドオン登録

copied.module.exports = {
    stories: ['../src/**/*.stories.[tj]s'],
    addons: [
        '@storybook/addon-actions',
        '@storybook/addon-knobs',
    ]
};

{project_root}/src/components/atoms/Button/Button.stories.js を書き換え。

copied.import {storiesOf} from '@storybook/vue';
import BaseButton from './BaseButton';
import {action} from '@storybook/addon-actions';
import {text} from '@storybook/addon-knobs';

storiesOf('Button', module)
  .add('buttons', () => ({
    components: {BaseButton},
    props: {
      buttonLabel: {
        default: text('label', 'button')
      },
    },
    template: '<div>' +
        '<base-button @click="action">{{ buttonLabel }}</base-button>' +
        '</div>',
    methods: {
      action: action('clicked')
    }
  }));

{project_root}/src/components/atoms/Button/BaseButton.vue を書き換え。

copied.<template>
  <button>button</button>
</template>

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

<style scoped>

</style>

@storybook/addon-storysource

copied.npm i @storybook/addon-storysource --save-dev

{project_root}/.storybook/main.js にアドオン登録

copied.module.exports = {
    stories: ['../src/**/*.stories.[tj]s'],
    addons: [
        '@storybook/addon-actions',
        '@storybook/addon-knobs',
        '@storybook/addon-storysource',
    ]
};

@storybook/addon-viewport

copied.npm i @storybook/addon-viewport --save-dev

{project_root}/.storybook/main.js にアドオン登録

copied.module.exports = {
    stories: ['../src/**/*.stories.[tj]s'],
    addons: [
        '@storybook/addon-actions',
        '@storybook/addon-knobs',
        '@storybook/addon-storysource',
        '@storybook/addon-viewport',
    ]
};

{project_root}/.storybook/preview.js にviewport(iPhoneなどの画面情報)情報を追加。デフォルトでも使用可能。

copied.import {addParameters} from "@storybook/vue";
import {INITIAL_VIEWPORTS} from "@storybook/addon-viewport";

addParameters({
    viewport: {
        viewports: INITIAL_VIEWPORTS
    }
});

storybook-readme

copied.npm i storybook-readme --save-dev

{project_root}/.storybook/main.js にアドオン登録

copied.module.exports = {
    stories: ['../src/**/*.stories.[tj]s'],
    addons: [
        '@storybook/addon-actions',
        '@storybook/addon-knobs',
        '@storybook/addon-storysource',
        '@storybook/addon-viewport',
        'storybook-readme',
    ]
};

{project_root}/.storybook/preview.js に設定を追加。

copied.import {addDecorator, addParameters} from "@storybook/vue";
import {INITIAL_VIEWPORTS} from "@storybook/addon-viewport";
import {addReadme} from 'storybook-readme/vue';

addParameters({
    viewport: {
        viewports: INITIAL_VIEWPORTS
    }
});

addDecorator(addReadme);

{project_root}/src/components/atoms/Button/Button.stories.js を書き換え。

copied.import {storiesOf} from '@storybook/vue';
import ButtonReadme from './README.md';
import BaseButton from './BaseButton';
import {action} from '@storybook/addon-actions';
import {text} from '@storybook/addon-knobs';

storiesOf('Button', module)
  .addParameters({
    readme: {
      sidebar: ButtonReadme
    }
  })
  .add('buttons', () => ({
    components: {BaseButton},
    props: {
      buttonLabel: {
        default: text('label', 'button')
      },
    },
    template: '<div>' +
        '<base-button @click="action">{{ buttonLabel }}</base-button>' +
        '</div>',
    methods: {
      action: action('clicked')
    }
  }));

{project_root}/src/components/atoms/Button/README.md を追加。

copied.# Usage🛠

その他設定はご自由に

  • @babel/core
  • vue-svg-loader
  • @fortawesome/fontawesome-free
  • bootstrap

のようなプロジェクトの要件などに関わるパッケージなどはご自由にインストールして下さい。

最後に

ほぼ、私のメモの用に書いてみました。

「こうしたほうがいいよ」とか、「ここ間違ってるくない?」とかあるかもしれませんが、その辺は自身の環境に合わせて設定を変えていただければと思います。

特にコンポーネントのパッケージ化についてはインストールする側がVueを利用していることを前提にしているので、使いにくいところがあるかもしれません。
CDN化やpure jsの対応はそのうちしておきたいなとは思っています。

個人的にはフレームワーク依存したくない(Vue / ReactのようなFWを除く)ので、それ単体で動く環境をCLIなどなしで構築できるようになっているはずです。

みなさんの役に立てれば幸いです。

React記事書こうとしていましたが、Storybookのまとめしてなかったなぁと思ったのでこちらを優先しました。Nuxtの案件も近いのでついでに...といった感じ。

Nuxtについてもそのうち書きたい。

そのときはよしなに。

.