feat: enable rolldown native plugin (#3502)

This commit is contained in:
JellyBrick
2025-07-02 16:20:46 +09:00
committed by GitHub
parent 3e9429879d
commit 0253bfe193
2 changed files with 131 additions and 93 deletions

View File

@ -1,16 +1,17 @@
import { dirname, join, resolve } from 'node:path'; import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { UserConfig } from 'vite';
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 Inspect from 'vite-plugin-inspect'; import Inspect from 'vite-plugin-inspect';
import solidPlugin from 'vite-plugin-solid'; import solidPlugin from 'vite-plugin-solid';
import viteResolve from 'vite-plugin-resolve';
import { withFilter, type UserConfig } from 'vite';
import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer.mjs'; import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer.mjs';
import pluginLoader from './vite-plugins/plugin-loader.mjs'; import pluginLoader from './vite-plugins/plugin-loader.mjs';
import { i18nImporter } from './vite-plugins/i18n-importer.mjs'; import { i18nImporter } from './vite-plugins/i18n-importer.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
@ -23,6 +24,9 @@ const resolveAlias = {
export default defineConfig({ export default defineConfig({
main: defineViteConfig(({ mode }) => { main: defineViteConfig(({ mode }) => {
const commonConfig: UserConfig = { const commonConfig: UserConfig = {
experimental: {
enableNativePlugin: true,
},
plugins: [ plugins: [
pluginLoader('backend'), pluginLoader('backend'),
viteResolve({ viteResolve({
@ -72,6 +76,9 @@ export default defineConfig({
}), }),
preload: defineViteConfig(({ mode }) => { preload: defineViteConfig(({ mode }) => {
const commonConfig: UserConfig = { const commonConfig: UserConfig = {
experimental: {
enableNativePlugin: true,
},
plugins: [ plugins: [
pluginLoader('preload'), pluginLoader('preload'),
viteResolve({ viteResolve({
@ -120,13 +127,18 @@ export default defineConfig({
}), }),
renderer: defineViteConfig(({ mode }) => { renderer: defineViteConfig(({ mode }) => {
const commonConfig: UserConfig = { const commonConfig: UserConfig = {
experimental: {
enableNativePlugin: mode !== 'development', // Disable native plugin in development mode to avoid issues with HMR (bug in rolldown-vite)
},
plugins: [ plugins: [
pluginLoader('renderer'), pluginLoader('renderer'),
viteResolve({ viteResolve({
'virtual:i18n': i18nImporter(), 'virtual:i18n': i18nImporter(),
'virtual:plugins': pluginVirtualModuleGenerator('renderer'), 'virtual:plugins': pluginVirtualModuleGenerator('renderer'),
}), }),
solidPlugin(), withFilter(solidPlugin(), {
load: { id: [/\.(tsx|jsx)$/, '/@solid-refresh'] },
}),
], ],
root: './src/', root: './src/',
build: { build: {

View File

@ -2,7 +2,6 @@ import { readFileSync } from 'node:fs';
import { resolve, basename, dirname } from 'node:path'; import { resolve, basename, dirname } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { createFilter } from 'vite';
import { import {
Project, Project,
ts, ts,
@ -39,108 +38,135 @@ const getPropertyName = (prop: Node): string | null => {
export default function ( export default function (
mode: 'backend' | 'preload' | 'renderer' | 'none', mode: 'backend' | 'preload' | 'renderer' | 'none',
): PluginOption { ): PluginOption {
const pluginFilter = createFilter([
'src/plugins/*/index.{js,ts}',
'src/plugins/*',
]);
return { return {
name: 'ytm-plugin-loader', name: 'ytm-plugin-loader',
load(id) { load: {
if (!pluginFilter(id)) return null; filter: {
id: /(?:\/plugins\/[^/]+\/index\.(?:js|ts|jsx|tsx)|\/plugins\/[^/]+\.(?:js|ts|jsx|tsx))$/,
},
handler(id) {
const fileContent = readFileSync(id, 'utf8');
// Create or update source file in the global project instance
const src = globalProject.createSourceFile(
'_pf' + basename(id),
fileContent,
{ overwrite: true },
);
// Read file asynchronously let objExpr: ObjectLiteralExpression | undefined;
const fileContent = readFileSync(id, 'utf8');
// Create or update source file in the global project instance
const src = globalProject.createSourceFile(
'_pf' + basename(id),
fileContent,
{ overwrite: true },
);
const exports = src.getExportedDeclarations(); // Check for `export default ...`
let objExpr: ObjectLiteralExpression | undefined; const defaultExportAssignment = src.getExportAssignment(
(ea) => !ea.isExportEquals(), // Filter out `export = `
);
// Identify the default export as an object literal, or via a 'createPlugin' call if (defaultExportAssignment) {
for (const [exportName, declarations] of exports) { const expression = defaultExportAssignment.getExpression();
if (exportName !== 'default') continue; if (expression.getKind() === ts.SyntaxKind.ObjectLiteralExpression) {
const expr = declarations[0]; objExpr = expression.asKindOrThrow(
ts.SyntaxKind.ObjectLiteralExpression,
const exprKind = expr.getKind(); );
if (exprKind === ts.SyntaxKind.ObjectLiteralExpression) { } else if (expression.getKind() === ts.SyntaxKind.CallExpression) {
objExpr = expr.asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression); const callExpr = expression.asKindOrThrow(
break; ts.SyntaxKind.CallExpression,
} else if (exprKind === ts.SyntaxKind.CallExpression) { );
const callExpr = expr.asKindOrThrow(ts.SyntaxKind.CallExpression); if (
if ( callExpr.getArguments().length === 1 &&
callExpr.getArguments().length === 1 && callExpr.getExpression().getText() === 'createPlugin'
callExpr.getExpression().getText() === 'createPlugin' ) {
) { const arg = callExpr.getArguments()[0];
const arg = callExpr.getArguments()[0]; if (arg.getKind() === ts.SyntaxKind.ObjectLiteralExpression) {
if (arg.getKind() === ts.SyntaxKind.ObjectLiteralExpression) { objExpr = arg.asKindOrThrow(
objExpr = arg.asKindOrThrow( ts.SyntaxKind.ObjectLiteralExpression,
ts.SyntaxKind.ObjectLiteralExpression, );
); }
break;
} }
} }
} }
}
if (!objExpr) return null; // If not found via `export default`, check for a named export aliased as 'default'
if (!objExpr) {
// Build a map of property names to their AST nodes for fast lookup const defaultExportDeclaration = src
const propMap = new Map<string, ObjectLiteralElementLike>(); .getExportedDeclarations()
for (const prop of objExpr.getProperties()) { .get('default');
const name = getPropertyName(prop); if (defaultExportDeclaration && defaultExportDeclaration.length > 0) {
if (name) propMap.set(name, prop); const expr = defaultExportDeclaration[0];
} if (expr.getKind() === ts.SyntaxKind.ObjectLiteralExpression) {
objExpr = expr.asKindOrThrow(
const contexts = ['backend', 'preload', 'renderer', 'menu']; ts.SyntaxKind.ObjectLiteralExpression,
for (const ctx of contexts) { );
if (mode === 'none' && propMap.has(ctx)) { } else if (expr.getKind() === ts.SyntaxKind.CallExpression) {
propMap.get(ctx)?.remove(); const callExpr = expr.asKindOrThrow(ts.SyntaxKind.CallExpression);
continue; if (
callExpr.getArguments().length === 1 &&
callExpr.getExpression().getText() === 'createPlugin'
) {
const arg = callExpr.getArguments()[0];
if (arg.getKind() === ts.SyntaxKind.ObjectLiteralExpression) {
objExpr = arg.asKindOrThrow(
ts.SyntaxKind.ObjectLiteralExpression,
);
}
}
}
}
} }
if (ctx === mode || (ctx === 'menu' && mode === 'backend')) continue;
if (propMap.has(ctx)) propMap.get(ctx)?.remove();
}
// Add an exported variable 'pluginStub' with the modified object literal's text if (!objExpr) return null;
const varStmt = src.addVariableStatement({
isExported: true,
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: 'pluginStub',
initializer: (writer) => writer.write(objExpr.getText()),
},
],
});
const stubObjExpr = varStmt
.getDeclarations()[0]
.getInitializerIfKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression);
// Similarly build a map for the stub properties // Build a map of property names to their AST nodes for fast lookup
const stubMap = new Map<string, ObjectLiteralElementLike>(); const propMap = new Map<string, ObjectLiteralElementLike>();
for (const prop of stubObjExpr.getProperties()) { for (const prop of objExpr.getProperties()) {
const name = getPropertyName(prop); const name = getPropertyName(prop);
if (name) stubMap.set(name, prop); if (name) propMap.set(name, prop);
}
const stubContexts =
mode === 'backend'
? contexts.filter((ctx) => ctx !== 'menu')
: contexts;
for (const ctx of stubContexts) {
if (stubMap.has(ctx)) {
stubMap.get(ctx)?.remove();
} }
}
return { const contexts = ['backend', 'preload', 'renderer', 'menu'];
code: src.getText(), for (const ctx of contexts) {
}; if (mode === 'none' && propMap.has(ctx)) {
propMap.get(ctx)?.remove();
continue;
}
if (ctx === mode || (ctx === 'menu' && mode === 'backend')) continue;
if (propMap.has(ctx)) propMap.get(ctx)?.remove();
}
// Add an exported variable 'pluginStub' with the modified object literal's text
const varStmt = src.addVariableStatement({
isExported: true,
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: 'pluginStub',
initializer: (writer) => writer.write(objExpr.getText()),
},
],
});
const stubObjExpr = varStmt
.getDeclarations()[0]
.getInitializerIfKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression);
// Similarly build a map for the stub properties
const stubMap = new Map<string, ObjectLiteralElementLike>();
for (const prop of stubObjExpr.getProperties()) {
const name = getPropertyName(prop);
if (name) stubMap.set(name, prop);
}
const stubContexts =
mode === 'backend'
? contexts.filter((ctx) => ctx !== 'menu')
: contexts;
for (const ctx of stubContexts) {
if (stubMap.has(ctx)) {
stubMap.get(ctx)?.remove();
}
}
return {
code: src.getText(),
};
},
}, },
}; };
} }