Skip to content

Instantly share code, notes, and snippets.

@donabrams
Last active January 29, 2016 23:25
Show Gist options
  • Save donabrams/385401785e5aa89bf5d8 to your computer and use it in GitHub Desktop.
Save donabrams/385401785e5aa89bf5d8 to your computer and use it in GitHub Desktop.

babel-plugin-encapsulate-jsx

TLDR; A babel plugin to namespace your components to package/version combinations. There is an associated css plugin.

why

At Craftsy, we use a monorepo: each React component is in it's own npm package.

src/example/package.json:

{
  "name": "@craftsy/example",
  "version": "1.0.0"
}

src/example/index.js:

import React from 'react';

export default Example({text, url, background, imgUrl, imgAlt}) {
    return (
        <div className="example" style={{backgroundImage: `url(${background})`}}>
            <h2>{text}</h2>
            <a href={url}>
                <div className="awesomeness">
                    <img src={imgUrl} alt={imgAlt}/>
                </div>
            </a>
        </div>
    );
}

src/example/yay.css:

.example {
    background: #00ff00 no-repeat fixed center;
}

.awesomeness {
    border: 1px solid black;
}

We realized that to maximize component reuse, we'd have to make sure that css is scoped to just that component AND it's version (so we could potentially have 2 versions of one component on a page). We could just use BEM or a namespacing pattern, but then we'd have to change the CSS and JSX EVERY time we pushed a new version. We also knew we'd make a lot of mistakes.

So we automated namespacing.

This plugin reads the package name and version from the nearest package.json and namespaces the JSX in all files to use it.

src/example/dist/index.js after this plugin runs:

import React from 'react';

export default Example({text, url, background, imgUrl, imgAlt}) {
    return (
        <div className="example _craftsy_example_1_0_0" style={{backgroundImage: `url(${background})`}}>
            <h2 className="_craftsy_example_1_0_0">{text}</h2>
            <a className="_craftsy_example_1_0_0" href={url}>
                <div className="awesomeness _craftsy_example_1_0_0">
                    <img className="_craftsy_example_1_0_0" src={imgUrl} alt={imgAlt}/>
                </div>
            </a>
        </div>
    );
}

src/example/dist/index.css after the css plugin runs:

.example._craftsy_example_1_0_0 {
    background: #00ff00 no-repeat fixed center;
}

.awesomeness._craftsy_example_1_0_0 {
    border: 1px solid black;
}

We're pretty aggressive here and there are more optimal ways to add class names everywhere. However, with gzip, the gains for adding this complexity are pretty minimal.

Use

Install with npm install --save-dev babel-plugin-encapsulate-jsx. Then, add the plugin section to your .babelrc:

.babelrc:

{
    "presets": ["es2015"],
    "plugins": ["encapsulate-jsx", "transform-react-jsx"]
}

If for some reason you are using babel programatically:

babel.transform(code, {
    plugins: ['encapsulate-jsx'],
}).code

Configuration

By including this plugin, it assumes you want to encapsulate all the files. This obviously isn't true for all cases.

Configuring this plugin to not encapsulate a package with a specific package.json flag:

.babelrc:

{
    "presets": ["es2015"],
    "plugins": [
        ["encapsulate-jsx", {
          "optKey": "dontEncapsulate"
        }],
        "transform-react-jsx"
    ]
}

src/example/package.json will be encapsulated:

{
  "name": "@craftsy/example",
  "version": "1.0.0"
}

src/example2/package.json will NOT be encapsulated:

{
  "name": "@craftsy/example2",
  "version": "1.0.0",
  "dontEncapsulate": true
}

src/example3/package.json will be encapsulated:

{
  "name": "@craftsy/example3",
  "version": "1.0.0",
  "dontEncapsulate": false
}

Configuring this plugin to optIn instead of optOut:

.babelrc:

{
    "presets": ["es2015"],
    "plugins": [
        ["encapsulate-jsx", {
          "optIn": true,
          "optKey": "encapsulated"
        }],
        "transform-react-jsx"
    ]
}

src/example/package.json will NOT be encapsulated:

{
  "name": "@craftsy/example",
  "version": "1.0.0"
}

src/example2/package.json will be encapsulated:

{
  "name": "@craftsy/example2",
  "version": "1.0.0",
  "encapsulated": true
}

src/example3/package.json will NOT be encapsulated:

{
  "name": "@craftsy/example3",
  "version": "1.0.0",
  "encapsulated": false
}

In the future we may support regex through optRegex: Feel free to make a PR!

Building

npm run test runs the tests against the ES6 src code.

npm run build transpiles the encapsulate code into the dist directory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment