├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package.json ├── react-addons-jsx.d.ts ├── react-jsx.d.ts └── test ├── comment.test ├── comment.test.output ├── dollar-brace.test ├── dollar-brace.test.output ├── error.test ├── error.test.output ├── es3.test ├── es3.test.output ├── es5.test ├── es5.test.output ├── generateOutputs.js ├── harmony.test ├── harmony.test.output ├── identifier.test ├── identifier.test.output ├── jsx-comment.test ├── jsx-comment.test.output ├── run.js ├── stringtemplate.test └── stringtemplate.test.output /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.2.1 4 | 5 | - Add support for embedded expressions (#12, #13) 6 | 7 | ## v0.2.0 8 | 9 | - Update to react-tools v0.13 10 | - Update definition file to work with React v0.13 definition files from DefinitelyTyped 11 | - Add support for --target (#4) 12 | - Add support for --identifier 13 | 14 | ## v0.1.2 15 | 16 | - Add support for /*jsx*/ syntax (#2) 17 | 18 | ## v0.1.1 19 | 20 | - Add support for JSX spread attributes in react-jsx.d.ts (#1) 21 | 22 | ## v0.1.0 23 | 24 | - Initial version -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 James Brantly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts-jsx-loader 2 | 3 | Webpack loader for transforming JSX based on special syntax. Meant to be used 4 | with a [TypeScript loader](https://github.com/jbrantly/ts-loader) to allow using JSX with TypeScript. 5 | 6 | # Important Deprecation Note 7 | 8 | Since [native JSX support](http://www.jbrantly.com/typescript-and-jsx/) is coming to TypeScript 1.6, I consider this project to be complete. No new features will be added. However, bugs will still be fixed prior to the release of TypeScript 1.6. 9 | 10 | ## Installation 11 | 12 | ``` 13 | npm install ts-jsx-loader 14 | ``` 15 | 16 | ## Usage 17 | 18 | The loader chain should go from ts-jsx-loader into a TypeScript loader. Your 19 | configuration will look something like this: 20 | 21 | ```javascript 22 | module.exports = { 23 | entry: './app.ts', 24 | output: { 25 | filename: 'bundle.js' 26 | }, 27 | resolve: { 28 | extensions: ['', '.webpack.js', '.web.js', '.js', '.ts'] 29 | }, 30 | module: { 31 | loaders: [ 32 | { test: /\.ts$/, loader: 'ts-loader!ts-jsx-loader' } 33 | ] 34 | } 35 | } 36 | ``` 37 | 38 | ts-jsx-loader defines a fake API on React called React.jsx(). You should 39 | reference the included `react-jsx.d.ts` or `react-addons-jsx.d.ts` 40 | definition file for IDE support. This API accepts either a string or nothing. 41 | You can then create JSX as a template string or within multiline comments. 42 | 43 | ```javascript 44 | /// 45 | 46 | import React = require('react'); 47 | 48 | var message = 'Hello world' 49 | 50 | React.render(React.jsx(/* 51 |
52 | {message} 53 |
54 | */), document.body) 55 | 56 | // or if you're using TypeScript 1.4 or above with template strings, with or without embedded expressions 57 | 58 | React.render(React.jsx(` 59 |
60 | ${message} 61 |
62 | `), document.body) 63 | 64 | React.render(React.jsx(` 65 |
66 | {message} 67 |
68 | `), document.body) 69 | 70 | // or if you simply want to use JSX without making it valid TypeScript 71 | 72 | React.render( 73 | /*jsx*/ 74 |
75 | {message} 76 |
77 | /*jsx*/ 78 | , document.body) 79 | ``` 80 | 81 | The loader will find occurrences of `React.jsx()` or `/*jsx*/` and transform them into 82 | React.createElement() calls prior to being passed to the TypeScript 83 | loader. 84 | 85 | ## Options 86 | 87 | Specify options to the loader via query string: 88 | 89 | ```javascript 90 | ... 91 | module: { 92 | loaders: [ 93 | { test: /\.ts$/, loader: 'ts-loader!ts-jsx-loader?target=es3&identifier=react.jsx' } 94 | ] 95 | } 96 | ... 97 | ``` 98 | ### harmony *(boolean) (default=true)* 99 | 100 | Allows the use of ES6 features within JSX. 101 | 102 | - es3 103 | - **es5 (default)** 104 | 105 | ### identifier *(string) (default='React.jsx')* 106 | 107 | Change the identifier to something other than `React.jsx`. For example, you 108 | could import React with a lower-case *r* like so: 109 | 110 | ```javascript 111 | import react = require('react') 112 | 113 | react.jsx(`
`) 114 | ``` 115 | 116 | ### target *(string)* 117 | 118 | Specify the output target. [See this](http://facebook.github.io/react/blog/2015/03/10/react-v0.13.html#react-tools) 119 | for more information. 120 | 121 | - es3 122 | - **es5 (default)** 123 | 124 | ## License 125 | 126 | The MIT License (MIT) 127 | 128 | Copyright (c) 2015 James Brantly 129 | 130 | Permission is hereby granted, free of charge, to any person obtaining a copy 131 | of this software and associated documentation files (the "Software"), to deal 132 | in the Software without restriction, including without limitation the rights 133 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 134 | copies of the Software, and to permit persons to whom the Software is 135 | furnished to do so, subject to the following conditions: 136 | 137 | The above copyright notice and this permission notice shall be included in all 138 | copies or substantial portions of the Software. 139 | 140 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 141 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 142 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 143 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 144 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 145 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 146 | SOFTWARE. 147 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var reactTools = require('react-tools') 2 | var path = require('path') 3 | var loaderUtils = require('loader-utils') 4 | var escapeStringRegexp = require('escape-string-regexp'); 5 | 6 | module.exports = function (content) { 7 | this.cacheable() 8 | 9 | var query = loaderUtils.parseQuery(this.query); 10 | 11 | var reactToolsOptions = { 12 | harmony: query.harmony == undefined ? true : query.harmony 13 | } 14 | 15 | if (query.target) reactToolsOptions.target = query.target; 16 | 17 | var identifier = escapeStringRegexp(query.identifier || "React.jsx"); 18 | 19 | var that = this; 20 | 21 | var replace = function (match, jsx) { 22 | try { 23 | var reactCode = reactTools.transform(jsx, reactToolsOptions) 24 | } 25 | catch (ex) { 26 | that.emitError('Problem transforming the following:\n' + jsx + '\n\n' + ex) 27 | return match; 28 | } 29 | return '(' + reactCode + ')' 30 | }; 31 | 32 | var dollarBraceReplace = function (match, jsx) { 33 | jsx = jsx.replace(/\$\{([^]*?)\}/gm, "{$1}"); 34 | return replace(match, jsx); 35 | }; 36 | 37 | return content 38 | .replace(new RegExp(identifier + '\\(\\s*?`([^`\\\\]*(\\\\.[^`\\\\]*)*)`\\s*?\\)', 'gm'), dollarBraceReplace) // using template strings 39 | .replace(new RegExp(identifier + '\\(\\/\\*((.|[\\r\\n])*?)\\*\\/\\)', 'gm'), replace) // using multiline comments 40 | .replace(/\/\*jsx\*\/((.|[\r\n])*?)\/\*jsx\*\//gm, replace); // using jsx comments 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-jsx-loader", 3 | "version": "0.2.1", 4 | "description": "Webpack loader for transforming JSX in TypeScript files.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node ./node_modules/mocha/bin/mocha --reporter spec test/run.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jbrantly/ts-jsx-loader.git" 12 | }, 13 | "keywords": [ 14 | "webpack", 15 | "loader", 16 | "jsx", 17 | "typescript", 18 | "ts" 19 | ], 20 | "author": "James Brantly (http://www.jbrantly.com/)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/jbrantly/ts-jsx-loader/issues" 24 | }, 25 | "homepage": "https://github.com/jbrantly/ts-jsx-loader", 26 | "dependencies": { 27 | "escape-string-regexp": "^1.0.3", 28 | "loader-utils": "^0.2.6", 29 | "react-tools": "^0.13.1" 30 | }, 31 | "devDependencies": { 32 | "mocha": "^2.1.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /react-addons-jsx.d.ts: -------------------------------------------------------------------------------- 1 | // This file is intended to be used with the DefinitelyTyped React v0.13 addons definition (react-addons.d.ts) 2 | 3 | declare module "react/addons" { 4 | function jsx(jsx?: string): ReactElement; 5 | function __spread(...args: any[]): any; // for JSX Spread Attributes 6 | } 7 | -------------------------------------------------------------------------------- /react-jsx.d.ts: -------------------------------------------------------------------------------- 1 | // This file is intended to be used with the DefinitelyTyped React v0.13 definition (react.d.ts) 2 | 3 | declare module "react" { 4 | function jsx(jsx?: string): ReactElement; 5 | function __spread(...args: any[]): any; // for JSX Spread Attributes 6 | } 7 | -------------------------------------------------------------------------------- /test/comment.test: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return React.jsx(/* 4 |
5 | Testing 6 |
7 | */) 8 | } 9 | } -------------------------------------------------------------------------------- /test/comment.test.output: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return ( 4 | React.createElement("div", null, 5 | React.createElement("span", null, "Testing") 6 | ) 7 | ) 8 | } 9 | } -------------------------------------------------------------------------------- /test/dollar-brace.test: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return React.jsx(` 4 |
5 | More $ $$ signs ${this.static} 6 |
7 | `); 8 | } 9 | } 10 | 11 | class Test { 12 | render() { 13 | return React.jsx(` 14 |
15 | 16 | 17 |
18 | `); 19 | } 20 | } 21 | 22 | class Test { 23 | render() { 24 | return React.jsx(` 25 |
28 | `); 29 | } 30 | } 31 | 32 | class Test { 33 | render() { 34 | var validString = `This is a valid template string ${this.static}`; 35 | return React.jsx(/* 36 |
37 | Cost: 5${this.static} 38 |
39 | */); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/dollar-brace.test.output: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return ( 4 | React.createElement("div", null, 5 | React.createElement("span", {prop: this.static}, "More $ $$ signs ", this.static) 6 | ) 7 | ); 8 | } 9 | } 10 | 11 | class Test { 12 | render() { 13 | return ( 14 | React.createElement("div", null, 15 | React.createElement("span", {prop: {key: 'value'}}), 16 | React.createElement("span", {prop: {key: {key: 'value'}, key2: {key: 'value'}}}) 17 | ) 18 | ); 19 | } 20 | } 21 | 22 | class Test { 23 | render() { 24 | return ( 25 | React.createElement("div", {dangerouslySetInnerHTML: { 26 | __html: this.static 27 | }}) 28 | ); 29 | } 30 | } 31 | 32 | class Test { 33 | render() { 34 | var validString = `This is a valid template string ${this.static}`; 35 | return ( 36 | React.createElement("div", null, 37 | React.createElement("span", null, "Cost: 5$", this.static) 38 | ) 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/error.test: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return React.jsx(/* 4 |
5 |
6 | */) 7 | } 8 | } -------------------------------------------------------------------------------- /test/error.test.output: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return React.jsx(/* 4 |
5 |
6 | */) 7 | } 8 | } -------------------------------------------------------------------------------- /test/es3.test: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return React.jsx(/* 4 |
5 | Testing 6 |
7 | */) 8 | } 9 | } -------------------------------------------------------------------------------- /test/es3.test.output: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return ( 4 | React.createElement("div", null, 5 | React.createElement("span", {prop: this["static"]}, "Testing") 6 | ) 7 | ) 8 | } 9 | } -------------------------------------------------------------------------------- /test/es5.test: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return React.jsx(/* 4 |
5 | Testing 6 |
7 | */) 8 | } 9 | } -------------------------------------------------------------------------------- /test/es5.test.output: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return ( 4 | React.createElement("div", null, 5 | React.createElement("span", {prop: this.static}, "Testing") 6 | ) 7 | ) 8 | } 9 | } -------------------------------------------------------------------------------- /test/generateOutputs.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var jsxLoader = require('../index'); 4 | 5 | var context = { 6 | cacheable: function() {}, 7 | emitError: function() {} 8 | } 9 | 10 | var files = fs.readdirSync(__dirname) 11 | .filter(function(f) { return path.extname(f) == '.test' }) 12 | .forEach(function(f) { 13 | 14 | fs.readFile(path.join(__dirname, f), {encoding: 'utf-8'}, function(err, testFile) { 15 | if (err) return done(err) 16 | 17 | fs.writeFile(path.join(__dirname, f+'.output'), jsxLoader.call(context, testFile), {encoding: 'utf-8'}); 18 | }) 19 | 20 | }) 21 | -------------------------------------------------------------------------------- /test/harmony.test: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return React.jsx(/* 4 |
5 | console.log('clicked')}> 6 |
7 | */) 8 | } 9 | } -------------------------------------------------------------------------------- /test/harmony.test.output: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return ( 4 | React.createElement("div", null, 5 | React.createElement("span", {onClick: (function() {return console.log('clicked');})}) 6 | ) 7 | ) 8 | } 9 | } -------------------------------------------------------------------------------- /test/identifier.test: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return react.jsx(/* 4 |
5 | Testing 6 |
7 | */) 8 | } 9 | } 10 | 11 | class Test2 { 12 | render() { 13 | return react.jsx(` 14 |
15 | Testing 16 |
17 | `) 18 | } 19 | } -------------------------------------------------------------------------------- /test/identifier.test.output: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return ( 4 | React.createElement("div", null, 5 | React.createElement("span", null, "Testing") 6 | ) 7 | ) 8 | } 9 | } 10 | 11 | class Test2 { 12 | render() { 13 | return ( 14 | React.createElement("div", null, 15 | React.createElement("span", null, "Testing") 16 | ) 17 | ) 18 | } 19 | } -------------------------------------------------------------------------------- /test/jsx-comment.test: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | var subcomponent = /*jsx*/
Subcomponent
/*jsx*/ 4 | return ( 5 | /*jsx*/ 6 |
7 | {subcomponent} 8 | Testing 9 |
10 | /*jsx*/ 11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/jsx-comment.test.output: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | var subcomponent = (React.createElement("div", null, "Subcomponent")) 4 | return ( 5 | ( 6 | React.createElement("div", null, 7 | subcomponent, 8 | React.createElement("span", null, "Testing") 9 | ) 10 | ) 11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/run.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert") 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var jsxLoader = require('../index'); 5 | 6 | function runTests(tests, query) { 7 | 8 | var context = { 9 | cacheable: function() {}, 10 | emitError: function() {}, 11 | query: query 12 | } 13 | 14 | tests.forEach(function(f) { 15 | var filename = f + '.test'; 16 | describe(filename, function() { 17 | it('should have the correct output', function(done){ 18 | fs.readFile(path.join(__dirname, filename), {encoding: 'utf-8'}, function(err, testFile) { 19 | if (err) return done(err) 20 | fs.readFile(path.join(__dirname, filename+'.output'), {encoding: 'utf-8'}, function(err, outputFile) { 21 | if (err) return done(err) 22 | 23 | assert.equal(jsxLoader.call(context, testFile), outputFile); 24 | done(); 25 | }) 26 | }) 27 | }) 28 | }) 29 | }) 30 | } 31 | 32 | runTests([ 33 | 'comment', 34 | 'error', 35 | 'es5', 36 | 'harmony', 37 | 'jsx-comment', 38 | 'stringtemplate', 39 | 'dollar-brace' 40 | ]) 41 | 42 | runTests([ 43 | 'identifier' 44 | ], "?identifier=react.jsx") 45 | 46 | runTests([ 47 | 'es3' 48 | ], "?target=es3") 49 | -------------------------------------------------------------------------------- /test/stringtemplate.test: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return React.jsx(` 4 |
5 | Testing 6 |
7 | `) 8 | } 9 | } 10 | 11 | class Test { 12 | render() { 13 | return React.jsx( ` 14 |
15 | Testing 16 |
17 | ` ) 18 | } 19 | } 20 | 21 | class Test { 22 | render() { 23 | return React.jsx( 24 | ` 25 |
26 | Testing 27 |
28 | ` 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/stringtemplate.test.output: -------------------------------------------------------------------------------- 1 | class Test { 2 | render() { 3 | return ( 4 | React.createElement("div", null, 5 | React.createElement("span", null, "Testing") 6 | ) 7 | ) 8 | } 9 | } 10 | 11 | class Test { 12 | render() { 13 | return ( 14 | React.createElement("div", null, 15 | React.createElement("span", null, "Testing") 16 | ) 17 | ) 18 | } 19 | } 20 | 21 | class Test { 22 | render() { 23 | return ( 24 | React.createElement("div", null, 25 | React.createElement("span", null, "Testing") 26 | ) 27 | ) 28 | } 29 | } 30 | --------------------------------------------------------------------------------