1

React と Webpack (3.4.1) を使用して「コンポーネント ライブラリ」を構築しようとしています。アイデアは、 React Cosmosを使用して 1 つの React アプリを作成し、再利用可能なコンポーネントの「ライブラリ」をインタラクティブに構築して探索することです。そのリポジトリには、buildそれらのコンポーネントだけを (Github や NPM に) プッシュできるファイルにコンパイルするタスクもあります。また、そのファイル/パッケージを他のプロジェクトにインポートして、再利用可能なコンポーネントにアクセスできます。

注/更新- これを自分で試してみたい方のために、(機能しない) パッケージを NPM: https://www.npmjs.com/package/rs-componentsに公開しました。同じyarn add問題に遭遇する可能性が高いのは、プロジェクトだけです。

この作業の 90% が完了しました。React Cosmos はコンポーネントを正しく表示しており、ビルド タスク ( rimraf dist && webpack --display-error-details --config config/build.config.js) はエラーなしで実行されています。ただし、リポジトリを Github からパッケージとして別のプロジェクトにプルすると、エラーが発生します。

エラーは、Webpack がコンポーネント ライブラリの依存関係を正しくインポートしていないことが原因のようです。ビルド時にライブラリを縮小/マングルしないと、インポート時に最初に表示されるエラーは次のとおりです。

TypeError: React.Component is not a constructor

実際、デバッガーを投入して調べると、React は空のオブジェクトです。

node_modules(ごまかして) React をコンパイル済み/ダウンロード済みファイル( ) に直接インポートすることでこれを回避するとconst React = require('../../react/react.js')、そのエラーは発生しませんが、prop-typesライブラリのインポートの失敗に関連する同様のエラーが発生します。

TypeError: Cannot read property 'isRequired' of undefined

したがって、私のコードは正しく取り込まれているようですが、コンポーネント ライブラリ ファイルのインポートは正しくバンドルされていないようです。

関連ファイルのほとんどは次のとおりです。

config/build.config.js

const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

const config = {
  entry: path.resolve(__dirname, '../src/index.js'),
  devtool: false,
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].js'
  },
  resolve: {
    modules: [
      path.resolve(__dirname, '../src'),
      'node_modules'
    ],
    extensions: ['.js']
  },
  module: {
    rules: []
  }
};

// JavaScript
// ------------------------------------
config.module.rules.push({
  test: /\.(js|jsx)$/,
  exclude: /node_modules/,
  use: [{
    loader: 'babel-loader',
    query: {
      cacheDirectory: true,
      plugins: [
        'babel-plugin-transform-class-properties',
        'babel-plugin-syntax-dynamic-import',
        [
          'babel-plugin-transform-runtime',
          {
            helpers: true,
            polyfill: false, // We polyfill needed features in src/normalize.js
            regenerator: true
          }
        ],
        [
          'babel-plugin-transform-object-rest-spread',
          {
            useBuiltIns: true // We polyfill Object.assign in src/normalize.js
          }
        ]
      ],
      presets: [
        'babel-preset-react',
        ['babel-preset-env', {
          targets: {
            ie9: true,
            uglify: false,
            modules: false
          }
        }]
      ]
    }
  }]
});

// Styles
// ------------------------------------
const extractStyles = new ExtractTextPlugin({
  filename: 'styles/[name].[contenthash].css',
  allChunks: true,
  disable: false
});

config.module.rules.push({
  test: /\.css$/,
  use: 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]__[hash:base64:5]'
});

config.module.rules.push({
  test: /\.(sass|scss)$/,
  loader: extractStyles.extract({
    fallback: 'style-loader',
    use: [
      {
        loader: 'css-loader',
        options: {
          sourceMap: false,
          minimize: {
            autoprefixer: {
              add: true,
              remove: true,
              browsers: ['last 2 versions']
            },
            discardComments: {
              removeAll: true
            },
            discardUnused: false,
            mergeIdents: false,
            reduceIdents: false,
            safe: true,
            sourcemap: false
          }
        }
      },
      {
        loader: 'postcss-loader',
        options: {
          autoprefixer: {
            add: true,
            remove: true,
            browsers: ['last 2 versions']
          },
          discardComments: {
            removeAll: true
          },
          discardUnused: false,
          mergeIdents: false,
          reduceIdents: false,
          safe: true,
          sourceMap: true
        }
      },
      {
        loader: 'sass-loader',
        options: {
          sourceMap: false,
          includePaths: [
            path.resolve(__dirname, '../src/styles')
          ]
        }
      }
    ]
  })
});
config.plugins = [extractStyles];

