From 5073ea47604bfe85ac48b3ab411c2e082ac0f104 Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Fri, 9 Feb 2024 17:49:08 +0100 Subject: [PATCH] feat: split up validate-messages per entrypoint for smarter bundling --- .changeset/swift-deers-taste.md | 22 ++ .../src/getLocalizedMessage.js | 111 +++++++++++ ...oadDefaultFeedbackMessagesNoSideEffects.js | 188 +----------------- .../src/per-entrypoint/README.md | 16 ++ .../loadComboboxMessagesNoSideEffects.js | 22 ++ .../loadFormCoreMessagesNoSideEffects.js | 77 +++++++ .../loadInputTelMessagesNoSideEffects.js | 39 ++++ .../validate-messages-no-side-effects.js | 3 + 8 files changed, 299 insertions(+), 179 deletions(-) create mode 100644 .changeset/swift-deers-taste.md create mode 100644 packages/ui/components/validate-messages/src/getLocalizedMessage.js create mode 100644 packages/ui/components/validate-messages/src/per-entrypoint/README.md create mode 100644 packages/ui/components/validate-messages/src/per-entrypoint/loadComboboxMessagesNoSideEffects.js create mode 100644 packages/ui/components/validate-messages/src/per-entrypoint/loadFormCoreMessagesNoSideEffects.js create mode 100644 packages/ui/components/validate-messages/src/per-entrypoint/loadInputTelMessagesNoSideEffects.js diff --git a/.changeset/swift-deers-taste.md b/.changeset/swift-deers-taste.md new file mode 100644 index 000000000..fa73e4e23 --- /dev/null +++ b/.changeset/swift-deers-taste.md @@ -0,0 +1,22 @@ +--- +'@lion/ui': patch +--- + +feat: split validate-messages-no-side-effects methods, so they can be bundled along with entrypoints. + +For optimized bundling, it's reccommended to load feedback messages per entrypoint. For instance, when you only use form-core in your app: + +```js +import { LionInputTel } from '@lion/ui/input-tel.js'; +import { getLocalizeManager } from '@lion/ui/localize-no-side-effects.js'; +import { loadInputTelMessagesNoSideEffects } from '@lion/ui/validate-messages-no-side-effects.js'; + +export class MyInputTel extends LionInputTel { + constructor() { + super(); + loadInputTelMessagesNoSideEffects({ localize: getLocalizeManager() }); + } +} +``` + +This prevents you from loading unused entrypoints like input-tel (which loads a full phone validation library) etc. diff --git a/packages/ui/components/validate-messages/src/getLocalizedMessage.js b/packages/ui/components/validate-messages/src/getLocalizedMessage.js new file mode 100644 index 000000000..e52de2150 --- /dev/null +++ b/packages/ui/components/validate-messages/src/getLocalizedMessage.js @@ -0,0 +1,111 @@ +/** + * @typedef {import('../../form-core/types/validate/validate.js').FeedbackMessageData} FeedbackMessageData + * @typedef {import('@lion/ui/localize.js').LocalizeManager} LocalizeManager + */ + +/** @type {Promise} */ +let pendingPromise; + +/** + * @param {{localize: LocalizeManager}} opts + */ +export async function loadValidateNamespace({ localize }) { + if (pendingPromise) { + return pendingPromise; + } + + pendingPromise = localize.loadNamespace( + { + 'lion-validate': /** @param {string} locale */ locale => { + switch (locale) { + case 'bg-BG': + return import('@lion/ui/validate-messages-translations/bg-BG.js'); + case 'bg': + return import('@lion/ui/validate-messages-translations/bg.js'); + case 'cs-CZ': + return import('@lion/ui/validate-messages-translations/cs-CZ.js'); + case 'cs': + return import('@lion/ui/validate-messages-translations/cs.js'); + case 'de-DE': + return import('@lion/ui/validate-messages-translations/de-DE.js'); + case 'de': + return import('@lion/ui/validate-messages-translations/de.js'); + case 'en-AU': + return import('@lion/ui/validate-messages-translations/en-AU.js'); + case 'en-GB': + return import('@lion/ui/validate-messages-translations/en-GB.js'); + case 'en-US': + return import('@lion/ui/validate-messages-translations/en-US.js'); + case 'en-PH': + return import('@lion/ui/validate-messages-translations/en-PH.js'); + case 'en': + return import('@lion/ui/validate-messages-translations/en.js'); + case 'es-ES': + return import('@lion/ui/validate-messages-translations/es-ES.js'); + case 'es': + return import('@lion/ui/validate-messages-translations/es.js'); + case 'fr-FR': + return import('@lion/ui/validate-messages-translations/fr-FR.js'); + case 'fr-BE': + return import('@lion/ui/validate-messages-translations/fr-BE.js'); + case 'fr': + return import('@lion/ui/validate-messages-translations/fr.js'); + case 'hu-HU': + return import('@lion/ui/validate-messages-translations/hu-HU.js'); + case 'hu': + return import('@lion/ui/validate-messages-translations/hu.js'); + case 'it-IT': + return import('@lion/ui/validate-messages-translations/it-IT.js'); + case 'it': + return import('@lion/ui/validate-messages-translations/it.js'); + case 'nl-BE': + return import('@lion/ui/validate-messages-translations/nl-BE.js'); + case 'nl-NL': + return import('@lion/ui/validate-messages-translations/nl-NL.js'); + case 'nl': + return import('@lion/ui/validate-messages-translations/nl.js'); + case 'pl-PL': + return import('@lion/ui/validate-messages-translations/pl-PL.js'); + case 'pl': + return import('@lion/ui/validate-messages-translations/pl.js'); + case 'ro-RO': + return import('@lion/ui/validate-messages-translations/ro-RO.js'); + case 'ro': + return import('@lion/ui/validate-messages-translations/ro.js'); + case 'ru-RU': + return import('@lion/ui/validate-messages-translations/ru-RU.js'); + case 'ru': + return import('@lion/ui/validate-messages-translations/ru.js'); + case 'sk-SK': + return import('@lion/ui/validate-messages-translations/sk-SK.js'); + case 'sk': + return import('@lion/ui/validate-messages-translations/sk.js'); + case 'uk-UA': + return import('@lion/ui/validate-messages-translations/uk-UA.js'); + case 'uk': + return import('@lion/ui/validate-messages-translations/uk.js'); + case 'zh-CN': + case 'zh': + return import('@lion/ui/validate-messages-translations/zh.js'); + default: + return import('@lion/ui/validate-messages-translations/en.js'); + } + }, + }, + { locale: localize.locale }, + ); + + return pendingPromise; +} + +/** + * @param {{data:FeedbackMessageData; localize: LocalizeManager}} opts + * @returns {Promise} + */ +export const getLocalizedMessage = async ({ data, localize }) => { + await loadValidateNamespace({ localize }); + if (data) { + return localize.msg(`lion-validate:${data.type}.${data.name}`, data); + } + return ''; +}; diff --git a/packages/ui/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.js b/packages/ui/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.js index 0662ef3d4..11f61649b 100644 --- a/packages/ui/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.js +++ b/packages/ui/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.js @@ -1,197 +1,27 @@ /* eslint-disable import/no-extraneous-dependencies */ -import { MatchesOption } from '@lion/ui/combobox.js'; -import { - DefaultSuccess, - EqualsLength, - IsDate, - IsDateDisabled, - IsEmail, - IsNumber, - MaxDate, - MaxLength, - MaxNumber, - MinDate, - MinLength, - MinMaxDate, - MinMaxLength, - MinMaxNumber, - MinNumber, - Pattern, - Required, -} from '@lion/ui/form-core.js'; -import { PhoneNumber } from '@lion/ui/input-tel.js'; +import { loadComboboxMessagesNoSideEffects } from './per-entrypoint/loadComboboxMessagesNoSideEffects.js'; +import { loadFormCoreMessagesNoSideEffects } from './per-entrypoint/loadFormCoreMessagesNoSideEffects.js'; +import { loadInputTelMessagesNoSideEffects } from './per-entrypoint/loadInputTelMessagesNoSideEffects.js'; /** * @typedef {import('../../form-core/types/validate/validate.js').FeedbackMessageData} FeedbackMessageData * @typedef {import('@lion/ui/localize.js').LocalizeManager} LocalizeManager */ -let loaded = false; +let isLoaded = false; /** * @param {{localize: LocalizeManager}} opts allow multiple lion/extension lib versions to provide their deduped instance of LocalizeManager * @returns */ export function loadDefaultFeedbackMessagesNoSideEffects({ localize }) { - if (loaded === true) { + if (isLoaded === true) { return; } - const forMessagesToBeReady = () => - localize.loadNamespace( - { - 'lion-validate': /** @param {string} locale */ locale => { - switch (locale) { - case 'bg-BG': - return import('@lion/ui/validate-messages-translations/bg-BG.js'); - case 'bg': - return import('@lion/ui/validate-messages-translations/bg.js'); - case 'cs-CZ': - return import('@lion/ui/validate-messages-translations/cs-CZ.js'); - case 'cs': - return import('@lion/ui/validate-messages-translations/cs.js'); - case 'de-DE': - return import('@lion/ui/validate-messages-translations/de-DE.js'); - case 'de': - return import('@lion/ui/validate-messages-translations/de.js'); - case 'en-AU': - return import('@lion/ui/validate-messages-translations/en-AU.js'); - case 'en-GB': - return import('@lion/ui/validate-messages-translations/en-GB.js'); - case 'en-US': - return import('@lion/ui/validate-messages-translations/en-US.js'); - case 'en-PH': - return import('@lion/ui/validate-messages-translations/en-PH.js'); - case 'en': - return import('@lion/ui/validate-messages-translations/en.js'); - case 'es-ES': - return import('@lion/ui/validate-messages-translations/es-ES.js'); - case 'es': - return import('@lion/ui/validate-messages-translations/es.js'); - case 'fr-FR': - return import('@lion/ui/validate-messages-translations/fr-FR.js'); - case 'fr-BE': - return import('@lion/ui/validate-messages-translations/fr-BE.js'); - case 'fr': - return import('@lion/ui/validate-messages-translations/fr.js'); - case 'hu-HU': - return import('@lion/ui/validate-messages-translations/hu-HU.js'); - case 'hu': - return import('@lion/ui/validate-messages-translations/hu.js'); - case 'it-IT': - return import('@lion/ui/validate-messages-translations/it-IT.js'); - case 'it': - return import('@lion/ui/validate-messages-translations/it.js'); - case 'nl-BE': - return import('@lion/ui/validate-messages-translations/nl-BE.js'); - case 'nl-NL': - return import('@lion/ui/validate-messages-translations/nl-NL.js'); - case 'nl': - return import('@lion/ui/validate-messages-translations/nl.js'); - case 'pl-PL': - return import('@lion/ui/validate-messages-translations/pl-PL.js'); - case 'pl': - return import('@lion/ui/validate-messages-translations/pl.js'); - case 'ro-RO': - return import('@lion/ui/validate-messages-translations/ro-RO.js'); - case 'ro': - return import('@lion/ui/validate-messages-translations/ro.js'); - case 'ru-RU': - return import('@lion/ui/validate-messages-translations/ru-RU.js'); - case 'ru': - return import('@lion/ui/validate-messages-translations/ru.js'); - case 'sk-SK': - return import('@lion/ui/validate-messages-translations/sk-SK.js'); - case 'sk': - return import('@lion/ui/validate-messages-translations/sk.js'); - case 'uk-UA': - return import('@lion/ui/validate-messages-translations/uk-UA.js'); - case 'uk': - return import('@lion/ui/validate-messages-translations/uk.js'); - case 'zh-CN': - case 'zh': - return import('@lion/ui/validate-messages-translations/zh.js'); - default: - return import('@lion/ui/validate-messages-translations/en.js'); - } - }, - }, - { locale: localize.locale }, - ); + loadComboboxMessagesNoSideEffects({ localize }); + loadFormCoreMessagesNoSideEffects({ localize }); + loadInputTelMessagesNoSideEffects({ localize }); - /** - * @param {FeedbackMessageData} data - * @returns {Promise} - */ - const getLocalizedMessage = async data => { - await forMessagesToBeReady(); - if (data) { - return localize.msg(`lion-validate:${data.type}.${data.name}`, data); - } - return ''; - }; - - /** @param {FeedbackMessageData} data */ - Required.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - EqualsLength.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MinLength.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MaxLength.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MinMaxLength.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - Pattern.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - IsEmail.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - IsNumber.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MinNumber.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MaxNumber.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MinMaxNumber.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - IsDate.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MinDate.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MaxDate.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MinMaxDate.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - IsDateDisabled.getMessage = async data => getLocalizedMessage(data); - /** @param {FeedbackMessageData} data */ - MatchesOption.getMessage = async data => getLocalizedMessage(data); - - DefaultSuccess.getMessage = async data => { - await forMessagesToBeReady(); - const randomKeys = localize.msg('lion-validate:success.RandomOk').split(','); - const key = randomKeys[Math.floor(Math.random() * randomKeys.length)].trim(); - return localize.msg(`lion-validate:${key}`, data); - }; - - /** @param {FeedbackMessageData} data */ - // @ts-ignore - PhoneNumber.getMessage = async data => { - await forMessagesToBeReady(); - const { type, outcome } = data; - if (outcome === 'too-long') { - // TODO: get max-length of country and use MaxLength validator - return localize.msg(`lion-validate:${type}.Pattern`, data); - } - if (outcome === 'too-short') { - // TODO: get min-length of country and use MinLength validator - return localize.msg(`lion-validate:${type}.Pattern`, data); - } - // TODO: add a more specific message here - if (outcome === 'invalid-country-code') { - return localize.msg(`lion-validate:${type}.Pattern`, data); - } - return localize.msg(`lion-validate:${type}.Pattern`, data); - }; - - loaded = true; + isLoaded = true; } diff --git a/packages/ui/components/validate-messages/src/per-entrypoint/README.md b/packages/ui/components/validate-messages/src/per-entrypoint/README.md new file mode 100644 index 000000000..dc041f9f7 --- /dev/null +++ b/packages/ui/components/validate-messages/src/per-entrypoint/README.md @@ -0,0 +1,16 @@ +For optimized bundling, it's reccommended to load feedback messages per entrypoint. For instance, when you only use form-core in your app: + +```js +import { LionInputTel } from '@lion/ui/input-tel.js'; +import { getLocalizeManager } from '@lion/ui/localize-no-side-effects.js'; +import { loadInputTelMessagesNoSideEffects } from '@lion/ui/validate-messages-no-side-effects.js'; + +export class MyInputTel extends LionInputTel { + constructor() { + super(); + loadInputTelMessagesNoSideEffects({ localize: getLocalizeManager() }); + } +} +``` + +This prevents you from loading unused entrypoints like input-tel (which loads a full phone validation library) etc. diff --git a/packages/ui/components/validate-messages/src/per-entrypoint/loadComboboxMessagesNoSideEffects.js b/packages/ui/components/validate-messages/src/per-entrypoint/loadComboboxMessagesNoSideEffects.js new file mode 100644 index 000000000..59b6013bc --- /dev/null +++ b/packages/ui/components/validate-messages/src/per-entrypoint/loadComboboxMessagesNoSideEffects.js @@ -0,0 +1,22 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { MatchesOption } from '@lion/ui/combobox.js'; +import { getLocalizedMessage } from '../getLocalizedMessage.js'; + +/** + * @typedef {import('../../../form-core/types/validate/validate.js').FeedbackMessageData} FeedbackMessageData + * @typedef {import('@lion/ui/localize.js').LocalizeManager} LocalizeManager + */ + +let isLoaded = false; + +/** + * @param {{localize: LocalizeManager}} opts allow multiple lion/extension lib versions to provide their deduped instance of LocalizeManager + */ +export function loadComboboxMessagesNoSideEffects({ localize }) { + if (isLoaded === true) return; + + /** @param {FeedbackMessageData} data */ + MatchesOption.getMessage = async data => getLocalizedMessage({ data, localize }); + + isLoaded = true; +} diff --git a/packages/ui/components/validate-messages/src/per-entrypoint/loadFormCoreMessagesNoSideEffects.js b/packages/ui/components/validate-messages/src/per-entrypoint/loadFormCoreMessagesNoSideEffects.js new file mode 100644 index 000000000..0936eeeae --- /dev/null +++ b/packages/ui/components/validate-messages/src/per-entrypoint/loadFormCoreMessagesNoSideEffects.js @@ -0,0 +1,77 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { + DefaultSuccess, + EqualsLength, + IsDate, + IsDateDisabled, + IsEmail, + IsNumber, + MaxDate, + MaxLength, + MaxNumber, + MinDate, + MinLength, + MinMaxDate, + MinMaxLength, + MinMaxNumber, + MinNumber, + Pattern, + Required, +} from '@lion/ui/form-core.js'; +import { getLocalizedMessage, loadValidateNamespace } from '../getLocalizedMessage.js'; + +/** + * @typedef {import('../../../form-core/types/validate/validate.js').FeedbackMessageData} FeedbackMessageData + * @typedef {import('@lion/ui/localize.js').LocalizeManager} LocalizeManager + */ + +let isLoaded = false; + +/** + * @param {{localize: LocalizeManager}} opts allow multiple lion/extension lib versions to provide their deduped instance of LocalizeManager + */ +export function loadFormCoreMessagesNoSideEffects({ localize }) { + if (isLoaded === true) return; + + /** @param {FeedbackMessageData} data */ + Required.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + EqualsLength.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + MinLength.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + MaxLength.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + MinMaxLength.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + Pattern.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + IsEmail.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + IsNumber.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + MinNumber.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + MaxNumber.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + MinMaxNumber.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + IsDate.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + MinDate.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + MaxDate.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + MinMaxDate.getMessage = async data => getLocalizedMessage({ data, localize }); + /** @param {FeedbackMessageData} data */ + IsDateDisabled.getMessage = async data => getLocalizedMessage({ data, localize }); + + DefaultSuccess.getMessage = async data => { + await loadValidateNamespace({ localize }); + const randomKeys = localize.msg('lion-validate:success.RandomOk').split(','); + const key = randomKeys[Math.floor(Math.random() * randomKeys.length)].trim(); + return localize.msg(`lion-validate:${key}`, data); + }; + + isLoaded = true; +} diff --git a/packages/ui/components/validate-messages/src/per-entrypoint/loadInputTelMessagesNoSideEffects.js b/packages/ui/components/validate-messages/src/per-entrypoint/loadInputTelMessagesNoSideEffects.js new file mode 100644 index 000000000..a8722d859 --- /dev/null +++ b/packages/ui/components/validate-messages/src/per-entrypoint/loadInputTelMessagesNoSideEffects.js @@ -0,0 +1,39 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { PhoneNumber } from '@lion/ui/input-tel.js'; +import { loadValidateNamespace } from '../getLocalizedMessage.js'; + +/** + * @typedef {import('../../../form-core/types/validate/validate.js').FeedbackMessageData} FeedbackMessageData + * @typedef {import('@lion/ui/localize.js').LocalizeManager} LocalizeManager + */ + +let isLoaded = false; + +/** + * @param {{localize: LocalizeManager}} opts allow multiple lion/extension lib versions to provide their deduped instance of LocalizeManager + */ +export function loadInputTelMessagesNoSideEffects({ localize }) { + if (isLoaded === true) return; + + /** @param {FeedbackMessageData} data */ + // @ts-ignore + PhoneNumber.getMessage = async data => { + await loadValidateNamespace({ localize }); + const { type, outcome } = data; + if (outcome === 'too-long') { + // TODO: get max-length of country and use MaxLength validator + return localize.msg(`lion-validate:${type}.Pattern`, data); + } + if (outcome === 'too-short') { + // TODO: get min-length of country and use MinLength validator + return localize.msg(`lion-validate:${type}.Pattern`, data); + } + // TODO: add a more specific message here + if (outcome === 'invalid-country-code') { + return localize.msg(`lion-validate:${type}.Pattern`, data); + } + return localize.msg(`lion-validate:${type}.Pattern`, data); + }; + + isLoaded = true; +} diff --git a/packages/ui/exports/validate-messages-no-side-effects.js b/packages/ui/exports/validate-messages-no-side-effects.js index 447b4986e..aa2045ecf 100644 --- a/packages/ui/exports/validate-messages-no-side-effects.js +++ b/packages/ui/exports/validate-messages-no-side-effects.js @@ -1 +1,4 @@ export { loadDefaultFeedbackMessagesNoSideEffects } from '../components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.js'; +export { loadComboboxMessagesNoSideEffects } from '../components/validate-messages/src/per-entrypoint/loadComboboxMessagesNoSideEffects.js'; +export { loadFormCoreMessagesNoSideEffects } from '../components/validate-messages/src/per-entrypoint/loadFormCoreMessagesNoSideEffects.js'; +export { loadInputTelMessagesNoSideEffects } from '../components/validate-messages/src/per-entrypoint/loadInputTelMessagesNoSideEffects.js';