mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-09 01:31:46 +00:00
feat: code splitting (#3593)
Co-authored-by: Angelos Bouklis <me@arjix.dev>
This commit is contained in:
@ -11,9 +11,9 @@ export function getPlugins() {
|
||||
return store.get('plugins') as Record<string, PluginConfig>;
|
||||
}
|
||||
|
||||
export function isEnabled(plugin: string) {
|
||||
export async function isEnabled(plugin: string) {
|
||||
const pluginConfig = deepmerge(
|
||||
allPlugins[plugin].config ?? { enabled: false },
|
||||
(await allPlugins())[plugin].config ?? { enabled: false },
|
||||
(store.get('plugins') as Record<string, PluginConfig>)[plugin] ?? {},
|
||||
);
|
||||
return pluginConfig !== undefined && pluginConfig.enabled;
|
||||
|
||||
@ -4,7 +4,7 @@ import { languageResources } from 'virtual:i18n';
|
||||
|
||||
export const loadI18n = async () =>
|
||||
await init({
|
||||
resources: languageResources,
|
||||
resources: await languageResources(),
|
||||
lng: 'en',
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
|
||||
38
src/index.ts
38
src/index.ts
@ -62,10 +62,10 @@ import { defaultAuthProxyConfig } from '@/plugins/auth-proxy-adapter/config';
|
||||
import type { PluginConfig } from '@/types/plugins';
|
||||
|
||||
if (!is.macOS()) {
|
||||
delete allPlugins['touchbar'];
|
||||
delete (await allPlugins())['touchbar'];
|
||||
}
|
||||
if (!is.windows()) {
|
||||
delete allPlugins['taskbar-mediacontrol'];
|
||||
delete (await allPlugins())['taskbar-mediacontrol'];
|
||||
}
|
||||
|
||||
// Catch errors and log them
|
||||
@ -139,13 +139,13 @@ if (is.linux()) {
|
||||
app.setName('com.github.th_ch.youtube_music');
|
||||
|
||||
// Stops chromium from launching its own MPRIS service
|
||||
if (config.plugins.isEnabled('shortcuts')) {
|
||||
if (await config.plugins.isEnabled('shortcuts')) {
|
||||
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
|
||||
}
|
||||
}
|
||||
|
||||
if (config.get('options.proxy')) {
|
||||
const authProxyEnabled = config.plugins.isEnabled('auth-proxy-adapter');
|
||||
const authProxyEnabled = await config.plugins.isEnabled('auth-proxy-adapter');
|
||||
|
||||
let proxyToUse = '';
|
||||
if (authProxyEnabled) {
|
||||
@ -183,19 +183,23 @@ function onClosed() {
|
||||
mainWindow = null;
|
||||
}
|
||||
|
||||
ipcMain.handle('ytmd:get-main-plugin-names', () => Object.keys(mainPlugins));
|
||||
ipcMain.handle('ytmd:get-main-plugin-names', async () =>
|
||||
Object.keys(await mainPlugins()),
|
||||
);
|
||||
|
||||
const initHook = async (win: BrowserWindow) => {
|
||||
const allPluginStubs = await allPlugins();
|
||||
|
||||
const initHook = (win: BrowserWindow) => {
|
||||
ipcMain.handle(
|
||||
'ytmd:get-config',
|
||||
(_, id: string) =>
|
||||
deepmerge(
|
||||
allPlugins[id].config ?? { enabled: false },
|
||||
allPluginStubs[id].config ?? { enabled: false },
|
||||
config.get(`plugins.${id}`) ?? {},
|
||||
) as PluginConfig,
|
||||
);
|
||||
ipcMain.handle('ytmd:set-config', (_, name: string, obj: object) =>
|
||||
config.setPartial(`plugins.${name}`, obj, allPlugins[name].config),
|
||||
config.setPartial(`plugins.${name}`, obj, allPluginStubs[name].config),
|
||||
);
|
||||
|
||||
config.watch((newValue, oldValue) => {
|
||||
@ -214,7 +218,7 @@ const initHook = (win: BrowserWindow) => {
|
||||
if (!isEqual) {
|
||||
const oldConfig = oldPluginConfigList[id] as PluginConfig;
|
||||
const config = deepmerge(
|
||||
allPlugins[id].config ?? { enabled: false },
|
||||
allPluginStubs[id].config ?? { enabled: false },
|
||||
newPluginConfig ?? {},
|
||||
) as PluginConfig;
|
||||
|
||||
@ -229,7 +233,7 @@ const initHook = (win: BrowserWindow) => {
|
||||
forceUnloadMainPlugin(id, win);
|
||||
}
|
||||
|
||||
if (allPlugins[id]?.restartNeeded) {
|
||||
if (allPluginStubs[id]?.restartNeeded) {
|
||||
showNeedToRestartDialog(id);
|
||||
}
|
||||
}
|
||||
@ -250,8 +254,8 @@ const initHook = (win: BrowserWindow) => {
|
||||
});
|
||||
};
|
||||
|
||||
const showNeedToRestartDialog = (id: string) => {
|
||||
const plugin = allPlugins[id];
|
||||
const showNeedToRestartDialog = async (id: string) => {
|
||||
const plugin = (await allPlugins())[id];
|
||||
|
||||
const dialogOptions: Electron.MessageBoxOptions = {
|
||||
type: 'info',
|
||||
@ -325,7 +329,7 @@ async function createMainWindow() {
|
||||
const windowSize = config.get('window-size');
|
||||
const windowMaximized = config.get('window-maximized');
|
||||
const windowPosition: Electron.Point = config.get('window-position');
|
||||
const useInlineMenu = config.plugins.isEnabled('in-app-menu');
|
||||
const useInlineMenu = await config.plugins.isEnabled('in-app-menu');
|
||||
|
||||
const defaultTitleBarOverlayOptions: Electron.TitleBarOverlay = {
|
||||
color: '#00000000',
|
||||
@ -369,7 +373,7 @@ async function createMainWindow() {
|
||||
},
|
||||
...decorations,
|
||||
});
|
||||
initHook(win);
|
||||
await initHook(win);
|
||||
initTheme(win);
|
||||
|
||||
await loadAllMainPlugins(win);
|
||||
@ -614,12 +618,12 @@ app.on('activate', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
const getDefaultLocale = (locale: string) =>
|
||||
Object.keys(languageResources).includes(locale) ? locale : null;
|
||||
const getDefaultLocale = async (locale: string) =>
|
||||
Object.keys(await languageResources()).includes(locale) ? locale : null;
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
if (!config.get('options.language')) {
|
||||
const locale = getDefaultLocale(app.getLocale());
|
||||
const locale = await getDefaultLocale(app.getLocale());
|
||||
if (locale) {
|
||||
config.set('options.language', locale);
|
||||
}
|
||||
|
||||
@ -20,13 +20,17 @@ const createContext = (
|
||||
id: string,
|
||||
win: BrowserWindow,
|
||||
): BackendContext<PluginConfig> => ({
|
||||
getConfig: () =>
|
||||
getConfig: async () =>
|
||||
deepmerge(
|
||||
allPlugins[id].config ?? { enabled: false },
|
||||
(await allPlugins())[id].config ?? { enabled: false },
|
||||
config.get(`plugins.${id}`) ?? {},
|
||||
) as PluginConfig,
|
||||
setConfig: (newConfig) => {
|
||||
config.setPartial(`plugins.${id}`, newConfig, allPlugins[id].config);
|
||||
setConfig: async (newConfig) => {
|
||||
config.setPartial(
|
||||
`plugins.${id}`,
|
||||
newConfig,
|
||||
(await allPlugins())[id].config,
|
||||
);
|
||||
},
|
||||
|
||||
ipc: {
|
||||
@ -96,7 +100,7 @@ export const forceLoadMainPlugin = async (
|
||||
id: string,
|
||||
win: BrowserWindow,
|
||||
): Promise<void> => {
|
||||
const plugin = mainPlugins[id];
|
||||
const plugin = (await mainPlugins())[id];
|
||||
if (!plugin) return;
|
||||
|
||||
try {
|
||||
@ -133,7 +137,7 @@ export const loadAllMainPlugins = async (win: BrowserWindow) => {
|
||||
const pluginConfigs = config.plugins.getPlugins();
|
||||
const queue: Promise<void>[] = [];
|
||||
|
||||
for (const [plugin, pluginDef] of Object.entries(mainPlugins)) {
|
||||
for (const [plugin, pluginDef] of Object.entries(await mainPlugins())) {
|
||||
const config = deepmerge(pluginDef.config, pluginConfigs[plugin] ?? {});
|
||||
if (config.enabled) {
|
||||
queue.push(forceLoadMainPlugin(plugin, win));
|
||||
|
||||
@ -17,19 +17,23 @@ const createContext = (
|
||||
id: string,
|
||||
win: BrowserWindow,
|
||||
): MenuContext<PluginConfig> => ({
|
||||
getConfig: () =>
|
||||
getConfig: async () =>
|
||||
deepmerge(
|
||||
allPlugins[id].config ?? { enabled: false },
|
||||
(await allPlugins())[id].config ?? { enabled: false },
|
||||
config.get(`plugins.${id}`) ?? {},
|
||||
) as PluginConfig,
|
||||
setConfig: (newConfig) => {
|
||||
config.setPartial(`plugins.${id}`, newConfig, allPlugins[id].config);
|
||||
setConfig: async (newConfig) => {
|
||||
config.setPartial(
|
||||
`plugins.${id}`,
|
||||
newConfig,
|
||||
(await allPlugins())[id].config,
|
||||
);
|
||||
},
|
||||
window: win,
|
||||
refresh: async () => {
|
||||
await setApplicationMenu(win);
|
||||
|
||||
if (config.plugins.isEnabled('in-app-menu')) {
|
||||
if (await config.plugins.isEnabled('in-app-menu')) {
|
||||
win.webContents.send('refresh-in-app-menu');
|
||||
}
|
||||
},
|
||||
@ -37,7 +41,7 @@ const createContext = (
|
||||
|
||||
export const forceLoadMenuPlugin = async (id: string, win: BrowserWindow) => {
|
||||
try {
|
||||
const plugin = allPlugins[id];
|
||||
const plugin = (await allPlugins())[id];
|
||||
if (!plugin) return;
|
||||
|
||||
const menu = plugin.menu?.(createContext(id, win));
|
||||
@ -68,7 +72,7 @@ export const forceLoadMenuPlugin = async (id: string, win: BrowserWindow) => {
|
||||
export const loadAllMenuPlugins = async (win: BrowserWindow) => {
|
||||
const pluginConfigs = config.plugins.getPlugins();
|
||||
|
||||
for (const [pluginId, pluginDef] of Object.entries(allPlugins)) {
|
||||
for (const [pluginId, pluginDef] of Object.entries(await allPlugins())) {
|
||||
const config = deepmerge(
|
||||
pluginDef.config ?? { enabled: false },
|
||||
pluginConfigs[pluginId] ?? {},
|
||||
|
||||
@ -15,13 +15,17 @@ const loadedPluginMap: Record<
|
||||
PluginDef<unknown, unknown, unknown>
|
||||
> = {};
|
||||
const createContext = (id: string): PreloadContext<PluginConfig> => ({
|
||||
getConfig: () =>
|
||||
getConfig: async () =>
|
||||
deepmerge(
|
||||
allPlugins[id].config ?? { enabled: false },
|
||||
(await allPlugins())[id].config ?? { enabled: false },
|
||||
config.get(`plugins.${id}`) ?? {},
|
||||
) as PluginConfig,
|
||||
setConfig: (newConfig) => {
|
||||
config.setPartial(`plugins.${id}`, newConfig, allPlugins[id].config);
|
||||
setConfig: async (newConfig) => {
|
||||
config.setPartial(
|
||||
`plugins.${id}`,
|
||||
newConfig,
|
||||
(await allPlugins())[id].config,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@ -48,7 +52,7 @@ export const forceUnloadPreloadPlugin = async (id: string) => {
|
||||
|
||||
export const forceLoadPreloadPlugin = async (id: string) => {
|
||||
try {
|
||||
const plugin = preloadPlugins[id];
|
||||
const plugin = (await preloadPlugins())[id];
|
||||
if (!plugin) return;
|
||||
|
||||
const hasStarted = await startPlugin(id, plugin, {
|
||||
@ -78,10 +82,10 @@ export const forceLoadPreloadPlugin = async (id: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const loadAllPreloadPlugins = () => {
|
||||
export const loadAllPreloadPlugins = async () => {
|
||||
const pluginConfigs = config.plugins.getPlugins();
|
||||
|
||||
for (const [pluginId, pluginDef] of Object.entries(preloadPlugins)) {
|
||||
for (const [pluginId, pluginDef] of Object.entries(await preloadPlugins())) {
|
||||
const config = deepmerge(
|
||||
pluginDef.config ?? { enable: false },
|
||||
pluginConfigs[pluginId] ?? {},
|
||||
|
||||
@ -18,7 +18,7 @@ const loadedPluginMap: Record<
|
||||
export const createContext = <Config extends PluginConfig>(
|
||||
id: string,
|
||||
): RendererContext<Config> => ({
|
||||
getConfig: async () =>
|
||||
getConfig: () =>
|
||||
window.ipcRenderer.invoke('ytmd:get-config', id) as Promise<Config>,
|
||||
setConfig: async (newConfig) => {
|
||||
await window.ipcRenderer.invoke('ytmd:set-config', id, newConfig);
|
||||
@ -47,7 +47,7 @@ export const forceUnloadRendererPlugin = async (id: string) => {
|
||||
delete unregisterStyleMap[id];
|
||||
delete loadedPluginMap[id];
|
||||
|
||||
const plugin = rendererPlugins[id];
|
||||
const plugin = (await rendererPlugins())[id];
|
||||
if (!plugin) return;
|
||||
|
||||
const hasStopped = await stopPlugin(id, plugin, {
|
||||
@ -71,7 +71,7 @@ export const forceUnloadRendererPlugin = async (id: string) => {
|
||||
};
|
||||
|
||||
export const forceLoadRendererPlugin = async (id: string) => {
|
||||
const plugin = rendererPlugins[id];
|
||||
const plugin = (await rendererPlugins())[id];
|
||||
if (!plugin) return;
|
||||
|
||||
const hasEvaled = await startPlugin(id, plugin, {
|
||||
@ -117,7 +117,7 @@ export const forceLoadRendererPlugin = async (id: string) => {
|
||||
export const loadAllRendererPlugins = async () => {
|
||||
const pluginConfigs = window.mainConfig.plugins.getPlugins();
|
||||
|
||||
for (const [pluginId, pluginDef] of Object.entries(rendererPlugins)) {
|
||||
for (const [pluginId, pluginDef] of Object.entries(await rendererPlugins())) {
|
||||
const config = deepmerge(pluginDef.config, pluginConfigs[pluginId] ?? {});
|
||||
|
||||
if (config.enabled) {
|
||||
|
||||
81
src/menu.ts
81
src/menu.ts
@ -29,21 +29,21 @@ import packageJson from '../package.json';
|
||||
export type MenuTemplate = Electron.MenuItemConstructorOptions[];
|
||||
|
||||
// True only if in-app-menu was loaded on launch
|
||||
const inAppMenuActive = config.plugins.isEnabled('in-app-menu');
|
||||
const inAppMenuActive = await config.plugins.isEnabled('in-app-menu');
|
||||
|
||||
const pluginEnabledMenu = (
|
||||
const pluginEnabledMenu = async (
|
||||
plugin: string,
|
||||
label = '',
|
||||
description: string | undefined = undefined,
|
||||
isNew = false,
|
||||
hasSubmenu = false,
|
||||
refreshMenu: (() => void) | undefined = undefined,
|
||||
): Electron.MenuItemConstructorOptions => ({
|
||||
): Promise<Electron.MenuItemConstructorOptions> => ({
|
||||
label: label || plugin,
|
||||
sublabel: isNew ? t('main.menu.plugins.new') : undefined,
|
||||
toolTip: description,
|
||||
type: 'checkbox',
|
||||
checked: config.plugins.isEnabled(plugin),
|
||||
checked: await config.plugins.isEnabled(plugin),
|
||||
click(item: Electron.MenuItem) {
|
||||
if (item.checked) {
|
||||
config.plugins.enable(plugin);
|
||||
@ -71,19 +71,21 @@ export const mainMenuTemplate = async (
|
||||
const { navigationHistory } = win.webContents;
|
||||
await loadAllMenuPlugins(win);
|
||||
|
||||
const menuResult = Object.entries(getAllMenuTemplate()).map(
|
||||
([id, template]) => {
|
||||
const plugin = allPlugins[id];
|
||||
const allPluginsStubs = await allPlugins();
|
||||
|
||||
const menuResult = await Promise.all(
|
||||
Object.entries(getAllMenuTemplate()).map(async ([id, template]) => {
|
||||
const plugin = allPluginsStubs[id];
|
||||
const pluginLabel = plugin?.name?.() ?? id;
|
||||
const pluginDescription = plugin?.description?.() ?? undefined;
|
||||
const isNew = plugin?.addedVersion
|
||||
? satisfies(packageJson.version, plugin.addedVersion)
|
||||
: false;
|
||||
|
||||
if (!config.plugins.isEnabled(id)) {
|
||||
if (!(await config.plugins.isEnabled(id))) {
|
||||
return [
|
||||
id,
|
||||
pluginEnabledMenu(
|
||||
await pluginEnabledMenu(
|
||||
id,
|
||||
pluginLabel,
|
||||
pluginDescription,
|
||||
@ -101,7 +103,7 @@ export const mainMenuTemplate = async (
|
||||
sublabel: isNew ? t('main.menu.plugins.new') : undefined,
|
||||
toolTip: pluginDescription,
|
||||
submenu: [
|
||||
pluginEnabledMenu(
|
||||
await pluginEnabledMenu(
|
||||
id,
|
||||
t('main.menu.plugins.enabled'),
|
||||
undefined,
|
||||
@ -114,39 +116,42 @@ export const mainMenuTemplate = async (
|
||||
],
|
||||
} satisfies Electron.MenuItemConstructorOptions,
|
||||
] as const;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const availablePlugins = Object.keys(allPlugins);
|
||||
const pluginMenus = availablePlugins
|
||||
.sort((a, b) => {
|
||||
const aPluginLabel = allPlugins[a]?.name?.() ?? a;
|
||||
const bPluginLabel = allPlugins[b]?.name?.() ?? b;
|
||||
const availablePlugins = Object.keys(await allPlugins());
|
||||
const pluginMenus = await Promise.all(
|
||||
availablePlugins
|
||||
.sort((a, b) => {
|
||||
const aPluginLabel = allPluginsStubs[a]?.name?.() ?? a;
|
||||
const bPluginLabel = allPluginsStubs[b]?.name?.() ?? b;
|
||||
|
||||
return aPluginLabel.localeCompare(bPluginLabel);
|
||||
})
|
||||
.map((id) => {
|
||||
const predefinedTemplate = menuResult.find((it) => it[0] === id);
|
||||
if (predefinedTemplate) return predefinedTemplate[1];
|
||||
return aPluginLabel.localeCompare(bPluginLabel);
|
||||
})
|
||||
.map((id) => {
|
||||
const predefinedTemplate = menuResult.find((it) => it[0] === id);
|
||||
if (predefinedTemplate) return predefinedTemplate[1];
|
||||
|
||||
const plugin = allPlugins[id];
|
||||
const pluginLabel = plugin?.name?.() ?? id;
|
||||
const pluginDescription = plugin?.description?.() ?? undefined;
|
||||
const isNew = plugin?.addedVersion
|
||||
? satisfies(packageJson.version, plugin.addedVersion)
|
||||
: false;
|
||||
const plugin = allPluginsStubs[id];
|
||||
const pluginLabel = plugin?.name?.() ?? id;
|
||||
const pluginDescription = plugin?.description?.() ?? undefined;
|
||||
const isNew = plugin?.addedVersion
|
||||
? satisfies(packageJson.version, plugin.addedVersion)
|
||||
: false;
|
||||
|
||||
return pluginEnabledMenu(
|
||||
id,
|
||||
pluginLabel,
|
||||
pluginDescription,
|
||||
isNew,
|
||||
true,
|
||||
innerRefreshMenu,
|
||||
);
|
||||
});
|
||||
return pluginEnabledMenu(
|
||||
id,
|
||||
pluginLabel,
|
||||
pluginDescription,
|
||||
isNew,
|
||||
true,
|
||||
innerRefreshMenu,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const availableLanguages = Object.keys(languageResources);
|
||||
const langResources = await languageResources();
|
||||
const availableLanguages = Object.keys(langResources);
|
||||
|
||||
return [
|
||||
{
|
||||
@ -445,7 +450,7 @@ export const mainMenuTemplate = async (
|
||||
availableLanguages
|
||||
.map(
|
||||
(lang): Electron.MenuItemConstructorOptions => ({
|
||||
label: `${languageResources[lang].translation.language?.name ?? 'Unknown'} (${languageResources[lang].translation.language?.['local-name'] ?? 'Unknown'})`,
|
||||
label: `${langResources[lang].translation.language?.name ?? 'Unknown'} (${langResources[lang].translation.language?.['local-name'] ?? 'Unknown'})`,
|
||||
type: 'checkbox',
|
||||
checked: (config.get('options.language') ?? 'en') === lang,
|
||||
click() {
|
||||
|
||||
@ -45,7 +45,7 @@ export const onPlayerApiReady = async (
|
||||
}, 2500);
|
||||
|
||||
/** Restore saved volume and setup tooltip */
|
||||
function firstRun() {
|
||||
async function firstRun() {
|
||||
if (typeof options.savedVolume === 'number') {
|
||||
// Set saved volume as tooltip
|
||||
setTooltip(options.savedVolume);
|
||||
@ -66,7 +66,7 @@ export const onPlayerApiReady = async (
|
||||
injectVolumeHud(noVid);
|
||||
if (!noVid) {
|
||||
setupVideoPlayerOnwheel();
|
||||
if (!window.mainConfig.plugins.isEnabled('video-toggle')) {
|
||||
if (!await window.mainConfig.plugins.isEnabled('video-toggle')) {
|
||||
// Video-toggle handles hud positioning on its own
|
||||
const videoMode = () =>
|
||||
api.getPlayerResponse().videoDetails?.musicVideoType !==
|
||||
@ -280,7 +280,7 @@ export const onPlayerApiReady = async (
|
||||
);
|
||||
context.ipc.on('setVolume', (value: number) => setVolume(value));
|
||||
|
||||
firstRun();
|
||||
await firstRun();
|
||||
};
|
||||
|
||||
export const onConfigChange = (config: PreciseVolumePluginConfig) => {
|
||||
|
||||
@ -309,8 +309,8 @@ function registerMPRIS(win: BrowserWindow) {
|
||||
player.volume = Number.parseFloat((newVol / 100).toFixed(2));
|
||||
});
|
||||
|
||||
player.on('volume', (newVolume: number) => {
|
||||
if (config.plugins.isEnabled('precise-volume')) {
|
||||
player.on('volume', async (newVolume: number) => {
|
||||
if (await config.plugins.isEnabled('precise-volume')) {
|
||||
// With precise volume we can set the volume to the exact value.
|
||||
win.webContents.send('setVolume', ~~(newVolume * 100));
|
||||
} else {
|
||||
|
||||
@ -159,9 +159,9 @@ export default createPlugin({
|
||||
const config = await getConfig();
|
||||
this.config = config;
|
||||
|
||||
const moveVolumeHud = window.mainConfig.plugins.isEnabled(
|
||||
const moveVolumeHud = (await window.mainConfig.plugins.isEnabled(
|
||||
'precise-volume',
|
||||
)
|
||||
))
|
||||
? (preciseVolumeMoveVolumeHud as (_: boolean) => void)
|
||||
: () => {};
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ import { loadI18n, setLanguage } from '@/i18n';
|
||||
|
||||
loadI18n().then(async () => {
|
||||
await setLanguage(config.get('options.language') ?? 'en');
|
||||
loadAllPreloadPlugins();
|
||||
await loadAllPreloadPlugins();
|
||||
});
|
||||
|
||||
ipcRenderer.on('plugin:unload', async (_, id: string) => {
|
||||
|
||||
13
src/virtual-module.d.ts
vendored
13
src/virtual-module.d.ts
vendored
@ -3,18 +3,17 @@ declare module 'virtual:plugins' {
|
||||
|
||||
type Plugin = PluginDef<unknown, unknown, unknown, PluginConfig>;
|
||||
|
||||
export const mainPlugins: Record<string, Plugin>;
|
||||
export const preloadPlugins: Record<string, Plugin>;
|
||||
export const rendererPlugins: Record<string, Plugin>;
|
||||
export const mainPlugins: () => Promise<Record<string, Plugin>>;
|
||||
export const preloadPlugins: () => Promise<Record<string, Plugin>>;
|
||||
export const rendererPlugins: () => Promise<Record<string, Plugin>>;
|
||||
|
||||
export const allPlugins: Record<
|
||||
string,
|
||||
Omit<Plugin, 'backend' | 'preload' | 'renderer'>
|
||||
export const allPlugins: () => Promise<
|
||||
Record<string, Omit<Plugin, 'backend' | 'preload' | 'renderer'>>
|
||||
>;
|
||||
}
|
||||
|
||||
declare module 'virtual:i18n' {
|
||||
import type { LanguageResources } from '@/i18n/resources/@types';
|
||||
|
||||
export const languageResources: LanguageResources;
|
||||
export const languageResources: () => Promise<LanguageResources>;
|
||||
}
|
||||
|
||||
@ -4,9 +4,6 @@ import { fileURLToPath } from 'node:url';
|
||||
import { globSync } from 'glob';
|
||||
import { Project } from 'ts-morph';
|
||||
|
||||
const snakeToCamel = (text: string) =>
|
||||
text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase());
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const globalProject = new Project({
|
||||
tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'),
|
||||
@ -27,20 +24,20 @@ export const i18nImporter = () => {
|
||||
const src = globalProject.createSourceFile(
|
||||
'vm:i18n',
|
||||
(writer) => {
|
||||
// prettier-ignore
|
||||
writer.writeLine('export const languageResources = async () => {');
|
||||
writer.writeLine(' const entries = await Promise.all([');
|
||||
for (const { name, path } of plugins) {
|
||||
const relativePath = relative(resolve(srcPath, '..'), path).replace(/\\/g, '/');
|
||||
writer.writeLine(`import ${snakeToCamel(name)}Json from "./${relativePath}";`);
|
||||
}
|
||||
const relativePath = relative(resolve(srcPath, '..'), path).replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
);
|
||||
|
||||
writer.blankLine();
|
||||
|
||||
writer.writeLine('export const languageResources = {');
|
||||
for (const { name } of plugins) {
|
||||
writer.writeLine(` "${name}": {`);
|
||||
writer.writeLine(` translation: ${snakeToCamel(name)}Json,`);
|
||||
writer.writeLine(' },');
|
||||
writer.writeLine(
|
||||
` import('./${relativePath}').then((mod) => ({ "${name}": { translation: mod.default } })),`,
|
||||
);
|
||||
}
|
||||
writer.writeLine(' ]);');
|
||||
writer.writeLine(' return Object.assign({}, ...entries);');
|
||||
writer.writeLine('};');
|
||||
writer.blankLine();
|
||||
},
|
||||
|
||||
@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
|
||||
import { globSync } from 'glob';
|
||||
import { Project } from 'ts-morph';
|
||||
|
||||
const snakeToCamel = (text: string) =>
|
||||
const kebabToCamel = (text: string) =>
|
||||
text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase());
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@ -43,31 +43,83 @@ export const pluginVirtualModuleGenerator = (
|
||||
const src = globalProject.createSourceFile(
|
||||
'vm:pluginIndexes',
|
||||
(writer) => {
|
||||
// prettier-ignore
|
||||
for (const { name, path } of plugins) {
|
||||
const relativePath = relative(resolve(srcPath, '..'), path).replace(/\\/g, '/');
|
||||
writer.writeLine(`import ${snakeToCamel(name)}Plugin, { pluginStub as ${snakeToCamel(name)}PluginStub } from "./${relativePath}";`);
|
||||
}
|
||||
const relativePath = relative(resolve(srcPath, '..'), path).replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
);
|
||||
if (mode === 'main') {
|
||||
// dynamic import (for main)
|
||||
writer.writeLine(
|
||||
`const ${kebabToCamel(name)}PluginImport = () => import('./${relativePath}');`,
|
||||
);
|
||||
writer.writeLine(
|
||||
`const ${kebabToCamel(name)}Plugin = async () => (await ${kebabToCamel(name)}PluginImport()).default;`,
|
||||
);
|
||||
writer.writeLine(
|
||||
`const ${kebabToCamel(name)}PluginStub = async () => (await ${kebabToCamel(name)}PluginImport()).pluginStub;`,
|
||||
);
|
||||
} else {
|
||||
// static import (preload does not support dynamic import)
|
||||
writer.writeLine(
|
||||
`import ${kebabToCamel(name)}PluginImport, { pluginStub as ${kebabToCamel(name)}PluginStubImport } from "./${relativePath}";`,
|
||||
);
|
||||
writer.writeLine(
|
||||
`const ${kebabToCamel(name)}Plugin = () => Promise.resolve(${kebabToCamel(name)}PluginImport);`,
|
||||
);
|
||||
writer.writeLine(
|
||||
`const ${kebabToCamel(name)}PluginStub = () => Promise.resolve(${kebabToCamel(name)}PluginStubImport);`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
writer.blankLine();
|
||||
|
||||
// Context-specific exports
|
||||
writer.writeLine(`export const ${mode}Plugins = {`);
|
||||
writer.writeLine(`let ${mode}PluginsCache = null;`);
|
||||
writer.writeLine(`export const ${mode}Plugins = async () => {`);
|
||||
writer.writeLine(
|
||||
` if (${mode}PluginsCache) return await ${mode}PluginsCache;`,
|
||||
);
|
||||
writer.writeLine(
|
||||
' const { promise, resolve } = Promise.withResolvers();',
|
||||
);
|
||||
writer.writeLine(' ' + `${mode}PluginsCache = promise;`);
|
||||
writer.writeLine(' const pluginEntries = await Promise.all([');
|
||||
for (const { name } of plugins) {
|
||||
const checkMode = mode === 'main' ? 'backend' : mode;
|
||||
// HACK: To avoid situation like importing renderer plugins in main
|
||||
writer.writeLine(
|
||||
` ...(${snakeToCamel(name)}Plugin['${checkMode}'] ? { "${name}": ${snakeToCamel(name)}Plugin } : {}),`,
|
||||
` ${kebabToCamel(name)}Plugin().then((plg) => plg['${checkMode}'] ? ["${name}", plg] : null),`,
|
||||
);
|
||||
}
|
||||
writer.writeLine(' ]);');
|
||||
writer.writeLine(
|
||||
' resolve(pluginEntries.filter((entry) => entry).reduce((acc, [name, plg]) => { acc[name] = plg; return acc; }, {}));',
|
||||
);
|
||||
writer.writeLine(` return await ${mode}PluginsCache;`);
|
||||
writer.writeLine('};');
|
||||
writer.blankLine();
|
||||
|
||||
// All plugins export (stub only) // Omit<Plugin, 'backend' | 'preload' | 'renderer'>
|
||||
writer.writeLine('export const allPlugins = {');
|
||||
writer.writeLine('let allPluginsCache = null;');
|
||||
writer.writeLine('export const allPlugins = async () => {');
|
||||
writer.writeLine(' if (allPluginsCache) return await allPluginsCache;');
|
||||
writer.writeLine(
|
||||
' const { promise, resolve } = Promise.withResolvers();',
|
||||
);
|
||||
writer.writeLine(' allPluginsCache = promise;');
|
||||
writer.writeLine(' const stubEntries = await Promise.all([');
|
||||
for (const { name } of plugins) {
|
||||
writer.writeLine(` "${name}": ${snakeToCamel(name)}PluginStub,`);
|
||||
writer.writeLine(
|
||||
` ${kebabToCamel(name)}PluginStub().then((stub) => ["${name}", stub]),`,
|
||||
);
|
||||
}
|
||||
writer.writeLine(' ]);');
|
||||
writer.writeLine(
|
||||
' resolve(stubEntries.reduce((acc, [name, plg]) => { acc[name] = plg; return acc; }, {}));',
|
||||
);
|
||||
writer.writeLine(' return await promise;');
|
||||
writer.writeLine('};');
|
||||
writer.blankLine();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user