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