Skip to content
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

Dynamically import locales #2047

Merged
merged 10 commits into from
Feb 19, 2024
Merged

Dynamically import locales #2047

merged 10 commits into from
Feb 19, 2024

Conversation

AmauryD
Copy link
Contributor

@AmauryD AmauryD commented Feb 12, 2024

No description provided.

@AmauryD
Copy link
Contributor Author

AmauryD commented Feb 12, 2024

@RobbieTheWagner
The only downside of the initializer is that it always import the core of the flatpickr library. Which is fine most of the time because the core is imported when we use the component in the app at least once.
My only concern is when route splitting is enabled. The library will probably always be imported even if not used in the route.
In this case i could create a @embroider/macro configuration flag that disable the initializer import when requested.

@@ -19,4 +19,16 @@ module.exports = function (defaults) {
const { maybeEmbroider } = require('@embroider/test-setup');

return maybeEmbroider(app);

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this commented out code?

@RobbieTheWagner
Copy link
Owner

@RobbieTheWagner The only downside of the initializer is that it always import the core of the flatpickr library. Which is fine most of the time because the core is imported when we use the component in the app at least once. My only concern is when route splitting is enabled. The library will probably always be imported even if not used in the route. In this case i could create a @embroider/macro configuration flag that disable the initializer import when requested.

I would love to find a way to make things work without needing to force flatpickr onto the window so early. I admit, I still don't understand why we need it on the window for locales to work. I would expect importing the locale and then passing what you imported in to work.

@AmauryD
Copy link
Contributor Author

AmauryD commented Feb 13, 2024

I would love to find a way to make things work without needing to force flatpickr onto the window so early. I admit, I still don't understand why we need it on the window for locales to work. I would expect importing the locale and then passing what you imported in to work.

Me too, it was working before because the lib was put in vendor.js that was loaded at the start of the app. https://github.com/RobbieTheWagner/ember-flatpickr/blob/93d73ca7d0f501c68702730b708f2f0eca321ed1/index.js.

For the explanation, i'll try to make an example repo for a clearer explanation of the problem. My english is sometimes not the best. And flatpickr has a weird initialization logic.

I'll do some research on my side.

@AmauryD
Copy link
Contributor Author

AmauryD commented Feb 13, 2024

I've made an example repo with console.logs to see the problem.
https://github.com/AmauryD/demo-flapickbug

image.

To be able to register a locale globaly, flatpickr must be registed before the locale file imported. Otherwise, the locale is never registered to the global window.flatpickr.l10ns. The problem is that ember calls the import 'flatpickr' after you import the locale. That moment is when the EmberFlapickr component is invoked. So the locale is never registered to the global object and can't be resolved using the string syntax.

I know it's a weird behavior but that's how flatpickr deals with locales.

Not sure if the explanation is better.

@AmauryD
Copy link
Contributor Author

AmauryD commented Feb 13, 2024

I added an option to disable this behavior. So it can be manually disabled and then tree-shaked if needed.
What do you think ?

* This file is only here to initialize the flatpickr registry at the start of the app
* This behavior can be disabled by setting `initializeFlatpickr` to `false` in the `@embroider/macros` config. The module will be then tree-shaken away if not used in the app.
*/
interface FlatpickrConfig {
initializeFlatpickr: boolean;
}
if (macroCondition(getOwnConfig<FlatpickrConfig>()?.initializeFlatpickr)) {
importSync('flatpickr');
}

Will need a glass of whiskey after this

@AmauryD
Copy link
Contributor Author

AmauryD commented Feb 13, 2024

The merge from main broke the tests. I will take a look tomorrow

@RobbieTheWagner
Copy link
Owner

To be able to register a locale globaly, flatpickr must be registed before the locale file imported

What if the ember-flatpickr component took locale="fr" for example and then we handled the logic inside the component to dynamically import the locale files? At that point, the component should have already been instantiated and the flatpickr reference should exist.

@AmauryD
Copy link
Contributor Author

AmauryD commented Feb 14, 2024

To be able to register a locale globaly, flatpickr must be registed before the locale file imported

What if the ember-flatpickr component took locale="fr" for example and then we handled the logic inside the component to dynamically import the locale files? At that point, the component should have already been instantiated and the flatpickr reference should exist.

That's a possibility, but i don't know if the tree shaking will work if you dynamically import the locale based on runtime variables. Will try this later.

@AmauryD
Copy link
Contributor Author

AmauryD commented Feb 14, 2024

It looks like if i do something like this:
(Just a test code, not the final result)


  async setupFlatpickr(element: HTMLInputElement): Promise<void> {
    const { date, onChange, wrap, locale } = this.args;

    // Require that users pass a date
    assert(
      '<EmberFlatpickr> requires a `date` to be passed as the value for flatpickr.',
      date !== undefined,
    );

    // Require that users pass an onChange
    assert(
      '<EmberFlatpickr> requires an `onChange` action or null for no action.',
      onChange !== undefined,
    );

    // Wrap is not supported
    assert(
      '<EmberFlatpickr> does not support the wrap option. Please see documentation for an alternative.',
      wrap !== true,
    );

    if (locale) {
      await import(`flatpickr/dist/l10n/${locale}`);
    }


    // Pass all values and setup flatpickr
    scheduleOnce('afterRender', this, this._setFlatpickrOptions, element);
  }

It works. Webpack generates a bundle for each lang. These are lazy-loaded later. Great ! These bundlers are very smart.

image

All the small red squares are a locale.

image

When i choose a lang that is not already loaded it calls the chunk.

@RobbieTheWagner
Copy link
Owner

Great, glad that dynamic importing seems to work! Let's do that then, and not add an initializer.

@AmauryD
Copy link
Contributor Author

AmauryD commented Feb 14, 2024

Just a side note, but i tried to upgrade eslint-plugin-ember and there are some deprecations concerning using @ember/runloop and 'afterRender' queue.

@RobbieTheWagner RobbieTheWagner changed the title Add initializer for window.flatpickr Dynamically import locales Feb 15, 2024
);

// Pass all values and setup flatpickr
scheduleOnce('afterRender', this, this._setFlatpickrOptions, element);
}

_setFlatpickrOptions(element: HTMLInputElement): void {
const fastboot = getOwner(this)?.lookup('service:fastboot') as unknown as (FastbootService | undefined);
@waitFor
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AmauryD can you please explain this waitFor here? I thought test waiters would be in test files or test helpers?

Copy link
Contributor Author

@AmauryD AmauryD Feb 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the change of _setFlatpickrOptions to async, it caused issues with one test. I needed to add a waitFor statement in the suite. However, for user convenience, I preferred to add @waitFor from @ember/test-waiters to the addon code and do not change the test suite, as there's a chance that consumers' test suites might also break.

What do you think ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AmauryD I don't think application code should be handling cases for testing. If anything, we should add some of the waiting to the test helpers we ship, but I don't think we want to add waitFor to the addon code. I could be wrong, but that addon sounds like it is for test helpers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with it.
Adding the waiting logic to the current test-support would be great. But i currently don't know how i would check that the operation of the flatpickr component is done without service level code or some trickery.

Copy link
Owner

@RobbieTheWagner RobbieTheWagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, thanks so much for taking this on!

@RobbieTheWagner RobbieTheWagner merged commit 0ba6725 into RobbieTheWagner:main Feb 19, 2024
10 checks passed
@AmauryD
Copy link
Contributor Author

AmauryD commented Feb 19, 2024

No problem, thank's for your support !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants