-
Notifications
You must be signed in to change notification settings - Fork 149
Caching issues (leading to Webpack crashing or component templates not being picked up) #1619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This fixes the first scenario detailed in embroider-build#1619, where a synthesized template-only component .js file is not cleaned up when the corresponding .hbs file is deleted. There are actually two bugs here. The first issue is we never attempt to clean up these unneeded files, which is fixed by keeping track of which of the emitted files are still needed after the main loop. The second issue is that, even with that fix, it still does not work, because we are incorrectly using the async version of the fs API here without awaiting for the promise. With the way the promisify code is written, the fs delete is actually scheduled to happen on the microtask queue, so by the time the `mergeTrees` code runs, the deletion hasn't actually happened yet. Since the synthesized tree appears after the appTree, I believe (but haven't tested) this has also been causing a different bug, where if you started with just a `.hbs` file, then add a `.js` file later, the new file does not actually take effect until one more build later, since the build triggered by the `.js` file creation would get shadowed by the previously synthesized template-only component file. Perhaps no caught this issue because the typical workflow involves creating either an empty file or generating a stub `.js` file, then filling it in with the actual content. By the "first meaningful save", the deletion would probably have gone through and this would self-resolve.
I am not sure that there is a Good™ way to fix the babel (?) caching issue – fundamentally the co-location babel plugin breaks Webpack's (or at least, babel-loader's) assumption that transforms are strictly based on the input source. I think this is basically the same kind of problem that something like babel-plugin-inline-import has, and it seems like their solution is a custom alternative to babel-loader (which probably wraps it under-the-hood). Perhaps we could do that too, but that would only work for Webpack. I came up with an alternative that may work. In synthesize-template-only-components.js, we can also attempt to detect the cases where there is a lone Alternatively, we can flip it around so that we add I'm not sure which way is better. On the one hand, lone I am not sure how much of a performance impact this really has, but if we have to worry about source maps and stuff then it may start to add up? |
Actually: https://dev.to/goatandsheep/how-to-use-babels-new-addexternaldependency-api-338h This seems promising, I'll look into it |
I'm not sure if Come to think of it, doesn't |
Yeah. I don't think we can use |
Yes, I think this class of solution is probably best. By which I mean, incorporating co-location details directly into the JS in a dedicated plugin that happens before babel. Ideally, this plugin would simply do everything and produce reasonable javascript output with no hidden dependencies. The challenge is doing that without paying the parse cost twice. A special comment is probably a good compromise, so that parse-aware work can still happen in babel but all the filesystem concerns got handled already. An alternative design for the comment would be to actually make the comment contain the inlined contents of the colocated template. Then the colocation babel plugin would only be responsible for converting the comment to the proper equivalent javascript. |
That is basically the |
Regarding the approach of inlining the content into the
TL;DR I think retrofitting "has co-located template: Y/N" bit is easy enough, but actually inlining it requires more redesign |
These tests are currently written to exhibit the buggy behavior. When I fix the issue I'll adjust the tests to the correct/expected behavior.
These tests are currently written to exhibit the buggy behavior. When I fix the issue I'll adjust the tests to the correct/expected behavior.
These tests are currently written to exhibit the buggy behavior. When I fix the issue I'll adjust the tests to the correct/expected behavior.
These tests are currently written to exhibit the buggy behavior. When I fix the issue I'll adjust the tests to the correct/expected behavior.
This fixes the first scenario detailed in embroider-build#1619, where a synthesized template-only component .js file is not cleaned up when the corresponding .hbs file is deleted. There are actually two bugs here. The first issue is we never attempt to clean up these unneeded files, which is fixed by keeping track of which of the emitted files are still needed after the main loop. The second issue is that, even with that fix, it still does not work, because we are incorrectly using the async version of the fs API here without awaiting for the promise. With the way the promisify code is written, the fs delete is actually scheduled to happen on the microtask queue, so by the time the `mergeTrees` code runs, the deletion hasn't actually happened yet. Since the synthesized tree appears after the appTree, I believe (but haven't tested) this has also been causing a different bug, where if you started with just a `.hbs` file, then add a `.js` file later, the new file does not actually take effect until one more build later, since the build triggered by the `.js` file creation would get shadowed by the previously synthesized template-only component file. Perhaps no caught this issue because the typical workflow involves creating either an empty file or generating a stub `.js` file, then filling it in with the actual content. By the "first meaningful save", the deletion would probably have gone through and this would self-resolve.
As discussed in embroider-build#1619, tools like babel caches the ouput based on the source content of the input files. For component javascript files, whether there is a co-located template file is an extra bit of information that doesn't show up in the source file, but that information does get used in producing the output. This causes the caches to not invalidate when a co-located tempalte file is added or deleted. This fixes the problem by ensuring we include that information in the input source file. For now, it is just an inert comment, but we can actually adjust our babel plugin to rely on this information rather than doing its own filesystem probing again, which should have some performance benefit.
As discussed in embroider-build#1619, tools like babel caches the ouput based on the source content of the input files. For component javascript files, whether there is a co-located template file is an extra bit of information that doesn't show up in the source file, but that information does get used in producing the output. This causes the caches to not invalidate when a co-located tempalte file is added or deleted. This fixes the problem by ensuring we include that information in the input source file. For now, it is just an inert comment, but we can actually adjust our babel plugin to rely on this information rather than doing its own filesystem probing again, which should have some performance benefit.
Fix co-located components regressions (#1619)
This can probably be closed now, but @void-mAlex @mansona @NullVoxPopuli do you think the vite integration has the same bug? In particular the more subtle case 2. Just realized that it has the same general set up so it's probably worth checking |
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
.../rewritten-app/components/foo.hbs
exists (as expected).../rewritten-app/components/foo.js
exists (in addition tofoo.hbs
).../rewritten-app/assets/zomg.js
has an entry forcomponents/foo.js
rm app/components/foo.hbs
.../rewritten-app/components/foo.hbs
does not exists (as expected).../rewritten-app/components/foo.js
exists (unexpected).../rewritten-app/assets/zomg.js
has 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.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 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.js
with the following content:rewritten-app
application.hbs
) it renders an empty span in the browserapp/components/foo.hbs
with any non-empty content (e.g.hello world!
)rewritten-app
tagName: "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
.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 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.js
andapp/components/foo.hbs
, the content doesn't matterapp/components/foo.hbs
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!The text was updated successfully, but these errors were encountered: