mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-02 10:18:42 +00:00
[plugin] HMR lib hooks (#12108)
* Expose HMR plugin * Expose function for localizing a plugin component * Update npm version * better docs * Plugin provides i18n instance * Expose HMR plugin on different path * Bump version (again) * Ensure HMR plugin is properly built * Bump (again) * Specify callback function * Bump package version * Improved docstrings * Stricter type hinting
This commit is contained in:
@@ -153,3 +153,9 @@ export {
|
||||
useStoredTableState
|
||||
} from './states/StoredTableState';
|
||||
export { useLocalLibState } from './states/LocalLibState';
|
||||
|
||||
// Plugin development utilities and hooks
|
||||
export {
|
||||
default as LocalizedComponent,
|
||||
type LocaleLoader
|
||||
} from './plugin/LocalizedComponent';
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
/**
|
||||
* Vite plugin which enables hot module replacement (HMR) for InvenTree plugin development.
|
||||
*
|
||||
* This is for use with the InvenTree plugin creator tool,
|
||||
* allowing frontend plugin code to be "live reloaded" during development.
|
||||
*/
|
||||
export default function InventreeHmrPlugin(): Plugin {
|
||||
const fileRegex = /\.(js|jsx|ts|tsx)(\?|$)/;
|
||||
|
||||
const hmrBlock = [
|
||||
'',
|
||||
'// __inventree_hmr_injected__',
|
||||
'if (import.meta.hot) {',
|
||||
' import.meta.hot.accept((newModule) => {',
|
||||
' const key = new URL(import.meta.url).origin + new URL(import.meta.url).pathname;',
|
||||
' window.__plugin_hmr_callbacks?.[key]?.forEach(callback => {',
|
||||
' callback(newModule);',
|
||||
' });',
|
||||
' })',
|
||||
'}'
|
||||
];
|
||||
|
||||
return {
|
||||
name: 'inventree-hmr-plugin',
|
||||
enforce: 'post',
|
||||
|
||||
transform(code, id) {
|
||||
if (!fileRegex.test(id)) return;
|
||||
if (id.includes('node_modules')) return;
|
||||
if (code.includes('__inventree_hmr_injected__')) return;
|
||||
|
||||
return {
|
||||
code: code + hmrBlock.join('\n'),
|
||||
map: null
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import type { I18n } from '@lingui/core';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { Skeleton } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
/*
|
||||
* To dynamically load locale messages from a plugin context,
|
||||
* the plugin MUST supply a callback function which can be used to load the locale messages for the plugin.
|
||||
* This is because the plugin frontend code is built separately from the main frontend,
|
||||
* and so cannot directly import locale messages from the main frontend.
|
||||
*
|
||||
* Refer to the inventree-plugin-creator tool for an example of how to use this component in a plugin context.
|
||||
*/
|
||||
export type LocaleLoader = (locale: string) => Promise<any>;
|
||||
|
||||
async function tryLoadLocale(
|
||||
locale: string,
|
||||
loader: LocaleLoader
|
||||
): Promise<any> {
|
||||
try {
|
||||
return await loader(locale);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load locale ${locale}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param i18n - The i18n instance from the plugin context
|
||||
* @param locale - The current locale to load
|
||||
* @param loader - The callback function to load the locale messages for the plugin
|
||||
* @returns A React component which will load the locale messages and render the children once loaded
|
||||
*/
|
||||
async function loadPluginLocale(
|
||||
i18n: I18n,
|
||||
locale: string,
|
||||
loader: LocaleLoader
|
||||
) {
|
||||
let messages = null;
|
||||
|
||||
messages = await tryLoadLocale(locale, loader);
|
||||
|
||||
if (!messages && locale.includes('-')) {
|
||||
const fallbackLocale = locale.split('-')[0];
|
||||
console.debug(
|
||||
`Locale ${locale} not found, trying fallback locale ${fallbackLocale}`
|
||||
);
|
||||
messages = await tryLoadLocale(fallbackLocale, loader);
|
||||
}
|
||||
|
||||
if (!messages && locale.includes('_')) {
|
||||
const fallbackLocale = locale.split('_')[0];
|
||||
console.debug(
|
||||
`Locale ${locale} not found, trying fallback locale ${fallbackLocale}`
|
||||
);
|
||||
messages = await tryLoadLocale(fallbackLocale, loader);
|
||||
}
|
||||
|
||||
if (!messages && locale !== 'en') {
|
||||
console.debug(`Locale ${locale} not found, trying fallback locale en`);
|
||||
messages = await tryLoadLocale('en', loader);
|
||||
}
|
||||
|
||||
if (messages?.messages) {
|
||||
i18n.load(locale, messages.messages);
|
||||
i18n.activate(locale);
|
||||
} else {
|
||||
console.error(`Failed to load any locale for ${locale}`);
|
||||
}
|
||||
}
|
||||
|
||||
// A default locale loader which can be used if the plugin does not supply its own loader function
|
||||
// Note: This will return null, as the plugin is expected to supply its own loader function which can load the locale messages for the plugin
|
||||
const defaultLocaleLoader: LocaleLoader = async (_locale: string) => null;
|
||||
|
||||
/**
|
||||
* Wrapper function for a plugin-defined component which needs to support dynamic locale loading.
|
||||
*
|
||||
* This is primarily designed for usage by the InvenTree plugin creator tool
|
||||
*
|
||||
* @param i18n - The i18n instance from the plugin context
|
||||
* @param locale - The current locale to load
|
||||
* @param loadLocale - The callback function to load the locale messages for the plugin
|
||||
* @param children - The child components to render once the locale is loaded
|
||||
*/
|
||||
export default function LocalizedComponent({
|
||||
i18n,
|
||||
locale,
|
||||
loadLocale,
|
||||
children
|
||||
}: {
|
||||
i18n: I18n;
|
||||
locale: string;
|
||||
loadLocale?: LocaleLoader;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLoaded(false);
|
||||
loadPluginLocale(i18n, locale, loadLocale ?? defaultLocaleLoader).then(
|
||||
() => {
|
||||
setLoaded(true);
|
||||
}
|
||||
);
|
||||
}, [i18n, locale, loadLocale]);
|
||||
|
||||
return loaded ? (
|
||||
<I18nProvider i18n={i18n}>{children}</I18nProvider>
|
||||
) : (
|
||||
<Skeleton w='100%' animate />
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user