mirror of
https://github.com/inventree/InvenTree.git
synced 2026-06-11 19:27:02 +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:
@@ -2,6 +2,32 @@
|
||||
|
||||
This file contains historical changelog information for the InvenTree UI components library.
|
||||
|
||||
### 1.4.5 - June 2026
|
||||
|
||||
Fixes callback signature for `<LocalizedComponent>` to allow for an optional `loadLocale` function to be passed in, which is used to dynamically load locale messages for the plugin.
|
||||
|
||||
### 1.4.4 - June 2026
|
||||
|
||||
Fixes bundling issues associated with the `InventreeHmrPlugin` plugin function.
|
||||
|
||||
### 1.4.3 - June 2026
|
||||
|
||||
Expose the `InventreeHmrPlugin` on a different path (`@inventreedb/ui/vite`) to avoid vite bundling issues.
|
||||
|
||||
### 1.4.2 - June 2026
|
||||
|
||||
Fixes a bug in the `LocalizedComponent` function
|
||||
|
||||
### 1.4.1 - June 2026
|
||||
|
||||
### HMR Support
|
||||
|
||||
Adds support for React Fast Refresh in plugin development. This allows for a much smoother development experience when working on UI plugins, as changes to React components will now trigger a component-level update rather than a full page reload.
|
||||
|
||||
### Localized Components
|
||||
|
||||
Exposes a new `LocalizedComponent` function, which can be used to create React components that are automatically localized using the InvenTree server's localization system.
|
||||
|
||||
### 1.4.0 - May 2026
|
||||
|
||||
#### Version Numbering
|
||||
|
||||
@@ -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 />
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@inventreedb/ui",
|
||||
"description": "UI components for the InvenTree project",
|
||||
"version": "1.4.0",
|
||||
"version": "1.4.5",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -11,7 +11,8 @@
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
".": "./dist/index.js",
|
||||
"./vite": "./dist/plugin/InventreeHmrPlugin.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -52,7 +52,11 @@ export default defineConfig((cfg) =>
|
||||
},
|
||||
lib: {
|
||||
entry: {
|
||||
index: resolve(__dirname, 'lib/index.ts')
|
||||
index: resolve(__dirname, 'lib/index.ts'),
|
||||
'plugin/InventreeHmrPlugin': resolve(
|
||||
__dirname,
|
||||
'lib/plugin/InventreeHmrPlugin.tsx'
|
||||
)
|
||||
},
|
||||
name: 'InvenTree',
|
||||
formats: ['es']
|
||||
|
||||
Reference in New Issue
Block a user