React-styleguidist: Changes in .scss Require Server Restart

Created on 22 Aug 2017  ·  10Comments  ·  Source: styleguidist/react-styleguidist

The background:
I want to use Sass to set color and text styling variables that are all in one place
I want CSS modularity for components to prevent pollution of global namespace
It's working beautifully, except...

When I'm running the Styleguidist dev server, it compiles fine and I can load the app in the browser. When I make changes to any .scss files, it shows Compiled successfully! but the browser window goes blank. If I refresh, it's still blank. If I restart the Styleguidist dev server, the content comes back. I get the following error message in console:

Uncaught TypeError: Cannot read property 'isRequired' of undefined
    at Object.<anonymous> (main.bundle.js:35228)
    at __webpack_require__ (main.bundle.js:706)
    at fn (main.bundle.js:112)
    at Object.<anonymous> (main.bundle.js:35105)
    at __webpack_require__ (main.bundle.js:706)
    at fn (main.bundle.js:112)
    at Object.<anonymous> (main.bundle.js:35094)
    at __webpack_require__ (main.bundle.js:706)
    at fn (main.bundle.js:112)
    at Object.<anonymous> (main.bundle.js:10514)

I've checked on the regular Webpack dev server and it hot reloads as expected. I'm running Webpack 3.5.5 with Webpack Dev Server 2.7.1, Styleguidist 6.0.20 on macOS Sierra, browser is Chrome.

Styleguide config:

module.exports = {
  webpackConfig:                require('./webpack.config.js'),
  components:                   '../app_src/components/**/*.js*',
  skipComponentsWithoutExample: true
};

Webpack config:

const webpack           = require('webpack');
const path              = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const combineLoaders    = require('webpack-combine-loaders');

module.exports = {
  entry:  './app_src/index.js',
  output: {
    path:     path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test:    /\.(js|jsx)$/,
        loader:  'babel-loader',
        exclude: /node_modules/
      },
      {
        test:    /\.(css|scss)$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
            'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
            'sass-loader'
          ]
        }),
        exclude: /node_modules/
      }      
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './app_src/index.html',
      filename: 'index.html',
      inject:   'body'
    }),
    new ExtractTextPlugin('styles.css')
  ],
  externals: {
    'react/addons':                   'react',
    'react/lib/ExecutionEnvironment': 'react',
    'react/lib/ReactContext':         'react'
  }
};

I can create the example project if needed to reproduce the issue, just don't have time right now. Thanks in advance for any and all help - this styling setup is ideal for the way my head works and I'd really love to be able to make it work with Styleguidist!

question

Most helpful comment

Haha true! So I got a chance to give webpack-blocks a try, and what a treat! This config is super clean now ✨

config/webpack.js

const {
  createConfig,
  entryPoint,
  setOutput,
  match,
  addPlugins,
  extractText,
  env,

  babel,
  css,
  sass,

  webpack
} = require('webpack-blocks');

const HtmlWebpackPlugin = require('html-webpack-plugin');
const cssPatterns       = ['*.scss', '*.css'];
const cssLoaders        = [css.modules({localIdentName: '[name]__[local]___[hash:base64:5]'}), sass()];
const cssLoadersProd    = cssLoaders.concat([extractText('styles.css')]);

module.exports = createConfig([
  entryPoint('./app/index.js'),
  setOutput('./dist/bundle.js'),
  babel(),
  addPlugins([
    new HtmlWebpackPlugin({
      template: './app/index.html',
      filename: 'index.html',
      inject:   'body'
    })
  ]),
  env('development', [
    match(
      cssPatterns,
      cssLoaders
    )
  ]),
  env('production', [
    match(
      cssPatterns,
      cssLoadersProd
    )
  ])
]);

config/styleguide.js

module.exports = {
  title:                        "UI Style Guide",
  components:                   '../app/components/**/*.js*',
  webpackConfig:                require('./webpack.js'),
  skipComponentsWithoutExample: true,
  styleguideDir:                '../dist/styleguide'
};

