react-scripts + cross-env で簡単に React をサブディレクトリにデプロイする

経緯

react-spring のサンプルを試したくなったのでやってみました。

特にこのサンプルを見てみると、 react-scripts というのを使っていたようなので、それに倣ってみました。

react-scripts について軽く調べてみると、 create-react-app に採用されているパッケージでありながら、 create-react-app よりもシンプル。

一方、 webpack 内で babel-loadersass-loader も処理される模様。

これならば、手軽にテストやモックアップを作るのにちょうど良さそうなので触ってみることにしました。

react-scripts

package.jsonscripts に以下を指定。

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "eject": "react-scripts eject",
    "test": "react-scripts test"
}

必要なファイル・ディレクトリを用意。

  • public/index.html
    • JSの読み込みを既述する必要はない
  • src/index.js
    • エントリポイント

これでコマンドを叩いて開発していけばOK。

scss

react-scriptswebpack の設定もなく sass-loader も付いてくるようなので、試してみます。

"devDependencies": {
    //略
    "node-sass": "^4.14.*"
},

devDependencies に足して yarn

@charset "utf-8";

@import "./base";

$bg-color: #17509f;

#root {
    position: relative;
    background-color: $bg-color;
    height: 100vh;
}

例えばこんな src/scss/styles.scss をに用意して、

import './scss/styles.scss';

src/App.jsimport すればOK。

styled-components

scssが動くことを確認しましたが、折角なので styled-components に入信。

"dependencies": {
    //略
    "styled-components": "^5.1.*"
},

dependencies に足して yarn

import React from 'react';
import styled from 'styled-components';

const MainConatiner = styled.main.attrs(props => ({
    headerHeight: props.headerheight || '60vh',
    footerHeight: props.footerheight || '60px',
    textColor:    props.textcolor || 'white'
}))`
    height: calc(100vh - (${props => props.headerHeight} + ${props => props.footerheight}));
    padding-top: 5rem;
    text-align: center;
`;
const Title = styled.h1.attrs(props => ({
    textColor: props.textcolor || 'white'
}))`
    font-size: 2rem;
    color: ${props => props.textColor};
`;

const Main = props => {
    const { config, commonVars } = props.data;

    return (
        <MainConatiner headerheight={commonVars.headerHeight} footerheight={commonVars.footerHeight}>
            <Title textcolor={commonVars.textColor}>{config.title}</Title>
        </MainConatiner>
    );
};

export default Main;

普通に行けますね。

  • 記法は慣れ
  • props の初期値を決定しておく場合は attrs を使用し、 styled.ELEM.attrs(props => ({ textColor: props.textcolor || 'white' })) のように記述する
  • 処理がなく即 return で戻る場合は以下のように関数コンポーネントを記述することも可
import React from 'react';

//略

const Main = () => (
    <MainConatiner>
        <Title>タイトルサンプル</Title>
    </MainConatiner>
);

export default Main;

props

親側。

import React from 'react';
import Main from './components/Main';
import './scss/styles.scss';

const App = () => {

    const commons = {
        config: {
            title:  'サンプル'
        },
        commonVars: {
            headerHeight:    '60vh',
            footerHeight:    '56px',
            textColor:       'white',
            backgroundColor: '#17509f',
            basePath:        process.env.PUBLIC_URL || '',
            backgroundImage: '/img/sample.png'
        }
    };

    return (
        <>
            <Main data={commons} />
        </>
    );
};

export default App;

子側。

import React from 'react';
import styled from 'styled-components';

const MainConatiner = styled.main.attrs(props => ({
    headerHeight: props.headerheight || '60vh',
    footerHeight: props.footerheight || '60px',
    textColor:    props.textcolor || 'white'
}))`
    height: calc(100vh - (${props => props.headerHeight} + ${props => props.footerheight}));
    padding-top: 5rem;
    text-align: center;
`;
const Title = styled.h1.attrs(props => ({
    textColor: props.textcolor || 'white'
}))`
    font-size: 2rem;
    color: ${props => props.textColor};
`;

