├── .prettierignore ├── test ├── fixtures │ ├── Basic.html │ ├── Data.html │ ├── ObjectData.html │ ├── Event.html │ └── CheckDestroy.html ├── types.ts ├── __snapshots__ │ └── index.spec.ts.snap ├── tsconfig.json └── index.spec.ts ├── .prettierrc.yml ├── .gitignore ├── src ├── tsconfig.json └── index.ts ├── tslint.json ├── tsconfig.json ├── bili.config.js ├── README.md ├── LICENSE ├── .circleci └── config.yml └── package.json /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | *.json -------------------------------------------------------------------------------- /test/fixtures/Basic.html: -------------------------------------------------------------------------------- 1 |

Hello

-------------------------------------------------------------------------------- /test/types.ts: -------------------------------------------------------------------------------- 1 | declare module '*.html' 2 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /dist/ 3 | .rpt2_cache/ -------------------------------------------------------------------------------- /test/fixtures/Data.html: -------------------------------------------------------------------------------- 1 |

Message: {{ message }}

-------------------------------------------------------------------------------- /test/fixtures/ObjectData.html: -------------------------------------------------------------------------------- 1 |

Name: {{ user.name }}

-------------------------------------------------------------------------------- /test/fixtures/Event.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true 5 | }, 6 | "include": [ 7 | "**/*.ts" 8 | ] 9 | } -------------------------------------------------------------------------------- /test/__snapshots__/index.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Vue Svelte Adapter renders template 1`] = `"

Hello

