-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
Enable Embroider/Webpack code spliting for Wizard #23678
Conversation
Lint failure will be fixed by ember-template-lint/ember-template-lint#2982, or we can fix it in our own config |
Yaaaaas! Excitement! |
|
||
export default { | ||
"styling-preview": StylingPreview, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is the only thing left, we may want to consider getting rid of the type: "component"
infrastructure and just do type: "styling-preview"
?
@@ -8,7 +8,7 @@ | |||
</canvas> | |||
</div> | |||
<div class="wizard-container__preview homepage-preview"> | |||
<HomepagePreview @wizard={{this.wizard}} @step={{this.step}} /> | |||
<this.HomepagePreview @wizard={{this.wizard}} @step={{this.step}} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davidtaylorhq this is what I mentioned on the call
@@ -19,15 +20,20 @@ export default WizardPreviewBaseComponent.extend({ | |||
draggingActive: false, | |||
startX: 0, | |||
scrollLeft: 0, | |||
HomepagePreview, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davidtaylorhq counterpart of the above
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose this could just use the <this.component>
syntax too
|
||
get fieldClass() { | ||
return `field-${dasherize(this.field.id)} wizard-focusable`; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is is interesting. It seems like the fields are expected to put the @fieldClass
on their element, but very few actually do, and this is almost dead code. And those that do use it, does not put it on an actual "focusable" DOM element. This class is in turns used by the autoFocus
code in the wizard. So, basically the "autofocus" code doesn't really do anything right now. But I kept it around (not) working exactly as it was before to let someone else tackle that after.
|
||
$(".wizard-focusable:nth-of-type(1)").focus(); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above: this doesn't really do anything.
Jump In: on the "ready" page, it exits the setup ("finish"), on the | ||
last page, it saves, and if successful, go to the home page | ||
|
||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a considerable improvement over the status quo, in that the only step that we sort of hardcode is "ready". It will work just fine if that step isn't present, but if it does, it splits the wizard flow into the "required" and "optional" steps and displays the buttons correctly, without hard coding in "styling", "branding", etc. And the final step is now only "special" by way of being last, rather than hard coding "corporate".
} finally { | ||
this.saving = false; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't exactly do what you would imagine (but is ported over exactly as-is). You would think the try/catch is there to guard the await this.step.save()
step failing, but that step actually never fails, because it has a catch
that never re-throws the error.
What actually happens is that if an error happens in this.step.save()
, it will fail to return a response value (response === undefined
), and the code in @goNext
is guarded with response?.blah
such that it doesn't actually do anything if response is undefined.
I don't think that is how anyone is envisioning things to work 😬
Probably should make step.save
re-throw, so that we never call this.args.goNext
when the save fails. But I left it for someone else to tackle.
this.router.transitionTo("wizard.step", next); | ||
} else if (response?.success) { | ||
this.router.transitionTo("discovery.latest"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the counterpart of the comment above. If response
is undefined
, this doesn't end up doing anything
@@ -7,6 +7,7 @@ import { | |||
query, | |||
updateCurrentUser, | |||
} from "discourse/tests/helpers/qunit-helpers"; | |||
import "discourse/routes/wizard"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the wizard route is not guaranteed to be in the main bundle, tests that want to have it loaded needs to do this.
@@ -20,24 +20,47 @@ export default function (helpers) { | |||
description: "Your name", | |||
}, | |||
], | |||
next: "styling", | |||
next: "hello-again", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beefed up the test to have all five possibilities in the table
@@ -1,31 +1,31 @@ | |||
import Route from "@ember/routing/route"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this file lives in the normal location (app/routes/wizard.js
). With splitAtRoute: ["wizard"]
, app/routes/wizard
, app/routes/wizard/*
, app/controllers/wizard
, app/controllers/wizard/*
, app/templates/wizard
, app/templates/wizard/*
all get split off into the lazy bundle. In an earlier iteration, I experimented with putting all of these files primarily in app/static/wizard
, and added a stub to re-export them in the normal location, but that ended up being a lot of extra boilerplate IMO.
|
||
get component() { | ||
let id = dasherize(this.args.field.id); | ||
assert(`"${id}" is not a valid wizard component`, id in components); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could supplement this with a resolver lookup as a fallback if necessary
} catch (error) { | ||
for (let err of error.jqXHR.responseJSON.errors) { | ||
this.fieldError(err.field, err.description); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where it doesn't re-throw (or throw a new error)
<div class="discourse-logo"> | ||
<DiscourseLogo /> | ||
</div> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the canvas into the step, which works just as well (since it's position: fixed
anyway), but avoids having having access to the child route's step here somehow, which was a bit awkward
<template> | ||
{{#if this.showCanvas}} | ||
<WizardCanvas /> | ||
{{/if}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may even want to move this into WizardStep itself. It probably wan't possibly previously because the wrapping div was used for styling, but since I migrated it to glimmer component it should be no problem now
@include breakpoint("mobile-extra-large") { | ||
order: 1; | ||
margin-left: 0; | ||
} | ||
&:hover { | ||
background-color: var(--primary-300); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't actually think this is intentional, but I kept it here.
Previously, the "Jump In" button did not have the .primary
CSS class, but it very much is and wants to be the primary action, and duplicated a bunch of styles from .primary
. Now, I just added the primary class to the "Jump In" button and removed the redundant rules. This is the only difference left and it makes the hover shade a bit different than the "Next" button. To my eyes it looked off and I think we probably just want to remove this, but I left it as is for now.
2bb3bbb
to
dbe71dd
Compare
This is extracted from discourse#23678, look there for a longer description
This is extracted from discourse#23678, look there for a longer description
This is extracted from discourse#23678, look there for a longer description
dbe71dd
to
e92eb03
Compare
0a062a7
to
0f55e63
Compare
This is extracted from #23678, look there for a longer description
+ native classes + tracked properties - Ember.Object - Ember.Evented - observers - mixins - computed/discourseComputed Also removes unused wizard infrastructure for warnings. It appears that once upon on time, either the server can generate warnings, or some client code can generate them, which requires an extra confirmation from the user before they can continue to the next step. This code is not tested and appears unused and defunct. Nothing generates such warning and the server does not serialize them. Extracted from #23678
This code has gone through a lot of refactors and repurposing, and things have become more complicated and confusing than they needed to be. At present, the Wizard goes through a few "required" steps for the basics, then shows a "ready" screen directing the user to "jump in" but have an option to continue on for optional screens, eventually ending on the "corporate" screen which is the final step. On desktop, there are two groups of buttons – 1. on the left side, it shows a back button except for the first screen 2. on the right side, there is a always a primary button (either "next" or "jump in"), and then to its left, there is sometimes a secondary action styled as a link (either "configure more" or "exit setup") On mobile, these buttons are stacked, with the back button sitting at the bottom, if present. In table form: Step Back Button? Primary Action Secondary Action ------------------------------------------------------------------ First No Next N/A ------------------------------------------------------------------ ... Yes Next N/A ------------------------------------------------------------------ Ready Yes Jump In Configure More ------------------------------------------------------------------ ... Yes Next Exit Setup ------------------------------------------------------------------ Last Yes Jump In N/A ------------------------------------------------------------------ Back Button: without saving, go back to the last page Next Button: save, and if successful, go to the next page Configure More: re-skinned next button Exit Setup: without saving, go to the home page ("finish") Jump In: on the "ready" page, it exits the setup ("finish"), on the last page, it saves, and if successful, go to the home page This commit re-aligns the code to reflect this reality without the unnecessary indirections leftover from previous refactors.
Move field components into subfolders, remove dependency on string resolution with the runtime resolver.
0f55e63
to
60cc6b0
Compare
* Move Wizard back into main app, remove Wizard addon * Remove Wizard-related resolver or build hacks * Add a `app/static` folder in the app, add it to `staticAppPaths` * Install and enable `@embroider/router` * Add "wizard" to `splitAtRoutes` In a fully optimized Embroider app, route-based code splitting more or less Just Work™ – install `@embroider/router`, subclass from it, configure which routes you want to split and that's about it. However, our app is not "fully optimized", by which I mean we are not able to turn on all the `static*` flags. In Embroider, "static" means "statically analyzable". Specifically it means that all inter-dependencies between modules (files) are explicitly expressed as `import`s, as opposed to `{{i18n ...}}` magically means "look for the default export in app/helpers/i18n.js" or something even more dynamic with the resolver. Without turning on those flags, Embroider behaves conservatively, slurps up all `app` files eagerly into the primary bundle/chunks. So, while you _could_ turn on route-based code splitting, there won't be much to split. The commits leading up to this involves a bunch of refactors and cleanups that 1) works perfectly fine in the classic build, 2) are good and useful in their own right, but also 3) re-arranged thigns such that most dependencies are now explicit. With those in place, I was able to move all the wizard code into the "app/static" folder. Embrodier does not eagerly pull things from this folder into any bundle, unless something explicitly "asks" for them via `imports`. Conversely, thigns from this folder are not registered with the resolver and are not added to the `loader.js` registry. In conjunction with route-based code splitting, we now have the ability to split out islands of on-demand functionalities from the main app bundle. When you split a route in Embroider, it automatically creates a bundle/entrypoint with the relevant routes/templates/controllers matching that route prefix. Anything they import will be added to the bundle as well, assuming they are not alreay in the main app bundle, which is where the "app/static" folder comes into play. The "app/static" folder name is not special. It is configured in ember-cli-build.js. Alternatively, we could have left everything in their normal locations, and add more fine-grained paths to the `staticAppPaths` array. I just thought it would be easy to manage and scale, and less error-prone to do it this way. Note that putting things in `app/static` does not guarentee that it would not be part of the main app bundle. For example, if we were to add an `import ... from "app/static/wizard/...";` in a main bundle file (say, `app.js`), then that chunk of the module graph would be pulled in. (Consider using `await import(...)`?) Overtime, we can build better tooling (e.g. lint rules and babel macros to make things less repetitive) as we expand the use of this pattern, but this is a start.
60cc6b0
to
42ef186
Compare
Extracted from #23678 Co-authored-by: Godfrey Chan <godfreykfc@gmail.com>
Extracted from #23678 Co-authored-by: Godfrey Chan <godfreykfc@gmail.com>
This commit refactors the Wizard component code in preparation for moving it to the 'static' directory for Embroider route-splitting. It also includes a number of general improvements and simplifications. Extracted from #23678 Co-authored-by: Godfrey Chan <godfreykfc@gmail.com>
(extracted from #23678) * Move Wizard back into main app, remove Wizard addon * Remove Wizard-related resolver or build hacks * Install and enable `@embroider/router` * Add "wizard" to `splitAtRoutes` In a fully optimized Embroider app, route-based code splitting more or less Just Work™ – install `@embroider/router`, subclass from it, configure which routes you want to split and that's about it. However, our app is not "fully optimized", by which I mean we are not able to turn on all the `static*` flags. In Embroider, "static" means "statically analyzable". Specifically it means that all inter-dependencies between modules (files) are explicitly expressed as `import`s, as opposed to `{{i18n ...}}` magically means "look for the default export in app/helpers/i18n.js" or something even more dynamic with the resolver. Without turning on those flags, Embroider behaves conservatively, slurps up all `app` files eagerly into the primary bundle/chunks. So, while you _could_ turn on route-based code splitting, there won't be much to split. The commits leading up to this involves a bunch of refactors and cleanups that 1) works perfectly fine in the classic build, 2) are good and useful in their own right, but also 3) re-arranged things such that most dependencies are now explicit. With those in place, I was able to move all the wizard code into the "app/static" folder. Embroider does not eagerly pull things from this folder into any bundle, unless something explicitly "asks" for them via `imports`. Conversely, things from this folder are not registered with the resolver and are not added to the `loader.js` registry. In conjunction with route-based code splitting, we now have the ability to split out islands of on-demand functionalities from the main app bundle. When you split a route in Embroider, it automatically creates a bundle/entrypoint with the relevant routes/templates/controllers matching that route prefix. Anything they import will be added to the bundle as well, assuming they are not already in the main app bundle, which is where the "app/static" folder comes into play. The "app/static" folder name is not special. It is configured in ember-cli-build.js. Alternatively, we could have left everything in their normal locations, and add more fine-grained paths to the `staticAppPaths` array. I just thought it would be easy to manage and scale, and less error-prone to do it this way. Note that putting things in `app/static` does not guarantee that it would not be part of the main app bundle. For example, if we were to add an `import ... from "app/static/wizard/...";` in a main bundle file (say, `app.js`), then that chunk of the module graph would be pulled in. (Consider using `await import(...)`?) Overtime, we can build better tooling (e.g. lint rules and babel macros to make things less repetitive) as we expand the use of this pattern, but this is a start. Co-authored-by: Godfrey Chan <godfreykfc@gmail.com>
Final part merged via cbc28e8 |
This PR includes a lot of refactors and cleanups that can stand on their own/works in the current build. For your reviewing pleasure, you can view these chunks separately:
I do recommend reviewing the commits individually, since the move/rename to gjs lost track of some of the diff
To verify, you can run this branch locally, go to any non-wizard route, inspect the loaded chunks to see that none of the wizard code is loaded (other than the stub for the route entrypoint), and then navigate to
/wizard
to see that it loaded an additional chunk with all the wizard code in there.app/static
folder in the app, add it tostaticAppPaths
@embroider/router
splitAtRoutes
In a fully optimized Embroider app, route-based code splitting more or less Just Work™ – install
@embroider/router
, subclass from it, configure which routes you want to split and that's about it.However, our app is not "fully optimized", by which I mean we are not able to turn on all the
static*
flags.In Embroider, "static" means "statically analyzable". Specifically it means that all inter-dependencies between modules (files) are explicitly expressed as
import
s, as opposed to{{i18n ...}}
magically means "look for the default export in app/helpers/i18n.js" or something even more dynamic with the resolver.Without turning on those flags, Embroider behaves conservatively, slurps up all
app
files eagerly into the primary bundle/chunks. So, while you could turn on route-based code splitting, there won't be much to split.The commits leading up to this involves a bunch of refactors and cleanups that 1) works perfectly fine in the classic build, 2) are good and useful in their own right, but also 3) re-arranged things such that most dependencies are now explicit.
With those in place, I was able to move all the wizard code into the "app/static" folder. Embrodier does not eagerly pull things from this folder into any bundle, unless something explicitly "asks" for them via
imports
. Conversely, thigns from this folder are not registered with the resolver and are not added to theloader.js
registry.In conjunction with route-based code splitting, we now have the ability to split out islands of on-demand functionalities from the main app bundle.
When you split a route in Embroider, it automatically creates a bundle/entrypoint with the relevant routes/templates/controllers matching that route prefix. Anything they import will be added to the bundle as well, assuming they are not already in the main app bundle, which is where the "app/static" folder comes into play.
The "app/static" folder name is not special. It is configured in ember-cli-build.js. Alternatively, we could have left everything in their normal locations, and add more fine-grained paths to the
staticAppPaths
array. I just thought it would be easy to manage and scale, and less error-prone to do it this way.Note that putting things in
app/static
does not guarantee that it would not be part of the main app bundle. For example, if we were to add animport ... from "app/static/wizard/...";
in a main bundle file (say,app.js
), then that chunk of the module graph would be pulled in. (Consider usingawait import(...)
?)Overtime, we can build better tooling (e.g. lint rules and babel macros to make things less repetitive) as we expand the use of this pattern, but this is a start.