Skip to content

Commit

Permalink
slim down core library
Browse files Browse the repository at this point in the history
  • Loading branch information
ryansolid committed Apr 1, 2020
1 parent b175dca commit cebdaa8
Show file tree
Hide file tree
Showing 11 changed files with 540 additions and 1,351 deletions.
1,630 changes: 469 additions & 1,161 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "component-register",
"description": "Basic Wrapper around Web Components",
"version": "0.7.1",
"version": "0.8.0",
"author": "Ryan Carniato",
"license": "MIT",
"readmeFilename": "README.md",
"main": "lib/component-register.js",
"module": "dist/component-register.js",
"types": "types/index.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/ryansolid/component-register"
Expand All @@ -18,17 +19,17 @@
"test": "npm run build && jest --coverage"
},
"jest": {
"testEnvironment": "@skatejs/ssr/jest"
"testEnvironment": "jest-environment-jsdom-sixteen"
},
"devDependencies": {
"@babel/core": "7.8.7",
"@babel/preset-typescript": "7.8.3",
"@skatejs/ssr": "0.19.11",
"@babel/core": "7.9.0",
"@babel/preset-typescript": "7.9.0",
"coveralls": "3.0.9",
"jest": "25.1.0",
"jest-environment-jsdom-sixteen": "^1.0.3",
"rollup": "^2.1.0",
"rollup-plugin-babel": "4.4.0",
"rollup-plugin-node-resolve": "5.2.0",
"@rollup/plugin-node-resolve": "7.1.1",
"typescript": "3.8.3"
}
}
2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import nodeResolve from 'rollup-plugin-node-resolve';
import nodeResolve from '@rollup/plugin-node-resolve';
import babel from 'rollup-plugin-babel';

