Skip to content

Commit

Permalink
fix(listeners): add host event listeners within constructor
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Bradley <adamdbradley@users.noreply.github.com>
  • Loading branch information
simonhaenisch and adamdbradley committed Feb 20, 2020
1 parent cc7e154 commit dc1ba91
Show file tree
Hide file tree
Showing 44 changed files with 773 additions and 87 deletions.
15 changes: 15 additions & 0 deletions .vscode/launch.json
Expand Up @@ -142,6 +142,21 @@
],
"protocol": "inspector"
},
{
"type": "node",
"request": "launch",
"name": "Ionic App NEXT",
"args": [
"${workspaceFolder}/bin/stencil",
"build",
"--next",
"--dev",
"--max-workers=0",
"--config",
"${workspaceFolder}/test/ionic-app/stencil.config.ts"
],
"protocol": "inspector"
},
{
"type": "node",
"request": "launch",
Expand Down
2 changes: 2 additions & 0 deletions src/client/client-host-ref.ts
@@ -1,4 +1,5 @@
import * as d from '../declarations';
import { addHostEventListeners } from '@runtime';
import { BUILD } from '@app-data';


Expand Down Expand Up @@ -28,6 +29,7 @@ export const registerHost = (elm: d.HostElement, cmpMeta: d.ComponentRuntimeMeta
elm['s-p'] = [];
elm['s-rc'] = [];
}
addHostEventListeners(elm, hostRef, cmpMeta.$listeners$, false);
return hostRefs.set(elm, hostRef);
};
export const isMemberInElement = (elm: any, memberName: string) => {
Expand Down
2 changes: 1 addition & 1 deletion src/declarations/stencil-private.ts
Expand Up @@ -1879,7 +1879,7 @@ export interface HostRef {
$onRenderResolve$?: () => void;
$vnode$?: VNode;
$queuedListeners$?: [string, any][];
$rmListeners$?: () => void;
$rmListeners$?: (() => void)[];
$modeName$?: string;
$renderCount$?: number;
}
Expand Down
2 changes: 2 additions & 0 deletions src/hydrate/platform/index.ts
@@ -1,4 +1,5 @@
import * as d from '../../declarations';
import { addHostEventListeners } from '@runtime';

export const cmpModules = new Map<string, {[exportName: string]: d.ComponentConstructor}>();

Expand Down Expand Up @@ -120,6 +121,7 @@ export const registerHost = (elm: d.HostElement, cmpMeta: d.ComponentRuntimeMeta
hostRef.$onReadyPromise$ = new Promise(r => hostRef.$onReadyResolve$ = r);
elm['s-p'] = [];
elm['s-rc'] = [];
addHostEventListeners(elm, hostRef, cmpMeta.$listeners$, false);
return hostRefs.set(elm, hostRef);
};

Expand Down
8 changes: 4 additions & 4 deletions src/runtime/bootstrap-lazy.ts
Expand Up @@ -52,8 +52,8 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
}
}

