├── .gitignore ├── README.md ├── index.js ├── package.json └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | ### JetBrains template 63 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 64 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 65 | 66 | # User-specific stuff: 67 | .idea/**/workspace.xml 68 | .idea/**/tasks.xml 69 | .idea/dictionaries 70 | 71 | # Sensitive or high-churn files: 72 | .idea/**/dataSources/ 73 | .idea/**/dataSources.ids 74 | .idea/**/dataSources.xml 75 | .idea/**/dataSources.local.xml 76 | .idea/**/sqlDataSources.xml 77 | .idea/**/dynamic.xml 78 | .idea/**/uiDesigner.xml 79 | 80 | # Gradle: 81 | .idea/**/gradle.xml 82 | .idea/**/libraries 83 | 84 | # CMake 85 | cmake-build-debug/ 86 | 87 | # Mongo Explorer plugin: 88 | .idea/**/mongoSettings.xml 89 | 90 | ## File-based project format: 91 | *.iws 92 | 93 | ## Plugin-specific files: 94 | 95 | # IntelliJ 96 | out/ 97 | 98 | # mpeltonen/sbt-idea plugin 99 | .idea_modules/ 100 | 101 | # JIRA plugin 102 | atlassian-ide-plugin.xml 103 | 104 | # Cursive Clojure plugin 105 | .idea/replstate.xml 106 | 107 | # Crashlytics plugin (for Android Studio and IntelliJ) 108 | com_crashlytics_export_strings.xml 109 | crashlytics.properties 110 | crashlytics-build.properties 111 | fabric.properties 112 | 113 | .idea/babel-plugin-jsx-if-directive.iml 114 | .idea/inspectionProfiles/ 115 | .idea/misc.xml 116 | .idea/modules.xml 117 | .idea/vcs.xml 118 | .idea/watcherTasks.xml 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ Not maintained!DO NOT use it on production environment. 2 | 3 | # Using "if" directive in JSX 4 | 5 | [![GitHub stars](https://img.shields.io/github/stars/HuQingyang/babel-plugin-jsx-if-directive.svg?style=social&label=Stars&style=plastic)](https://github.com/HuQingyang/babel-plugin-jsx-if-directive) 6 | [![GitHub forks](https://img.shields.io/github/forks/HuQingyang/babel-plugin-jsx-if-directive.svg?style=social&label=Fork&style=plastic)](https://github.com/HuQingyang/babel-plugin-jsx-if-directive) 7 | [![npm](https://img.shields.io/npm/dw/babel-plugin-jsx-if-directive.svg)](https://www.npmjs.com/package/babel-plugin-jsx-if-directive) 8 | [![npm](https://img.shields.io/npm/v/babel-plugin-jsx-if-directive.svg)](https://www.npmjs.com/package/babel-plugin-jsx-if-directive) 9 | [![npm](https://img.shields.io/npm/l/babel-plugin-jsx-if-directive.svg)](https://www.npmjs.com/package/babel-plugin-jsx-if-directive) 10 | 11 | A easy-to-use "if" directive solution for front-end frameworks using JSX like React. 12 | 13 | 14 | **See Also:** 15 | * Using two-way data binding in JSX: [babel-plugin-jsx-two-way-binding](https://github.com/HuQingyang/babel-plugin-jsx-two-way-binding) 16 | * Using for-directive in JSX: [babel-plugin-jsx-for-directive](https://github.com/HuQingyang/babel-plugin-jsx-for-directive) 17 | 18 | 19 | ## 1. Install 20 | `npm install --save-dev babel-plugin-jsx-if-directive` 21 | 22 | ## 2. Basic Usage 23 | Edit your __.babelrc__ file: 24 | ```json 25 | { 26 | "plugins": [ 27 | "jsx-if-directive" 28 | ] 29 | } 30 | ``` 31 | In your jsx file: 32 | ```jsx harmony 33 | class App extends Component { 34 | constructor() { 35 | super(); 36 | } 37 | 38 | state = { 39 | age: 0 40 | } 41 | 42 | plus = () => { 43 | const { state: { age } } = this; 44 | this.setState({ age: age + 10 }); 45 | } 46 | 47 | render() { 48 | const { age } = this.state; 49 | return ( 50 |
51 | 52 |

You are child.

53 |

You are youth.

54 |

You are middle-aged.

55 |

You are old man.

56 |

You are {age} years old

