Lawrence I. Siden
Lawrence I. Siden

Reputation: 9379

How does one deploy a ReactJs component that imports its own stylesheets?

I'm developing a large React component that I want to import into another React app. (I could have done it all as one big app, but I decided to separate this component into a its own project so that I could focus on the component separately.) It uses its own stylesheets, which it pulls in with "require('path/to/stylesheet.[s]css')".

For development, I serve it with webpack-dev-server which takes care of loading and pre-processing those stylesheets. My problem is that I don't know how to deploy the component so that another app can include it without breaking when the browser encounters those calls to require(path/to/stylesheet).

I've found lots of examples where projects use webpack-dev-server for development, but invoke babel (or other pre-processors) directly in order to deploy components to a dist/ directory, so this seems to be a common practice. But what to do about those invocations of "require('path/to/stylesheet.[s]css')"? Without webpack and its loaders to rely on, they all fail.

Ideally, I'd like to be able to use webpack for both development and production. Then I could deploy my component as a complete bundle and the css would be included in the code. I tried this earlier but it didn't work. It also has the disadvantage of making it hard for the main app to over-ride the styles in the component.

I suppose another way might be to set up my pipeline so that a main.scss includes all the other stylesheets and let Sass output a single style.css file that the consuming app has to include separately. Less elegant, but it makes it easier to override these styles.

I'm still mastering React and its eco-system, so I'd love to know what the best practice is for this in case anyone knows.

Upvotes: 0

Views: 309

Answers (1)

Austin Hunt
Austin Hunt

Reputation: 136

You need to run webpack and bundle all of your JS/CSS so that it can be consumed in your other project. If you use the correct loaders, the CSS is bundled directly into your JS bundle. Or, you can use a different loader to have webpack generate you a nice style.css file that bundles all your require(/path/to/css) into one file.

There are three or four basic things you need to do (Assuming webpack@3.*):

  1. Use the "externals" in your webpack config file to exclude libraries in your package.json dependencies list.
  2. Use babel-loader to transpile your javascript (ES2015 is supported by most browsers).
  3. Use 'style-loader', 'css-loader', and 'sass-loader' to bundle your css/scss into your webpack js bundle. (This assumes you aren't using server-side react rendering. That's a bit trickier. You would need to use an isomorphic style loader instead.)
  4. Optionally, use the extract-text-webpack-plugin to pull your css out into a separate file. (Commented out in the example below)

Be sure to make your bundle.js file the entry point in your package.json

Here is a good example of what you are trying to do: (This example works for server-side rendering as well as browser rendering. The BABEL_ENV lets us only require css files on the browser.)

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

const env = process.env.NODE_ENV;
console.log('Environment: ', env)
module.exports = {
  devtool: 'source-map',
  entry: env == 'production' ? './src/components/index.js' : './src/index.js',
  output: {
    path: env !== 'production' ? require('path').resolve('./dev'): require('path').resolve('./dist'),
    filename: 'bundle.js',
    publicPath: '/',
    library: 'reactPorto',
    libraryTarget: 'umd'
  },
  externals: env == 'production' ? [
    'jquery',
    'react',
    'react-dom',
    'react-bootstrap',
    /^react-bootstrap\/.+$/,
    'classnames',
    'dom-helpers',
    'react-owl-carousel',
    'react-owl-carousel2',
    'uncontrollable',
    'warning',
    'keycode',
    'font-awesome'
  ] : [],
  devServer: {
    inline: true,
    contentBase: './dev',
    staticOptions: { index: 'test.html' },
    historyApiFallback: {
      rewrites:[{ from: /./, to: 'test.html' }],
    },
    hot: true,
  },
  plugins: [
    // new ExtractTextPlugin({
    //   filename: 'style.css',
    //   allChunks: true
    // }),
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify(env || 'development'),
        'BABEL_ENV': JSON.stringify(env || 'development')
      }
    }),
    new webpack.optimize.OccurrenceOrderPlugin(),
    ...(env != 'production' ? [new webpack.HotModuleReplacementPlugin()] : []),
    ...(env == 'production' ? [new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } })] : []),
    new webpack.NoEmitOnErrorsPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          query: {
            presets: [
              'es2015',
              'react',
              'stage-2',
              ...(env != 'production' ? ['react-hmre'] : [])
            ],
            plugins: []
          }
        }
      },
      {
        test : /(\.css|\.scss)/,
        // exclude: /node_modules/,
        // use : ExtractTextPlugin.extract({
        //   use: [
        //     'isomorphic-style-loader',
        //     {
        //       loader: 'css-loader',
        //       options: {
        //         importLoaders: 1
        //       }
        //     },
        //     'sass-loader'
        //   ]
        // }),
        use: ['iso-morphic-style-loader', 'css-loader', 'sass-loader']
      },
      {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader?mimetype=image/svg+xml'},
      {test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?mimetype=application/font-woff"},
      {test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?mimetype=application/font-woff"},
      {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?mimetype=application/octet-stream"},
      {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader"},
      {test: /\.(png|jpg|jpeg)/, loader: 'file-loader' }
    ]
  }
}

Upvotes: 1

Related Questions