├── .gitignore
├── dev
├── index.html
└── dev.jsx
├── fixtures
└── simple.jsx
├── webpack.config.js
├── test.js
├── package.json
├── README.md
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/dev/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/fixtures/simple.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | let foo = (
4 |
5 | {
6 | true && <$frag>
7 | {''}
8 | gg
9 | {false}
10 | heeello
11 |
12 | $frag>
13 | }
14 |
15 | )
16 |
--------------------------------------------------------------------------------
/dev/dev.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 |
3 | class Component extends React.Component {
4 |
5 | render(){
6 |
7 | return (
8 |
9 | hello{' '}
10 | john
11 | {
12 | true &&
13 | hello
14 | john
15 |
16 | }
17 |
18 | )
19 | }
20 | }
21 |
22 | React.render(, document.body)
23 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var plugin = require(__dirname, './index.js')
2 |
3 | //console.log(typeof plugin)
4 |
5 | module.exports = {
6 |
7 | devtool: 'source-map',
8 |
9 | entry: './dev/dev.jsx',
10 |
11 | output: {
12 | filename: 'bundle.js',
13 | path: __dirname + '/dev'
14 | },
15 |
16 | babel: {
17 | presets: ['es2015', 'react'],
18 | plugins: [ plugin ]
19 | },
20 |
21 | module: {
22 | loaders: [
23 | {
24 | test: /\.jsx$|\.js$/,
25 | loader: 'babel-loader',
26 | exclude: /node_modules/
27 | }
28 | ]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | var babel = require('babel-core')
2 | , util = require('util')
3 | , plugin = require('./index')
4 | , fs = require('fs')
5 |
6 | require('chai').should()
7 |
8 | describe('should detect fragments', function(){
9 |
10 | it('should transpile to array', function(){
11 | console.log(transform('simple.jsx').code)
12 | })
13 | })
14 |
15 |
16 | function transform(file){
17 | return babel.transform(fs.readFileSync(__dirname + '/fixtures/' + file), {
18 | presets: ['es2015', 'react'],
19 | plugins: [
20 | [plugin, { tagName: '$frag' }]
21 | ]
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "babel-plugin-jsx-fragment",
3 | "version": "4.0.2",
4 | "description": "better element arrays in jsx",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha test.js",
8 | "webpack": "webpack --config webpack.config.js --watch"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/jquense/babel-plugin-jsx-fragment.git"
13 | },
14 | "keywords": [
15 | "babel-plugin",
16 | "jsx",
17 | "fragment"
18 | ],
19 | "author": "jquense",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/jquense/babel-plugin-jsx-fragment/issues"
23 | },
24 | "homepage": "https://github.com/jquense/babel-plugin-jsx-fragment#readme",
25 | "peerDependencies": {
26 | "react": ">=0.14.0"
27 | },
28 | "devDependencies": {
29 | "babel-core": "^6.13.2",
30 | "babel-loader": "^6.2.4",
31 | "babel-preset-es2015": "^6.13.2",
32 | "babel-preset-react": "^6.11.1",
33 | "chai": "^3.0.0",
34 | "mocha": "^2.2.5",
35 | "react": "^0.14.0",
36 | "webpack": "^1.9.10",
37 | "webpack-dev-server": "^1.9.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jsx fragment babel plugin
2 |
3 | ```sh
4 | npm i -S babel-plugin-jsx-fragment
5 | ```
6 |
7 | To use include `jsx-fragment` in your plugins array in your `.babelrc` or config object. Works with React `0.13`+ .
8 | By default the fragment tag name is `` but you can configure it to whatever you want with the `tagName` option.
9 |
10 | ```json
11 | {
12 | "plugins": [
13 | ["jsx-fragment", { "tagName": "fragment" }]
14 | ]
15 | }
16 | ```
17 |
18 |
19 | ### The Problem
20 |
21 | JSX gets ugly when using conditionals that return more than one jsx element
22 |
23 | ```js
24 |
25 | { true && [
26 |
,
27 | ]
28 | }
29 |
30 | ```
31 |
32 | You need to use commas (gross) and an array literal (yuck). `jsx-fragment` allows for a simple syntax to hide the ugliness, based on the discussion [here](https://github.com/facebook/react/issues/690#issuecomment-39679871). Just use the pseudo element `` to wrap arrays of Elements.
33 |
34 | ```js
35 |
36 | { condition &&
37 |
38 |
39 |
40 | }
41 |
42 | ```
43 |
44 | the `` element will be compiled away into the following.
45 |
46 | ```js
47 | React.createElement("div", null, condition && ReactFragment.create({
48 | key_0: React.createElement("span", null),
49 | key_1: React.createElement("div", null)
50 | })
51 | );
52 | ```
53 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function(babel) {
3 | var t = babel.types;
4 | var template = babel.template;
5 |
6 | var buildMapper = template(`
7 | CHILDREN.Children.map(FRAGMENT, function (child){
8 | return child;
9 | })
10 | `);
11 |
12 | function isFrag(node, tagName){
13 | return t.isJSXIdentifier(node.name) && node.name.name === tagName
14 | }
15 |
16 | return {
17 | visitor: {
18 | JSXElement: function(path, state) {
19 | var node = path.node
20 | var tagName = state.opts.tagName || 'frag';
21 | var opening = node.openingElement;
22 |
23 | if (isFrag(opening, tagName)) {
24 | if (opening.selfClosing)
25 | throw new Error(
26 | '<' + tagName + '> jsx elements cannot be self closing. The point of them is to contain children')
27 |
28 |
29 | var fragments = t.ArrayExpression(
30 | node.children.map(function(child, idx) {
31 | var value = child;
32 |
33 | if (t.isJSXText(value)) {
34 | if (value.value.trim() === '') {
35 | return null;
36 | }
37 | value = t.stringLiteral(value.value.trim())
38 | }
39 |
40 | if (t.isJSXExpressionContainer(value))
41 | value = value.expression;
42 |
43 | return value
44 | })
45 | .filter(f => f)
46 | )
47 |
48 | node = buildMapper({
49 | CHILDREN: state.file.addImport('react', 'default', 'fragment'),
50 | FRAGMENT: fragments
51 | })
52 |
53 | // why would you do this?
54 | if (t.isJSXElement(path.parent)) {
55 | node = t.JSXExpressionContainer(node.expression)
56 | }
57 |
58 | path.replaceWith(node)
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------