Refactor fastboot build to be more performant#369
Refactor fastboot build to be more performant#369stefanpenner merged 1 commit intoember-fastboot:masterfrom kratiahuja:build-test
Conversation
|
Please feel free to try this out in your app without any other fastboot complaint addons. |
|
#374 resolves |
|
|
||
| if (isBrowserInitializersPresent) { | ||
| var errorMsg = `FastBoot build no longer supports ${rootPath}/app/(instance-)?initializers/browser structure. ` + | ||
| `Please refer to www.ember-fastboot.com for a migration path.`; |
There was a problem hiding this comment.
@kratiahuja I have picked up migrating an addon as we have discussed on Slack, and after updating ember-cli-fastboot to the latest version of your PR, I immediately ran into this error message, being caused by some other (unknown) addon.
The problem I see here is that a) probably every existing app will run into this error once trying to migrate to FastBoot 1.0, and b) it gives no clue where that offending initializer actually comes from, which could be quite frustrating. It could be even some nested addon dependency the user does not see in his package.json.
I think it would be cool (in this PR or a follow up) to have some better tracing available, like which addon is causing this, or at least what the initializers actual name is, to be able to effectively investigate into this!
There was a problem hiding this comment.
Please forget it! ;) I was not paying enough attention to see it actually does exactly this. 🙄
Need some more sleep probably...
There was a problem hiding this comment.
Yeah we can't auto-migrate browser based initializers, hence we will need to fail the build. I am happy to refactor the error messages to be more better. Probably sometime this week i'll come up with a migration cheat sheet so that it is easier for addons 😄
There was a problem hiding this comment.
That would be awesome! Would be nice to have a link in the above mentioned error message, maybe a dedicated migration page on www.ember-fastboot.com?
There was a problem hiding this comment.
Sorry I missed replying to this. Yes would be nice to have a dedicated migration page. I think before this PR is merged, we should send in a warning (but getting another PR) and link to the migration page.
| * | ||
| * @param {Object} project | ||
| */ | ||
| function migrateInitializers(project) { |
There was a problem hiding this comment.
I just noticed that this works on the original files. So in my app after running ember s the files were modifed, the old instance-initializer/fastboot/foo.js was physically moved to fastboot/instance-initializer/foo.js (from POV of git a new, not yet added file). The same for addons, some yarn linked addons got silently modified (which I did not want as that would have broken them for pre 1.0 FastBoot).
I wonder if that migration magic should not happen inside some broccoli tree rather than operating on the original files of the app and in node_modules/someAddon?
There was a problem hiding this comment.
Why will it break for pre fastboot 1.0? This migration will not apply for pre fastboot 1.0 but only after.
Reason for moving is because if we don't, these initializers will end up running in browser too. Hence it needs to be moved. I am hoping reading the message, addon owners would move the fastboot intiializers themselves post fastboot 1.0. This migration would be dropped after a few releases of fastboot 1.0
There was a problem hiding this comment.
The breaking pre FastBoot 1.0 thing was related to the changes in ember-useragent, that you also reviewed. There we left the instance-initializer/fastboot/ember-useragent in place, so it works in pre 1.0 as well as in post 1.0 (because of the automatic migration). But because it was symlinked, and the migration works on the original files, it modified the original addon code, moving the initializer to a the new place...
So this is the point I was trying to make, that this migration did not only move files in a temporary state while building the app like a broccoli plugin would do (take the previous approach of fastboot-filter-initializers as an example, which filters one of the browser|fastboot initializers in a broccoli tree), but it modified the original in-repo files of the app, as well as any symlinked addon (in fact any addon in node_modules). I would expect something like a codemod to do that, but certainly not ember s.
There was a problem hiding this comment.
I am like the idea of a codemod doing something like this. But what if they forget to run the codemod tool post 1.0 before serving? It will break for them more. I guess we can detect if they ran codemod too and not serve.
There was a problem hiding this comment.
I am actually not arguing in favor of a codemod, I am just arguing that ember s should not modify any original file in my app/addons. It can move things around in a broccoli tree when building, but not the original in-repo files IMHO.
There was a problem hiding this comment.
@simonihmig My earlier comment was me just musing on the idea.
Wouldn't moving in broccoli-tree be very expensive? Are you talking about after the treeForApp is generated? I don't think we will solve the build performance problem then (at least in a temporary sense).
I totally agree it is not the best way and I am happy to explore new ways to do it. Do you have an example that you can explain with?
Reason for changing the original in-repo files is (maybe moving in broccoli tree solves the below issue):
the problem not moving in-repo files is (as I mentioned earlier) that if we don't move you will end up with two AMD modules after the build:
<app-name>/initializers/fastboot/x.js<-- This one will get loaded in browser and FastBoot<app-name>/initializers/x.js<--- this one is only loaded in FastBoot
When the app will boot in the browser, it will run app/initializers/fastboot/x.js (this because we no longer filter the fastboot initializers) which will cause issues. Hence there is a need to move the in-repo files so that we don't generate <app-name>/initializers/fastboot/x.js.
Maybe there is a better way to solve this problem that I am not seeing?
| const fs = require('fs-extra'); | ||
|
|
||
| const fastbootInitializerTypes = [ 'initializers', 'instance-initializers']; | ||
| const FASTBOOT_DIR = 'fastboot'; |
There was a problem hiding this comment.
Given the description of the PR I was expecting the new FastBoot folder to be named fastboot-app rather than fastboot, but this might be just some misunderstanding?
There was a problem hiding this comment.
I prefer calling it fastboot instead of fastboot-app since it is more generic
|
Draft and WIP cheatsheet on how fastboot compatible addons can continue to be backward compatible (with this build changes) if they do these changes: https://gist.github.com/kratiahuja/d22de0fb1660cf0ef58f07a6bcbf1a1c This may change subject to the feedback on this PR and feedback on the cheatsheet. I'll try bringing this up in the next FastBoot meeting. cc: @simonihmig |
|
Removing WIP since this is ready to review. The review has got pretty large that we can do the remaining TODOs in a subsquent PR. |
index.js
Outdated
|
|
||
| // invoke addToFastBootTree for every addon | ||
| if (addon.addToFastBootTree) { | ||
| var additionalFastBootTree = addon.addToFastBootTree(); |
There was a problem hiding this comment.
Related to this problem: ember-fastboot/ember-cli-head#21 (comment)
Is it actually required that addons return a tree that was created by lib/build-utilities/fastboot-builder.js? Isn't it possible that the addon just return a simple tree (like this.treeGenerator(path)), and the mergedFastBootTree (Line 152) is then moved to appName (Funnel destDir)?
There was a problem hiding this comment.
I think we could do that that. Will need to try it. This would also enable linting automatically if I remember correctly (cc: @tsubomii).
There was a problem hiding this comment.
how does this differ from treeForFastboot and would the following cover this use-case?
treeForFastboot(tree) {
return mergeTrees([
tree, // `<addon-root>/fastboot/`
additionalTree // from whereve you would want.
]);
}note: at first glance ^ would feel more aligned to existing API's, but let me know if I am missing someting
There was a problem hiding this comment.
@simonihmig Fixed. You can just use this.treeGenerator(path) and return that.
There was a problem hiding this comment.
@stefanpenner I actually like the idea of having treeForFastBoot which is more aligned to other existing APIs. And yes it would cover the usecase I wanted it to cover as well. Will update it to providing treeForFastBoot :)
index.js
Outdated
| this._super.init && this._super.init.apply(this, arguments); | ||
| // set a environment variable to allow addons to use `fastboot-filter-initializers` | ||
| // for old versions. | ||
| process.env.FASTBOOT_NEW_BUILD = true; |
There was a problem hiding this comment.
would it make more sense to set this when this module is included rather than later when it is initialized? That may reduce the window where this variable appears to be unset but will soon be set.
There was a problem hiding this comment.
I wanted this variable to be set early on. Isn't included called after init?
index.js
Outdated
|
|
||
| // invoke addToFastBootTree for every addon | ||
| if (addon.addToFastBootTree) { | ||
| var additionalFastBootTree = addon.addToFastBootTree(); |
There was a problem hiding this comment.
how does this differ from treeForFastboot and would the following cover this use-case?
treeForFastboot(tree) {
return mergeTrees([
tree, // `<addon-root>/fastboot/`
additionalTree // from whereve you would want.
]);
}note: at first glance ^ would feel more aligned to existing API's, but let me know if I am missing someting
index.js
Outdated
| }); | ||
|
|
||
| var fileAppName = path.basename(this.app.options.outputPaths.app.js).split('.')[0]; | ||
| var finalFastbootTree = Concat(processExtraTree, { |
index.js
Outdated
| } | ||
| trees.push(fastbootTree); | ||
|
|
||
| let newTree = new mergeTrees(trees); |
There was a problem hiding this comment.
new MergeTrees or no new if lowercase.
index.js
Outdated
| var fastbootConfig = config.fastboot; | ||
| // do not boot the app automatically in fastboot. The instance is booted and | ||
| // lives for the lifetime of the request. | ||
| if (config.hasOwnProperty('APP')) { |
|
|
||
| const fastbootDirPath = path.join(rootPath, FASTBOOT_DIR); | ||
| // check if fastboot/app exists | ||
| if(!existsSync(fastbootDirPath)) { |
There was a problem hiding this comment.
missing space between if and (
|
|
||
| // copy over app/initializers/fastboot and app/instance/initializers/fastboot | ||
| fastbootInitializerTypes.forEach((fastbootInitializerType) => { | ||
| var srcFastbootPath = path.join(rootPath, 'app', fastbootInitializerType, 'fastboot'); |
There was a problem hiding this comment.
if we are using const and => could we also use let in-place of var?
There was a problem hiding this comment.
Yup definitely yes :)
package.json
Outdated
| "dependencies": { | ||
| "broccoli-funnel": "^1.0.0", | ||
| "broccoli-concat": "^3.2.2", | ||
| "broccoli-funnel": "^1.1.0", |
| "ember-cli-babel": "^5.1.7", | ||
| "ember-cli-eslint": "^3.0.2", | ||
| "ember-cli-version-checker": "^1.3.1", | ||
| "exists-sync": "0.0.4", |
There was a problem hiding this comment.
I think we should just use fs.existsSync as it is provided by the STDLIB, although fs.exists is deprecated the sync variant is not.
There was a problem hiding this comment.
I thought fs.existsSync is deprecated. The readme of exists-sync says this: https://github.com/ember-cli/exists-sync#exists-sync
There was a problem hiding this comment.
fs.existsSync was deprecated along with fs.exists a long way back in nodejs/node@7b5ffa4, but then fs.existsSync was undeprecated in nodejs/node@7b5ffa4.
There was a problem hiding this comment.
Aah didn't know that. Will use fs.existsSync and update readme for exists-sync too.
|
|
||
| describe('head content acceptance', function() { | ||
| // TODO: Unskip this test once ember-cli-head is released with new fastboot build spec. | ||
| describe.skip('head content acceptance', function() { |
There was a problem hiding this comment.
|
Left some thoughts, going to test it out manually next :) |
|
@stefanpenner Thank you so much for taking time to review a very very long PR :) Really appreciate it. I'll address the comments this weekend! |
NP! Excited to see progress on this front!
Some of them are intended more as discussion (don't assume I'm right) |
Yup I meant I'll reply back this weekend :) |
|
Local testing is looking good, rebuild times feel great. Everyone seems to work. I was a tad surprised that |
stefanpenner
left a comment
There was a problem hiding this comment.
LGTM, IMHO lets ![]()
@tomdale c/d?
|
Anything I can help with to push this over the edge - we're keen on using it in our fastboot migration |
We have a meeting tomorrow afternoon/evening to get this shipped. If delegatable tasks fall out, we will open them as issues :) Best you can do to help us today, is test out this PR and report issues. |
|
I tried out this branch and it's working great! The one problem I've encountered is handling In addition to converting my vendor libs over, I tried getting Before this PR, the In working with |
|
Thanks @scottmessinger for your feedback. I appreciate it.
You could do this using
I agree with your thoughts on this. Did you read the build spec here ? There is an unresolved item about
I understand that but the new build schema is different than the current one. The idea is instead of forking assets for different environments you want to load the browser assets and additional fastboot assets in fastboot environment. So you will need to load the fastboot assets in a different way. An app that is being server side rendered will nneed to have knowledge of fastboot manifest. I don't think that is wrong assumption.
The gist is very much in early draft stages. It was meant to iron out the different usecases where we need a migration path forward. As mentioned in #360 there needs to be a migration guide to help addon owners. |
|
@kratiahuja Thanks for the response! I'm excited you're open to letting
While I do think it's completely reasonable an app/addon to have knowledge of fastboot, I don't follow why an app/addon needs an understanding of the fastboot manifest. When "you want to load the browser assets and additional fastboot assets in fastboot environment", I think you'd only need a higher level api (like Totally understand about the gist being an early draft -- I figured as much and really appreciate that you wrote it up. |
I believe this already exists, or at-least the machinery to make it work. app.import(thing, { outputFile: 'fastboot/some-file-only-required-in-input.js' }But this discussion should likely find a new issue / rfc or something. |
|
Awesome job @kratiahuja! |
|
We should cut a beta today, then ask for community help in upgrading existing add-ons. Burn down issue for addons that need <3 #387 |
TL;DR: This unblocks FastBoot 1.0 release with a breaking change. Resolves most of the issues mentioned in #360, #292, #264, #246 .
Sending out a PR since substantial work is done that it can be reviewed for directional purposes.
NOTE: I took a slightly different approach in how the fastboot initializers will be written per the spec
Fixes in this PR currently are
treeForPublichook per the new build spec. It will works as follows:fastboot-apptree from the project addons and create a funnel for every addon tree with destDir asappName-fastbootfastboot-apptree from the root app and create a funnel for the app fastboot tree with destDir asappName-fastbootember-cli-preprocess-registry/preprocessorsAPI. Use thepreprocessJsmethod from the preprocessors with providing the registryoutputFileset toassets/appName-fastboot.jstreeForPublicfastboot-apptree in app is a watched directory tree. Most likely it will be otherwise followtreeForGeneratordoes hereFastBootConfigbroccoli plugin to addassets/appName-fastboot.jsinappFilesmanifest list of package.json. The corresponding code is hereember-cli-fastbootper the new specember-clipatching fromember-cli-fastboot. See example herefastboot-filter-initializersinember-cli-fastboot. See relevant example changes hereconfigfunction fromember-cli-fastboot/index.jsapp-bootcontentFor correctly. See example hereconfig-modulecontentFor hook to not rely on__is_building_fastboot__flag. See example fix herepreconcatfunction as it will be unused in new build specpostprocessTreehook, we should invokeFastBootConfigbroccoli plugin directly instead of invokingFastBootBuildbroccoli plugin. See example changes hereFastBootBuildbroccoli plugin as it will be unusedfastboot-filter-initializersorapp/(instance-)?initializers/fastbootapp/[instance-]initializers/browser/*.jsapp-lt-2.9.0) -->treeForFastBootprocess.env.FASTBOOT_NEW_BUILDso that addons likeember-cli-headcan support both versions.TODO (In subsquent PRs):
ember-cli-headonce that addon embraces new format.index.jsapp/[instance-]initializers/[browser|fastboot]/*.jsfolder structure (owner: @simonihmig since he has already started this work)cc: @rwjblue @tomdale @danmcclain @ryanone @simonihmig @arjansingh @stefanpenner