Upgrade from 5.4 to 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 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',
              },
            },
          },
        },
      ],

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>

component.js

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

thanks for asking! I saw your StackOverflow post before this one – and replied there: ember.js - packageRules definition for embroider to allow component from @glimmer/component to work in ember 5.8 - Stack Overflow

I appreciate all the really good advice and help. Thank you so very much!

But no good deed goes unpunished.

The <this.tool … works but only if I add a static reference to the CaseNote component into the tool bar. It seems like embroider doesn’t add CaseNote to the Case route unless there’s a hard reference.

Ideas?

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

    {{!-- Tools --}}
    {{#if this.tool}}
        <div class="absolute right-24">
            <this.tool @caseId={{@caseId}} />
        </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-6 h-6" @icon="sticky-note" />
        </button>
{{!-- THIS IS REQUIRED IN ORDER TO MAKE THE TOOLS SECTION ABOVE DISPLAY THE TOOL --}}
        {{#if this.fakery}}
        <CaseNote @caseId={{@caseId}} />
        {{/if}}
    </div>
</div>

You missed some details from my answer on stack overflow :sweat_smile: No worries!

<this.tool /> has the same “dynamic” problem you were encountering before.

What you’re noticing about CaseNote is solved by the TOOLS-map/object concept below:

Also, copy pasting, in case stackoverflow loses threads or something:


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>
}

I clearly missed some bits from the answer you posted on SO. Thanks for the repost here.

I think I applied what you’ve provided correctly. But I still can get rid of the hard reference in the .hbs. I’m very new to Ember, and mostly new to JS, browser apps, npm, etc… So it takes me a while to absorb, understand, and eventually put all the help I’ve received to good use.

–component–

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { ensureSafeComponent } from '@embroider/util';
import CaseNoteComponent from '/routes/components/case-note/component.js';

Of note here in the component, I found another example of how to use CaseNoteComponent from within the component itself. However, adding that doesn’t help.

const TOOLS = {
    'case-note': CaseNoteComponent,
}

export default class CaseToolsComponent extends Component {
    @tracked tool = null
    @tracked toolName = null

    @action toggleTool(toolName) {
        if (this.toolName === toolName) {
            console.log('closing tool', toolName)
            this.toolName = null
            this.tool = null
            return
        }
        console.log('opening tool', toolName)

I’ve tried the following two different ways. This is the first.

        this.tool = TOOLS[toolName]
        ensureSafeComponent(TOOLS[toolName] || toolName, this)

This is the second.

        this.tool = ensureSafeComponent(TOOLS[toolName] || toolName, this)

        this.toolName = toolName
        console.log('tool', this.tool)
    }
}

–template–

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

    {{!-- Tools --}}
    {{#if this.tool}}
    {{#let (ensure-safe-component this.tool) as |Tool|}}
        <div class="absolute right-24">
            <Tool @caseId={{@caseId}} />
        </div>
    {{/let}}
    {{/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"
            type="button" id="btnCaseListNavBarCaseNote">
            <Bui::Icon class="w-6 h-6" @icon="sticky-note" />
        </button>

This is the bit I can’t seem to do without.

        {{#if 0}}<CaseNote/>{{/if}}
    </div>
</div>

thanks for posting your updated code!!

the ensureSafeComponent in JS isn’t needed – it’s fine to just use the template – nothing in the JS is saying that anything is going to be used as a component, so we don’t need to worry about that.

If you remove {{#if 0}}<CaseNote/>{{/if}} what error message do you get?

As far as I can tell, because you’re importing CaseNote you should be good to go.

However! one goofy thing I do see is the import path form CaseNote looks off:

import CaseNote from '/routes/components/case-note/component.js';

in particular, starting an import with / means the root URL – and we’re not working with URLs when authoring these files – we’re working with “packages” (sorta) :tada: (like you said, npm is a lot! lots of definition overloading / confusion) – so you import should actually be:

import CaseNote from '<name-of-your-app>/components/case-note';

or, if case-note is co-located with case-tools,

import CaseNote from './case-note';

note also that I removed the suffixing component.js – this is deprecated (this may have been the “pods” location of the component, where you have component.js + template.hbs – you’d want to migrate to “co-located components”, which means “case-note.js” and “case-note.hbs” (or if you go straight to gjs, just case-note.gjs (one file))

If I remove {{#if 0}} … There is no build error. There is no console error. The case-note component just doesn’t appear.

Here’s the real kicker, the case-note calls the backend to query a list of notes. That still fires and with the correct case id.

It seems to be invisible.

Also the import statement…

import CaseNoteComponent from './case-note';
Module not found: Error: Can't resolve './case-note' 
import CaseNoteComponent from './case-note/component';
Module not found: Error: Can't resolve './case-note/component'
import CaseNoteComponent from './case-note/component.js';
Module not found: Error: Can't resolve './case-note/component.js'

This builds.

import CaseNoteComponent from '/routes/components/case-note/component';

image

Since they’re both in separate folders, your import should start with ../

The ‘import that works’ probably just undefined and doesn’t error at build time by accident.

(Sorry for brevity, mobile)

Thanks again. I made that change. I should have realized that the path worked like a directory.

import CaseNoteComponent from '../case-note/component';

I had hoped that by fixing this error that I could remove the {{#if 0}} hack.

But that still remains necessary. Without it, the CaseNotesComponent seems to be active but invisible.

can you confirm that CaseNoteComponent has a value? (and isn’t undefined)?

Also, if you rename case-note/component.js and case-note/template.hbs to case-note/index.js and casen-note/index.hbs (component + template are “pods”, which are now deprecated in ember 5.9 or 5.10, I forget) – does CaseNotComponent have a value?

Console log(this.tool) yields

this.tool class CaseNoteComponent extends _glimmer_component__WEBPACK_IMPORTED_MODULE_4__["default"] {
  createNoteNotification() {
    this.updateList();
  }
  updateList() {
    let search = this.args.caseId;
    …

As for renaming case-note/component.js & template.hbs to index.js & index.hbs, ember really doesn’t like that. I think ember 5.8.0 is the latest version. Is 5.9 some pre-release version?

naw, the format I suggest is supported all the way back to 3.14 or something like that – it’s “Component co-location”, and is the default in new apps.

Can you provide the error? what do you mean by “doesn’t like that”? is your ember-cli-htmlbars up to date?

Console log(this.tool) yields

that’s good – I guess I don’t know what’s wrong (what you’re doing should work), and will need an OSS minimal reproduction to help further :sweat_smile:

though, to be extra sure, can you post all your code again?

First: I changed the import to look for index instead of coponent.

import CaseNoteComponent from '../case-note/index';

Then from file explorer rename those two files…

ERROR in ./routes/components/case-tools/template.hbs 5:0-63
Module not found: Error: Can't resolve '#embroider_compat/components/case-note' in 'C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI'
 @ ./assets/OKRA.js 307:13-70

ERROR in ./routes/components/case-tools/component.js 9:0-55
Module not found: Error: Can't resolve '../case-note/component' in 'C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-tools'
 @ ./assets/OKRA.js 310:13-70

ERROR in ./tests/integration/routes/components/case-note/component-test.js 5:0-63
Module not found: Error: Can't resolve '#embroider_compat/components/case-note' in 'C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI'
 @ ./assets/test.js 137:13-92

3 errors have detailed information that is not shown.
Use 'stats.errorDetails: true' resp. '--stats-error-details' to show it.

webpack 5.91.0 compiled with 3 errors in 29217 ms
Build Error (PackagerRunner) in routes\components\case-tools\template.hbs

Module not found: Error: Can't resolve '#embroider_compat/components/case-note' in 'C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI'

This is my code.

BEFORE TRYING TO USE INDEX IN LIEU OF COMPONENT/TEMPLATE case-tool/component.js

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import CaseNoteComponent from '../case-note/component';

const TOOLS = {
    'CaseNote': CaseNoteComponent,
}

export default class CaseToolsComponent extends Component {
    @tracked tool = null
    @tracked toolName = null

    @action toggleTool(toolName) {
        if (this.toolName === toolName) {
            this.toolName = null
            this.tool = null
            return
        }
        this.tool = TOOLS[toolName]
        this.toolName = toolName
        console.log('this.toolName', this.toolName)
        console.log('this.tool', this.tool)
    }
}

case-tool/template.hbs

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

    {{!-- Tools --}}
    {{#if this.tool}}
    {{#let (ensure-safe-component this.tool) as |Tool|}}
        <div class="absolute right-24">
            <Tool @caseId={{@caseId}} />
        </div>
    {{/let}}
    {{/if}}

    {{!-- Tool bar --}}
    <div class="bg-slate-400 ml-auto p-2">
        <button {{on "click" (fn this.toggleTool "CaseNote" )}}
            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-6 h-6" @icon="sticky-note" />
        </button>
        {{#if 0}}<CaseNote/>{{/if}}
    </div>
</div>

change:

import CaseNoteComponent from '../case-note/component';

to

import CaseNoteComponent from '../case-note';

and after, if you try the `index paths, the import is the same:

import CaseNoteComponent from '../case-note';

that should do it!

WITHOUT renaming the files and just making this change.

import CaseNoteComponent from '../case-note';

Yields this error

ERROR in ./routes/components/case-tools/component.js 9:0-45
Module not found: Error: Can't resolve '../case-note' in 'C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-tools'
resolve '../case-note' in 'C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-tools'
  using description file: C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\package.json (relative path: ./routes/components/case-tools)
    Field 'browser' doesn't contain a valid alias configuration
    using description file: C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\package.json (relative path: ./routes/components/case-note)
      no extension
        Field 'browser' doesn't contain a valid alias configuration
        C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note is not a file
      .wasm
        Field 'browser' doesn't contain a valid alias configuration
        C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note.wasm doesn't exist
      .mjs
        Field 'browser' doesn't contain a valid alias configuration
        C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note.mjs doesn't exist
      .js
        Field 'browser' doesn't contain a valid alias configuration
        C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note.js doesn't exist
      .json
        Field 'browser' doesn't contain a valid alias configuration
        C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note.json doesn't exist
      .ts
        Field 'browser' doesn't contain a valid alias configuration
        C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note.ts doesn't exist
      .hbs
        Field 'browser' doesn't contain a valid alias configuration
        C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note.hbs doesn't exist
      .hbs.js
        Field 'browser' doesn't contain a valid alias configuration
        C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note.hbs.js doesn't exist
      as directory
        existing directory C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note
          using description file: C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\package.json (relative path: ./routes/components/case-note)
            using path: C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note\index
              using description file: C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\package.json (relative path: ./routes/components/case-note/index)
                no extension
                  Field 'browser' doesn't contain a valid alias configuration
                  C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note\index doesn't exist
                .wasm
                  Field 'browser' doesn't contain a valid alias configuration
                  C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note\index.wasm doesn't exist
                .mjs
                  Field 'browser' doesn't contain a valid alias configuration
                  C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note\index.mjs doesn't exist
                .js
                  Field 'browser' doesn't contain a valid alias configuration
                  C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note\index.js doesn't exist
                .json
                  Field 'browser' doesn't contain a valid alias configuration
                  C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note\index.json doesn't exist
                .ts
                  Field 'browser' doesn't contain a valid alias configuration
                  C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note\index.ts doesn't exist
                .hbs
                  Field 'browser' doesn't contain a valid alias configuration
                  C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note\index.hbs doesn't exist
                .hbs.js
                  Field 'browser' doesn't contain a valid alias configuration
                  C:\dev\OKRA\upgrade_ember\src\OKRA.WebUI\node_modules\.embroider\rewritten-app\routes\components\case-note\index.hbs.js doesn't exist
 @ ./assets/OKRA.js 313:13-70

what about renaming the files and using:

import CaseNoteComponent from '../case-note';

? I wonder if all this pods component stuff is just super weird – (one of the reasons its deprecated for removal in ember v6).

If you rename your files to:

case-tools.js
case-tools.hbs
case-note.js
case-note.hbs

you can get away with:

import CaseNoteComponent from './case-note';

I just wanted to say thank your for all your help.

I dragged in our ember expert. He threw in the towel.

We’ve decided that instead of going this route, that we’ll just put conditionals around future tools and let the hbs handle it with if’s for each tool entry.

It was soaking up WAY too much developer time.

But thank you once again!

If you can create a minimal repro in a separate OSS app, I can PR you a fix <3