Skip to content

Commit

Permalink
fix(runtime): cloneNode fix opt-in
Browse files Browse the repository at this point in the history
fixes #1070
fixes #1948

Co-Authored-By: Manu MA <manu.mtza@gmail.com>
  • Loading branch information
adamdbradley and manucorporat committed Dec 18, 2019
1 parent 94707f9 commit 6de57f7
Show file tree
Hide file tree
Showing 17 changed files with 296 additions and 10 deletions.
28 changes: 27 additions & 1 deletion src/client/import-shims.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as d from '../declarations';
import { BUILD, NAMESPACE } from '@build-conditionals';
import { consoleDevInfo } from './client-log';
import { doc, plt, win } from './client-window';
import { H, doc, plt, win } from './client-window';
import { getDynamicImportFunction } from '@utils';


Expand All @@ -26,6 +26,9 @@ export const patchBrowser = async (): Promise<d.CustomElementsDefineOptions> =>
if (BUILD.cssVarShim) {
plt.$cssShim$ = (win as any).__stencil_cssshim;
}
if (BUILD.cloneNodeFix) {
patchCloneNodeFix((H as any).prototype);
}

// @ts-ignore
const importMeta = import.meta.url;
Expand Down Expand Up @@ -85,3 +88,26 @@ export const patchDynamicImport = (base: string) => {
}
};


export const patchCloneNodeFix = (HTMLElementPrototype: any) => {
const nativeCloneNodeFn = HTMLElementPrototype.cloneNode;

HTMLElementPrototype.cloneNode = function(this: Node, deep: boolean) {
if (this.nodeName === 'TEMPLATE') {
return nativeCloneNodeFn.call(this, deep);
}
const clonedNode = nativeCloneNodeFn.call(this, false);
const srcChildNodes = this.childNodes;
if (deep) {
for (let i = 0; i < srcChildNodes.length; i++) {
// Node.ATTRIBUTE_NODE === 2, and checking because IE11
if (srcChildNodes[i].nodeType !== 2) {
clonedNode.appendChild(
srcChildNodes[i].cloneNode(true)
);
}
}
}
return clonedNode;
};
};
5 changes: 5 additions & 0 deletions src/compiler/app-core/build-conditionals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function getBuildFeatures(cmps: d.ComponentCompilerMeta[]) {
vdomText: cmps.some(c => c.hasVdomText),
watchCallback: cmps.some(c => c.hasWatchCallback),
taskQueue: true,
cloneNodeFix: false,
};
f.asyncLoading = f.cmpWillUpdate || f.cmpWillLoad || f.cmpWillRender;

Expand Down Expand Up @@ -124,6 +125,10 @@ export function updateBuildConditionals(config: d.Config, b: d.Build) {
b.constructableCSS = !b.hotModuleReplacement || !!config._isTesting;
b.asyncLoading = !!(b.asyncLoading || b.lazyLoad || b.taskQueue || b.initializeNextTick);
b.cssAnnotations = true;

if (config.extras) {
b.cloneNodeFix = !!config.extras.cloneNodeFix;
}
}


Expand Down
3 changes: 2 additions & 1 deletion src/compiler/browser/build-conditionals-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export const BUILD: Required<d.Build> = {
lifecycleDOMEvents: false,
lazyLoad: false,
profile: false,
slotRelocation: true
slotRelocation: true,
cloneNodeFix: false,
};

export const NAMESPACE = 'app';
1 change: 1 addition & 0 deletions src/compiler/component-hydrate/generate-hydrate-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ function getBuildConditionals(config: d.Config, cmps: d.ComponentCompilerMeta[])
build.lifecycleDOMEvents = false;
build.devTools = false;
build.hotModuleReplacement = false;
build.cloneNodeFix = false;

return build;
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/output-targets/output-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function getBuildConditionals(config: d.Config, cmps: d.ComponentCompilerMeta[])
build.taskQueue = false;
updateBuildConditionals(config, build);
build.devTools = false;
build.cloneNodeFix = false;

return build;
}
Expand Down
3 changes: 3 additions & 0 deletions src/declarations/build-conditionals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export interface BuildFeatures {
reflect: boolean;

taskQueue: boolean;

// extras
cloneNodeFix: boolean;
}

