Skip to content

feat: add dynamic imports for .json assets #2740

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

Merged
merged 42 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
1b3b3d4
create dynamic imports for i18n
pskelin Jan 22, 2021
0307939
remove asset registry mapping function
pskelin Jan 26, 2021
a2a2565
additional loaders can override specific locales that they handle
pskelin Jan 26, 2021
e892fdd
add .properties support, change registerLoader signature
pskelin Jan 28, 2021
571dfbf
remove icons package assets imports
pskelin Jan 28, 2021
c6a4c5b
improve warning readability
pskelin Jan 28, 2021
0190a21
fix locale ids for static assets
pskelin Jan 28, 2021
56c5850
lefovers
pskelin Jan 28, 2021
5f3e59f
add dynamic imports for icons
pskelin Jan 29, 2021
f191f93
add dynamic assets for fiori package
pskelin Jan 29, 2021
696691e
dynamic imports for theming
pskelin Jan 29, 2021
74682fd
switch locale data with static imports to loader API
pskelin Feb 1, 2021
08a5948
add dynamic imports for cldr
pskelin Feb 1, 2021
2e6d284
unify registerLoader APIs
pskelin Feb 2, 2021
941b572
new api for .properties example
pskelin Feb 4, 2021
14a195d
unify registerLoader APIs
pskelin Feb 4, 2021
2148207
switch module naming to dynamic default, static suffix
pskelin Feb 8, 2021
b3a36e7
remove theme load log from bundle
pskelin Feb 8, 2021
218192e
meaningful error when dynamic imports use URL plugin
pskelin Feb 8, 2021
b523052
meaningful dynamic import error messages
pskelin Feb 8, 2021
1cc4806
add default cldr loader with CDN fetch
pskelin Feb 9, 2021
fe2edc9
remove feature PropertiesFormatSupport
pskelin Feb 9, 2021
770b8a9
depracate registerIconBundle
pskelin Feb 9, 2021
8080846
Merge branch 'master' into wip-dynamic-assets
pskelin Feb 9, 2021
ff72058
return lint and fix errors
pskelin Feb 9, 2021
5da646b
remove EffectiveAssetPath
pskelin Feb 9, 2021
dbf7baf
fix cldr and i18n imports
pskelin Feb 9, 2021
afc17cc
fix url plugin generating empty bundle
pskelin Feb 9, 2021
3f7427c
make icons package i18n aware
pskelin Feb 9, 2021
36c2edd
fiori dynamic imports
pskelin Feb 9, 2021
bc35042
register english CLDR as default only
pskelin Feb 10, 2021
c957467
switch inline theme registration to async and deprecate sync
pskelin Feb 10, 2021
f5ce462
remove internal deprecated usage
pskelin Feb 10, 2021
3cc3cbf
remove async in function without await
pskelin Feb 10, 2021
7f3eb8f
use public imports for bundle
pskelin Feb 10, 2021
23d864b
remove deprecated method documentation pointing to removed modules
pskelin Feb 10, 2021
e80898e
add openui5 cldr reuse, simplify code
pskelin Feb 10, 2021
55f3eca
extract parseProperties util to public module
pskelin Feb 10, 2021
1c6bdfd
add missing return for i18n imports
pskelin Feb 11, 2021
e4acae3
remove named exports from JSON
pskelin Feb 11, 2021
fe0bba2
rework icon registry for lazy fetch
pskelin Feb 11, 2021
203ea17
Merge branch 'master' into wip-dynamic-assets
pskelin Feb 12, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions packages/base/bundle.esm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { registerThemeProperties } from "./dist/AssetRegistry.js";
import { registerThemePropertiesLoader } from "./dist/AssetRegistry.js";

// ESM bundle targets browsers with native support
import "./dist/features/OpenUI5Support.js";
Expand All @@ -20,11 +20,10 @@ import { isIE } from "./dist/Device.js";
window.isIE = isIE; // attached to the window object for testing purposes

// used for tests - to register a custom theme
window.registerThemeProperties = registerThemeProperties;
window.registerThemePropertiesLoader = registerThemePropertiesLoader;