Still needed the webpackConfig because of how I structured the project - all config files go into a config folder and no longer need the .config delimiter. (I also renamed the source folder app - since that's where the brunt of the work gets done I like it to be at the top of the file list like in Rails 😄 )

All 10 comments

Please use fenced blocks with language tags (for example ```js) to make code examples more readable.

I can create the example project if needed to reproduce the issue

Please do it ;-)

And, just an idea, try to remove ExtractTextPlugin and see if it helps.

I added the js to code examples to make for easier reading.

Looks like the example project won't be needed, removing ExtractTextPlugin worked! I'll probably just go the route of customizing the Webpack config for Styleguidist so that Webpack is still able to emit a CSS via ExtractTextPlugin or maybe extract-loader for production build but won't interfere with Styleguidist. Once I find out a solid setup for this I'll post it here, but I'm considering this issue closed.

@sapegin Thanks so much for the quick response!

Got some time today to get this running nice and smoothly if anyone else wants a similar setup.

The trick is to get some separate configs set up for development and production. I like to keep things as DRY as possible so I made use of webpack-merge to share commonality between the configs. Here they are:

webpack.common.js

const webpack           = require('webpack');
const path              = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry:  './app_src/index.js',
  output: {
    path:     path.resolve('dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test:    /\.(js|jsx)$/,
        loader:  'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './app_src/index.html',
      filename: 'index.html',
      inject:   'body'
    }),
  ],
  externals: {
    'react/addons':                   'react',
    'react/lib/ExecutionEnvironment': 'react',
    'react/lib/ReactContext':         'react'
  }
};

webpack.dev.js

const Merge        = require('webpack-merge');
const CommonConfig = require('./webpack.common.js');

module.exports = Merge(CommonConfig, {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use:  [
          'style-loader',
          'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
          'sass-loader'
        ],
        exclude: /node_modules/
      }
    ]
  }
});

webpack.prod.js

const Merge             = require('webpack-merge');
const CommonConfig      = require('./webpack.common.js');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = Merge(CommonConfig, {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use:  ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
            'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
            'sass-loader'
          ]
        }),
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('styles.css')
  ]
});

styleguide.common.js

module.exports = {
  title:          "UI Style Guide",
  components:     '../app_src/components/**/*.js*'
};

styleguide.dev.js

const Merge = require('webpack-merge');
const CommonConfig = require('./styleguide.common.js');

module.exports = Merge(CommonConfig,
  {
    webpackConfig:  require('./webpack.dev.js'),
  }
);

styleguide.prod.js

const Merge = require('webpack-merge');
const CommonConfig = require('./styleguide.common.js');

module.exports = Merge(CommonConfig,
  {
    webpackConfig:  require('./webpack.prod.js'),
    styleguideDir:  '../dist/styleguide'
  }
);

This setup gives me exactly what I was going for - allows for bundling of CSS into its own file for production, but gives me the hot reloading to develop components more rapidly. It also allows for a central location to keep Sass variables that apply to the entire project while enabling the modularity of CSS modules.

If anybody sees where this might not be a great idea, feel free to let me know. The only thing I can think of is that the @import required to pull in variables from Sass might become painful with a large code base.

If you do branching inside webpack.config.js by using NODE_ENV you’ll simplify everything:

  • You could rely on the default config path in webpack, webpack-dev-server and Styleguidist.
  • Have one style guide config without webpackConfig option.

You’ll only need to pass NODE_ENV when you run webpack from npm scripts:

"scripts": {
    "start": "cross-env NODE_ENV=development webpack-dev-server",
    "build": "cross-env NODE_ENV=production webpack"
}

And webpack-blocks could make it even simpler ;-)

@sapegin Thanks for the tip! I'll try to get this working today and post the simplified version.

Is it obvious yet that I'm new to Webpack? 😝

@Jack-Barry Everyone is new to webpack except Tobias Koppers ;-)

Haha true! So I got a chance to give webpack-blocks a try, and what a treat! This config is super clean now ✨

config/webpack.js

const {
  createConfig,
  entryPoint,
  setOutput,
  match,
  addPlugins,
  extractText,
  env,

  babel,
  css,
  sass,

  webpack
} = require('webpack-blocks');

const HtmlWebpackPlugin = require('html-webpack-plugin');
const cssPatterns       = ['*.scss', '*.css'];
const cssLoaders        = [css.modules({localIdentName: '[name]__[local]___[hash:base64:5]'}), sass()];
const cssLoadersProd    = cssLoaders.concat([extractText('styles.css')]);

module.exports = createConfig([
  entryPoint('./app/index.js'),
  setOutput('./dist/bundle.js'),
  babel(),
  addPlugins([
    new HtmlWebpackPlugin({
      template: './app/index.html',
      filename: 'index.html',
      inject:   'body'
    })
  ]),
  env('development', [
    match(
      cssPatterns,
      cssLoaders
    )
  ]),
  env('production', [
    match(
      cssPatterns,
      cssLoadersProd
    )
  ])
]);

config/styleguide.js

module.exports = {
  title:                        "UI Style Guide",
  components:                   '../app/components/**/*.js*',
  webpackConfig:                require('./webpack.js'),
  skipComponentsWithoutExample: true,
  styleguideDir:                '../dist/styleguide'
};

Still needed the webpackConfig because of how I structured the project - all config files go into a config folder and no longer need the .config delimiter. (I also renamed the source folder app - since that's where the brunt of the work gets done I like it to be at the top of the file list like in Rails 😄 )

Thanks for posting the issue and the solution! I was struggling with this problem for quite some time, but couldn't reproduce it on a smaller example project to post here.

Was this page helpful?
0 / 5 - 0 ratings