React Storybook with Coffeescript and SCSS

hey there. we are still using coffeescript and want to start using storybook.
however, i cannot find anywere anything about how to make storybook work with coffeescript (and meteor)

I figured out, how. Wow, that wasn’t totally easy.

  • First, you need to install storybook in your resporitories directory where also your meteor root is, as described in the storybook tutorial. This creates a .storybook folder.
  • Now you create a webpack.config.js in inside the .storybook folder.

this webpack config basically looks like this:

const path = require("path")

module.exports = ({ config }) => {
    /* Manipulate config here */

	config.resolve.extensions.push('.coffee');
	config.resolve.extensions.push('.scss');
	config.resolve.extensions.push('.js');
    return config;
};

also, i created a preview-head.html inside the `.storybook* directory:

<!-- Supply your CSS from Meteor, if you cannot convince storybook to build it  -->
<link rel="stylesheet" href="http://localhost:3000/merged-stylesheets.css">

<script>
	window.__meteor_runtime_config__ =       {
      "meteorRelease": "METEOR@1.8.0.2",
      "meteorEnv": {
        "NODE_ENV": "development",
        "TEST_METADATA": "{}"
      },
      "PUBLIC_SETTINGS": {

      },
      "ROOT_URL": "http://localhost:3000",
      "ROOT_URL_PATH_PREFIX": "",
      "appId": "zivmvxxevpdg1xu8kc5",
      "isModern": true
    }
	window.Package = {};
	window.Roles = {
		GLOBAL_GROUP: '__global_roles__'
	};
</script>

now you can edit your webpack.config.js to load coffeescript (coffeescript 2):

	config.module.rules.push({
		test: /\.coffee$/,
		exclude: /(node_modules|bower_components)/,
		loaders: [
			{
				loader: 'babel-loader',
				query: {
					plugins: ['transform-react-jsx'],
				}
			},
			{
				loader: 'coffee-loader',
			}
		],
	})

next we want JSX-Support:

	config.module.rules.push({
		test: /\.(js|jsx)$/,
		exclude: /node_modules/,
		use: ['babel-loader']
	})

and basic CSS support:

	config.module.rules.push({
		test: /\.css$/,
		use: ['style-loader', 'css-loader']
	})

If you have a SCSS file with a single entrypoint and no meteor packages that also provide Styling code, you can add the entrypoint for the storybook scss. However, Storybook needs Tilde-Imports ~/... instead of meteor’s {}/.... So what I’ve done is creating a partial import scss file (starting with an underscore) that won’t be compiled by meteor’s scss compiler, that imports the scss code from the imports folder using tilde-global-imports and then within the imports folder only used relative imports

	config.entry.push(path.resolve(__dirname, '../client/stylesheets/_storybook.scss'))

	config.module.rules.push({
		test: /\.scss$/,
		use: [
			"style-loader", // creates style nodes from JS strings
			"css-loader", // translates CSS into CommonJS
			"sass-loader" // compiles Sass to CSS, using Node Sass by default
		]
	})

now the coffeescript meteor global import aliases:

    ['imports', 'client', 'server', 'locales'].forEach((pathName) => {
		config.resolve.alias['/' + pathName] = path.resolve(__dirname, '../' + pathName)
	})
	config.resolve.modules.push(path.resolve(__dirname, '../'))

and finally some trickery that when a component calls Meteor-Code (or at least imports it), it won’t crash:

config.resolve.alias['meteor/meteor'] = path.resolve(__dirname, './meteor-stubs/meteor.coffee')
	config.resolve.alias['meteor/mongo'] = path.resolve(__dirname, './meteor-stubs/mongo.coffee')
	config.resolve.alias['meteor/tracker'] = path.resolve(__dirname, './meteor-stubs/tracker.js')
	config.resolve.alias['meteor/check'] = path.resolve(__dirname, './meteor-stubs/check.js')
	config.resolve.alias['meteor/templating'] = path.resolve(__dirname, './meteor-stubs/templating.js')
	config.resolve.alias['meteor/reactive-var'] = path.resolve(__dirname, './meteor-stubs/reactive-var.js')
	config.resolve.alias['meteor/http'] = path.resolve(__dirname, './meteor-stubs/http.coffee')
	config.resolve.alias['meteor/session'] = path.resolve(__dirname, './meteor-stubs/session.js')
	config.resolve.alias['meteor/raix:eventemitter'] = path.resolve(__dirname, './meteor-stubs/eventemitter.js')
	config.resolve.alias['meteor/aldeed:collection2'] = path.resolve(__dirname, './meteor-stubs/collection2.js')

	config.resolve.alias['meteor/reactive-dict'] = path.resolve(__dirname, './meteor-stubs/reactive-dict.js')
	config.resolve.alias['meteor/react-meteor-data'] = path.resolve(__dirname, './meteor-stubs/react-meteor-data.js')

for most of the stubs, I just made empty methods that do nothing.

2 Likes