export interface Build extends Partial<BuildFeatures> {
Expand Down
5 changes: 5 additions & 0 deletions src/declarations/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ export interface StencilConfig {
excludeUnusedDependencies?: boolean;

stencilCoreResolvedId?: string;
extras?: ConfigExtras;
}

export interface ConfigExtras {
cloneNodeFix?: boolean;
}

export interface Config extends StencilConfig {
Expand Down
2 changes: 1 addition & 1 deletion src/mock-doc/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ MockElement.prototype.cloneNode = function(this: MockElement, deep?: boolean) {
// because we're creating elements, which extending specific HTML base classes there
// is a MockElement circular reference that bundling has trouble dealing with so
// the fix is to add cloneNode() to MockElement's prototype after the HTML classes
const cloned = createElement(null, this.nodeName);
const cloned = createElement(this.ownerDocument, this.nodeName);
cloned.attributes = cloneAttributes(this.attributes);

const styleCssText = this.getAttribute('style');
Expand Down
33 changes: 27 additions & 6 deletions src/runtime/bootstrap-lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,6 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
plt.jmp(() => disconnectedCallback(this));
}

's-hmr'(hmrVersionId: string) {
if (BUILD.hotModuleReplacement) {
hmrStart(this, cmpMeta, hmrVersionId);
}
}

forceUpdate() {
forceUpdate(this, cmpMeta);
}
Expand All @@ -130,6 +124,33 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
return getHostRef(this).$onReadyPromise$;
}
};

if (BUILD.cloneNodeFix) {
const orgCloneNode = HostElement.prototype.cloneNode;
HostElement.prototype.cloneNode = function(deep?: boolean) {
const srcNode = this;
const isShadowDom = BUILD.shadowDom ? srcNode.shadowRoot && supportsShadowDom : false;
const clonedNode = orgCloneNode.call(this, isShadowDom ? deep : false) as Node;
if (BUILD.slot && !isShadowDom && deep) {
let i = 0;
let slotted;
for (; i < srcNode.childNodes.length; i++) {
slotted = (srcNode.childNodes[i] as any)['s-nr'];
if (slotted) {
clonedNode.appendChild(slotted.cloneNode(true));
}
}
}
return clonedNode;
};
}

if (BUILD.hotModuleReplacement) {
(HostElement as any).prototype['s-hmr'] = function(hmrVersionId: string) {
hmrStart(this, cmpMeta, hmrVersionId);
};
}

cmpMeta.$lazyBundleIds$ = lazyBundle[0];