export default {
Expand Down
70 changes: 7 additions & 63 deletions src/element.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import {
connectedToDOM,
propValues,
isConstructor,
toComponentName,
initializeProps,
parseAttributeValue,
ICustomElement,
ConstructableComponent,
FunctionComponent,
PropsDefinition,
PropsDefinition
} from "./utils";

let currentElement: HTMLElement & ICustomElement;
Expand All @@ -17,7 +15,9 @@ export function getCurrentElement() {
}

export function noShadowDOM() {
Object.defineProperty(currentElement, "renderRoot", { value: currentElement });
Object.defineProperty(currentElement, "renderRoot", {
value: currentElement
});
}

export function createElementType<T>(
Expand All @@ -29,7 +29,6 @@ export function createElementType<T>(
>;
return class CustomElement extends BaseElement implements ICustomElement {
[prop: string]: any;
__initializing: boolean;
__initialized: boolean;
__released: boolean;
__releaseCallbacks: any[];
Expand All @@ -43,7 +42,6 @@ export function createElementType<T>(

constructor() {
super();
this.__initializing = false;
this.__initialized = false;
this.__released = false;
this.__releaseCallbacks = [];
Expand All @@ -54,8 +52,7 @@ export function createElementType<T>(

connectedCallback() {
// check that infact it connected since polyfill sometimes double calls
if (!connectedToDOM(this) || this.__initializing || this.__initialized)
return;
if (!this.isConnected || this.__initialized) return;
this.__releaseCallbacks = [];
this.__propertyChangedCallbacks = [];
this.__updating = {};
Expand All @@ -66,8 +63,8 @@ export function createElementType<T>(
| { new (...args: any[]): any },
outerElement = currentElement;
try {
this.__initializing = true;
currentElement = this;
this.__initialized = true;
if (isConstructor(ComponentType))
new (ComponentType as ConstructableComponent<T>)(props, {
element: this as ICustomElement
Expand All @@ -76,24 +73,15 @@ export function createElementType<T>(
(ComponentType as FunctionComponent<T>)(props, {
element: this as ICustomElement
});
} catch (err) {
console.error(
`Error creating component ${toComponentName(
this.nodeName.toLowerCase()
)}:`,
err
);
} finally {
currentElement = outerElement;
delete this.__initializing;
}
this.__initialized = true;
}

async disconnectedCallback() {
// prevent premature releasing when element is only temporarely removed from DOM
await Promise.resolve();
if (connectedToDOM(this)) return;
if (this.isConnected) return;
this.__propertyChangedCallbacks.length = 0;
let callback = null;
while ((callback = this.__releaseCallbacks.pop())) callback(this);
Expand All @@ -111,14 +99,6 @@ export function createElementType<T>(
}
}

reloadComponent() {
let callback = null;
while ((callback = this.__releaseCallbacks.pop())) callback(this);
delete this.__initialized;
this.renderRoot.textContent = "";
this.connectedCallback();
}

lookupProp(attrName: string) {
if (!propDefinition) return;
return propKeys.find(
Expand All @@ -130,42 +110,6 @@ export function createElementType<T>(
return this.shadowRoot || this.attachShadow({ mode: "open" });
}

setProperty(name: string, value: unknown) {
if (!(name in this.props)) return;
const prop = this.props[name],
oldValue = prop.value;
this[name] = value;
if (prop.notify)
this.trigger("propertychange", { detail: { value, oldValue, name } });
}

trigger(
name: string,
{
detail,
bubbles = true,
cancelable = true,
composed = true
}: {
detail?: any;
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
} = {}
) {
const event = new CustomEvent(name, {
detail,
bubbles,
cancelable,
composed
});
let cancelled = false;
if (this["on" + name]) cancelled = this["on" + name](event) === false;
if (cancelled) event.preventDefault();
this.dispatchEvent(event);
return event;
}

addReleaseCallback(fn: () => void) {
this.__releaseCallbacks.push(fn);
}
Expand Down
4 changes: 2 additions & 2 deletions src/hot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type ReloadableComponent = HTMLElement & { reloadComponent: () => void };
import { reloadElement, ICustomElement } from "./utils";

function walk(root: Node, call: (node: Node) => void) {
call(root);
Expand All @@ -19,7 +19,7 @@ export function hot(module: NodeModule & { hot?: any }, tagName: string) {
}

walk(document.body, (node: Node) =>
(node as HTMLElement).localName === tagName && setTimeout(() => (node as ReloadableComponent).reloadComponent(), 0)
(node as HTMLElement).localName === tagName && setTimeout(() => reloadElement(node as unknown as ICustomElement), 0)
);
}

Expand Down
4 changes: 1 addition & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,12 @@ export function register<T>(
}

export {
nativeShadowDOM,
isConstructor,
isObject,
isFunction,
toComponentName,
toAttribute,
toProperty,
connectedToDOM
reloadElement
} from "./utils";
export { createMixin, compose } from "./mixin";
export * from "./context";
Expand Down
38 changes: 6 additions & 32 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,14 @@ interface PropDefinition<T> {
}
export interface ICustomElement {
[prop: string]: any;
__initializing: boolean;
__initialized: boolean;
__released: boolean;
__releaseCallbacks: any[];
__propertyChangedCallbacks: any[];
__updating: { [prop: string]: any };
props: { [prop: string]: any };
reloadComponent(): void;
lookupProp(attrName: string): string | undefined;
renderRoot: Element | Document | ShadowRoot | DocumentFragment;
setProperty(name: string, value: unknown): void;
trigger(
name: string,
options: {
detail?: any;
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
}
): CustomEvent;
addReleaseCallback(fn: () => void): void;
addPropertyChangedCallback(fn: (name: string, value: any) => void): void;
}
Expand All @@ -46,8 +34,6 @@ export type PropsDefinition<T> = {
};
export type ComponentType<T> = FunctionComponent<T> | ConstructableComponent<T>;

const testElem = document.createElement("div");

function cloneProps<T>(props: PropsDefinition<T>) {
const propKeys = Object.keys(props) as Array<keyof PropsDefinition<T>>;
return propKeys.reduce((memo, k) => {
Expand All @@ -65,8 +51,6 @@ function cloneProps<T>(props: PropsDefinition<T>) {
}, {} as PropsDefinition<T>);
}

export const nativeShadowDOM = !!testElem.attachShadow;

export function normalizePropDefs<T>(
props: PropsDefinitionInput<T>
): PropsDefinition<T> {
Expand All @@ -77,7 +61,6 @@ export function normalizePropDefs<T>(
memo[k] = !(isObject(v) && "value" in v)
? (({ value: v } as unknown) as PropDefinition<T[keyof T]>)
: (v as PropDefinition<T[keyof T]>);
memo[k].notify != null || (memo[k].notify = false);
memo[k].attribute || (memo[k].attribute = toAttribute(k as string));
return memo;
}, {} as PropsDefinition<T>);
Expand Down Expand Up @@ -174,12 +157,6 @@ export function toProperty(attr: string) {
.replace(/(-)([a-z])/g, test => test.toUpperCase().replace("-", ""));
}

export function toComponentName(tag: string) {
return tag
.toLowerCase()
.replace(/(^|-)([a-z])/g, test => test.toUpperCase().replace("-", ""));
}

export function isObject(obj: any) {
return obj != null && (typeof obj === "object" || typeof obj === "function");
}
Expand All @@ -192,13 +169,10 @@ export function isConstructor(f: Function) {
return typeof f === "function" && f.toString().indexOf("class") === 0;
}

export function connectedToDOM(node: Node) {
if ("isConnected" in node) return node.isConnected;
const doc = (node as Node).ownerDocument;
if (!doc) return false;
if (doc.body.contains(node)) return true;
while (node && node !== doc.documentElement) {
node = node.parentNode || (node as ShadowRoot).host;
}
return node === doc.documentElement;
export function reloadElement(node: ICustomElement) {
let callback = null;
while ((callback = node.__releaseCallbacks.pop())) callback(node);
delete node.__initialized;
node.renderRoot.textContent = "";
node.connectedCallback();
}
29 changes: 12 additions & 17 deletions test/context.spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
const {compose, register, createContext, withProvider, withConsumer, consume} = require('../lib/component-register');
const render = require('@skatejs/ssr');

// Patch isConnected for sake of tests
Object.defineProperty(HTMLElement.prototype, 'isConnected', {
get() { return true; }
});
const render = (node) => {
document.body.append(node);
return node.outerHTML;
}

FIXTURES = [
'<parent-elem><shadowroot><child-elem><shadowroot><h1>Hi</h1><script>__ssr()</script></shadowroot></child-elem><script>__ssr()</script></shadowroot></parent-elem>'
'<h1>Hi</h1>'
]

const Context = createContext(() => {
Expand All @@ -22,11 +20,8 @@ const Parent = compose(
});

register('child-elem')((_, { element }) => {
// tests need defer microtask for some reason
Promise.resolve().then(() => {
const c = consume(Context, element);
element.renderRoot.innerHTML = `<h1>${c.greeting}</h1>`;
})
const c = consume(Context, element);
element.renderRoot.innerHTML = `<h1>${c.greeting}</h1>`;
});

// timing in test env doesn't allow this
Expand All @@ -38,16 +33,16 @@ const Child2 = compose(
});

describe('Test Context API', () => {
it('should pass context down', async () => {
it('should pass context down', () => {
let p;
const results = await render(p = new Parent());
expect(results).toBe(FIXTURES[0]);
render(p = new Parent());
expect(p.renderRoot.firstChild.renderRoot.innerHTML).toBe(FIXTURES[0]);
})
});

// due to limitations of test env
describe('Fake Test withConsumer', () => {
it('should create context key', async () => {
await render(new Child2());
it('should create context key', () => {
render(new Child2());
});
})
Loading

0 comments on commit cebdaa8

Please sign in to comment.