Code Monkey home page Code Monkey logo

layers-loader's Introduction

maintenance npm travis coverage deps gitter

Webpack loader for composing sets (layers) of React components. It allows you to easily create themes and share entire component libraries. A couple of use-cases:

products

platforms

Usage

#

Components from layers are imported with a special #-character along with their styles. So instead of:

import Button from '../../some-layer/button/';
import from '../../some-theme/button/styles.css';
import from '../../another-theme/button/styles.css';

you just write:

import Button from '#button';

It imports component from the nearest layer and styles from all layers.

With reBEM

Button is imported as factory (we just wrap it with React.createFactory(Button)), so we can use function calls instead of React.createElement(Button):

import { Component } from 'react';
import { BEM } from 'rebem';
import Button from '#button';

class SomeComponent extends Component {
  render() {
    return BEM({ block: 'some-block' },
      Button({ block: 'some-block', elem: 'button' }, 'Click me');
    )
  }
}

With JSX

Button is imported as is (see importFactory option in webpack config):

import React from 'react';
import Button from '#button';

class SomeComponent extends React.Component {
  render() {
    return (
      <div block="some-block">
        <Button block="some-block" elem="button">{'Click me'}</Button>;
      </div>
    );
  }
}

Example

core-components

Initiate the component

.
└── core-components/
    └── button/
        └── index.js
export default class extends React.Component {
  render() {//...}
}

theme-reset

Reset browser specific styles

.
└── theme-reset/
    └── button/
        └── styles.less

custom-components

At some point we can extend our component in a separate layer. For example, add an icon to a button:

.
└── custom-components/
    └── button
        └── index.js
// import Button from 'core-components/button/index.js';
// import from 'theme-reset/button/styles.less';
import Button from '#button';

export default class extends React.Component {
  renderIcon() { /*...*/ }
  render() {
    return (
      <Button {...this.props}>
        {children}
        {this.renderIcon()}
      </Button>
    );
  }
}

product-theme

Now we may need to apply some theme styles:

.
└── product-theme/
    └── button/
        └── index.js
// import Button from 'custom-components/button/index.js';
// import from 'theme-reset/button/styles.less';
// import from './styles.less';
import Button from '#button';

export default class extends React.Component {
    return (
      <Button {...this.props}>
        {children}
        <div className="button-mask" />
      </Button>
    );
}
.
└── product-theme/
    └── button/
        └── styles.less
.button {
  // ...

  &__mask {
    position: absolute;
    background: #f00;
    border: 2px solid #000;
  }
}

app

And finally we can use this button in our app with the optional local styles

.
└── app/
    └── somewhere.js
// import Button from 'product-theme/button/index.js';
// import from 'theme-reset/button/styles.less';
// import from 'product-theme/button/styles.less';
// import from 'app/components/button/styles.less';
import Button from '#button';

class SomeAppComponent extends React.Component {
    // ...
    return (
      //...
        <Button
          icon="never-gonna-give-you-up.png"
          onClick={doStuff}>
          {'Click me'}
        </Button>
      //...
    );
}

Creating a layer

You can use any technologies in your layers (css-preprocessors, babel, etc.). A good practice in this case is to prebuild it, so consumer of your layer wouldn't have to do it for you. Some examples of prebuilded layers:

folders structure

You can use any structure you want, the example below is just a guideline:

.
└── custom-layer/
    ├── index.js
    └── components/
        ├── button/
        │   ├── index.js
        │   └── styles.css
        ├── checkbox/
        ├── input/
        │   ├── index.js
        │   └── styles.css
        ├── radio/
        └── ...

layer config

Consumers of your layer need to know how to work with it, so a good practice is to create a layer config like this:

// custom-layer/index.js
var path = require('path');

module.exports = {
    path: path.resolve(__dirname, 'components/'),
    files: {
        main: 'index.js',
        styles: 'styles.css'
    },
    importFactory: true
};

path

Path to the components folder (it can be named components, lib, src, build, whatever).

files

File names to use when importing components from this layer.

  • main — component source: it can be optional if you are creating just css-theme
  • styles — component styles: always optional. You can have entire layer (theme) made only with styles. But actually you can extend your components in themes too — for example if you want to add some presentation element in children (like we did in the Button example above)

importFactory

If you use #-requires inside your layer, it's better to specify if you use factories there or not. For more details please see the importFactory option below.

Webpack config

In your app you need to configure how layers should be composed, where you app components are, etc. Example:

  // ...
  preLoaders: [
    {
      test: /\.js$/,
      loader: 'rebem-layers',
      query: {
        layers: [
          // shared layers
          require('core-components'),
          require('theme-reset'),
          require('../custom-layer'),

          // app components
          {
            path: path.resolve('src/components/'),
            files: {
              main: 'index.js',
              styles: 'styles.less'
            }
          }
        ],
        // app source
        consumers: [
          path.resolve('src/')
        ]
      }
    }
  ],
  // ...

layers

Array of layer configs. If some layers already have config, you can just import it.

consumers

Array of paths where you want to use (consume) components from the layers (with #-imports). For example, files outside your app component folder or in a unit-tests folder.

importFactory

default: true

By default when you use #-imports, all components are importing wrapped with React factories (React.createFactory(...)), but you can disable it by setting this option to false in root config or in specific layer config.

When you set importFactory option in root config it will be applied to consumers and all layers where importFactory option is not specified.

In the example below consumers and src/components layer will have importFactory: true, but src/containers will have importFactory: false, because it is specified there explicetely:

  preLoaders: [
    {
      test: /\.js$/,
      loader: 'rebem-layers',
      query: {
        layers: [
          {
            path: path.resolve('src/components/'),
            files: {
              main: 'index.js',
              styles: 'styles.less'
            }
          },
          {
            path: path.resolve('src/containers/'),
            files: {
              main: 'index.js',
              styles: 'styles.less'
            },
            importFactory: false
          }
        ],
        importFactory: true,
        consumers: [
          path.resolve('src/')
        ]
      }
    }
  ],

However if you chose to leave it as true, for example if you use reBEM without JSX, you may encounter with a situation when you need class in unit-tests. In this case you can use ?class option:

import Button from '#button?class';

it('is a component', function() {
	expect(ReactTestUtils.isCompositeComponent(Button)).to.be.true;
});

inject

default: false

If you want to mock dependencies of your components for unit-tests, set this option to true. It will allow you to do this:

// `inject` option in import path makes component injectable
import AppComponentInjector from '#app?inject';

const App = AppInjector({
    '~/some-flux-store': (function() {
    	return class MockedStore {
        	// ...
        };
    })()
});

TestUtils.renderIntoDocument(App);
// ... tests

You can use inject along with ?class-option as well:

// injectable component imported as React class
import AppComponentInjector from '#app?class&inject';

layers-loader's People

Contributors

guria avatar mistadikay avatar smacker avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

layers-loader's Issues

failed to use loader on windows

> [email protected] start D:\wspace\starter-kit
> start-runner ./tasks.js "dev"

→ env: start
→ env: NODE_ENV = development
→ env: done

→ webpack dev: start
→ webpack dev: http://localhost:3000/webpack-dev-server/
→ webpack dev: done
Time: 1491ms
chunk    {0} bundle.js (main) 644 kB [rendered]

ERROR in ./src/index.js
Module not found: Error: Cannot resolve module 'D:wspacestarter-kitsrccomponentsappindex.js' in D:\wspace\starter-kit\src
 @ ./src/index.js 5:53-113

ERROR in ./src/index.js
Module not found: Error: Cannot resolve module 'D:wspacestarter-kit
ebem-theme-reseuildappstyles.css' in D:\wspace\starter-kit\src
 @ ./src/index.js 5:127-211

ERROR in ./src/index.js
Module not found: Error: Cannot resolve module 'D:wspacestarter-kitsrc  heme-bootstrapappstyles.less' in D:\wspace\starter-kit\src
 @ ./src/index.js 5:213-281

ERROR in ./src/index.js
Module not found: Error: Cannot resolve module 'D:wspacestarter-kitsrccomponentsappstyles.less' in D:\wspace\starter-kit\src
 @ ./src/index.js 5:283-346
webpack: bundle is now VALID.

The importFactory flag's value ignored

Hi
Just as the documentation says, the importFactory flag changes what is returned by using #. But for some reason, when requesting a component in the consumer component (in a file defined as consumer in the rebem-layers-loader configuration) the returned value is always wrapped in React.createFactory, even if importFactory is set to false.

Not sure if this is intentional (then the documentation should probably mention this) or I am doing something wrong.

way to create a merged layer

I wonder if it possible with current toolset to bundle a "merged" layer in dist. It would be great way to expose components to projects that not necessarily should know about underlie tech stack.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.