change plugin system

This commit is contained in:
Angelos Bouklis
2023-11-26 01:17:24 +02:00
parent 10a54b9de0
commit 3ab4cd5d05
34 changed files with 1670 additions and 990 deletions

View File

@ -0,0 +1,63 @@
import { basename, relative, resolve, extname } from 'node:path';
import { globSync } from 'glob';
import { Project } from 'ts-morph';
const snakeToCamel = (text: string) =>
text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase());
export const pluginVirtualModuleGenerator = (
mode: 'main' | 'preload' | 'renderer' | 'menu',
) => {
const project = new Project({
tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'),
skipAddingFilesFromTsConfig: true,
skipLoadingLibFiles: true,
skipFileDependencyResolution: true,
});
const srcPath = resolve(__dirname, '..', 'src');
const plugins = globSync([
'src/plugins/*/index.{js,ts}',
'src/plugins/*.{js,ts}',
'!src/plugins/utils/**/*',
'!src/plugins/utils/*',
]).map((path) => {
let name = basename(path);
if (name === 'index.ts' || name === 'index.js') {
name = basename(resolve(path, '..'));
}
name = name.replace(extname(name), '');
return { name, path };
});
const src = project.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 from "./${relativePath}";`);
}
writer.blankLine();
// Context-specific exports
writer.writeLine(`export const ${mode}Plugins = {`);
for (const { name } of plugins) {
writer.writeLine(` "${name}": ${snakeToCamel(name)}Plugin,`);
}
writer.writeLine('};');
writer.blankLine();
// All plugins export
writer.writeLine('export const allPlugins = {');
for (const { name } of plugins) {
writer.writeLine(` "${name}": ${snakeToCamel(name)}Plugin,`);
}
writer.writeLine('};');
writer.blankLine();
});
return src.getText();
};

View File

@ -0,0 +1,105 @@
import { readFile } from 'node:fs/promises';
import { resolve, basename } from 'node:path';
import { createFilter } from 'vite';
import { Project, ts, ObjectLiteralExpression } from 'ts-morph';
import type { PluginOption } from 'vite';
export default function (mode: 'backend' | 'preload' | 'renderer' | 'none') {
const pluginFilter = createFilter([
'src/plugins/*/index.{js,ts}',
'src/plugins/*',
]);
return <PluginOption>{
name: 'ytm-plugin-loader',
async load(id) {
if (!pluginFilter(id)) return null;
const project = new Project({
tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'),
skipAddingFilesFromTsConfig: true,
skipLoadingLibFiles: true,
skipFileDependencyResolution: true,
});
const src = project.createSourceFile(
'_pf' + basename(id),
await readFile(id, 'utf8'),
);
const exports = src.getExportedDeclarations();
let objExpr: ObjectLiteralExpression | undefined = undefined;
for (const [name, [expr]] of exports) {
if (name !== 'default') continue;
switch (expr.getKind()) {
case ts.SyntaxKind.ObjectLiteralExpression: {
objExpr = expr.asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression);
break;
}
case ts.SyntaxKind.CallExpression: {
const callExpr = expr.asKindOrThrow(ts.SyntaxKind.CallExpression);
if (callExpr.getArguments().length !== 1) continue;
const name = callExpr.getExpression().getText();
if (name !== 'createPlugin') continue;
const arg = callExpr.getArguments()[0];
if (arg.getKind() !== ts.SyntaxKind.ObjectLiteralExpression)
continue;
objExpr = arg.asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression);
break;
}
}
}
if (!objExpr) return null;
const properties = objExpr.getProperties();
const propertyNames = properties.map((prop) => {
switch (prop.getKind()) {
case ts.SyntaxKind.PropertyAssignment:
return prop
.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)
.getName();
case ts.SyntaxKind.ShorthandPropertyAssignment:
return prop
.asKindOrThrow(ts.SyntaxKind.ShorthandPropertyAssignment)
.getName();
case ts.SyntaxKind.MethodDeclaration:
return prop
.asKindOrThrow(ts.SyntaxKind.MethodDeclaration)
.getName();
default:
throw new Error('Not implemented');
}
});
const contexts = ['backend', 'preload', 'renderer'];
for (const ctx of contexts) {
if (mode === 'none') {
const index = propertyNames.indexOf(ctx);
if (index === -1) continue;
objExpr.getProperty(propertyNames[index])?.remove();
continue;
}
if (ctx === mode) continue;
const index = propertyNames.indexOf(ctx);
if (index === -1) continue;
objExpr.getProperty(propertyNames[index])?.remove();
}
return {
code: src.getText(),
ast: src,
};
},
};
}

View File

@ -1,49 +0,0 @@
import { existsSync } from 'node:fs';
import { basename, relative, resolve } from 'node:path';
import { globSync } from 'glob';
type PluginType = 'index' | 'main' | 'preload' | 'renderer' | 'menu';
const snakeToCamel = (text: string) => text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase());
const getName = (mode: PluginType, name: string) => {
if (mode === 'index') {
return snakeToCamel(name);
}
return `${snakeToCamel(name)}Plugin`;
};
const getListName = (mode: PluginType) => {
if (mode === 'index') return 'pluginBuilders';
return `${mode}Plugins`;
};
export const pluginVirtualModuleGenerator = (mode: PluginType) => {
const srcPath = resolve(__dirname, '..', 'src');
const plugins = globSync(`${srcPath}/plugins/*`)
.map((path) => ({ name: basename(path), path }))
.filter(({ name, path }) => {
if (name.startsWith('utils')) return false;
return existsSync(resolve(path, `${mode}.ts`)) || (mode !== 'index' && existsSync(resolve(path, `${mode}`, 'index.ts')));
});
console.log('converted plugin list');
console.log(plugins.map((it) => it.name));
let result = '';
for (const { name, path } of plugins) {
result += `import ${getName(mode, name)} from "./${relative(resolve(srcPath, '..'), path).replace(/\\/g, '/')}/${mode}";\n`;
}
result += `export const ${getListName(mode)} = {\n`;
for (const { name } of plugins) {
result += ` "${name}": ${getName(mode, name)},\n`;
}
result += '};';
return result;
};