lazyBundles.forEach(lazyBundle =>
lazyBundle[1].forEach(compactMeta => {
lazyBundles.map(lazyBundle =>
lazyBundle[1].map(compactMeta => {
const cmpMeta: d.ComponentRuntimeMeta = {
$flags$: compactMeta[0],
$tagName$: compactMeta[1],
Expand Down Expand Up @@ -170,8 +170,8 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.

// Process deferred connectedCallbacks now all components have been registered
isBootstrapping = false;
if (deferredConnectedCallbacks.length > 0) {
deferredConnectedCallbacks.forEach(host => host.connectedCallback());
if (deferredConnectedCallbacks.length) {
deferredConnectedCallbacks.map(host => host.connectedCallback());
} else {
if (BUILD.profile) {
plt.jmp(() => appLoadFallback = setTimeout(appDidLoad, 30, 'timeout'));
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/client-hydrate.ts
Expand Up @@ -23,7 +23,7 @@ export const initializeClientHydrate = (hostElm: d.HostElement, tagName: string,

clientHydrate(vnode, childRenderNodes, slotNodes, shadowRootNodes, hostElm, hostElm, hostId);

childRenderNodes.forEach(c => {
childRenderNodes.map(c => {
const orgLocationId = c.$hostId$ + '.' + c.$nodeId$;
const orgLocationNode = plt.$orgLocNodes$.get(orgLocationId);
const node = c.$elm$ as d.RenderNode;
Expand All @@ -48,7 +48,7 @@ export const initializeClientHydrate = (hostElm: d.HostElement, tagName: string,
});

if (BUILD.shadowDom && shadowRoot) {
shadowRootNodes.forEach(shadowRootNode => {
shadowRootNodes.map(shadowRootNode => {
if (shadowRootNode) {
shadowRoot.appendChild(shadowRootNode as any);
}
Expand Down
22 changes: 11 additions & 11 deletions src/runtime/connected-callback.ts
@@ -1,14 +1,13 @@
import * as d from '../declarations';
import { addEventListeners } from './host-listener';
import { addHostEventListeners, doc, getHostRef, nextTick, plt, supportsShadowDom } from '@platform';
import { addStyle } from './styles';
import { attachToAncestor } from './update-component';
import { BUILD } from '@app-data';
import { CMP_FLAGS, HOST_FLAGS, MEMBER_FLAGS } from '@utils';
import { doc, getHostRef, nextTick, plt, supportsShadowDom } from '@platform';
import { createTime } from './profile';
import { HYDRATE_ID, NODE_TYPE, PLATFORM_FLAGS } from './runtime-constants';
import { initializeClientHydrate } from './client-hydrate';
import { initializeComponent, fireConnectedCallback } from './initialize-component';
import { attachToAncestor } from './update-component';
import { createTime } from './profile';


export const connectedCallback = (elm: d.HostElement) => {
Expand All @@ -17,11 +16,9 @@ export const connectedCallback = (elm: d.HostElement) => {
const cmpMeta = hostRef.$cmpMeta$;
const endConnected = createTime('connectedCallback', cmpMeta.$tagName$);

if (BUILD.hostListener && cmpMeta.$listeners$) {
// initialize our event listeners on the host element
// we do this now so that we can listening to events that may
// have fired even before the instance is ready
hostRef.$rmListeners$ = addEventListeners(elm, hostRef, cmpMeta.$listeners$);
if (BUILD.hostListenerTargetParent) {
// only run if we have listeners being attached to a parent
addHostEventListeners(elm, hostRef, cmpMeta.$listeners$, true);
}

if (!(hostRef.$flags$ & HOST_FLAGS.hasConnected)) {
Expand Down Expand Up @@ -76,7 +73,7 @@ export const connectedCallback = (elm: d.HostElement) => {
// Lazy properties
// https://developers.google.com/web/fundamentals/web-components/best-practices#lazy-properties
if (BUILD.prop && BUILD.lazyLoad && !BUILD.hydrateServerSide && cmpMeta.$members$) {
Object.entries(cmpMeta.$members$).forEach(([memberName, [memberFlags]]) => {
Object.entries(cmpMeta.$members$).map(([memberName, [memberFlags]]) => {
if (memberFlags & MEMBER_FLAGS.Prop && elm.hasOwnProperty(memberName)) {
const value = (elm as any)[memberName];
delete (elm as any)[memberName];
Expand All @@ -95,8 +92,11 @@ export const connectedCallback = (elm: d.HostElement) => {
} else {
initializeComponent(elm, hostRef, cmpMeta);
}

} else {
fireConnectedCallback(hostRef.$lazyInstance$);
}
fireConnectedCallback(hostRef.$lazyInstance$);

endConnected();
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/disconnected-callback.ts
Expand Up @@ -12,7 +12,7 @@ export const disconnectedCallback = (elm: d.HostElement) => {

if (BUILD.hostListener) {
if (hostRef.$rmListeners$) {
hostRef.$rmListeners$();
hostRef.$rmListeners$.map(rmListener => rmListener());
hostRef.$rmListeners$ = undefined;
}
}
Expand Down
52 changes: 35 additions & 17 deletions src/runtime/host-listener.ts
Expand Up @@ -4,34 +4,53 @@ import { doc, plt, supportsListenerOptions, win } from '@platform';
import { HOST_FLAGS, LISTENER_FLAGS } from '@utils';


export const addEventListeners = (elm: d.HostElement, hostRef: d.HostRef, listeners: d.ComponentRuntimeHostListener[]) => {
hostRef.$queuedListeners$ = hostRef.$queuedListeners$ || [];
const removeFns = listeners.map(([flags, name, method]) => {
const target = (BUILD.hostListenerTarget ? getHostListenerTarget(elm, flags) : elm);
const handler = hostListenerProxy(hostRef, method);
const opts = hostListenerOpts(flags);
plt.ael(target, name, handler, opts);
return () => plt.rel(target, name, handler, opts);
});
return () => removeFns.forEach(fn => fn());
export const addHostEventListeners = (elm: d.HostElement, hostRef: d.HostRef, listeners: d.ComponentRuntimeHostListener[], attachParentListeners: boolean) => {
if (BUILD.hostListener && listeners) {
// this is called immediately within the element's constructor
// initialize our event listeners on the host element
// we do this now so that we can listen to events that may
// have fired even before the instance is ready

if (BUILD.hostListenerTargetParent) {
// this component may have event listeners that should be attached to the parent
if (attachParentListeners) {
// this is being ran from within the connectedCallback
// which is important so that we know the host element actually has a parent element
// filter out the listeners to only have the ones that ARE being attached to the parent
listeners = listeners.filter(([flags]) => (flags & LISTENER_FLAGS.TargetParent));
} else {
// this is being ran from within the component constructor
// everything BUT the parent element listeners should be attached at this time
// filter out the listeners that are NOT being attached to the parent
listeners = listeners.filter(([flags]) => !(flags & LISTENER_FLAGS.TargetParent));
}
}

listeners.map(([flags, name, method]) => {
const target = (BUILD.hostListenerTarget ? getHostListenerTarget(elm, flags) : elm);
const handler = hostListenerProxy(hostRef, method);
const opts = hostListenerOpts(flags);
plt.ael(target, name, handler, opts);
(hostRef.$rmListeners$ = (hostRef.$rmListeners$ || [])).push(
() => plt.rel(target, name, handler, opts)
);
});
}
};

const hostListenerProxy = (hostRef: d.HostRef, methodName: string) => {
return (ev: Event) => {
const hostListenerProxy = (hostRef: d.HostRef, methodName: string) =>
(ev: Event) => {
if (BUILD.lazyLoad) {
if (hostRef.$flags$ & HOST_FLAGS.isListenReady) {
// instance is ready, let's call it's member method for this event
hostRef.$lazyInstance$[methodName](ev);

} else {
hostRef.$queuedListeners$.push([methodName, ev]);
(hostRef.$queuedListeners$ = (hostRef.$queuedListeners$ || [])).push([methodName, ev]);
}
} else {
(hostRef.$hostElement$ as any)[methodName](ev);
}
};
};


const getHostListenerTarget = (elm: Element, flags: number): EventTarget => {
if (BUILD.hostListenerTargetDocument && flags & LISTENER_FLAGS.TargetDocument) return doc;
Expand All @@ -41,7 +60,6 @@ const getHostListenerTarget = (elm: Element, flags: number): EventTarget => {
return elm;
};


const hostListenerOpts = (flags: number) =>
supportsListenerOptions ?
({
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/index.ts
@@ -1,4 +1,4 @@

export { addHostEventListeners } from './host-listener';
export { attachShadow, defineCustomElement, forceModeUpdate, proxyCustomElement} from './bootstrap-custom-element';
export { bootstrapLazy } from './bootstrap-lazy';
export { connectedCallback } from './connected-callback';
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/proxy-component.ts
Expand Up @@ -15,7 +15,7 @@ export const proxyComponent = (Cstr: d.ComponentConstructor, cmpMeta: d.Componen
const members = Object.entries(cmpMeta.$members$);
const prototype = (Cstr as any).prototype;

members.forEach(([memberName, [memberFlags]]) => {
members.map(([memberName, [memberFlags]]) => {
if ((BUILD.prop || BUILD.state) && (
(memberFlags & MEMBER_FLAGS.Prop) ||
(
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/set-value.ts
Expand Up @@ -48,7 +48,7 @@ export const setValue = (ref: d.RuntimeRef, propName: string, newVal: any, cmpMe

if (watchMethods) {
// this instance is watching for when this property changed
watchMethods.forEach(watchMethodName => {
watchMethods.map(watchMethodName => {
try {
// fire off each of the watch methods that are watching this property
instance[watchMethodName](
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/update-component.ts
Expand Up @@ -33,7 +33,7 @@ export const scheduleUpdate = (hostRef: d.HostRef, isInitialLoad: boolean) => {
if (BUILD.lazyLoad && BUILD.hostListener) {
hostRef.$flags$ |= HOST_FLAGS.isListenReady;
if (hostRef.$queuedListeners$) {
hostRef.$queuedListeners$.forEach(([methodName, event]) => safeCall(instance, methodName, event));
hostRef.$queuedListeners$.map(([methodName, event]) => safeCall(instance, methodName, event));
hostRef.$queuedListeners$ = null;
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/testing/platform.ts
@@ -1,6 +1,7 @@
import * as d from '@stencil/core/internal';
import { resetTaskQueue } from './task-queue';
import { addHostEventListeners } from '@runtime';
import { flushAll } from './task-queue';
import { resetTaskQueue } from './task-queue';
import { setupGlobal } from '@mock-doc';

export * from './task-queue';
Expand Down Expand Up @@ -123,6 +124,7 @@ export const registerHost = (elm: d.HostElement, cmpMeta: d.ComponentRuntimeMeta
hostRef.$onReadyPromise$ = new Promise(r => hostRef.$onReadyResolve$ = r);
elm['s-p'] = [];
elm['s-rc'] = [];
addHostEventListeners(elm, hostRef, cmpMeta.$listeners$, false);
hostRefs.set(elm, hostRef);
};

Expand Down
4 changes: 4 additions & 0 deletions test/.scripts/analysis.js
Expand Up @@ -31,5 +31,9 @@ fileSizeProfile('End-to-end App',
output
);

fileSizeProfile('Ionic App',
path.join(rootDir, 'ionic-app', 'www', 'build'),
output
);

fs.writeFileSync(path.join(rootDir, 'readme.md'), output.join('\n'));
12 changes: 7 additions & 5 deletions test/.scripts/file-size-profile.js
Expand Up @@ -12,8 +12,8 @@ module.exports = function fileSizeProfile(appName, buildDir, output) {
output.push(``);
output.push('`' + path.relative(path.join(__dirname, '..'), buildDir) + '`');
output.push(``);
output.push(`| File | Brotli | Gzipped | Minified |`)
output.push(`|---------------------------------|----------|----------|----------|`);
output.push(`| File | Brotli | Gzipped | Minified |`)
output.push(`|------------------------------------------|----------|----------|----------|`);

totalBrotli = 0;
totalGzip = 0;
Expand All @@ -23,7 +23,9 @@ module.exports = function fileSizeProfile(appName, buildDir, output) {
.filter(f => !f.includes('system'))
.filter(f => !f.includes('css-shim'))
.filter(f => !f.includes('dom'))
.filter(f => !f.includes('shadow-css'));
.filter(f => !f.includes('shadow-css'))
.filter(f => f !== 'svg')
.filter(f => f !== 'swiper');

buildFiles.forEach(buildFile => {
const o = getBuildFileSize(path.join(buildDir, buildFile));
Expand All @@ -33,7 +35,7 @@ module.exports = function fileSizeProfile(appName, buildDir, output) {
});

// render SUM
output.push(render('TOTAL', totalBrotli, totalGzip, totalMinify));
output.push(render('**TOTAL**', totalBrotli, totalGzip, totalMinify));

output.push(``, ``);
}
Expand Down Expand Up @@ -88,7 +90,7 @@ function render(fileName, brotliSize, gzipSize, minifiedSize) {
dashSplt[dashSplt.length - 1] = 'hash';
fileName = dashSplt.join('-') + '.' + dotSplt[1];
}
return `| ${fileName.padEnd(31)} | ${getFileSize(brotliSize).padEnd(8)} | ${getFileSize(gzipSize).padEnd(8)} | ${getFileSize(minifiedSize).padEnd(8)} |`;
return `| ${fileName.padEnd(40)} | ${getFileSize(brotliSize).padEnd(8)} | ${getFileSize(gzipSize).padEnd(8)} | ${getFileSize(minifiedSize).padEnd(8)} |`;
}

function getFileSize(bytes) {
Expand Down
4 changes: 4 additions & 0 deletions test/hello-vdom/package.json
@@ -1,6 +1,10 @@
{
"name": "@stencil/hello-vdom",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"collection": "./dist/collection/collection-manifest.json",
"types": "./dist/types/index.d.ts",
"scripts": {
"build": "node ../../bin/stencil build --next",
"start": "node ../../bin/stencil build --dev --watch --serve --next",
Expand Down
9 changes: 5 additions & 4 deletions test/hello-vdom/stencil.config.ts
Expand Up @@ -2,9 +2,10 @@ import { Config } from '../../internal';

export const config: Config = {
namespace: 'HelloVDom',
outputTargets: [{
type: 'dist'
}],
outputTargets: [
{ type: 'dist' },
{ type: 'www', serviceWorker: null },
],
devServer: {
logRequests: true
},
Expand All @@ -17,4 +18,4 @@ export const config: Config = {
scriptDataOpts: false,
shadowDomShim: false,
}
};
};

0 comments on commit dc1ba91

Please sign in to comment.