├── .gitignore ├── yarn.lock ├── package.json ├── src └── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /dist/ 3 | /coverage/ 4 | bundle-stats.html 5 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | source-map@^0.7.3: 6 | version "0.7.3" 7 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" 8 | integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsx-compress-loader", 3 | "version": "1.1.1", 4 | "description": "JSX optimization loader", 5 | "main": "src/index.js", 6 | "scripts": {}, 7 | "author": "theKashey ", 8 | "license": "MIT", 9 | "bugs": { 10 | "url": "https://github.com/theKashey/jsx-compress-loader/issues" 11 | }, 12 | "homepage": "https://github.com/theKashey/jsx-compress-loader#readme", 13 | "peerDependencies": { 14 | "source-map": "^0.7.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {SourceNode, SourceMapConsumer} = require('source-map'); 4 | 5 | function transform(source, map) { 6 | if (this.cacheable) { 7 | this.cacheable(); 8 | } 9 | if ( 10 | source.indexOf('React.createElement') >= 0 || 11 | source.indexOf('React__default.createElement') >= 0 || 12 | source.indexOf('_react2.default.createElement') >= 0 || 13 | 0 14 | ) { 15 | const separator = '\n\n;'; 16 | const appendText = ` 17 | // ${source.indexOf('React.createElement')}; 18 | var __react_jsx__ = require('react'); 19 | var _J$X_ = (__react_jsx__.default || __react_jsx__).createElement; 20 | var _J$F_ = (__react_jsx__.default || __react_jsx__).Fragment; 21 | `; 22 | 23 | const newSource = source 24 | // ts and es6 "react" 25 | .replace(/React\.createElement\(/g, '_J$X_(') 26 | .replace(/React\.Fragment\(/g, '_J$F_(') 27 | // transpiled react 28 | .replace(/_react2\.default\.createElement\(/g, '_J$X_(') 29 | .replace(/_react2\.default\.Fragment\(/g, '_J$F_(') 30 | // 31 | .replace(/React__default\.createElement\(/g, '_J$X_(') 32 | .replace(/React__default\.Fragment\(/g, '_J$F_('); 33 | 34 | 35 | if (!this.sourceMap || !map) { 36 | return this.callback(null, [ 37 | appendText, 38 | newSource, 39 | ].join(separator)); 40 | } 41 | 42 | const node = new SourceNode(null, null, null, [ 43 | new SourceNode(null, null, this.resourcePath, appendText), 44 | SourceNode.fromStringWithSourceMap(newSource, new SourceMapConsumer(map)), 45 | ]).join(separator); 46 | 47 | const result = node.toStringWithSourceMap(); 48 | return this.callback(null, result.code, result.map.toString()); 49 | } 50 | return this.callback(null, source, map); 51 | } 52 | 53 | module.exports = transform; 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsx-compress-loader 2 | ---- 3 | JSX optimization webpack loader 4 | 5 | ## What it does 6 | 7 | Replaces `React.createElement` by a local variable, thus __reduce__ "number of characters" per React Element 8 | from __17__ to __1__, as long local variable would be _uglified_. 9 | 10 | - "a.b.createElement".length === 17, 11 | - "React.default.createElement".length === 27. 12 | - usually - about 23 after all optimizations and transplications. 13 | - this solution - __1__ 14 | 15 | That is not a problem for Preact or Inferno, only to "default" React, as long only React has got "long element creation". 16 | See [this tweet from Dan Abramov](https://twitter.com/dan_abramov/status/841266032576724992), to find more about it. 17 | 18 | This technique also is almost NOT affecting gzipped size, only the _real_ amount of js code, browser has to parse. 19 | 20 | ### Bonus 21 | 22 | This also removes object property access (ie React.createElement), thus: 23 | - speeding up `Chrome` by 5% 24 | - speeding up `Safari 11` by 15% 25 | - speeding up `Safari 12` by 35% 26 | - not speeding up Mobile Safari 12(iPhone XS) 27 | - here is [the test](https://jsperf.com/single-dot-property-access/1) 28 | 29 | # Would it help? 30 | Just open your bundle, and count `createElement` inside. Or open any page, and count closing tags ` react -> babel -> js 38 | - and dont forget to apply it to node_modules as well. 39 | > in terms of webpack configuration - "after" goes "before", ie top-most loader is the "last" one. 40 | 41 | ### Only for ESM modules! 42 | babel "modules" should be "false" - you already should have it, for proper tree-shaking, and 43 | this is what this library is counting on. 44 | 45 | ### As separate loader 46 | ```js 47 | rules: [ 48 | { 49 | test: /\.js$/, // for any js file in your project 50 | use: 'jsx-compress-loader', 51 | }, 52 | { 53 | test: /\.js$/, 54 | exclude: /node_modules/, 55 | use: 'babel-loader', 56 | }, 57 | ]; 58 | ``` 59 | 60 | ### As chained loader 61 | ```js 62 | rules: [ 63 | { 64 | test: /\.js$/, // paired with babel loader 65 | exclude: /node_modules/, 66 | use: [ 67 | 'jsx-compress-loader' 68 | 'babel-loader', 69 | ], 70 | }, 71 | ]; 72 | ``` 73 | 74 | ## Other ways 75 | 76 | - [babel-plugin-transform-react-inline-elements](https://babeljs.io/docs/en/next/babel-plugin-transform-react-inline-elements.html) 77 | would inline "createElement", achieving almost the same result 78 | - [babel-plugin-transform-react-jsx](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx) 79 | has a `pragma jsx`, letting you to change JSX compilation rules. Preact, for example, configure it to produce just `h`. 80 | - [pragma jsx + webpack conf](https://medium.com/@jilizart/reduce-the-size-of-final-jsx-code-c39effca906f) - the same, but webpack plugin will inject right imports. 81 | - [babel-plugin-transform-react-constant-elements](https://babeljs.io/docs/en/babel-plugin-transform-react-constant-elements) 82 | would hoist Elements creating, thus removes _object property access_, as long it would be called 83 | only once. Plus - would remove GC pressure due to the same "one time" element creation. 84 | - [react-local](https://github.com/philosaf/react-local) - is doing absolutely the same, but as a babel plugin. Requires more work for proper instalation. 85 | - [runtime-compress-loader](https://github.com/theKashey/runtime-compress-loader) - compress all inlined babel helpers. The same action, but for js sugar. 86 | 87 | # Licence 88 | MIT 89 | --------------------------------------------------------------------------------