masalibの日記

システム開発、運用と猫の写真ブログです

「Bit」というコンポーネント共有サイトを使ってみた

Bitとは、チームでReactやangularやvueなどののコンポーネントを共有し、共同でより速く構築するためのサイトです

bit.dev

githubコンポーネント版みたいな感じです。 コンポーネントを公開するならば無限につくれます。プライベートな非公開のコンポーネントの場合は3つまで無料です。

海外のサイトの記事に便利だよ~と紹介されていたので試そうと思いました
先人の方がいらっしゃったのでそちらとチュートリアルを参考にしてみた

qiita.com

docs.bit.dev

関係ないけどうちの猫。ブログを邪魔する

bitのインストール

npm install bit-bin -g

tutorialプロジェクトをcloneする

$ git clone https://github.com/teambit/tutorial-react.git
$ cd tutorial-react

bitに自分が作成したコンポーネントを追加する

初期化

$ cd export-button
$ bit init
help us prioritize new features and bug fixes by enabling us to collect anonymous statistics about your usage. sharing anonymous usage information is completely voluntary and helps us improve Bit and build a better product.
for more information see analytics documentation - https://docs.bit.dev/docs/conf-analytics.html
would you like to help Bit with anonymous usage analytics? [yes(y)/no(n)]:  (yes) yes
successfully initialized a bit workspace.

ログイン

$ bit login

ブラウザが立ち上がるのでGithubのアカウントでログインする

f:id:masalib:20190620001625j:plain

f:id:masalib:20190620001718j:plain

コンポーネントの作る画面が表示されるので作りたいコンポーネントの名前を入力する

f:id:masalib:20190620001758j:plain

f:id:masalib:20190620001817j:plain

ローカルのマシンに戻ってscopeにコンポーネントを追加

$ bit add src/ui/button.js --id ui/button

reactのコンパイラを追加する

$ bit import bit.envs/compilers/react -c

ui/buttonをbuildする

$ bit build ui/button

コンポーネントのバージョンを設定するには、このbit tagコマンドを使用します。 ビットはすべてのコンポーネントの依存関係グラフの状態をロックし、そのバージョンを不変にします。コンポーネントにタグを付けるとき、Bitは最初にコンポーネントのビルドとテストタスクを実行します とりあえずcomponentのバージョンをロックをします

$ bit tag --all 1.0.0

作成したコンポーネントをexportする

$ bit export masalib.masalibtest-components

エクスポートが成功すると管理画面が変わる

f:id:masalib:20190620001956j:plain

クリックすると詳細が表示される

f:id:masalib:20190620002020j:plain

右にコンポーネントのimportのコマンドが表示されている

自分が作成したコンポーネントを使用する

bitのコンポーネントはさまざまな方法で使用できます。NPMやYarnなどのパッケージマネージャを使用してコンポーネント をインストールするには、Bitをスコープ付きレジストリとして設定します。

初期設定をする

npm config set '@bit:registry' https://node.bit.dev

チュートリアルのimportのところに移動してコンポーネントをインストールする

cd import-button
npm i @bit/masalib.masalibtest-components.ui.button

npm install

vi src/App.js
 import React, { Component } from 'react';
 import logo from './logo.svg';
 import './App.css';
+import Button from '@bit/masalib.masalibtest-components.ui.button';
 
 class App extends Component {
   render() {
     return (
       <div className="App">
         <header className="App-header">
           <img src={logo} className="App-logo" alt="logo" />
           <h1 className="App-title">Welcome to React</h1>
         </header>
         <p className="App-intro">
           To get started, edit <code>src/App.js</code> and save to reload.
         </p>
+        <Button />
 
       </div>
     );
   }
 }

f:id:masalib:20190620002351j:plain
結果画面

簡単に追加できました~♪

他人が作ったコンポーネントを使用する

コンポーネントを共有サイトなので他人がつくったコンポーネントをimportしたと思います

「step-button」というコンポーネントを追加しました

f:id:masalib:20190620005741j:plain

表示するとサンプルソースとその結果が表示されます

f:id:masalib:20190620005827j:plain

またコンポーネントのプロパティが表示されます

f:id:masalib:20190620005855j:plain

コンポーネントをインストールする。ついでに依存関係のコンポーネントを追加する

$ npm i @bit/mui-org.material-ui.step-button
$ npm i @bit/mui-org.material-ui.styles
$ npm i @bit/mui-org.material-ui.stepper
$ npm i @bit/mui-org.material-ui.step
$ npm i @bit/mui-org.material-ui.button
$ npm i @bit/mui-org.material-ui.typography

サンプルソースをもとに追加しました

$ vi src/Horizontal.js
import React from 'react';
import './App.css';
import PropTypes from 'prop-types';
import { withStyles } from "@bit/mui-org.material-ui.styles";
import Stepper from "@bit/mui-org.material-ui.stepper";
import Step from "@bit/mui-org.material-ui.step";
import StepButton from "@bit/mui-org.material-ui.step-button";
import Button from "@bit/mui-org.material-ui.button";
import Typography from "@bit/mui-org.material-ui.typography";


const styles = theme => ({
  root: {
    width: '90%'
  },
  button: {
    marginRight: theme.spacing.unit
  },
  backButton: {
    marginRight: theme.spacing.unit
  },
  completed: {
    display: 'inline-block'
  },
  instructions: {
    marginTop: theme.spacing.unit,
    marginBottom: theme.spacing.unit
  }
});

function getSteps() {
  return ['Select campaign settings', 'Create an ad group', 'Create an ad'];
}