// Images
// ------------------------------------
config.module.rules.push({
  test: /\.(png|jpg|gif)$/,
  loader: 'url-loader',
  options: {
    limit: 8192
  }
});

// Bundle Splitting
// ------------------------------------
const bundles = ['normalize', 'manifest'];

bundles.unshift('vendor');
config.entry.vendor = [
  'react',
  'react-dom',
  'redux',
  'react-redux',
  'redux-thunk',
  'react-router'
];

config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ names: bundles }));

// Production Optimizations
// ------------------------------------
config.plugins.push(
  new webpack.LoaderOptionsPlugin({
    minimize: false,
    debug: false
  }),
  new webpack.optimize.UglifyJsPlugin({
    sourceMap: false,
    comments: false,
    compress: {
      warnings: false,
      screw_ie8: true,
      conditionals: true,
      unused: true,
      comparisons: true,
      sequences: true,
      dead_code: true,
      evaluate: true,
      if_return: true,
      join_vars: true
    }
  })
);

module.exports = config;

コンポーネントの例:

Checkbox.js

import React from 'react';
import { connect } from 'react-redux';
import { map } from 'react-immutable-proptypes';
import classnames from 'classnames';

import { setCheckboxValue } from 'store/actions';

/**
 * Renders a Redux-connected Checkbox with label
 *
 * @param {string} boxID - Unique string identifier of checkbox
 * @param {string} name - Label text to display
 * @param {function} dispatch - Redux dispatch function
 * @param {Immutable.Map} checkboxes - Redux checkboxes Map
 * @param {string[]} className - Optional additional classes
 *
 * @returns {React.Component} A checkbox with globally-tracked value
 */
export function CheckboxUC ({ boxID, name, dispatch, checkboxes, className }) {
  const checked = checkboxes.get(boxID);
  return (
    <label className={ classnames('checkbox rscomp', className) } htmlFor={ boxID }>
      <input
        className="checkable__input"
        type="checkbox"
        onChange={ () => {
          dispatch(setCheckboxValue(boxID, !checked));
        } }
        name={ name }
        checked={ checked }
      />
      <span className="checkable__mark" />
      <span className="checkable__label">{ name }</span>
    </label>
  );
}

const mapStateToProps = state => ({
  checkboxes: state.checkboxes
});

const { string, func } = React.PropTypes;
CheckboxUC.propTypes = {
  boxID: string.isRequired,
  name: string.isRequired,
  checkboxes: map.isRequired,
  dispatch: func,
  className: string
};

export default connect(mapStateToProps)(CheckboxUC);

また、Webpack ビルド タスクの「エントリ」ファイル ( webpack --config config/build.config.js) は、このパッケージをインポートするアプリケーションがアクセスできるコンポーネントのみをエクスポートすることを目的としています。

src/index.js

import BaseForm from 'components/BaseForm';
import Checkbox from 'components/Checkbox';
import Dropdown from 'components/Dropdown';
import Link from 'components/Link';
import RadioGroup from 'components/RadioGroup';

import { validators } from 'components/BaseForm/utils';

export default {
  BaseForm,
  Checkbox,
  Dropdown,
  Link,
  RadioGroup,
  utils: {
    validators
  }
};

最後に、不適切に未定義のように見える変数を「インポート」している、コンパイルされた未醜化の JS の行を次に示します。

  • var React = __webpack_require__(1);
  • /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_prop_types__ = __webpack_require__(3);

これを整理するのに役立つものが他にあれば教えてください。非常に混乱しているので、助けていただければ幸いです。

ありがとう!

編集

多くのもの正しくインポートされているようです。コンパイルされたファイルでデバッガーをスローし、__webpack_require__(n)さまざまな を試してみると、nインポートされたさまざまなモジュールが表示されます。残念ながら、React と PropTypes はその中に含まれていないようです。

4

1 に答える 1