Search code examples
ember.jsember-octaneglimmer.js

packageRules definition for embroider to allow component from @glimmer/component to work in ember 5.8


Just upgraded from 5.4 to 5.8. Now I'm getting this error.

\src\node_modules\.embroider\rewritten-app\routes\components\case-tools\template.hbs:
Unsafe dynamic component: this.tool in
node_modules\.embroider\rewritten-app\routes\components\case-tools\template.hbs 

I've found only one link so far that discusses how to set up an exception for 'component'. But I don't understand what it's asking me to do.

I've tried variations on these values for packageRules. With these values it tells me that it can't parse "component".

What do the various values in packageRules mean, and where do I find what to shove into them?

packageRules

    const { Webpack } = require('@embroider/webpack');
    return require('@embroider/compat').compatBuild(app, Webpack, {
    ...
    packageRules: [
        {
          package: '@glimmer/component',
          components: {
            'component': {
              acceptsComponentArguments: ['this.tool'],
              layout: {
                addonPath: 'app\routes\components\case-tools\component.hbs',
              },
            },
          },
        },
      ],

component.js

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

export default class CaseToolsComponent extends Component {
    @tracked tool = ""
    @action  toggleTool(toolName) {
        if (this.tool == toolName) {
            this.tool = ""
        } else {
            this.tool = toolName
        }
    }
}

template.hbs

<div class="ml-auto flex float-right">

    {{!-- Tools --}}
    {{#if this.tool}}
    <div class="absolute right-24">
        <div>{{component this.tool caseId=@caseId}}</div>
    </div>
    {{/if}}

    {{!-- Tool bar --}}
    <div class="bg-slate-400 ml-auto p-2">
        <button {{on "click" (fn this.toggleTool "case-note" )}}
            class=" text-slate-600 hover:text-white hover:bg-slate-300 group flex gap-x-3 rounded-md p-3 text-sm leading-6 font-semibold"
            type="button" id="btnCaseListNavBarCaseNote">
            <Bui::Icon class="w-8 h-8" @icon="sticky-note" />
        </button>
    </div>
</div>

Solution

  • Good news: you don't need a packageRule for this,

    Looking back at your error:

    Unsafe dynamic component: this.tool in 
    routes\components\case-tools\template.hbs 
    

    is because of this code:

            <div>{{component this.tool caseId=@caseId}}</div>
    

    you can't do {{component this.tool}} anywhere in your app if you want to be fully strict compatible.

    Going forward {{component will not accept strings.

    The laziest thing you can do today is this:

    {{#let (component (ensure-safe-component this.tool)) as |Tool|}}
      <Tool />
    {{/let}}
    

    which you probably don't want to do, since to be more future proof, you'll benefit from importing all of your possible components and creating a map to choose from like this:

    import Component from '@glimmer/component';
    import { action } from '@ember/object';
    import { tracked } from '@glimmer/tracking';
    import { assert } from '@ember/debug';
    
    // import all your tools, they could come from anywhere, 
    // doesn't have to be sibling (./)
    import Hammer from './hammer';
    import Drill from './drill';
    import Shovel from './shover';
    
    const TOOLS = {
      // map of:
      // lower-case-hyphenated/maybe/namespaced-name => ActualComponent
      hammer: Hammer,
      drill: Drill,
      shover: Shovel,
    }
    
    export default class CaseToolsComponent extends Component {
        @tracked tool;
    
        @action  
        toggleTool(toolName) {
          let tool = TOOLS[toolName];
        
          assert(`Tool named ${toolName} is not known, and cannot be used`, tool);
    
          this.tool = tool;
        }
    }
    

    and then your template would look like this

    {{#if this.tool}}
    
      {{#let (ensure-safe-component this.tool) as |Tool|}}
        <Tool />
      {{/let}}
    
    {{/if}}
    

    not that this still requires ensure-safe-component because property accesses are dynamic.

    to have the least "weirdness" you can migrate all the way to GJS, which would look like this:

    import Component from '@glimmer/component';
    import { action } from '@ember/object';
    import { tracked } from '@glimmer/tracking';
    import { assert } from '@ember/debug';
    
    // import all your tools, they could come from anywhere, 
    // doesn't have to be sibling (./)
    import Hammer from './hammer';
    import Drill from './drill';
    import Shovel from './shover';
    
    const TOOLS = {
      // map of:
      // lower-case-hyphenated/maybe/namespaced-name => ActualComponent
      hammer: Hammer,
      drill: Drill,
      shover: Shovel,
    }
    
    export default class CaseToolsComponent extends Component {
        @tracked tool;
    
        @action  
        toggleTool(toolName) {
          let tool = TOOLS[toolName];
        
          assert(`Tool named ${toolName} is not known, and cannot be used`, tool);
    
          this.tool = tool;
        }
    
        <template>
            {{#if this.tool}}
                <this.tool />
            {{/if}}
        </template>
    }