"`; 4 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-config-ktsn", 4 | "tslint-config-prettier" 5 | ], 6 | "rules": { 7 | "no-extra-semi": null 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "sourceMap": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/CheckDestroy.html: -------------------------------------------------------------------------------- 1 |

Check Destroy

2 | 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "es2015", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true 10 | }, 11 | "include": [ 12 | "src/**/*.ts", 13 | "test/**/*.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /bili.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json') 2 | const typescript = require('rollup-plugin-typescript2') 3 | 4 | const banner = `/*! 5 | * ${pkg.name} v${pkg.version} 6 | * ${pkg.homepage} 7 | * 8 | * @license 9 | * Copyright (c) 2018 ${pkg.author} 10 | * Released under the MIT license 11 | */` 12 | 13 | function capitalize(name) { 14 | const camelized = name.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()) 15 | return camelized[0].toUpperCase() + camelized.slice(1) 16 | } 17 | 18 | module.exports = { 19 | input: 'src/index.ts', 20 | js: false, 21 | plugins: [ 22 | typescript({ 23 | tsconfig: './src/tsconfig.json', 24 | typescript: require('typescript') 25 | }) 26 | ], 27 | externals: ['vue'], 28 | globals: { 29 | vue: 'Vue' 30 | }, 31 | moduleName: capitalize(pkg.name), 32 | banner 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-svelte-adapter 2 | 3 | Using Svelte components in Vue.js. 4 | 5 | ## Usage 6 | 7 | Install it via npm: 8 | 9 | ```sh 10 | $ npm install vue-svelte-adapter 11 | ``` 12 | 13 | First, you make a Svelte component: 14 | 15 | ```html 16 |

{{ message }}

17 | 18 | 27 | ``` 28 | 29 | Then, you import the component and `toVue` function from `vue-svelte-component` so that transform it into a Vue component. 30 | 31 | ```js 32 | // Your Svelte component 33 | import Hello from './Hello.html' 34 | 35 | // Svelte to Vue adapter 36 | import { toVue } from 'vue-svelte-adapter' 37 | 38 | // Return a Vue component which converted from the Svelte component 39 | export default toVue(Hello, { 40 | // You can specify some Vue props to port to Svelte data 41 | props: { 42 | message: String 43 | } 44 | }) 45 | ``` 46 | 47 | ## License 48 | 49 | MIT 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 katashin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | defaults: &defaults 4 | working_directory: ~/working/repo 5 | docker: 6 | - image: circleci/node:8.9.4 7 | 8 | jobs: 9 | install: 10 | <<: *defaults 11 | steps: 12 | - checkout 13 | 14 | # Download and cache dependencies 15 | - restore_cache: 16 | keys: 17 | - v2-dependencies-{{ checksum "package-lock.json" }} 18 | # fallback to using the latest cache if no exact match is found 19 | - v2-dependencies- 20 | 21 | - run: npm install 22 | 23 | - save_cache: 24 | paths: 25 | - node_modules 26 | key: v2-dependencies-{{ checksum "package-lock.json" }} 27 | 28 | - persist_to_workspace: 29 | root: ~/working 30 | paths: 31 | - repo 32 | 33 | build: 34 | <<: *defaults 35 | steps: 36 | - attach_workspace: 37 | at: ~/working 38 | - run: npm run build 39 | 40 | test: 41 | <<: *defaults 42 | steps: 43 | - attach_workspace: 44 | at: ~/working 45 | - run: npm run test 46 | 47 | workflows: 48 | version: 2 49 | build_and_test: 50 | jobs: 51 | - install 52 | - build: 53 | requires: 54 | - install 55 | - test: 56 | requires: 57 | - install -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-svelte-adapter", 3 | "version": "0.2.3", 4 | "author": "katashin", 5 | "description": "Using Svelte components in Vue.js", 6 | "keywords": [ 7 | "Svelte", 8 | "Vue", 9 | "component", 10 | "adapter", 11 | "transformer", 12 | "converter" 13 | ], 14 | "license": "MIT", 15 | "main": "dist/vue-svelte-adapter.cjs.js", 16 | "module": "dist/vue-svelte-adapter.es.js", 17 | "unpkg": "dist/vue-svelte-adapter.js", 18 | "typings": "dist/index.d.ts", 19 | "files": [ 20 | "dist" 21 | ], 22 | "homepage": "https://github.com/ktsn/vue-svelte-adapter", 23 | "bugs": "https://github.com/ktsn/vue-svelte-adapter/issues", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/ktsn/vue-svelte-adapter.git" 27 | }, 28 | "scripts": { 29 | "prepublishOnly": "npm run clean && npm run test && npm run build", 30 | "clean": "rm -rf dist", 31 | "build": "bili --format cjs,es,umd,umd-min", 32 | "dev": "jest --watch", 33 | "lint": "tslint -p . && prettier --list-different \"{src,scripts,test}/**/*.{js,ts}\"", 34 | "format": "prettier --write \"{src,scripts,test}/**/*.{js,ts}\"", 35 | "test": "npm run lint && npm run test:unit", 36 | "test:unit": "jest" 37 | }, 38 | "jest": { 39 | "transform": { 40 | "^.+\\.ts$": "ts-jest", 41 | "^.+\\.html$": "svelte-jest" 42 | }, 43 | "testRegex": "/test/.+\\.spec\\.(js|ts)$", 44 | "moduleFileExtensions": [ 45 | "ts", 46 | "js", 47 | "json" 48 | ], 49 | "globals": { 50 | "ts-jest": { 51 | "tsConfigFile": "test/tsconfig.json" 52 | } 53 | } 54 | }, 55 | "devDependencies": { 56 | "@types/jest": "^22.2.0", 57 | "@vue/test-utils": "^1.0.0-beta.13", 58 | "bili": "^3.0.15", 59 | "jest": "^22.4.2", 60 | "prettier": "1.11.0", 61 | "rollup-plugin-typescript2": "^0.12.0", 62 | "svelte-jest": "^0.1.0", 63 | "ts-jest": "^22.4.1", 64 | "tslint": "^5.9.1", 65 | "tslint-config-ktsn": "^2.1.0", 66 | "tslint-config-prettier": "^1.9.0", 67 | "typescript": "^2.8.1", 68 | "vue": "^2.5.16", 69 | "vue-template-compiler": "^2.5.16" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VueConstructor, PropOptions } from 'vue' 2 | 3 | export type Prop = { (): T } | { new (...args: any[]): T & object } 4 | 5 | export type PropValidator = PropOptions | Prop | Prop[] 6 | 7 | export type RecordPropsDefinition = { [K in keyof T]: PropValidator } 8 | 9 | export type ArrayPropsDefinition = (keyof T)[] 10 | 11 | export type PropsDefinition = 12 | | ArrayPropsDefinition 13 | | RecordPropsDefinition 14 | 15 | export interface AdapterOptions { 16 | props?: PropsDefinition 17 | } 18 | 19 | export function toVue( 20 | Ctor: any, 21 | options: AdapterOptions = {} 22 | ): VueConstructor { 23 | return Vue.extend({ 24 | props: options.props as any, 25 | 26 | mounted() { 27 | const instance = ((this as any).instance = new Ctor({ 28 | target: this.$el, 29 | data: this.$props 30 | })) 31 | 32 | patchSvelteInstance(this, instance) 33 | 34 | observePropsDiff(this, diff => { 35 | instance.set(diff) 36 | }) 37 | }, 38 | 39 | beforeDestroy() { 40 | const { instance } = this as any 41 | instance.destroy() 42 | }, 43 | 44 | render(h) { 45 | return h('div') 46 | } 47 | }) 48 | } 49 | 50 | function noop(): void { 51 | // do nothing 52 | } 53 | 54 | function observePropsDiff( 55 | vm: Vue, 56 | cb: (diff: Record) => void 57 | ): void { 58 | const props = vm.$props || {} 59 | const propKeys = Object.keys(props) 60 | let prevProps = copy(props) 61 | 62 | if (propKeys.length === 0) return 63 | 64 | vm.$watch(() => { 65 | const diff: Record = {} 66 | 67 | propKeys.forEach(key => { 68 | const value = vm.$props[key] 69 | if ( 70 | prevProps[key] !== value || 71 | (value !== null && typeof value === 'object') 72 | ) { 73 | diff[key] = value 74 | } 75 | }) 76 | 77 | if (Object.keys(diff).length > 0) { 78 | cb(diff) 79 | prevProps = copy(vm.$props || {}) 80 | } 81 | }, noop) 82 | } 83 | 84 | function patchSvelteInstance(vm: Vue, svelte: any): void { 85 | const originalFire = svelte.fire 86 | svelte.fire = (eventName: string, data: any): void => { 87 | vm.$emit(eventName, data) 88 | return originalFire.call(svelte, eventName, data) 89 | } 90 | } 91 | 92 | function copy(value: T): T { 93 | return Object.keys(value).reduce((acc, key) => { 94 | acc[key] = (value as any)[key] 95 | return acc 96 | }, {}) 97 | } 98 | -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | import { mount } from '@vue/test-utils' 3 | import { toVue } from '../src/index' 4 | import Basic from './fixtures/Basic.html' 5 | import Data from './fixtures/Data.html' 6 | import ObjectData from './fixtures/ObjectData.html' 7 | import Event from './fixtures/Event.html' 8 | import CheckDestroy from './fixtures/CheckDestroy.html' 9 | 10 | describe('Vue Svelte Adapter', () => { 11 | it('renders template', () => { 12 | const BasicVue = toVue(Basic) 13 | const wrapper = mount(BasicVue) 14 | expect(wrapper.html()).toMatchSnapshot() 15 | }) 16 | 17 | it('ports Vue props to Svelte data', () => { 18 | const DataVue = toVue(Data, { 19 | props: { 20 | message: String 21 | } 22 | }) 23 | const wrapper = mount(DataVue, { 24 | propsData: { 25 | message: 'Test Message' 26 | } 27 | }) 28 | expect(wrapper.text()).toBe('Message: Test Message') 29 | }) 30 | 31 | it('updates Svelte data when props are updated', () => { 32 | const DataVue = toVue(Data, { 33 | props: { 34 | message: String 35 | } 36 | }) 37 | const wrapper = mount(DataVue, { 38 | propsData: { 39 | message: 'Test' 40 | } 41 | }) 42 | wrapper.setProps({ 43 | message: 'Updated' 44 | }) 45 | expect(wrapper.text()).toBe('Message: Updated') 46 | }) 47 | 48 | it('updates Svelte data when object props are mutated', () => { 49 | const DataVue = toVue(ObjectData, { 50 | props: { 51 | user: Object 52 | } 53 | }) 54 | const Wrapper = Vue.extend({ 55 | data() { 56 | return { 57 | user: { 58 | name: 'Foo' 59 | } 60 | } 61 | }, 62 | 63 | render(h): VNode { 64 | return h(DataVue, { 65 | props: { 66 | user: this.user 67 | } 68 | }) 69 | } 70 | }) 71 | const wrapper = mount(Wrapper) 72 | 73 | expect(wrapper.text()).toBe('Name: Foo') 74 | wrapper.vm.user.name = 'Bar' 75 | expect(wrapper.text()).toBe('Name: Bar') 76 | }) 77 | 78 | it('observes component events', () => { 79 | const EventVue = toVue(Event) 80 | const wrapper = mount(EventVue) 81 | wrapper.find('button').trigger('click') 82 | expect(wrapper.emitted('test')[0]).toEqual(['clicked']) 83 | }) 84 | 85 | it('teardowns Svelte component', () => { 86 | const spy = jest.fn() 87 | CheckDestroy.registerSpy(spy) 88 | 89 | const CheckDestroyVue = toVue(CheckDestroy) 90 | const wrapper = mount(CheckDestroyVue) 91 | 92 | expect(spy).not.toHaveBeenCalled() 93 | wrapper.destroy() 94 | expect(spy).toHaveBeenCalled() 95 | }) 96 | }) 97 | --------------------------------------------------------------------------------