const Main = props => {
    const { config, commonVars } = props.data;

    return (
        <MainConatiner headerheight={commonVars.headerHeight} footerheight={commonVars.footerHeight}>
            <Title textcolor={commonVars.textColor}>{config.title}</Title>
        </MainConatiner>
    );
};

export default Main;
  • src/App.jscommons なる変数を定義
  • 子コンポーネントに data={} の形で渡す
    • 子コンポーネントでは props として受け取る
      • 子コンポーネントでの props の書き換えは×
  • 関数コンポーネントに引数名を記述 (ここでは props )
    • 関数コンポーネント内で props.data から取り出す
      • styled-components引数名={上述で取り出したプロパティ内のキーを指定} の形で渡す
        • styled-components 内での既述は先述 styled-components の記述の通り

なるほど。

ちなみに App.js のようなルートのコンポーネントでは JSX に親要素が必要なため以下のようにブランクの要素でコンポーネントを挟む必要あり。

    return (
        <>
            <Main data={commons} />
        </>
    );

応用( styled-components + props )

import React from 'react';
import { useSpring, animated } from 'react-spring';
import styled from 'styled-components';

const CloudConatiner = styled(animated.div).attrs(props => ({
    basePath: props.basepath || ''
}))`
    position: absolute;
    background-image: url(${props => props.basePath}/img/sample2.png);
    background-position: center center;
    background-repeat: no-repeat;
    background-size: contain;
    opacity: 0.8;
    will-change: transform;
    &:first-of-type {
        width: 400px;
        height: 200px;
        left: 55%;
        top: 55%;
    }
    &:nth-of-type(2) {
        width: 600px;
        height: 300px;
        left: 2%;
        top: 8%;
    }
    &:last-of-type {
        width: 500px;
        height: 250px;
        left: 40%;
        top: 30%;
    }
    &:hover {
        opacity: 1;
    }
`;

//略

const Eyecatch = (props) => {
    const { commonVars } = props.data;
    //略

    return (
        [ 0, 1, 2 ].map((i) => {
        return (
            <CloudConatiner basepath={commonVars.basePath} />
            );
        })
    );
};

export default Eyecatch;
  • styled-components のテンプレートリテラル内はcssに準じるが、要素のネスト等一部scssっぽいことも可
    • 上述では &:first-of-type
    • プロパティのネストは不可
      • background-~ 系をまとめることはできない
  • カスタマイズされたコンポーネント(上述では animated.div )への styled-componentsstyled(animated.div) の形で対応している
    • プレーンなタグ以外でも行けることが分かった

cross-env

ビルドして確認する際に、通常はcssやjsがルートディレクトリからの参照になってしまうためサブディレクトリだと正常な表示ができなくなることをすっかり忘れていました。

そこで、サブディレクトリでも動作するように cross-env を導入しました。

"devDependencies": {
    //略
    "cross-env": "^7.0.*",
    "node-sass": "^4.14.*"
},

devDependencies に足して yarn

"scripts": {
    "build": "cross-env PUBLIC_URL=/path/to/project/build/ react-scripts build",
}

npm scriptsbuild を変更。これ以外特に何も触らずに yarn build するとサブディレクトリでも動作するようになりました。

応用( cross-env の変数を React 内部で使用)

import React from 'react';
import Main from './components/Main';
import './scss/styles.scss';

const App = () => {

    const commons = {
        commonVars: {
            //略
            basePath:        process.env.PUBLIC_URL || ''
            //略
        }
    };

    return (
        <>
            <Main data={commons} />
        </>
    );
};

export default App;

先ほどの arc/App.js で、よく見ると process.env.PUBLIC_URL の指定があります。

React 内でも cross-env で渡した引数を参照することができるので、こうすることでパスの情報が package.jsonApp.js のように散在することなく、一元管理することが出来ました。


以上、簡単ですがペライチの React をサクッと作ることができました。

参考

react-spring

react-dom

styled-components

react-scripts

cross-env

react

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。