if (!exclude.includes(tagName) && !customElements.get(tagName)) {
Expand Down
6 changes: 5 additions & 1 deletion test/karma/stencil.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { sass } from '@stencil/sass';
import { less } from '@stencil/less';
import { stylus } from '@stencil/stylus';
import { postcss } from '@stencil/postcss';
import { Config } from '../../dist/declarations';

import nodePolyfills from 'rollup-plugin-node-polyfills';

export const config = {
export const config: Config = {
namespace: 'TestApp',
srcDir: 'test-app',
tsconfig: 'tsconfig-stencil.json',
Expand Down Expand Up @@ -33,6 +34,9 @@ export const config = {
postcss(),
stylus()
],
extras: {
cloneNodeFix: true,
},
_lifecycleDOMEvents: true,
devServer: {
historyApiFallback: {
Expand Down
33 changes: 33 additions & 0 deletions test/karma/test-app/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export namespace Components {
'didUnload': number;
'willLoad': number;
}
interface DomReattachClone {}
interface DomReattachCloneDeepSlot {}
interface DomReattachCloneHost {}
interface DynamicCssVariable {}
interface DynamicImport {
'update': () => Promise<void>;
Expand Down Expand Up @@ -320,6 +323,24 @@ declare global {
new (): HTMLDomReattachElement;
};

interface HTMLDomReattachCloneElement extends Components.DomReattachClone, HTMLStencilElement {}
var HTMLDomReattachCloneElement: {
prototype: HTMLDomReattachCloneElement;
new (): HTMLDomReattachCloneElement;
};

interface HTMLDomReattachCloneDeepSlotElement extends Components.DomReattachCloneDeepSlot, HTMLStencilElement {}
var HTMLDomReattachCloneDeepSlotElement: {
prototype: HTMLDomReattachCloneDeepSlotElement;
new (): HTMLDomReattachCloneDeepSlotElement;
};

interface HTMLDomReattachCloneHostElement extends Components.DomReattachCloneHost, HTMLStencilElement {}
var HTMLDomReattachCloneHostElement: {
prototype: HTMLDomReattachCloneHostElement;
new (): HTMLDomReattachCloneHostElement;
};

interface HTMLDynamicCssVariableElement extends Components.DynamicCssVariable, HTMLStencilElement {}
var HTMLDynamicCssVariableElement: {
prototype: HTMLDynamicCssVariableElement;
Expand Down Expand Up @@ -825,6 +846,9 @@ declare global {
'css-variables-shadow-dom': HTMLCssVariablesShadowDomElement;
'custom-event-root': HTMLCustomEventRootElement;
'dom-reattach': HTMLDomReattachElement;
'dom-reattach-clone': HTMLDomReattachCloneElement;
'dom-reattach-clone-deep-slot': HTMLDomReattachCloneDeepSlotElement;
'dom-reattach-clone-host': HTMLDomReattachCloneHostElement;
'dynamic-css-variable': HTMLDynamicCssVariableElement;
'dynamic-import': HTMLDynamicImportElement;
'es5-addclass-svg': HTMLEs5AddclassSvgElement;
Expand Down Expand Up @@ -955,6 +979,9 @@ declare namespace LocalJSX {
'didUnload'?: number;
'willLoad'?: number;
}
interface DomReattachClone {}
interface DomReattachCloneDeepSlot {}
interface DomReattachCloneHost {}
interface DynamicCssVariable {}
interface DynamicImport {}
interface Es5AddclassSvg {}
Expand Down Expand Up @@ -1121,6 +1148,9 @@ declare namespace LocalJSX {
'css-variables-shadow-dom': CssVariablesShadowDom;
'custom-event-root': CustomEventRoot;
'dom-reattach': DomReattach;
'dom-reattach-clone': DomReattachClone;
'dom-reattach-clone-deep-slot': DomReattachCloneDeepSlot;
'dom-reattach-clone-host': DomReattachCloneHost;
'dynamic-css-variable': DynamicCssVariable;
'dynamic-import': DynamicImport;
'es5-addclass-svg': Es5AddclassSvg;
Expand Down Expand Up @@ -1230,6 +1260,9 @@ declare module "@stencil/core" {
'css-variables-shadow-dom': LocalJSX.CssVariablesShadowDom & JSXBase.HTMLAttributes<HTMLCssVariablesShadowDomElement>;
'custom-event-root': LocalJSX.CustomEventRoot & JSXBase.HTMLAttributes<HTMLCustomEventRootElement>;
'dom-reattach': LocalJSX.DomReattach & JSXBase.HTMLAttributes<HTMLDomReattachElement>;
'dom-reattach-clone': LocalJSX.DomReattachClone & JSXBase.HTMLAttributes<HTMLDomReattachCloneElement>;
'dom-reattach-clone-deep-slot': LocalJSX.DomReattachCloneDeepSlot & JSXBase.HTMLAttributes<HTMLDomReattachCloneDeepSlotElement>;
'dom-reattach-clone-host': LocalJSX.DomReattachCloneHost & JSXBase.HTMLAttributes<HTMLDomReattachCloneHostElement>;
'dynamic-css-variable': LocalJSX.DynamicCssVariable & JSXBase.HTMLAttributes<HTMLDynamicCssVariableElement>;
'dynamic-import': LocalJSX.DynamicImport & JSXBase.HTMLAttributes<HTMLDynamicImportElement>;
'es5-addclass-svg': LocalJSX.Es5AddclassSvg & JSXBase.HTMLAttributes<HTMLEs5AddclassSvgElement>;
Expand Down
25 changes: 25 additions & 0 deletions test/karma/test-app/dom-reattach-clone/angularjs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<meta charset="utf8">
<script src="/build/testapp.esm.js" type="module"></script>
<script src="/build/testapp.js" nomodule></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.10/angular.min.js"></script>

<script>
angular
.module('demo', [])
.controller('homeCtrl', homeCtrl);

function homeCtrl() {
var vm = this;
vm.visible = true;
vm.toggle = () => vm.visible = !vm.visible;
}
</script>
<body ng-app="demo" ng-controller="homeCtrl as ctrl">
<button ng-click="ctrl.toggle()">Toggle</button>
<div id="parent" ng-if="ctrl.visible">
<dom-reattach-hydrated>
<p>Slot content 1</p>
</dom-reattach-hydrated>
</div>
</body>
19 changes: 19 additions & 0 deletions test/karma/test-app/dom-reattach-clone/cmp-deep-slot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Component, h } from '@stencil/core';

@Component({
tag: 'dom-reattach-clone-deep-slot'
})
export class DomReattachCloneDeep {
render() {
return (
<div class='wrapper'>
<span class="component-mark-up">Component mark-up</span>
<div>
<section>
<slot></slot>
</section>
</div>
</div>
)
}
}
15 changes: 15 additions & 0 deletions test/karma/test-app/dom-reattach-clone/cmp-host.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component, Host, h } from '@stencil/core';

@Component({
tag: 'dom-reattach-clone-host'
})
export class DomReattachCloneHost {
render() {
return (
<Host>
<span class="component-mark-up">Component mark-up</span>
<slot></slot>
</Host>
)
}
}
15 changes: 15 additions & 0 deletions test/karma/test-app/dom-reattach-clone/cmp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component, h } from '@stencil/core';

@Component({
tag: 'dom-reattach-clone'
})
export class DomReattachClone {
render() {
return (
<div class='wrapper'>
<span class="component-mark-up">Component mark-up</span>
<slot></slot>
</div>
)
}
}

0 comments on commit 6de57f7

Please sign in to comment.