function getStepContent(step) {
  switch (step) {
    case 0:
      return 'Step 1: Select campaign settings...';
    case 1:
      return 'Step 2: What is an ad group anyways?';
    case 2:
      return 'Step 3: This is the bit I really care about!';
    default:
      return 'Unknown step';
  }
}

class HorizontalNonLinearAlternativeLabelStepper extends React.Component {
  state = {
    activeStep: 0,
    completed: new Set(),
    skipped: new Set()
  };

  totalSteps = () => getSteps().length;

  isStepOptional = step => step === 1;

  handleSkip = () => {
    const { activeStep } = this.state;
    if (!this.isStepOptional(activeStep)) {
      // You probably want to guard against something like this
      // it should never occur unless someone's actively trying to break something.
      throw new Error("You can't skip a step that isn't optional.");
    }

    this.setState(state => {
      const skipped = new Set(state.skipped.values());
      skipped.add(activeStep);
      return {
        activeStep: state.activeStep + 1,
        skipped
      };
    });
  };

  handleNext = () => {
    let activeStep;

    if (this.isLastStep() && !this.allStepsCompleted()) {
      // It's the last step, but not all steps have been completed
      // find the first step that has been completed
      const steps = getSteps();
      activeStep = steps.findIndex((step, i) => !this.state.completed.has(i));
    } else {
      activeStep = this.state.activeStep + 1;
    }
    this.setState({
      activeStep
    });
  };

  handleBack = () => {
    this.setState(state => ({
      activeStep: state.activeStep - 1
    }));
  };

  handleStep = step => () => {
    this.setState({
      activeStep: step
    });
  };

  handleComplete = () => {
    // eslint-disable-next-line react/no-access-state-in-setstate
    const completed = new Set(this.state.completed);
    completed.add(this.state.activeStep);
    this.setState({
      completed
    });

    /**
     * Sigh... it would be much nicer to replace the following if conditional with
     * `if (!this.allStepsComplete())` however state is not set when we do this,
     * thus we have to resort to not being very DRY.
     */
    if (completed.size !== this.totalSteps() - this.skippedSteps()) {
      this.handleNext();
    }
  };

  handleReset = () => {
    this.setState({
      activeStep: 0,
      completed: new Set(),
      skipped: new Set()
    });
  };

  skippedSteps() {
    return this.state.skipped.size;
  }

  isStepSkipped(step) {
    return this.state.skipped.has(step);
  }

  isStepComplete(step) {
    return this.state.completed.has(step);
  }

  completedSteps() {
    return this.state.completed.size;
  }

  allStepsCompleted() {
    return this.completedSteps() === this.totalSteps() - this.skippedSteps();
  }

  isLastStep() {
    return this.state.activeStep === this.totalSteps() - 1;
  }


  render() {
    const { classes } = this.props;
    const steps = getSteps();
    const { activeStep } = this.state;

    return <div className={classes.root}>
        <Stepper alternativeLabel nonLinear activeStep={activeStep}>
          {steps.map((label, index) => {
          const props = {};
          const buttonProps = {};
          if (this.isStepOptional(index)) {
            buttonProps.optional = <Typography variant="caption">Optional</Typography>;
          }
          if (this.isStepSkipped(index)) {
            props.completed = false;
          }
          return <Step key={label} {...props}>
                <StepButton onClick={this.handleStep(index)} completed={this.isStepComplete(index)} {...buttonProps}>
                  {label}
                </StepButton>
              </Step>;
        })}
        </Stepper>
        <div>
          {this.allStepsCompleted() ? <div>
              <Typography className={classes.instructions}>
                All steps completed - you're finished
              </Typography>
              <Button onClick={this.handleReset}>Reset</Button>
            </div> : <div>
              <Typography className={classes.instructions}>{getStepContent(activeStep)}</Typography>
              <div>
                <Button disabled={activeStep === 0} onClick={this.handleBack} className={classes.button}>
                  Back
                </Button>
                <Button variant="contained" color="primary" onClick={this.handleNext} className={classes.button}>
                  Next
                </Button>
                {this.isStepOptional(activeStep) && !this.state.completed.has(this.state.activeStep) && <Button variant="contained" color="primary" onClick={this.handleSkip} className={classes.button}>
                      Skip
                    </Button>}
                {activeStep !== steps.length && (this.state.completed.has(this.state.activeStep) ? <Typography variant="caption" className={classes.completed}>
                      Step {activeStep + 1} already completed
                    </Typography> : <Button variant="contained" color="primary" onClick={this.handleComplete}>
                      {this.completedSteps() === this.totalSteps() - 1 ? 'Finish' : 'Complete Step'}
                    </Button>)}
              </div>
            </div>}
        </div>
      </div>;
  }
}

HorizontalNonLinearAlternativeLabelStepper.propTypes = {
  classes: PropTypes.object
};

export default withStyles(styles)(HorizontalNonLinearAlternativeLabelStepper);

既存のappに追加する

 import React, { Component } from 'react';
 import logo from './logo.svg';
 import './App.css';
 import Button from '@bit/masalib.masalibtest-components.ui.button';
+import HorizontalNonLinearAlternativeLabelStepper from './Horizontal.js';
 
 class App extends Component {
   render() {
     return (
       <div className="App">
         <header className="App-header">
           <img src={logo} className="App-logo" alt="logo" />
           <h1 className="App-title">Welcome to React</h1>
         </header>
         <p className="App-intro">
           To get started, edit <code>src/App.js</code> and save to reload.
         </p>
         <Button />
+        <HorizontalNonLinearAlternativeLabelStepper />
 
       </div>
     );
   }
 }
 
 export default App;

結果画面

f:id:masalib:20190620011718j:plain

感想

component単位でimportできるのはありがたい。
githubサンプルソースで参考にすると色々な制約があるがこのサイトは少ないので助かる。