57 |
58 | ) 59 | } 60 | } 61 | ``` 62 | 63 | ## 3. Usage with custom attribute name 64 | Edit your __.babelrc__ file: 65 | ```json 66 | { 67 | "plugins": [ 68 | "jsx-if-directive", 69 | { 70 | "ifAttrName": "r-if", 71 | "elseAttrName": "r-else", 72 | "elseIfAttrName": "r-elif" 73 | } 74 | ] 75 | } 76 | ``` 77 | 78 | In your jsx file: 79 | ```jsx harmony 80 | class App extends Component { 81 | constructor() { 82 | super(); 83 | } 84 | 85 | state = { 86 | age: 0 87 | } 88 | 89 | plus = () => { 90 | const { state: { age } } = this; 91 | this.setState({ age: age + 10 }); 92 | } 93 | 94 | render() { 95 | const { age } = this.state; 96 | return ( 97 |
98 | 99 |

You are child.

100 |

You are youth.

101 |

You are middle-aged.

102 |

You are old man.

103 |

You are {age} years old

104 |
105 | ) 106 | } 107 | } 108 | ``` 109 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | const { 3 | removeNode, 4 | findNextNode, 5 | transformIfBinding, 6 | transformElseBinding, 7 | transformElseIfBindings, 8 | getAttrASTAndIndexByName 9 | } = require('./utils'); 10 | 11 | const defaultOpts = { 12 | ifAttrName: 'if', 13 | elseAttrName: 'else', 14 | elseIfAttrName: 'elseIf' 15 | }; 16 | 17 | module.exports = function () { 18 | function JSXElementVisitor(path, { opts = defaultOpts }) { 19 | const { 20 | ifAttrName, 21 | elseAttrName, 22 | elseIfAttrName 23 | } = opts; 24 | 25 | const ifBinding = getAttrASTAndIndexByName(path.node, ifAttrName); 26 | if (!ifBinding) return; 27 | 28 | let { 29 | parent: { children: siblings }, 30 | key: index 31 | } = path; 32 | 33 | let nextNode = findNextNode(path, siblings, index); 34 | if (!nextNode) return transformIfBinding(path, ifBinding); 35 | 36 | let elseBinding = getAttrASTAndIndexByName(nextNode, elseAttrName); 37 | const elseIfBindings = []; 38 | 39 | if (!elseBinding) { 40 | let elseIfBinding = getAttrASTAndIndexByName(nextNode, elseIfAttrName); 41 | while (elseIfBinding) { 42 | elseIfBindings.push(elseIfBinding); 43 | index += 1; 44 | nextNode = findNextNode(path, siblings, index); 45 | elseIfBinding = nextNode ? 46 | getAttrASTAndIndexByName(nextNode, elseIfAttrName) : 47 | null; 48 | } 49 | if (nextNode) { 50 | elseBinding = getAttrASTAndIndexByName(nextNode, elseAttrName); 51 | } 52 | } 53 | 54 | if (elseIfBindings.length > 0) { 55 | transformElseIfBindings(path, ifBinding, elseIfBindings, elseBinding); 56 | 57 | elseIfBindings.forEach((binding) => { 58 | removeNode(siblings, binding.node); 59 | }); 60 | if (elseBinding) { 61 | removeNode(siblings, elseBinding.node); 62 | } 63 | } else if (elseBinding) { 64 | transformElseBinding(path, ifBinding, elseBinding); 65 | removeNode(siblings, elseBinding.node); 66 | } 67 | } 68 | 69 | return { 70 | visitor: { 71 | JSXElement: JSXElementVisitor 72 | } 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-jsx-if-directive", 3 | "version": "0.1.3", 4 | "main": "index.js", 5 | "keywords": [ 6 | "JSX", 7 | "React", 8 | "directive", 9 | "if", 10 | "else", 11 | "else if" 12 | ], 13 | "author": "HuQingyang ", 14 | "license": "MIT", 15 | "homepage": "https://github.com/HuQingyang/babel-plugin-jsx-if-directive", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/HuQingyang/babel-plugin-jsx-if-directive" 19 | }, 20 | "dependencies": { 21 | "@babel/types": "^7.0.0-rc.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | 2 | const t = require('@babel/types'); 3 | 4 | function findNextNode(path, siblings, index) { 5 | if (!siblings) return null; 6 | 7 | const nextPath = siblings[index + 1]; 8 | if (!nextPath) return null; 9 | 10 | const { type, value } = nextPath; 11 | if (type === 'JSXText' && !value.trim()) 12 | return findNextNode(nextPath, siblings, index+1); 13 | return type === 'JSXElement' ? nextPath : null; 14 | } 15 | 16 | function getAttrASTAndIndexByName(node, attrName) { 17 | if (!node || !node.openingElement) return null; 18 | 19 | const { type, attributes } = node.openingElement; 20 | if (type !== 'JSXOpeningElement') return null; 21 | 22 | const index = attributes.findIndex( 23 | attr => attr.name && attr.name.name === attrName 24 | ); 25 | if (index < 0) return null; 26 | 27 | const attrBinding = attributes[index]; 28 | return { 29 | attrBinding, 30 | index, 31 | node 32 | }; 33 | } 34 | 35 | function removeNode(nodes, nodeToRemoved) { 36 | const index = nodes.findIndex((i) => i === nodeToRemoved); 37 | if (index >= 0) { 38 | nodes.splice(index, 1); 39 | } 40 | } 41 | 42 | function removeAttrASTByIndex(node, index) { 43 | const { openingElement } = node; 44 | if (!openingElement) return; 45 | 46 | const { attributes } = openingElement; 47 | attributes.splice(index, 1); 48 | } 49 | 50 | function transformIfBinding(path, ifBinding) { 51 | const { attrBinding, index, node } = ifBinding; 52 | 53 | removeAttrASTByIndex(node, index); 54 | 55 | const targetAST = t.conditionalExpression( 56 | attrBinding.value.expression, 57 | node, 58 | t.nullLiteral() 59 | ); 60 | path.replaceWith(targetAST); 61 | } 62 | 63 | function transformElseBinding(path, ifBinding, elseBinding) { 64 | const { 65 | attrBinding: ifAttr, 66 | index: ifIndex, 67 | node: ifNode 68 | } = ifBinding; 69 | const { 70 | node: elseNode, 71 | index: elseIndex 72 | } = elseBinding; 73 | 74 | removeAttrASTByIndex(ifNode, ifIndex); 75 | removeAttrASTByIndex(elseNode, elseIndex); 76 | 77 | const targetAST = t.conditionalExpression( 78 | ifAttr.value.expression, 79 | ifNode, 80 | elseNode 81 | ); 82 | path.replaceWith(targetAST); 83 | } 84 | 85 | function transformElseIfBindings(path, ifBinding, elseIfBindings, elseBinding) { 86 | const { 87 | attrBinding: ifAttr, 88 | index: ifIndex, 89 | node: ifNode 90 | } = ifBinding; 91 | 92 | removeAttrASTByIndex(ifNode, ifIndex); 93 | if (elseBinding) { 94 | const { 95 | node: elseNode, 96 | index: elseIndex 97 | } = elseBinding; 98 | removeAttrASTByIndex(elseNode, elseIndex); 99 | } 100 | elseIfBindings.forEach((binding) => { 101 | const { node, index } = binding; 102 | removeAttrASTByIndex(node, index); 103 | }); 104 | 105 | const callee = t.arrowFunctionExpression([], t.blockStatement([ 106 | t.ifStatement( 107 | ifAttr.value.expression, 108 | t.returnStatement(ifNode), 109 | getAlternteAST(elseIfBindings, elseBinding) 110 | ) 111 | ])); 112 | const targetAST = t.callExpression(callee, []); 113 | path.replaceWith(targetAST); 114 | } 115 | 116 | function getAlternteAST(elseIfBindings, elseBinding, index=0) { 117 | if (index+1 < elseIfBindings.length) { 118 | const elseIfBinding = elseIfBindings[index]; 119 | const { 120 | attrBinding, 121 | node 122 | } = elseIfBinding; 123 | return t.ifStatement( 124 | attrBinding.value.expression, 125 | t.returnStatement(node), 126 | getAlternteAST(elseIfBindings, elseBinding, index+1) 127 | ); 128 | } 129 | if (elseBinding) { 130 | return t.returnStatement(elseBinding.node); 131 | } 132 | return null; 133 | 134 | } 135 | 136 | function log(...args) { 137 | args.forEach(i => { 138 | console.log(JSON.stringify(i, '', ' ')); 139 | }) 140 | } 141 | 142 | 143 | module.exports = { 144 | getAttrASTAndIndexByName, 145 | findNextNode, 146 | transformIfBinding, 147 | transformElseBinding, 148 | removeNode, 149 | transformElseIfBindings, 150 | log 151 | }; 152 | --------------------------------------------------------------------------------