// i18n
import "./dist/features/PropertiesFormatSupport.js";
import { registerI18nBundle, fetchI18nBundle, getI18nBundle } from "./dist/i18nBundle.js";
import { registerI18nLoader, fetchI18nBundle, getI18nBundle } from "./dist/i18nBundle.js";

// Note: keep in sync with rollup.config value for IIFE
import { getAnimationMode } from "./dist/config/AnimationMode.js";
Expand All @@ -48,7 +47,7 @@ window["sap-ui-webcomponents-bundle"] = {
getFirstDayOfWeek,
},
getIconNames,
registerI18nBundle,
registerI18nLoader,
fetchI18nBundle,
getI18nBundle,
renderFinished,
Expand Down
17 changes: 8 additions & 9 deletions packages/base/src/AssetRegistry.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { registerI18nBundle } from "./asset-registries/i18n.js";
import { registerCldr, _registerMappingFunction as registerCldrMappingFunction } from "./asset-registries/LocaleData.js";
import { registerThemeProperties } from "./asset-registries/Themes.js";
import { registerAssetPathMappingFunction } from "./util/EffectiveAssetPath.js";
import { registerI18nLoader } from "./asset-registries/i18n.js";
import { registerLocaleDataLoader } from "./asset-registries/LocaleData.js";
import { registerThemePropertiesLoader } from "./asset-registries/Themes.js";
import { registerIconLoader } from "./asset-registries/Icons.js";
Copy link
Contributor

Choose a reason for hiding this comment

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

registerIconLoader -> registerIconsLoader to be consistent with the rest (Data, Properties) which are plural


