2
0
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:
Oliver
2026-06-07 23:56:26 +10:00
committed by GitHub
parent 7f1f2dbad2
commit 20309146aa
6 changed files with 193 additions and 3 deletions
+26
View File
@@ -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
+6
View File
@@ -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 />
);
}
+3 -2
View File
@@ -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",
+5 -1
View File
@@ -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']