├── .babelrc ├── .editorconfig ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src └── index.js └── test ├── fixtures └── transform-jsx-element-to-string-literal │ ├── converts JSX element to a string literal │ ├── actual.js │ ├── expected.js │ └── options.json │ ├── converts JSX elements matching options.tagNames to a string literal │ ├── actual.js │ ├── expected.js │ └── options.json │ └── does not convert JSX element to a string literal if options.tagNames is overwritten │ ├── actual.js │ ├── expected.js │ └── options.json └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-es2015-modules-commonjs", 4 | "transform-es2015-parameters", 5 | "transform-es2015-destructuring", 6 | "transform-flow-strip-types" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "canonical" 4 | ], 5 | "root": true 6 | } 7 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/config-chain/test/broken.json 3 | /node_modules/conventional-changelog-core/test/fixtures/_malformation.json 4 | /node_modules/npmconf/test/fixtures/package.json 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules 4 | *.log 5 | .* 6 | !.babelrc 7 | !.editorconfig 8 | !.eslintrc 9 | !.flowconfig 10 | !.gitignore 11 | !.npmignore 12 | !.travis.yml 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | coverage 4 | .* 5 | *.log 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 7 4 | - 6 5 | - 5 6 | after_success: 7 | - NODE_ENV=production npm run build 8 | - semantic-release pre && npm publish && semantic-release post 9 | notifications: 10 | email: false 11 | sudo: false 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Gajus Kuizinas (http://gajus.com/) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-transform-jsx-element-to-string-literal 2 | 3 | [![Travis build status](http://img.shields.io/travis/gajus/babel-plugin-transform-jsx-element-to-string-literal/master.svg?style=flat-square)](https://travis-ci.org/gajus/babel-plugin-transform-jsx-element-to-string-literal) 4 | [![NPM version](http://img.shields.io/npm/v/babel-plugin-transform-jsx-element-to-string-literal.svg?style=flat-square)](https://www.npmjs.org/package/babel-plugin-transform-jsx-element-to-string-literal) 5 | [![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical) 6 | [![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social&label=Follow)](https://twitter.com/kuizinas) 7 | 8 | Transforms JSX elements to a string literal. 9 | 10 | The default behaviour is to convert only `` elements (see [Configuration](#configuration)). 11 | 12 | * [Example transpilation](#example-transpilation) 13 | * [Motivation](#motivation) 14 | * [Configuration](#configuration) 15 | 16 | ## Example transpilation 17 | 18 | Input: 19 | 20 | ```js 21 | const foo = Hello, World!; 22 | 23 | const bar = 24 | Hello, 25 | World! 26 | ; 27 | 28 | ``` 29 | 30 | Output: 31 | 32 | ```js 33 | const foo = "Hello, World!"; 34 | 35 | const bar = "\n Hello,\n World!\n"; 36 | 37 | ``` 38 | 39 | ## Motivation 40 | 41 | My primary use case for this plugin is to enable writing multi-line MySQL queries. 42 | 43 | In MySQL, if an [identifier](https://dev.mysql.com/doc/refman/5.5/en/identifiers.html) contains special characters or is a reserved word, you must quote it whenever you refer to it. The identifier quote character is the backtick (`` ` ``). 44 | 45 | Nonreserved keywords are permitted as identifiers without quoting. However, it is a healthy habit to quote all identifiers to prevent accidental collision with [keywords, reserved words](https://dev.mysql.com/doc/refman/5.5/en/keywords.html) and names of [built-in functions](http://dev.mysql.com/doc/refman/5.7/en/functions.html). 46 | 47 | There is no problem writing single-line MySQL queries because you can use single or double quotes, e.g. 48 | 49 | ```js 50 | const = 'SELECT `p1`.`id`, `p1`.`name`, `t1`.`id` `tagId`, `t1`.`name` `tagName` FROM `page` `p1` INNER JOIN `page_tag` `pt1` ON `pt1`.`page_id` = `p1`.`id` GROUP BY `p1`.`id` HAVING COUNT(`pt1`.`id`) = 4'; 51 | 52 | ``` 53 | 54 | However, writing a long query on a single line hurts the readability of the query. You cannot use [template literals](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals) because the syntax clashes with the MySQL identifier quote character. 55 | 56 | A proponent of the template literals syntax will argue that you can escape the backtick character, e.g. 57 | 58 | ```js 59 | const = ` 60 | SELECT 61 | \`p1\`.\`id\`, 62 | \`p1\`.\`name\`, 63 | \`t1\`.\`id\` \`tagId\`, 64 | \`t1\`.\`name\` \`tagName\` 65 | FROM 66 | \`page\` \`p1\` 67 | INNER JOIN 68 | \`page_tag\` \`pt1\` 69 | ON 70 | \`pt1\`.\`page_id\` = \`p1\`.\`id\` 71 | GROUP BY 72 | \`p1\`.\`id\` 73 | HAVING 74 | COUNT(\`pt1\`.\`id\`) = 4 75 | `; 76 | 77 | ``` 78 | 79 | However, this approach has several disadvantages: 80 | 81 | * It makes it hard to read the query. 82 | * You cannot copy back and forth the query between your IDE and an SQL client (as is often the workflow). 83 | 84 | As a result, the [established practise for writing MySQL queries that span multiple lines](http://gajus.com/blog/8/using-mysql-in-node-js#queries-that-span-multiple-lines) is to store them in a separate file. This is a good approach, esp. for very long queries. However, sometimes you want to have queries in-file, e.g. when prototyping or if you simply prefer to. 85 | 86 | This is where the `babel-plugin-transform-jsx-element-to-string-literal` comes in. You can declare any query using JSX elements: 87 | 88 | ```js 89 | const = 90 | SELECT 91 | `p1`.`id`, 92 | `p1`.`name`, 93 | `t1`.`id` `tagId`, 94 | `t1`.`name` `tagName` 95 | FROM 96 | `page` `p1` 97 | INNER JOIN 98 | `page_tag` `pt1` 99 | ON 100 | `pt1`.`page_id` = `p1`.`id` 101 | GROUP BY 102 | `p1`.`id` 103 | HAVING 104 | COUNT(`pt1`.`id`) = 4 105 | ; 106 | 107 | ``` 108 | 109 | > Note: 110 | > The above example is using `{"tagNames": ["sql"]}` configuration. 111 | 112 | ## Configuration 113 | 114 | |Name|Default|Description| 115 | |---|---|---| 116 | |`tagNames`|`["heredoc"]`|List of JSX element names that need to be transpiled to a string.| 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "email": "gajus@gajus.com", 4 | "name": "Gajus Kuizinas", 5 | "url": "http://gajus.com" 6 | }, 7 | "dependencies": { 8 | "babel-plugin-syntax-jsx": "^6.18.0", 9 | "babel-types": "^6.19.0" 10 | }, 11 | "description": "Transforms JSX elements to a string literal.", 12 | "devDependencies": { 13 | "babel-cli": "^6.18.0", 14 | "babel-helper-plugin-test-runner": "^6.18.0", 15 | "babel-plugin-transform-es2015-destructuring": "^6.19.0", 16 | "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", 17 | "babel-plugin-transform-es2015-parameters": "^6.18.0", 18 | "babel-plugin-transform-flow-strip-types": "^6.18.0", 19 | "eslint": "^3.11.1", 20 | "eslint-config-canonical": "^5.8.0", 21 | "flow-bin": "^0.36.0", 22 | "husky": "^0.11.9", 23 | "mocha": "^3.2.0", 24 | "semantic-release": "^6.3.5" 25 | }, 26 | "engines": { 27 | "node": ">5.0.0" 28 | }, 29 | "keywords": [ 30 | "babel-plugin", 31 | "sql", 32 | "mysql", 33 | "heredoc" 34 | ], 35 | "license": "BSD-3-Clause", 36 | "main": "dist/index.js", 37 | "name": "babel-plugin-transform-jsx-element-to-string-literal", 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/gajus/babel-plugin-transform-jsx-element-to-string-literal" 41 | }, 42 | "scripts": { 43 | "build": "rm -fr ./dist && NODE_ENV=production babel ./src --out-dir ./dist --source-maps", 44 | "lint": "eslint ./src", 45 | "precommit": "npm run test", 46 | "test": "NODE_ENV=development npm run lint && npm run build && mocha --compilers js:babel-register && flow" 47 | }, 48 | "version": "1.0.0" 49 | } 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import babelPluginJsxSyntax from 'babel-plugin-syntax-jsx'; 4 | import BabelTypes from 'babel-types'; 5 | 6 | const defaultTagNames = [ 7 | 'heredoc' 8 | ]; 9 | 10 | export default ({ 11 | types: t 12 | }: { 13 | types: BabelTypes 14 | }) => { 15 | return { 16 | inherits: babelPluginJsxSyntax, 17 | visitor: { 18 | JSXElement (path: Object, stats: Object): void { 19 | const tagNames = stats.opts.tagNames || defaultTagNames; 20 | 21 | if (!tagNames.includes(path.node.openingElement.name.name)) { 22 | return; 23 | } 24 | 25 | if (path.node.children.length !== 1) { 26 | throw new Error('Unexpected children count.'); 27 | } 28 | 29 | const body = path.node.children[0]; 30 | 31 | if (!t.isJSXText(body)) { 32 | throw new Error('Unexpected body node type.'); 33 | } 34 | 35 | path.replaceWith( 36 | t.stringLiteral(body.value) 37 | ); 38 | } 39 | } 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /test/fixtures/transform-jsx-element-to-string-literal/converts JSX element to a string literal/actual.js: -------------------------------------------------------------------------------- 1 | const foo = Hello, World!; 2 | 3 | const bar = 4 | Hello, 5 | World! 6 | ; 7 | -------------------------------------------------------------------------------- /test/fixtures/transform-jsx-element-to-string-literal/converts JSX element to a string literal/expected.js: -------------------------------------------------------------------------------- 1 | const foo = "Hello, World!"; 2 | 3 | const bar = "\n Hello,\n World!\n"; 4 | -------------------------------------------------------------------------------- /test/fixtures/transform-jsx-element-to-string-literal/converts JSX element to a string literal/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "../../../../src" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/transform-jsx-element-to-string-literal/converts JSX elements matching options.tagNames to a string literal/actual.js: -------------------------------------------------------------------------------- 1 | const foo = Hello, World!; 2 | 3 | const bar = 4 | Hello, 5 | World! 6 | ; 7 | -------------------------------------------------------------------------------- /test/fixtures/transform-jsx-element-to-string-literal/converts JSX elements matching options.tagNames to a string literal/expected.js: -------------------------------------------------------------------------------- 1 | const foo = "Hello, World!"; 2 | 3 | const bar = "\n Hello,\n World!\n"; 4 | -------------------------------------------------------------------------------- /test/fixtures/transform-jsx-element-to-string-literal/converts JSX elements matching options.tagNames to a string literal/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "../../../../src", 5 | { 6 | "tagNames": [ 7 | "sql" 8 | ] 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/transform-jsx-element-to-string-literal/does not convert JSX element to a string literal if options.tagNames is overwritten/actual.js: -------------------------------------------------------------------------------- 1 | const foo = Hello, World!; 2 | 3 | const bar = 4 | Hello, 5 | World! 6 | ; 7 | -------------------------------------------------------------------------------- /test/fixtures/transform-jsx-element-to-string-literal/does not convert JSX element to a string literal if options.tagNames is overwritten/expected.js: -------------------------------------------------------------------------------- 1 | const foo = Hello, World!; 2 | 3 | const bar = 4 | Hello, 5 | World! 6 | ; 7 | -------------------------------------------------------------------------------- /test/fixtures/transform-jsx-element-to-string-literal/does not convert JSX element to a string literal if options.tagNames is overwritten/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "../../../../src", 5 | { 6 | "tagNames": [] 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-commonjs 2 | require('babel-helper-plugin-test-runner')(__dirname); 3 | --------------------------------------------------------------------------------