This article is about how to write a generic JavaScript component that can be used.
The world of JavaScript development is very fragmented. There are dozens of popular frameworks, most of which are completely incompatible with each other. In such conditions, the developers of JavaScript components and libraries, choosing one specific framework, automatically reject a very large audience, which this framework does not use. This is a serious problem, and the article suggested its solution.
For example, let's develop a Donut Chart of this type:
There is nothing surprising, we will not see - just the code.
import React from 'react'; export default class DonutChart extends React.Component { render() { const { radius, holeSize, text, value, total, backgroundColor, valueColor } = this.props; const r = radius * (1 - (1 - holeSize)/2); const width = radius * (1 - holeSize); const circumference = 2 * Math.PI * r; const strokeDasharray = ((value * circumference) / total) + ' ' + circumference; const transform = 'rotate(-90 ' + radius + ',' + radius + ')'; const fontSize = r * holeSize * 0.6; return ( <div style = {{ textAlign: 'center', fontFamily: 'sans-serif' }}> <svg width = {radius * 2 + 'px'} height = {radius * 2 + 'px'}> <circle r = {r + 'px'} cx = {radius + 'px'} cy = {radius + 'px'} transform = {transform} fill = 'none' stroke = {backgroundColor} strokeWidth = {width} /> <circle r = {r + 'px'} cx = {radius + 'px'} cy = {radius + 'px'} transform = {transform} fill = 'none' stroke = {valueColor} strokeWidth = {width} strokeDasharray = {strokeDasharray} /> <text x = {radius + 'px'} y = {radius + 'px' }dy = {fontSize/3 + 'px'} textAnchor = 'middle' fill = {valueColor} fontSize = {fontSize + 'px'} > {~~(value * 1000 / total) / 10}% </text> </svg> <div style = {{ marginTop: '10px' }}> {text} </div> </div> ); } } DonutChart.defaultProps = { holeSize : 0.8, radius : 65, backgroundColor : '#d1d8e7', valueColor : '#49649f' };
var webpack = require('webpack'); module.exports = { output: { path: './dist' }, resolve: { extensions: ['', '.js'], }, module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: { presets: [ 'latest', 'stage-0', 'react' ], plugins: [ 'transform-react-remove-prop-types', 'transform-react-constant-elements' ] } } ] }, plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': "'production'" }), new webpack.optimize.DedupePlugin(), new webpack.optimize.OccurrenceOrderPlugin(), new webpack.optimize.AggressiveMergingPlugin(), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false }, comments: false, sourceMap: true, mangle: true, minimize: true }) ] };
"scripts": { "build:preact": "node ./scripts/build-as-preact-component.js", "build:react": "node ./scripts/build-as-react-component.js", "build:webcomponent": "node ./scripts/build-as-web-component.js", "build:vanila": "node ./scripts/build-as-vanila-component.js", "build:jquery": "node ./scripts/build-as-jquery-component", "build:angular": "node ./scripts/build-as-angular-component", "build": "npm run build:preact && npm run build:react && npm run build:webcomponent && npm run build:vanila && npm run build:jquery && npm run build:angular" }
var webpack = require('webpack'); var config = require('./webpack.config'); var statsConfig = require('./statsConfig'); config.resolve.alias = { 'react': 'preact-compat', 'react-dom': 'preact-compat' }; config.entry = './src/DonutChartWebComponent.js'; config.output.filename = 'DonutChartWebComponent.js'; webpack(config).run(function (err, stats) { console.log(stats.toString(statsConfig)); });
import React from 'react'; import ReactDOM from 'react-dom'; import DonutChart from './DonutChart'; const proto = Object.create(HTMLElement.prototype, { attachedCallback: { value: function() { const mountPoint = document.createElement('span'); this.createShadowRoot().appendChild(mountPoint); const props = { radius : +this.getAttribute('radius') || undefined, holeSize : +this.getAttribute('hole-size') || undefined, text : this.getAttribute('text') || undefined, value : +this.getAttribute('value') || undefined, total : +this.getAttribute('total') || undefined, backgroundColor : this.getAttribute('background-color') || undefined, valueColor : this.getAttribute('value-color') || undefined }; ReactDOM.render(( <DonutChart {...props}/> ), mountPoint); } } }); document.registerElement('donut-chart', {prototype: proto});
<donut-chart value="39.6" total="100" text="Hello Web Components"></donut-chart>
var webpack = require('webpack'); var config = require('./webpack.config'); var statsConfig = require('./statsConfig'); config.resolve.alias = { 'react': 'preact-compat', 'react-dom': 'preact-compat' }; config.entry = './src/DonutChartAngularComponent.js'; config.output.filename = 'DonutChartAngularComponent.js'; config.output.library = 'DonutChart'; config.output.libraryTarget = 'umd'; webpack(config).run(function (err, stats) { console.log(stats.toString(statsConfig)); });
import React from 'react'; import ReactDOM from 'react-dom'; import DonutChart from './DonutChart'; const module = angular.module('future-charts-example', []); module.directive('donutChart', function() { return { restrict: 'E', link: function(scope, element, attrs) { const props = { radius : +attrs['radius'] || undefined, holeSize : +attrs['hole-size'] || undefined, text : attrs['text'] || undefined, value : +attrs['value'] || undefined, total : +attrs['total'] || undefined, backgroundColor : attrs['background-color'] || undefined, valueColor : attrs['value-color'] || undefined }; ReactDOM.render(( <DonutChart {...props}/> ), element[0]); } }; });
<body ng-app="future-charts-example"> <donut-chart value="89.6" total="100" text="Hello Angular"></donut-chart> </body>
var webpack = require('webpack'); var config = require('./webpack.config'); var statsConfig = require('./statsConfig'); config.resolve.alias = { 'react': 'preact-compat', 'react-dom': 'preact-compat' }; config.entry = './src/DonutChartJQueryComponent.js'; config.output.filename = 'DonutChartJQueryComponent.js'; config.output.library = 'DonutChart'; config.output.libraryTarget = 'umd'; webpack(config).run(function (err, stats) { console.log(stats.toString(statsConfig)); });
import React from 'react'; import ReactDOM from 'react-dom'; import DonutChart from './DonutChart'; jQuery.fn.extend({ DonutChart: function(props) { this.each( function () { ReactDOM.render(( <DonutChart {...props}/> ), this); } ); } });
$('#app').DonutChart({ value : 42.1, total : 100, text : 'Hello jQuery' });
var webpack = require('webpack'); var config = require('./webpack.config'); var statsConfig = require('./statsConfig'); config.resolve.alias = { 'react': 'preact-compat', 'react-dom': 'preact-compat' }; config.entry = './src/DonutChartVanilaComponent.js'; config.output.filename = 'DonutChartVanilaComponent.js'; config.output.library = 'DonutChart'; config.output.libraryTarget = 'umd'; webpack(config).run(function (err, stats) { console.log(stats.toString(statsConfig)); });
import React from 'react'; import ReactDOM from 'react-dom'; import DonutChart from './DonutChart'; module.exports = function DonutChartVanilaComponent(mountPoint, props) { ReactDOM.render(( <DonutChart {...props}/> ), mountPoint); };
DonutChart(document.getElementById('app'), { value : 57.4, total : 100, text : 'Hello Vanila' });
var webpack = require('webpack'); var config = require('./webpack.config'); var statsConfig = require('./statsConfig'); var react = { root: 'React', commonjs2: 'react', commonjs: 'react' }; var reactDom = { root: 'ReactDOM', commonjs2: 'react-dom', commonjs: 'react-dom' }; config.externals = { 'react': react, 'react-dom': reactDom }; config.entry = './src/DonutChartUMD.js'; config.output.filename = 'DonutChartReact.js'; config.output.library = 'DonutChart'; config.output.libraryTarget = 'umd'; webpack(config).run(function (err, stats) { console.log(stats.toString(statsConfig)); });
var webpack = require('webpack'); var config = require('./webpack.config'); var statsConfig = require('./statsConfig'); var preactCompat = { root: 'preactCompat', commonjs2: 'preact-compat', commonjs: 'preact-compat' }; config.externals = { 'react': preactCompat, 'react-dom': preactCompat }; config.entry = './src/DonutChartUMD.js'; config.output.filename = 'DonutChartPreact.js'; config.output.library = 'DonutChart'; config.output.libraryTarget = 'umd'; webpack(config).run(function (err, stats) { console.log(stats.toString(statsConfig)); });
How much will each option weigh in the end:
React | Preact | VanilaJS | jQuery | Angular | Web Components |
---|---|---|---|---|---|
Component code (3kb) | Component code (3kb) | Component code (3kb) | Component code (3kb) | Component code (3kb) | Component code (3kb) |
Wrap (1kb) | Wrap (1kb) | Wrap (1kb) | Wrap (1kb) | ||
preact.min.js (3kb) | preact.min.js (3kb) | preact.min.js (3kb) | preact.min.js (3kb) | ||
preact-compat.min.js (18kb) | preact-compat.min.js (18kb) | preact-compat.min.js (18kb) | preact-compat.min.js (18kb) | ||
3kb | 3kb | 25kb | 25kb | 25kb | 25kb |
An overhead of 20 kilobytes for the ability to use React components in any other frameworks or as Web Components is an excellent result. If you are developing some React-components, know - you can make them available to everyone and everyone - this is very simple. I hope that this tutorial will help to make the world at least a little bit better and reduce the terrible fragmentation of the universe of JavaScript development.
Source: https://habr.com/ru/post/316358/
All Articles