feat: plugin auto-importer with vite-plugin-resolve (#1385)

This commit is contained in:
JellyBrick
2023-11-09 09:22:23 +09:00
committed by GitHub
parent 18cd4c0c9a
commit 59426c56db
9 changed files with 105 additions and 122 deletions

View File

@ -26,6 +26,7 @@ module.exports = {
'import/newline-after-import': 'error', 'import/newline-after-import': 'error',
'import/no-default-export': 'off', 'import/no-default-export': 'off',
'import/no-duplicates': 'error', 'import/no-duplicates': 'error',
'import/no-unresolved': ['error', { ignore: ['^virtual:'] }],
'import/order': [ 'import/order': [
'error', 'error',
{ {

View File

@ -1,11 +1,19 @@
import { defineConfig, defineViteConfig } from 'electron-vite'; import { defineConfig, defineViteConfig } from 'electron-vite';
import builtinModules from 'builtin-modules'; import builtinModules from 'builtin-modules';
import viteResolve from 'vite-plugin-resolve';
import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-virtual-module-generator';
import type { UserConfig } from 'vite'; import type { UserConfig } from 'vite';
export default defineConfig({ export default defineConfig({
main: defineViteConfig(({ mode }) => { main: defineViteConfig(({ mode }) => {
const commonConfig: UserConfig = { const commonConfig: UserConfig = {
plugins: [
viteResolve({
'virtual:MainPlugins': pluginVirtualModuleGenerator('back'),
}),
],
publicDir: 'assets', publicDir: 'assets',
build: { build: {
lib: { lib: {
@ -38,6 +46,11 @@ export default defineConfig({
}), }),
preload: defineViteConfig(({ mode }) => { preload: defineViteConfig(({ mode }) => {
const commonConfig: UserConfig = { const commonConfig: UserConfig = {
plugins: [
viteResolve({
'virtual:PreloadPlugins': pluginVirtualModuleGenerator('preload'),
}),
],
build: { build: {
lib: { lib: {
entry: 'src/preload.ts', entry: 'src/preload.ts',
@ -69,6 +82,11 @@ export default defineConfig({
}), }),
renderer: defineViteConfig(({ mode }) => { renderer: defineViteConfig(({ mode }) => {
const commonConfig: UserConfig = { const commonConfig: UserConfig = {
plugins: [
viteResolve({
'virtual:RendererPlugins': pluginVirtualModuleGenerator('front'),
}),
],
root: './src/', root: './src/',
build: { build: {
lib: { lib: {

View File

@ -26,7 +26,9 @@
"!node_modules/**/*.map", "!node_modules/**/*.map",
"!node_modules/**/*.ts" "!node_modules/**/*.ts"
], ],
"asarUnpack": ["assets"], "asarUnpack": [
"assets"
],
"mac": { "mac": {
"identity": null, "identity": null,
"target": [ "target": [
@ -183,12 +185,14 @@
"eslint": "8.53.0", "eslint": "8.53.0",
"eslint-plugin-import": "2.29.0", "eslint-plugin-import": "2.29.0",
"eslint-plugin-prettier": "5.0.1", "eslint-plugin-prettier": "5.0.1",
"glob": "10.3.10",
"node-gyp": "10.0.1", "node-gyp": "10.0.1",
"playwright": "1.39.0", "playwright": "1.39.0",
"rollup": "4.3.0", "rollup": "4.3.0",
"typescript": "5.2.2", "typescript": "5.2.2",
"utf-8-validate": "6.0.3", "utf-8-validate": "6.0.3",
"vite": "4.5.0", "vite": "4.5.0",
"vite-plugin-resolve": "2.5.1",
"ws": "8.14.2", "ws": "8.14.2",
"yarpm": "1.2.0" "yarpm": "1.2.0"
}, },

16
pnpm-lock.yaml generated
View File

@ -162,6 +162,9 @@ devDependencies:
eslint-plugin-prettier: eslint-plugin-prettier:
specifier: 5.0.1 specifier: 5.0.1
version: 5.0.1(eslint@8.53.0)(prettier@3.0.3) version: 5.0.1(eslint@8.53.0)(prettier@3.0.3)
glob:
specifier: 10.3.10
version: 10.3.10
node-gyp: node-gyp:
specifier: 10.0.1 specifier: 10.0.1
version: 10.0.1 version: 10.0.1
@ -180,6 +183,9 @@ devDependencies:
vite: vite:
specifier: 4.5.0 specifier: 4.5.0
version: 4.5.0 version: 4.5.0
vite-plugin-resolve:
specifier: 2.5.1
version: 2.5.1
ws: ws:
specifier: 8.14.2 specifier: 8.14.2
version: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) version: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3)
@ -4049,6 +4055,10 @@ packages:
type-check: 0.4.0 type-check: 0.4.0
dev: true dev: true
/lib-esm@0.4.1:
resolution: {integrity: sha512-tdSqfyryhnl5k09357x2iWmw3WeU84SaoP/vMGw/nw8z8RPTrfu9sxwRApn6p6GyStuBNyASgwXIV8ctZWlG1A==}
dev: true
/lie@3.3.0: /lie@3.3.0:
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
dependencies: dependencies:
@ -5623,6 +5633,12 @@ packages:
dev: true dev: true
optional: true optional: true
/vite-plugin-resolve@2.5.1:
resolution: {integrity: sha512-9dD0Yq5JT1RxHQGZOyhC7e/JlhyhMCftCpQ8TPzQa7KEB/3ERnoCPinH3VJk/0C8qHsA+l41bIcHh5BcHBTmAw==}
dependencies:
lib-esm: 0.4.1
dev: true
/vite@4.5.0: /vite@4.5.0:
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}

View File

@ -11,6 +11,7 @@ import electronDebug from 'electron-debug';
import { parse } from 'node-html-parser'; import { parse } from 'node-html-parser';
import config from './config'; import config from './config';
import { refreshMenu, setApplicationMenu } from './menu'; import { refreshMenu, setApplicationMenu } from './menu';
import { fileExists, injectCSS, injectCSSAsFile } from './plugins/utils'; import { fileExists, injectCSS, injectCSSAsFile } from './plugins/utils';
import { isTesting } from './utils/testing'; import { isTesting } from './utils/testing';
@ -19,31 +20,10 @@ import { setupSongInfo } from './providers/song-info';
import { restart, setupAppControls } from './providers/app-controls'; import { restart, setupAppControls } from './providers/app-controls';
import { APP_PROTOCOL, handleProtocol, setupProtocolHandler } from './providers/protocol-handler'; import { APP_PROTOCOL, handleProtocol, setupProtocolHandler } from './providers/protocol-handler';
import adblocker from './plugins/adblocker/back'; // eslint-disable-next-line import/order
import albumColorTheme from './plugins/album-color-theme/back'; import { pluginList as mainPluginList } from 'virtual:MainPlugins';
import ambientMode from './plugins/ambient-mode/back';
import blurNavigationBar from './plugins/blur-nav-bar/back'; import { setOptions as pipSetOptions } from './plugins/picture-in-picture/back';
import captionsSelector from './plugins/captions-selector/back';
import crossfade from './plugins/crossfade/back';
import discord from './plugins/discord/back';
import downloader from './plugins/downloader/back';
import inAppMenu from './plugins/in-app-menu/back';
import lastFm from './plugins/last-fm/back';
import lumiaStream from './plugins/lumiastream/back';
import lyricsGenius from './plugins/lyrics-genius/back';
import navigation from './plugins/navigation/back';
import noGoogleLogin from './plugins/no-google-login/back';
import notifications from './plugins/notifications/back';
import pictureInPicture, { setOptions as pipSetOptions } from './plugins/picture-in-picture/back';
import preciseVolume from './plugins/precise-volume/back';
import qualityChanger from './plugins/quality-changer/back';
import shortcuts from './plugins/shortcuts/back';
import sponsorBlock from './plugins/sponsorblock/back';
import taskbarMediaControl from './plugins/taskbar-mediacontrol/back';
import touchbar from './plugins/touchbar/back';
import tunaObs from './plugins/tuna-obs/back';
import videoToggle from './plugins/video-toggle/back';
import visualizer from './plugins/visualizer/back';
import youtubeMusicCSS from './youtube-music.css'; import youtubeMusicCSS from './youtube-music.css';
@ -103,47 +83,18 @@ function onClosed() {
mainWindow = null; mainWindow = null;
} }
const mainPlugins = { export const mainPluginNames = Object.keys(mainPluginList);
'adblocker': adblocker,
'album-color-theme': albumColorTheme,
'ambient-mode': ambientMode,
'blur-nav-bar': blurNavigationBar,
'captions-selector': captionsSelector,
'crossfade': crossfade,
'discord': discord,
'downloader': downloader,
'in-app-menu': inAppMenu,
'last-fm': lastFm,
'lumiastream': lumiaStream,
'lyrics-genius': lyricsGenius,
'navigation': navigation,
'no-google-login': noGoogleLogin,
'notifications': notifications,
'picture-in-picture': pictureInPicture,
'precise-volume': preciseVolume,
'quality-changer': qualityChanger,
'shortcuts': shortcuts,
'sponsorblock': sponsorBlock,
'taskbar-mediacontrol': undefined as typeof taskbarMediaControl | undefined,
'touchbar': undefined as typeof touchbar | undefined,
'tuna-obs': tunaObs,
'video-toggle': videoToggle,
'visualizer': visualizer,
};
export const mainPluginNames = Object.keys(mainPlugins);
if (is.windows()) { if (is.windows()) {
mainPlugins['taskbar-mediacontrol'] = taskbarMediaControl; delete mainPluginList['touchbar'];
delete mainPlugins['touchbar'];
} else if (is.macOS()) { } else if (is.macOS()) {
mainPlugins['touchbar'] = touchbar; delete mainPluginList['taskbar-mediacontrol'];
delete mainPlugins['taskbar-mediacontrol'];
} else { } else {
delete mainPlugins['touchbar']; delete mainPluginList['touchbar'];
delete mainPlugins['taskbar-mediacontrol']; delete mainPluginList['taskbar-mediacontrol'];
} }
ipcMain.handle('get-main-plugin-names', () => Object.keys(mainPlugins)); ipcMain.handle('get-main-plugin-names', () => Object.keys(mainPluginList));
async function loadPlugins(win: BrowserWindow) { async function loadPlugins(win: BrowserWindow) {
injectCSS(win.webContents, youtubeMusicCSS); injectCSS(win.webContents, youtubeMusicCSS);
@ -172,9 +123,9 @@ async function loadPlugins(win: BrowserWindow) {
for (const [plugin, options] of config.plugins.getEnabled()) { for (const [plugin, options] of config.plugins.getEnabled()) {
try { try {
if (Object.hasOwn(mainPlugins, plugin)) { if (Object.hasOwn(mainPluginList, plugin)) {
console.log('Loaded plugin - ' + plugin); console.log('Loaded plugin - ' + plugin);
const handler = mainPlugins[plugin as keyof typeof mainPlugins]; const handler = mainPluginList[plugin as keyof typeof mainPluginList];
if (handler) { if (handler) {
await handler(win, options as never); await handler(win, options as never);
} }
@ -262,7 +213,6 @@ async function createMainWindow() {
type PiPOptions = typeof config.defaultConfig.plugins['picture-in-picture']; type PiPOptions = typeof config.defaultConfig.plugins['picture-in-picture'];
const setPiPOptions = config.plugins.isEnabled('picture-in-picture') const setPiPOptions = config.plugins.isEnabled('picture-in-picture')
// eslint-disable-next-line @typescript-eslint/no-var-requires
? (key: string, value: unknown) => pipSetOptions({ [key]: value }) ? (key: string, value: unknown) => pipSetOptions({ [key]: value })
: () => {}; : () => {};

View File

@ -3,7 +3,8 @@ import is from 'electron-is';
import config from './config'; import config from './config';
import adblockerPreload from './plugins/adblocker/preload'; // eslint-disable-next-line import/order
import { pluginList as preloadPluginList } from 'virtual:PreloadPlugins';
import type { ConfigType, OneOfDefaultConfigKey } from './config/dynamic'; import type { ConfigType, OneOfDefaultConfigKey } from './config/dynamic';
@ -15,15 +16,11 @@ export type PluginMapper<Type extends 'renderer' | 'preload' | 'backend'> = {
) )
}; };
const preloadPlugins: PluginMapper<'preload'> = {
'adblocker': adblockerPreload,
};
const enabledPluginNameAndOptions = config.plugins.getEnabled(); const enabledPluginNameAndOptions = config.plugins.getEnabled();
enabledPluginNameAndOptions.forEach(async ([plugin, options]) => { enabledPluginNameAndOptions.forEach(async ([plugin, options]) => {
if (Object.hasOwn(preloadPlugins, plugin)) { if (Object.hasOwn(preloadPluginList, plugin)) {
const handler = preloadPlugins[plugin]; const handler = preloadPluginList[plugin];
try { try {
await handler?.(); await handler?.();
} catch (error) { } catch (error) {

View File

@ -2,55 +2,8 @@ import setupSongInfo from './providers/song-info-front';
import { setupSongControls } from './providers/song-controls-front'; import { setupSongControls } from './providers/song-controls-front';
import { startingPages } from './providers/extracted-data'; import { startingPages } from './providers/extracted-data';
import albumColorThemeRenderer from './plugins/album-color-theme/front'; // eslint-disable-next-line import/order
import ambientModeRenderer from './plugins/ambient-mode/front'; import { pluginList as rendererPluginList } from 'virtual:RendererPlugins';
import audioCompressorRenderer from './plugins/audio-compressor/front';
import bypassAgeRestrictionsRenderer from './plugins/bypass-age-restrictions/front';
import captionsSelectorRenderer from './plugins/captions-selector/front';
import compactSidebarRenderer from './plugins/compact-sidebar/front';
import crossfadeRenderer from './plugins/crossfade/front';
import disableAutoplayRenderer from './plugins/disable-autoplay/front';
import downloaderRenderer from './plugins/downloader/front';
import exponentialVolumeRenderer from './plugins/exponential-volume/front';
import inAppMenuRenderer from './plugins/in-app-menu/front';
import lyricsGeniusRenderer from './plugins/lyrics-genius/front';
import navigationRenderer from './plugins/navigation/front';
import noGoogleLogin from './plugins/no-google-login/front';
import pictureInPictureRenderer from './plugins/picture-in-picture/front';
import playbackSpeedRenderer from './plugins/playback-speed/front';
import preciseVolumeRenderer from './plugins/precise-volume/front';
import qualityChangerRenderer from './plugins/quality-changer/front';
import skipSilencesRenderer from './plugins/skip-silences/front';
import sponsorblockRenderer from './plugins/sponsorblock/front';
import videoToggleRenderer from './plugins/video-toggle/front';
import visualizerRenderer from './plugins/visualizer/front';
import type { PluginMapper } from './preload';
const rendererPlugins: PluginMapper<'renderer'> = {
'album-color-theme': albumColorThemeRenderer,
'ambient-mode': ambientModeRenderer,
'audio-compressor': audioCompressorRenderer,
'bypass-age-restrictions': bypassAgeRestrictionsRenderer,
'captions-selector': captionsSelectorRenderer,
'compact-sidebar': compactSidebarRenderer,
'crossfade': crossfadeRenderer,
'disable-autoplay': disableAutoplayRenderer,
'downloader': downloaderRenderer,
'exponential-volume': exponentialVolumeRenderer,
'in-app-menu': inAppMenuRenderer,
'lyrics-genius': lyricsGeniusRenderer,
'navigation': navigationRenderer,
'no-google-login': noGoogleLogin,
'picture-in-picture': pictureInPictureRenderer,
'playback-speed': playbackSpeedRenderer,
'precise-volume': preciseVolumeRenderer,
'quality-changer': qualityChangerRenderer,
'skip-silences': skipSilencesRenderer,
'sponsorblock': sponsorblockRenderer,
'video-toggle': videoToggleRenderer,
'visualizer': visualizerRenderer,
};
const enabledPluginNameAndOptions = window.mainConfig.plugins.getEnabled(); const enabledPluginNameAndOptions = window.mainConfig.plugins.getEnabled();
@ -140,8 +93,8 @@ function onApiLoaded() {
(() => { (() => {
enabledPluginNameAndOptions.forEach(async ([pluginName, options]) => { enabledPluginNameAndOptions.forEach(async ([pluginName, options]) => {
if (Object.hasOwn(rendererPlugins, pluginName)) { if (Object.hasOwn(rendererPluginList, pluginName)) {
const handler = rendererPlugins[pluginName]; const handler = rendererPluginList[pluginName];
try { try {
await handler?.(options as never); await handler?.(options as never);
} catch (error) { } catch (error) {

16
src/virtual-module.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
declare module 'virtual:MainPlugins' {
import type { BrowserWindow } from 'electron';
import type { ConfigType } from './config/dynamic';
export const pluginList: Record<string, (win: BrowserWindow, options: ConfigType) => Promise<void>>;
}
declare module 'virtual:PreloadPlugins' {
export const pluginList: Record<string, () => Promise<void>>;
}
declare module 'virtual:RendererPlugins' {
import type { ConfigType } from './config/dynamic';
export const pluginList: Record<string, (options: ConfigType) => Promise<void>>;
}

View File

@ -0,0 +1,28 @@
import { existsSync } from 'node:fs';
import { basename, relative, resolve } from 'node:path';
import { globSync } from 'glob';
const snakeToCamel = (text: string) => text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase());
export const pluginVirtualModuleGenerator = (mode: 'back' | 'preload' | 'front') => {
const srcPath = resolve(__dirname, '..', 'src');
const plugins = globSync(`${srcPath}/plugins/*`)
.map((path) => ({ name: basename(path), path }))
.filter(({ name, path }) => !name.startsWith('utils') && existsSync(resolve(path, `${mode}.ts`)));
let result = '';
for (const { name, path } of plugins) {
result += `import ${snakeToCamel(name)}Plugin from "./${relative(resolve(srcPath, '..'), path).replace(/\\/g, '/')}/${mode}";\n`;
}
result += 'export const pluginList = {\n';
for (const { name } of plugins) {
result += ` "${name}": ${snakeToCamel(name)}Plugin,\n`;
}
result += '};';
return result;
};