export {
registerCldr,
registerCldrMappingFunction,
registerThemeProperties,
registerI18nBundle,
registerAssetPathMappingFunction,
registerI18nLoader,
registerLocaleDataLoader,
registerThemePropertiesLoader,
registerIconLoader,
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const mEscapes = {
"\\t": "\t",
};

/**
* Parses a .properties format
* @param {string} sText the contents a of a .properties file
* @returns a object with key/value pairs parsed from the .properties file format
* @public
*/
const parseProperties = sText => {
const properties = {},
aLines = sText.split(rLines);
Expand Down
21 changes: 14 additions & 7 deletions packages/base/src/asset-registries/Icons.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { registerIcon, registerCollectionPromise } from "../SVGIconRegistry.js";
import { fetchJsonOnce } from "../util/FetchHelper.js";
import { getEffectiveAssetPath } from "../util/EffectiveAssetPath.js";

/**
* @deprecated
*/
const registerIconBundle = async (collectionName, bundleData) => {
throw new Error("This method has been removed. Use `registerIconLoader` instead.");
};

const registerIconLoader = async (collectionName, loader) => {
let resolveFn;
const collectionFetched = new Promise(resolve => {
resolveFn = resolve;
});
registerCollectionPromise(collectionName, collectionFetched);

if (typeof bundleData !== "object") { // not inlined from build -> fetch it
bundleData = await fetchJsonOnce(getEffectiveAssetPath(bundleData));
try {
const iconData = await loader();
fillRegistry(iconData);
resolveFn();
} catch (e) {
console.error(e.message); /* eslint-disable-line */
}
fillRegistry(bundleData);
resolveFn();
};

const fillRegistry = bundleData => {
Expand All @@ -29,4 +36,4 @@ const fillRegistry = bundleData => {
});
};

export { registerIconBundle }; // eslint-disable-line
export { registerIconBundle, registerIconLoader }; // eslint-disable-line
108 changes: 47 additions & 61 deletions packages/base/src/asset-registries/LocaleData.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { fetchJsonOnce } from "../util/FetchHelper.js";
import { attachLanguageChange } from "../locale/languageChange.js";
import getLocale from "../locale/getLocale.js";
import { getFeature } from "../FeaturesRegistry.js";
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from "../generated/AssetParameters.js";
import { getEffectiveAssetPath } from "../util/EffectiveAssetPath.js";

const resources = new Map();
const cldrData = {};
const cldrUrls = {};
import { getFeature } from "../FeaturesRegistry.js";

// externally configurable mapping function for resolving (localeId -> URL)
// default implementation - ui5 CDN
let cldrMappingFn = locale => `https://ui5.sap.com/1.60.2/resources/sap/ui/core/cldr/${locale}.json`;
const localeDataMap = new Map();
const loaders = new Map();
const cldrPromises = new Map();
const reportedErrors = new Set();

const M_ISO639_OLD_TO_NEW = {
"iw": "he",
Expand Down Expand Up @@ -50,73 +45,67 @@ const calcLocale = (language, region, script) => {
return localeId;
};

// internal set data
const setLocaleData = (localeId, content) => {
localeDataMap.set(localeId, content);
};

const resolveMissingMappings = () => {
if (!cldrMappingFn) {
return;
// external getSync
const getLocaleData = localeId => {
const content = localeDataMap.get(localeId);
if (!content) {
throw new Error(`CLDR data for locale ${localeId} is not loaded!`);
}

const missingLocales = SUPPORTED_LOCALES.filter(locale => !cldrData[locale] && !cldrUrls[locale]);
missingLocales.forEach(locale => {
cldrUrls[locale] = cldrMappingFn(locale);
});
return content;
};

const registerModuleContent = (moduleName, content) => {
resources.set(moduleName, content);
};

const getModuleContent = moduleName => {
const moduleContent = resources.get(moduleName);
if (moduleContent) {
return moduleContent;
}
// load bundle over the network once
const _loadCldrOnce = localeId => {
const loadCldr = loaders.get(localeId);

const missingModule = moduleName.match(/sap\/ui\/core\/cldr\/(\w+)\.json/);
if (missingModule) {
throw new Error(`CLDR data for locale ${missingModule[1]} is not loaded!`);
if (!cldrPromises.get(localeId)) {
cldrPromises.set(localeId, loadCldr(localeId));
}

throw new Error(`Unknown module ${moduleName}`);
return cldrPromises.get(localeId);
};

// external getAsync
const fetchCldr = async (language, region, script) => {
resolveMissingMappings();
const localeId = calcLocale(language, region, script);

let cldrObj = cldrData[localeId];
const url = cldrUrls[localeId];

// reuse OpenUI5 CLDR if present
const OpenUI5Support = getFeature("OpenUI5Support");
if (!cldrObj && OpenUI5Support) {
cldrObj = OpenUI5Support.getLocaleDataObject();
if (OpenUI5Support) {
const cldrContent = OpenUI5Support.getLocaleDataObject();
if (cldrContent) {
// only if openui5 actually returned valid content
setLocaleData(localeId, cldrContent);
return;
}
}

if (cldrObj) {
// inlined from build or fetched independently
registerModuleContent(`sap/ui/core/cldr/${localeId}.json`, cldrObj);
} else if (url) {
// fetch it
const cldrContent = await fetchJsonOnce(getEffectiveAssetPath(url));
registerModuleContent(`sap/ui/core/cldr/${localeId}.json`, cldrContent);
// fetch it
try {
const cldrContent = await _loadCldrOnce(localeId);
setLocaleData(localeId, cldrContent);
} catch (e) {
if (!reportedErrors.has(e.message)) {
reportedErrors.add(e.message);
console.error(e.message); /* eslint-disable-line */
}
}
};

const registerCldr = (locale, url) => {
cldrUrls[locale] = url;
};

const setCldrData = (locale, data) => {
cldrData[locale] = data;
const registerLocaleDataLoader = (localeId, loader) => {
loaders.set(localeId, loader);
};

const getCldrData = locale => {
return cldrData[locale];
};

const _registerMappingFunction = mappingFn => {
cldrMappingFn = mappingFn;
};
// register default loader for "en" from ui5 CDN (dev workflow without assets)
registerLocaleDataLoader("en", async runtimeLocaleId => {
return (await fetch(`https://ui5.sap.com/1.60.2/resources/sap/ui/core/cldr/en.json`)).json();
});

// When the language changes dynamically (the user calls setLanguage),
// re-fetch the required CDRD data.
Expand All @@ -126,10 +115,7 @@ attachLanguageChange(() => {
});

export {
registerLocaleDataLoader,
fetchCldr,
registerCldr,
setCldrData,
getCldrData,
getModuleContent,
_registerMappingFunction,
getLocaleData,
};
56 changes: 23 additions & 33 deletions packages/base/src/asset-registries/Themes.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { fetchJsonOnce, fetchTextOnce } from "../util/FetchHelper.js";
import { DEFAULT_THEME } from "../generated/AssetParameters.js";
import getFileExtension from "../util/getFileExtension.js";
import { getEffectiveAssetPath } from "../util/EffectiveAssetPath.js";

const themeURLs = new Map();
const themeStyles = new Map();
const loaders = new Map();
const registeredPackages = new Set();
const registeredThemes = new Set();

Expand All @@ -16,29 +13,19 @@ const registeredThemes = new Set();
* Example usage:
* 1) Pass the CSS Vars as a string directly.
* registerThemeProperties("my-package", "my_theme", ":root{--var1: red;}");
* 2) Pass the CSS Vars as an object directly
* registerThemeProperties("my-package", "my_theme", {"_": ":root{--var1: red;}"});
* 3) Pass a URL to a CSS file, containing the CSS Vars. Will be fetched on demand, not upon registration.
* registerThemeProperties("my-package", "my_theme", "http://url/to/my/theme.css");
* 4) Pass a URL to a JSON file, containing the CSS Vars in its "_" property. Will be fetched on demand, not upon registration.
* registerThemeProperties("my-package", "my_theme", "http://url/to/my/theme.json");
*
* @public
* @param packageName - the NPM package for which CSS Vars are registered
* @param themeName - the theme which the CSS Vars implement
* @param style - can be one of four options: a string, an object with a "_" property, URL to a CSS file, or URL to a JSON file with a "_" property
* @param style - the style content directly
* @deprecated
*/
const registerThemeProperties = (packageName, themeName, style) => {
if (style._) {
// JSON object like ({"_": ":root"})
themeStyles.set(`${packageName}_${themeName}`, style._);
} else if (style.includes(":root") || style === "") {
// pure string, including empty string
themeStyles.set(`${packageName}_${themeName}`, style);
} else {
// url for fetching
themeURLs.set(`${packageName}_${themeName}`, style);
}
const registerThemeProperties = (_packageName, _themeName, _style) => {
throw new Error("`registerThemeProperties` has been depracated. Use `registerThemePropertiesLoader` instead.");
};

const registerThemePropertiesLoader = (packageName, themeName, loader) => {
loaders.set(`${packageName}/${themeName}`, loader);
registeredPackages.add(packageName);
registeredThemes.add(themeName);
};
Expand All @@ -55,23 +42,25 @@ const getThemeProperties = async (packageName, themeName) => {
return themeStyles.get(`${packageName}_${DEFAULT_THEME}`);
}

const data = await fetchThemeProperties(packageName, themeName);
const loader = loaders.get(`${packageName}/${themeName}`);
if (!loader) {
// no themes for package
console.error(`Theme [${themeName}] not registered for package [${pacakgeName}]`); /* eslint-disable-line */
return;
}
let data;
try {
data = await loader(themeName);
} catch (e) {
console.error(packageName, e.message); /* eslint-disable-line */
return;
}
const themeProps = data._ || data;

themeStyles.set(`${packageName}_${themeName}`, themeProps);
return themeProps;
};

const fetchThemeProperties = async (packageName, themeName) => {
const url = themeURLs.get(`${packageName}_${themeName}`);

if (!url) {
throw new Error(`You have to import the ${packageName}/dist/Assets.js module to switch to additional themes`);
}

return getFileExtension(url) === ".css" ? fetchTextOnce(url) : fetchJsonOnce(getEffectiveAssetPath(url));
};

const getRegisteredPackages = () => {
return registeredPackages;
};
Expand All @@ -81,6 +70,7 @@ const isThemeRegistered = theme => {
};

export {
registerThemePropertiesLoader,
registerThemeProperties,
getThemeProperties,
getRegisteredPackages,
Expand Down
Loading