├── .gitignore ├── LICENSE ├── README.md ├── ReactComponent.svelte ├── dist └── ReactComponent.js ├── package.json ├── rollup.config.js └── test ├── react ├── Counter.js └── FunctionCounter.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jesse Skinner 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svelte-react 2 | 3 | Use React components inside Svelte apps. 4 | 5 | ```html 6 | 10 | 11 |

this is a React component inside a Svelte app:

12 | 13 | ``` 14 | 15 | ## Limitations 16 | 17 | `ReactComponent` does not accept any children or slots. If you want to specify children to the React component, you have to pass them in through the `children` prop. 18 | 19 | ## Resources 20 | 21 | This project was heavily inspired by [react-svelte](https://github.com/Rich-Harris/react-svelte). If you want to use Svelte components inside React apps, have a look at [react-svelte](https://github.com/Rich-Harris/react-svelte). 22 | 23 | ## License 24 | 25 | [MIT](LICENSE) 26 | -------------------------------------------------------------------------------- /ReactComponent.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
23 | -------------------------------------------------------------------------------- /dist/ReactComponent.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('svelte/internal'), require('react'), require('react-dom'), require('svelte')) : 3 | typeof define === 'function' && define.amd ? define(['svelte/internal', 'react', 'react-dom', 'svelte'], factory) : 4 | (global = global || self, global.ReactComponent = factory(global.internal, global.React, global.ReactDOM, global.svelte)); 5 | }(this, function (internal, React, ReactDOM, svelte) { 'use strict'; 6 | 7 | /* ReactComponent.svelte generated by Svelte v3.29.0 */ 8 | 9 | function create_fragment(ctx) { 10 | let div; 11 | 12 | return { 13 | c() { 14 | div = internal.element("div"); 15 | }, 16 | m(target, anchor) { 17 | internal.insert(target, div, anchor); 18 | /*div_binding*/ ctx[1](div); 19 | }, 20 | p: internal.noop, 21 | i: internal.noop, 22 | o: internal.noop, 23 | d(detaching) { 24 | if (detaching) internal.detach(div); 25 | /*div_binding*/ ctx[1](null); 26 | } 27 | }; 28 | } 29 | 30 | function instance($$self, $$props, $$invalidate) { 31 | let container; 32 | 33 | svelte.afterUpdate(() => { 34 | const { this: component, children, ...props } = $$props; 35 | ReactDOM.render(React.createElement(component, props, children), container); 36 | }); 37 | 38 | svelte.onDestroy(() => { 39 | ReactDOM.unmountComponentAtNode(container); 40 | }); 41 | 42 | function div_binding($$value) { 43 | internal.binding_callbacks[$$value ? "unshift" : "push"](() => { 44 | container = $$value; 45 | $$invalidate(0, container); 46 | }); 47 | } 48 | 49 | $$self.$$set = $$new_props => { 50 | $$invalidate(2, $$props = internal.assign(internal.assign({}, $$props), internal.exclude_internal_props($$new_props))); 51 | }; 52 | 53 | $$props = internal.exclude_internal_props($$props); 54 | return [container, div_binding]; 55 | } 56 | 57 | class ReactComponent extends internal.SvelteComponent { 58 | constructor(options) { 59 | super(); 60 | internal.init(this, options, instance, create_fragment, internal.safe_not_equal, {}); 61 | } 62 | } 63 | 64 | return ReactComponent; 65 | 66 | })); 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-react", 3 | "version": "1.1.0", 4 | "description": "Use React components inside Svelte apps.", 5 | "main": "dist/ReactComponent.js", 6 | "svelte": "ReactComponent.svelte", 7 | "scripts": { 8 | "build": "rollup -c", 9 | "test": "npm run build && mocha" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/jesseskinner/svelte-react.git" 14 | }, 15 | "keywords": [ 16 | "svelte", 17 | "react" 18 | ], 19 | "author": "Jesse Skinner ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/jesseskinner/svelte-react/issues" 23 | }, 24 | "homepage": "https://github.com/jesseskinner/svelte-react#readme", 25 | "devDependencies": { 26 | "chai": "^4.2.0", 27 | "jsdom": "^15.1.1", 28 | "mocha": "^6.2.0", 29 | "react": "^16.9.0", 30 | "react-dom": "^16.9.0", 31 | "rollup": "^1.20.1", 32 | "rollup-plugin-svelte": "^5.1.0", 33 | "svelte": "^3.29.0" 34 | }, 35 | "dependencies": {} 36 | } 37 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | 3 | export default { 4 | input: './ReactComponent.svelte', 5 | output: { 6 | file: 'dist/ReactComponent.js', 7 | format: 'umd', 8 | name: 'ReactComponent', 9 | }, 10 | plugins: [svelte()], 11 | }; 12 | -------------------------------------------------------------------------------- /test/react/Counter.js: -------------------------------------------------------------------------------- 1 | const { useState, createElement } = require('react'); 2 | 3 | module.exports = function({ start = 0, children = 'count = ' }) { 4 | const [count, setCount] = useState(start); 5 | const onClick = () => setCount(count + 1); 6 | const text = `${children}${count}`; 7 | 8 | return createElement('button', { onClick }, [text]); 9 | }; 10 | -------------------------------------------------------------------------------- /test/react/FunctionCounter.js: -------------------------------------------------------------------------------- 1 | const { useState, createElement } = require('react'); 2 | 3 | module.exports = function({ start = 0, children = count => `count = ${count}` }) { 4 | const [count, setCount] = useState(start); 5 | const onClick = () => setCount(count + 1); 6 | 7 | return createElement('button', { onClick }, [children(count)]); 8 | }; 9 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const ReactComponent = require('../dist/ReactComponent.js'); 3 | const Counter = require('./react/Counter.js'); 4 | const FunctionCounter = require('./react/FunctionCounter.js'); 5 | const { JSDOM } = require('jsdom'); 6 | 7 | describe('Counter', () => { 8 | beforeEach(() => { 9 | const { window } = new JSDOM(); 10 | global.window = window; 11 | global.document = window.document; 12 | }); 13 | 14 | it('should render a button with text', () => { 15 | new ReactComponent({ 16 | target: document.body, 17 | props: { 18 | this: Counter, 19 | }, 20 | }); 21 | 22 | const button = document.querySelector('button'); 23 | 24 | expect(button.innerHTML).to.equal('count = 0'); 25 | }); 26 | 27 | it('should render a button with custom prop', () => { 28 | new ReactComponent({ 29 | target: document.body, 30 | props: { 31 | this: Counter, 32 | start: 1, 33 | }, 34 | }); 35 | 36 | const button = document.querySelector('button'); 37 | 38 | expect(button.innerHTML).to.equal('count = 1'); 39 | }); 40 | 41 | it('should render a button with custom children', () => { 42 | new ReactComponent({ 43 | target: document.body, 44 | props: { 45 | this: Counter, 46 | children: ['The count is '], 47 | }, 48 | }); 49 | 50 | const button = document.querySelector('button'); 51 | 52 | expect(button.innerHTML).to.equal('The count is 0'); 53 | }); 54 | 55 | it('should increase when clicked', () => { 56 | new ReactComponent({ 57 | target: document.body, 58 | props: { 59 | this: Counter, 60 | }, 61 | }); 62 | 63 | const button = document.querySelector('button'); 64 | 65 | button.click(); 66 | button.click(); 67 | 68 | expect(button.innerHTML).to.equal('count = 2'); 69 | }); 70 | 71 | it('should update when props change', () => { 72 | const r = new ReactComponent({ 73 | target: document.body, 74 | props: { 75 | this: Counter, 76 | }, 77 | }); 78 | 79 | const button = document.querySelector('button'); 80 | button.click(); 81 | 82 | r.$set({ children: 'Count is ', start: 5 }); 83 | 84 | setTimeout(() => { 85 | expect(button.innerHTML).to.equal('Count is 1'); 86 | }); 87 | }); 88 | 89 | it('should destory without issue', () => { 90 | const r = new ReactComponent({ 91 | target: document.body, 92 | props: { 93 | this: Counter, 94 | }, 95 | }); 96 | 97 | r.$destroy(); 98 | 99 | const button = document.querySelector('button'); 100 | expect(button).to.equal(null); 101 | }); 102 | 103 | it('should work with function children that return dom elements', () => { 104 | const r = new ReactComponent({ 105 | target: document.body, 106 | props: { 107 | this: FunctionCounter, 108 | children: count => `The count is ${count}!` 109 | }, 110 | }); 111 | 112 | const button = document.querySelector('button'); 113 | 114 | expect(button.innerHTML).to.equal('The count is 0!'); 115 | }); 116 | }); 117 | --------------------------------------------------------------------------------