├── .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 | 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 | --------------------------------------------------------------------------------