Setup
ember new --embroider --yarn zomg (yarn optional)
JOBS=0 ember s (JOBS=0 optional, but may get better stack traces/easier to step through the code without the workers)
Scenario 1
touch app/components/foo.hbs
- observe it rebuilds successfully
- observe that
.../rewritten-app/components/foo.hbs exists (as expected)
- observe that
.../rewritten-app/components/foo.js exists (in addition to foo.hbs)
- observe that
.../rewritten-app/assets/zomg.js has an entry for components/foo.js
rm app/components/foo.hbs
- observe that the build crashes
- observe that
.../rewritten-app/components/foo.hbs does not exists (as expected)
- observe that
.../rewritten-app/components/foo.js exists (unexpected)
- observe that
.../rewritten-app/assets/zomg.js has an entry for components/foo.js (unexpected)
The problem here is that the code the reflects the FS changes (deletion, in this case) into rewritten-app, in response to a sole .hbs file being deleted, should have also deleted the "virtual" js file (which it added in the first place), but did not.
While it is hard to "see" (not sure if there is a way to get the babel output written to disk), but because of the content-based caching (see the scenario below), the babel-transformed output of the leftover foo.js has a reference to foo.hbs, leading to the crash.
Scenario 2
(This scenario is to illustrate an independent issue to the previous one, so make sure you git reset the changes, restart the server, etc, to being from a fresh state)
- create
app/components/foo.js with the following content:
import Component from "@ember/component";
export default Component.extend({ tagName: "span" });
- observe it rebuilds successfully
- observe that the expected files and entries are present in
rewritten-app
- observe that if you invoke the component (from
application.hbs) it renders an empty span in the browser
- create
app/components/foo.hbs with any non-empty content (e.g. hello world!)
- observe it rebuilds successfully
- observe that the expected files and entries are present in
rewritten-app
- observe that if you invoke the component it renders an empty span without the template's content (unexpected)
- edit the component js file to have
tagName: "p"
- observe it rebuilds successfully
- observe that if you invoke the component it does render an p tag with the template's content (as expected)
I believe the issue is the babel transform for the co-located components plug-in only gets re-run solely if (and only if) the content of the .js file changes, which is semantically incorrect. The presence or absence of the adjacent .hbs file should effectively be part of the logical cache key.
Notably, simply touching the mtime of the .js file (either in the actual app, or in the rewritten-app) is not sufficient.
Scenario 3
I added this one later, and I think fundamentally it is just a variant of Scenario 2, but this results in a persistent crash, which shows that the content-based caching behavior survives rebuilds.
- before starting the server, create
app/components/foo.js and app/components/foo.hbs, the content doesn't matter
- start the server
- observe that it builds successfully
- delete
app/components/foo.hbs
- observes that the build crashes
- exit the server
- start the server again
- observe that the build is still crashing
You can fix the build by making any content changes to the .js file. However, if you then revert to the previous content at any later time, so long as the underlying cache entry hasn't been evicted yet, it will bring back the crash! So effectively that exact filename-content pair is semi-permanently poisoned to expect a corresponding .hbs file. Fun!
Setup
ember new --embroider --yarn zomg(yarn optional)JOBS=0 ember s(JOBS=0optional, but may get better stack traces/easier to step through the code without the workers)Scenario 1
touch app/components/foo.hbs.../rewritten-app/components/foo.hbsexists (as expected).../rewritten-app/components/foo.jsexists (in addition tofoo.hbs).../rewritten-app/assets/zomg.jshas an entry forcomponents/foo.jsrm app/components/foo.hbs.../rewritten-app/components/foo.hbsdoes not exists (as expected).../rewritten-app/components/foo.jsexists (unexpected).../rewritten-app/assets/zomg.jshas an entry forcomponents/foo.js(unexpected)The problem here is that the code the reflects the FS changes (deletion, in this case) into
rewritten-app, in response to a sole.hbsfile being deleted, should have also deleted the "virtual" js file (which it added in the first place), but did not.While it is hard to "see" (not sure if there is a way to get the babel output written to disk), but because of the content-based caching (see the scenario below), the babel-transformed output of the leftover
foo.jshas a reference tofoo.hbs, leading to the crash.Scenario 2
(This scenario is to illustrate an independent issue to the previous one, so make sure you git reset the changes, restart the server, etc, to being from a fresh state)
app/components/foo.jswith the following content:rewritten-appapplication.hbs) it renders an empty span in the browserapp/components/foo.hbswith any non-empty content (e.g.hello world!)rewritten-apptagName: "p"I believe the issue is the babel transform for the co-located components plug-in only gets re-run solely if (and only if) the content of the
.jsfile changes, which is semantically incorrect. The presence or absence of the adjacent.hbsfile should effectively be part of the logical cache key.Notably, simply touching the mtime of the
.jsfile (either in the actual app, or in therewritten-app) is not sufficient.Scenario 3
I added this one later, and I think fundamentally it is just a variant of Scenario 2, but this results in a persistent crash, which shows that the content-based caching behavior survives rebuilds.
app/components/foo.jsandapp/components/foo.hbs, the content doesn't matterapp/components/foo.hbsYou can fix the build by making any content changes to the
.jsfile. However, if you then revert to the previous content at any later time, so long as the underlying cache entry hasn't been evicted yet, it will bring back the crash! So effectively that exact filename-content pair is semi-permanently poisoned to expect a corresponding.hbsfile. Fun!