Skip to content

Commit

Permalink
fix: avoid relying on constructor name for isGlimmerComponent check (
Browse files Browse the repository at this point in the history
…#11)

This fixes an issue with minified builds, where the name of the Glimmer
component constructor may not be the same string used in development
builds.

The check now works without doing anything to check the name of the
class, so that it'll work even when minified.
  • Loading branch information
alexlafroscia authored and Ravenstine committed Jan 29, 2021
1 parent 9e74815 commit 75a14b0
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 47 deletions.
18 changes: 9 additions & 9 deletions addon/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// eslint-disable-next-line no-unused-vars
import EmberCustomElement, { CURRENT_CUSTOM_ELEMENT, INITIALIZERS } from './lib/custom-element';
import {
import {
getCustomElements,
addCustomElement,
getTargetClass,
isSupportedClass,
isComponent,
isGlimmerComponent,
isApp
} from './lib/common';
import { isGlimmerComponent } from './lib/glimmer-compat';
import { getOwner, setOwner } from '@ember/application';

export { default as EmberOutletElement } from './lib/outlet-element';
Expand Down Expand Up @@ -136,7 +136,7 @@ export function customElement() {

/**
* Gets the custom element node for a component or application instance.
*
*
* @param {*} entity
* @returns {HTMLElement|null}
*/
Expand All @@ -158,10 +158,10 @@ export function getCustomElement(entity) {
* Sets up a property or method to be interfaced via a custom element.
* When used, said property will be accessible on a custom element node
* and will retain the same binding.
*
* @param {*} target
* @param {String} name
* @param {Object} descriptor
*
* @param {*} target
* @param {String} name
* @param {Object} descriptor
*/
export function forwarded(target, name, descriptor) {
if (typeof target !== 'object')
Expand Down Expand Up @@ -231,7 +231,7 @@ function constructInstanceForCustomElement() {
const customElement = CURRENT_CUSTOM_ELEMENT.element;
// There should always be a custom element when the component is
// invoked by one, but if a decorated class isn't invoked by a custom
// element, it shouldn't fail when being constructed.
// element, it shouldn't fail when being constructed.
if (!customElement) return;
CUSTOM_ELEMENTS.set(this, customElement);
CURRENT_CUSTOM_ELEMENT.element = null;
Expand Down Expand Up @@ -263,4 +263,4 @@ function constructInstanceForCustomElement() {
}
}
}
}
}
40 changes: 2 additions & 38 deletions addon/lib/common.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Application from '@ember/application';
import Route from '@ember/routing/route';
import EmberComponent from '@ember/component';
import { isGlimmerComponent } from './glimmer-compat';

const EMBER_WEB_COMPONENTS_CUSTOM_ELEMENTS = Symbol('EMBER_WEB_COMPONENTS_CUSTOM_ELEMENTS');
const EMBER_WEB_COMPONENTS_TARGET_CLASS = Symbol('EMBER_WEB_COMPONENTS_TARGET_CLASS');
Expand Down Expand Up @@ -84,43 +85,6 @@ export function isComponent(targetClass) {
return isAncestorOf(targetClass, EmberComponent);
}

/**
* Indicates whether a class is a Glimmer component.
* This function makes a best guess rather than checking
* for a matching import because we can't guarantee that
* the consuming app will be using Glimmer components.
*
* This is acceptable because these checker functions are
* only currently used to throw an error when the developer
* tries to use the decorator on something they shouldn't.
*
* @param {Class} targetClass
* @private
* @returns {Boolean}
*/
export function isGlimmerComponent(targetClass) {
const flattenedPrototypeKeys = new Set();

if (!targetClass) return false;

let ancestor = targetClass;
let hasGlimmerDebugComponentAncestor = false;

while (ancestor) {
if (ancestor.name === 'GlimmerDebugComponent') hasGlimmerDebugComponentAncestor = true;
ancestor = Object.getPrototypeOf(ancestor);
if (!ancestor || !ancestor.prototype) continue;
for (const key in Object.getOwnPropertyDescriptors(ancestor.prototype)) {
flattenedPrototypeKeys.add(key);
}
}

return hasGlimmerDebugComponentAncestor &&
flattenedPrototypeKeys.has('bounds') &&
flattenedPrototypeKeys.has('element') &&
flattenedPrototypeKeys.has('debugName');
}

function isAncestorOf(a, b) {
if (!a) return false;

Expand All @@ -132,4 +96,4 @@ function isAncestorOf(a, b) {
}

return false;
}
}
31 changes: 31 additions & 0 deletions addon/lib/glimmer-compat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* global require */

// Import through `require` in case it is not a dependency
const GlimmerComponentModule = require('@glimmer/component');
const GlimmerComponent = GlimmerComponentModule && GlimmerComponentModule.default;

/**
* Indicates whether a class is a Glimmer component.
*
* @param {Class} targetClass
* @returns {Boolean}
* @private
*/
export function isGlimmerComponent(targetClass) {
if (!GlimmerComponent) {
return false;
}

let ancestor = targetClass;

while (ancestor) {
if (ancestor === GlimmerComponent) {
return true;
}

ancestor = Object.getPrototypeOf(ancestor);
}

return false;
}

0 comments on commit 75a14b0

Please sign in to comment.