Files
youtube-music/vite-plugins/plugin-importer.mts
JellyBrick b53ece5836 feat: code splitting (#3593)
Co-authored-by: Angelos Bouklis <me@arjix.dev>
2025-07-12 00:00:03 +09:00

131 lines
4.7 KiB
TypeScript

import { basename, relative, resolve, extname, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { globSync } from 'glob';
import { Project } from 'ts-morph';
const kebabToCamel = (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'),
skipAddingFilesFromTsConfig: true,
skipLoadingLibFiles: true,
skipFileDependencyResolution: true,
});
export const pluginVirtualModuleGenerator = (
mode: 'main' | 'preload' | 'renderer',
) => {
const srcPath = resolve(__dirname, '..', 'src');
const plugins = globSync([
'src/plugins/*/index.{js,ts,jsx,tsx}',
'src/plugins/*.{js,ts,jsx,tsx}',
'!src/plugins/utils/**/*',
'!src/plugins/utils/*',
]).map((path) => {
let name = basename(path);
if (
name === 'index.ts' ||
name === 'index.js' ||
name === 'index.jsx' ||
name === 'index.tsx'
) {
name = basename(resolve(path, '..'));
}
name = name.replace(extname(name), '');
return { name, path };
});
const src = globalProject.createSourceFile(
'vm:pluginIndexes',
(writer) => {
for (const { name, path } of plugins) {
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(`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(
` ${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('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(
` ${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();
},
{ overwrite: true },
);
return src.getText();
};