├── .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 |
--------------------------------------------------------------------------------