Skip to content

Commit

Permalink
copied files in + added readme
Browse files Browse the repository at this point in the history
  • Loading branch information
crisward committed Nov 28, 2020
0 parents commit 1e05665
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Svelte-tag

A webcomponet wrapper for svelte.

Be aware, I don't have a lot of time to support this package. I've mainly open sourced it
because I've noticed lots of people with similar use cases.

## Why?

Svelte already allows you to create webcomponents. However it has a couple of flaws:
* All of your nested components have to be webcomponents as the render flag applies to everything.
* You have to use shadow dom.
* You have to deal with lots of bugs.
* You loose many features svelte has for inter-component communication.

This solves this by just embedding your app inside a single component.

## How do I use it?

```javascript
import component from "svelte-tag"
import App from "your-app.svelte"
new component({component:App,tagname:"hello-world",href="/your/stylesheet.css",attributes:["name"]})
```

Now anywhere you use the `<hello-world>` tag you'll get a svelte app.

## Todo

- [ ] Upload Tests
- [ ] Setup CI
149 changes: 149 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
Usage - convert svelte app to web component
import component from "../libs/component.js"
new component({component:App,tagname:"hello-world",href="/your/stylesheet.css",attributes:["name"]})
*/

// witchcraft from svelte issue - https://github.com/sveltejs/svelte/issues/2588
import { detach, insert, noop } from 'svelte/internal';
function createSlots(slots) {
const svelteSlots = {};
for (const slotName in slots) {
svelteSlots[slotName] = [createSlotFn(slots[slotName])];
}
function createSlotFn(element) {
return function() {
return {
c: noop,
m: function mount(target, anchor) {
insert(target, element.cloneNode(true), anchor);
},
d: function destroy(detaching) {
if (detaching && element.innerHTML){
detach(element);
}
},
l: noop,
};
}
}
return svelteSlots;
}

export default function(opts){
class Wrapper extends HTMLElement{

constructor() {
super();
this.slotcount = 0
let root = opts.shadow ? this.attachShadow({ mode: 'open' }) : this
// link generated style
if(opts.href && opts.shadow){
let link = document.createElement('link');
link.setAttribute("href",opts.href)
link.setAttribute("rel","stylesheet")
root.appendChild(link);
}
if(opts.shadow){
this._root = document.createElement('div')
root.appendChild(this._root)
}else{
this._root = root
}
}

static get observedAttributes() {
return opts.attributes || []
}

connectedCallback(){
let props = opts.defaults ? opts.defaults : {};
let slots
props.$$scope = {}
Array.from(this.attributes).forEach( attr => props[attr.name] = attr.value )
props.$$scope = {}
if(opts.shadow){
slots = this.getShadowSlots()
let props = opts.defaults ? opts.defaults : {};
props.$$scope = {}
this.observer = new MutationObserver(this.processMutations.bind(this,{root:this._root,props}))
this.observer.observe(this,{childList: true, subtree: true, attributes: false})
}else{
slots = this.getSlots()
}
this.slotcount = Object.keys(slots).length
props.$$slots = createSlots(slots)
this.elem = new opts.component({ target: this._root, props});
}

disconnectedCallback(){
if(this.observe){
this.observer.disconnect()
}
try{ this.elem.$destroy()}catch(err){} // detroy svelte element when removed from dom
}

unwrap(from){
let node = new DocumentFragment()
while (from.firstChild) {
node.appendChild(from.removeChild(from.firstChild));
}
return node
}

getSlots(){
const namedSlots = this.querySelectorAll('[slot]')
let slots = {}
namedSlots.forEach(n=>{
slots[n.slot] = this.unwrap(n)
this.removeChild(n)
})
if(this.innerHTML.length){
slots.default = this.unwrap(this)
this.innerHTML = ""
}
return slots
}

getShadowSlots(){
const namedSlots = this.querySelectorAll('[slot]')
let slots = {}
let htmlLength = this.innerHTML.length
namedSlots.forEach(n=>{
slots[n.slot] = document.createElement("slot")
slots[n.slot].setAttribute("name",n.slot)
htmlLength-=n.outerHTML.length
})
if(htmlLength>0){
slots.default = document.createElement("slot")
}
return slots
}

processMutations({root,props},mutations){
for(let mutation of mutations){
if(mutation.type=="childList"){
let slots = this.getShadowSlots()
if(Object.keys(slots).length){
props.$$slots = createSlots(slots)
this.elem.$set({"$$slots":createSlots(slots)})
// do full re-render on slot count change - needed for tabs component
if(this.slotcount != Object.keys(slots).length){
Array.from(this.attributes).forEach( attr => props[attr.name] = attr.value )
this.slotcount = Object.keys(slots).length
root.innerHTML = ""
this.elem = new opts.component({ target: root, props});
}
}
}
}
}

attributeChangedCallback(name, oldValue, newValue) {
if(this.elem && newValue!=oldValue){
this.elem.$set({[name]:newValue})
}
}
}
window.customElements.define(opts.tagname, Wrapper);
}
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "svelte-tag",
"version": "1.0.0",
"description": "webcomponent wrapper for svelte",
"main": "index.js",
"scripts": {
"test": "npm test"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/crisward/svelte-tag.git"
},
"keywords": [
"svelte",
"webcomponent",
"slots",
"wrapper"
],
"author": "Cris Ward",
"license": "ISC",
"bugs": {
"url": "https://github.com/crisward/svelte-tag/issues"
},
"homepage": "https://github.com/crisward/svelte-tag#readme"
}

0 comments on commit 1e05665

Please sign in to comment.