Neatly Punctuated Musings

    @bitchcraft/injector

    1.1.3 • Public • Published

    @bitchcraft/injector

      ,   |
      |———|———————=——\
      |———|———————|)  ]%%===———,
      |———|———————=——/        · °
      '   |                  · °
    

    CircleCI

    Inject CSS styles on demand for ReactJS Components. Keeps your DOM lean.

    • Exclusively build for ReactJS and Webpack
    • Supports themes, including local theme overrides
    • Pick your poison: Supports CSS, LESS, SCSS and SASS

    What’s in the bento?

    • withInjector HoC (higher order component) — conveniently wraps your component and takes care of adding and removing HTMLStyleElements
    • injector-loader for Webpack (tested w/ 1.14.0, 3.11.0) — prepares your valid CSS to be converted into a template
    • Webpack config fragment (for Webpack 2+) — Webpack 1 user? Update pl0x 😹. Don’t worry, we got you covered. You will find installation instructions for Webpack 1 further down.

    So what does it do?

    Inline styles in ReactJS are great for simple tasks. But having to move from a pure function to a stateful Component simply because you need a visual hover effect on that button sucks ass.

    Monolithic stylesheets are awesome in terms of features (think break points and less/sass mixins etc.), but as they grow larger they are increasingly harder to maintain and affect browser performance.

    Mixing both approaches works reasonably well with smaller applications or cases where visual fidelity is not that important or the tools provided by a style library such as material-ui or react-bootstrap are sufficient to achieve the desired outcome. As soon as you venture into custom looks and even multiple themes monolithic stylesheets become either bloated or you have to add proper tooling. Also synchronizing variables across stylesheets and inline styles is a pain in the ass, that can only be solved by means of redundant variable declarations or again: tooling.

    That’s where Injector comes in and tries to give you the best out of all worlds. It follows ReactJS’ fully modular approach. Injector allows you to write modular stylesheets for your components and reuse LESS/SASS mixins and partials. It takes care of adding those styles when and only when the first instance of your component are mounted and remove them when the last instance of your component will be unmounted.

    With Injector you will have the ability to use themes and custom overrides, by extending your CSS/LESS/SASS syntax with handlebars variables.

    .my-class {
    	color: '{{{Colors.primaryHighlightText}}}';
    }

    Are their alternatives to injector?

    Have a look at

    • Radium, which is basically React Inline Styles on ’roids!
    • Material UI styles, which supports different ways to inject stylesheets (via classNames and styled components)

    Limitations

    • Currently it will probably not work with the awesome RTLCSS
    • Because of the way ReactJS context works, on-the-fly theme switching currently still requires you to remount all components

    Installation

    1) Install injector

    Commands provided for Yarn and NPM, please use either (but not both).

    $ yarn add @bitchcraft/injector
    $ npm install -P @bitchcraft/injector

    Install peer devDependencies

    Technically you only have to install sass-loader if you are using scss/sass. In that case Yarn/NPM will print a unmet peer dependencies warning tho.

    $ yarn add --dev handlebars-loader sass-loader less-loader postcss-loader
    $ npm install -D handlebars-loader sass-loader less-loader postcss-loader

    2) Add injector to your webpack.config.js

    Webpack 2+

    // webpack.config.js
    const { InjectorWebpackConfig } = require('@bitchcraft/injector');
    
    module.exports = {
    	// …
    	module: {
    		rules: [
    			// your other module rules config
    		].concat(InjectorWebpackConfig.rules),
    	},
    	resolveLoader: {
    		modules: [
    			'node_modules',
    		].concat(InjectorWebpackConfig.resolveLoader.modules),
    	},
    	// …
    };

    Create a postcss config:

    // postcss.config.js
    module.exports = {
    	plugins: [ require('autoprefixer') ]
    }

    Webpack 1

    // webpack.config.js
    const { InjectorWebpackConfig } = require('@bitchcraft/injector');
    const autoprefixer = require('autoprefixer');
    
    module.exports = {
    	// …
    	module: {
    		loaders: [
    			// …
    			{
    				// SASS (*.sasshbs) and SCSS (*.scsshbs) files
    				test: /\.s[ac]sshbs$/,
    				loader: 'handlebars-loader!injector-loader!postcss-loader!sass-loader'
    			}, {
    				// CSS (*.csshbs) and LESS (*.lesshbs) files
    				test: /\.(?:c|le)sshbs$/,
    				loader: 'handlebars-loader!injector-loader!postcss-loader!less-loader'
    			}
    		],
    	},
    	postcss: function () {
            return [autoprefixer];
        },
    	resolveLoader: {
    		modulesDirectories: [
    			'node_modules',
    			InjectorWebpackConfig.resolveLoader.modules[0],
    		],
    	},
    	// …
    };

    Usage

    Components

    Write your stylesheet for your component, using CSS, LESS or SASS/SCSS. You can use imports, mixins, partials and all other language features. The only limitation is, that for LESS/SASS your value is a string. So color tools are out of the picture.

    /* myComponent.scsshbs */
    .my-component {
    	color: '{{{color}}}';
    	font-size: '{{{fontSize}}}';
    
    	a:visited, a:link {
    		text-decoration: underline;
    		color: '{{{link.color}}}';
    	}
    
    	&.important {
    		color: '{{{importantColor}}}';
    	}
    }

    You can name your file whatever you like, but the file extension has to be one of these:

    File extension CSS LESS SASS SCSS
    .csshbs ×
    .lesshbs × ×
    .scsshbs × × ×
    .sasshbs × × ×

    Then write your component

    import React from 'react';
    import { withInjector } from '@bitchcraft/injector';
    import stylesheet from 'myComponent.scsshbs';
    
    const MyComponent = ({ children, important }) => (
    	// your selectors should match your stylesheet
    	<div className={`my-component ${!important ? '' : 'important'}`}>
    		<span>This is my link: </span>
    		<a href="#">{children}</a>
    	</div>
    );
    
    // only needed if you use minification in your build pipeline
    MyComponent.displayName = 'MyComponent';
    
    // this is your style function, which returns an object with your replacement key/values
    const style = () => ({
    	color: 'rgba(0, 0, 0, 0.75)',
    	fontSize: '35px',
    	importantColor: '#f00',
    	link: {
    		color: 'rgb(0, 20, 80)',
    	},
    });
    
    // Injector takes care of everything else
    export default withInjector(stylesheet, style)(MyComponent);

    Uglify/Minify and other compression/mangle tools

    The way most code compression and minification tools work is by replacing long strings in function, class and variable names with shorter ones, and stripping implicit features. That is why you explicitely have to set displayName for your components and pure functions when using Injector.

    class MyComponent extends Component {
    	static displayName = 'MyComponent'
    
    	render() {  }
    }
    
    const MyPureFunction = ({ children }) => 
    MyPureFunction.displayName = 'MyPureFunction'

    Themes

    In order to use themes with Injector, you have to declare them upstream of your components. Your theme is simply an object with key/value pairs.

    import React, { PureComponent } from 'react';
    import MyComponent from './MyComponent';
    
    class ThemeContainer extends PureComponent {
    	static childContextTypes = {
    		theme: PropTypes.objectOf(PropTypes.any),
    	}
    
    	getChildContext() {
    		return {
    			theme: {
    				Colors: {
    					important: '#ff0000',
    					defaultText: '#ff42a0',
    					defaultLink: 'rgb(0, 20, 80)',
    				},
    				Sizes: {
    					large: '35px',
    				},
    			},
    		};
    	}
    
    	render() {
    		return <MyComponent/>;
    	}
    }

    Now you can use that theme in the style function of your component.

    // …
    
    const style = (Theme) => ({
    	color: Theme.Colors.defaultText,
    	fontSize: Theme.Sizes.Text.large,
    	importantColor: Theme.Colors.important,
    	link: {
    		color: Theme.Colors.defaultLink,
    	},
    });
    
    export default withInjector(stylesheet, style)(MyComponent);

    Overriding themes

    You can override themes on a per Injector (= per Component) basis. If you have a light and a dark theme and want to specifically always use the dark theme in your component you could do this:

    import DarkTheme from './DarkTheme.json';
    /*
    	rest of your imports, component declaration, style function, etc.
     */
    
    const options = {
    	theme: DarkTheme,
    };
    
    export default withInjector(stylesheet, style, options)(MyComponent);

    API

    withInjector

    Higher order component that wraps a ReactJS Component, PureComponent or Pure Function.

    Composable (tested with recompose).

    withInjector(stylesheet, styles, options)(ReactComponent)
    Name Type Description
    stylesheet HandlebarsTemplate Required the styles template
    styles function(theme: Object) => Object Required returns the variable replacements for the template
    options Object overrides
    options.theme Object override theme
    options.displayName string override (or set) Component displayName

    Help and feedback

    Please file issues in Github

    Contribute

    We are open for PRs. Please respect to the linting rules.

    • Can you write Webpack Plugins? We want to migrate the current loader template based approach to a plugin

    License

    Injector is free software und the BSD-3-Clause (see LICENSE.md).

    Contributors

    Keywords

    none

    Install

    npm i @bitchcraft/injector

    DownloadsWeekly Downloads

    5

    Version

    1.1.3

    License

    BSD-3-Clause

    Unpacked Size

    789 kB

    Total Files

    24

    Last publish

    Collaborators

    • bcnpmjs
    • maddrag0n
    • thiemok