Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/docs/src/pages/docs/guides/vite.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export default defineConfig({
* Decide which of your other Vite plugins run inside surimi's internal
* evaluation server. By default every plugin (except surimi's own) is
* included, so aliases and custom resolvers keep working inside `.css.ts`
* files. Drop plugins that misbehave in a secondary, websocket-less server.
* files. Only their `resolveId`, `load` and `transform` hooks run there.
* Drop plugins whose transforms misbehave in a secondary server.
* default:
*/
pluginFilter: undefined,
Expand Down
20 changes: 17 additions & 3 deletions packages/vite-plugin-surimi/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path';
import type { CompileResult } from '@surimi/compiler';
import type { EnvironmentModuleGraph, EnvironmentModuleNode, Plugin } from 'vite';
import type { EnvironmentModuleGraph, EnvironmentModuleNode, Plugin, PluginOption } from 'vite';
import { createFilter } from 'vite';

import { VIRTUAL_CSS_REGEX, VIRTUAL_CSS_SUFFIX, VIRTUAL_SURIMI_PATH_REGEX } from './constants.js';
Expand Down Expand Up @@ -41,6 +41,8 @@ export default function surimiPlugin(options: SurimiOptions = {}): Plugin[] {
};

const filesWatched = new Set<string>();
/** Plugins registered by the host (user or framework), as opposed to ones Vite adds internally. */
let hostPlugins = new Set<Plugin>();

const normalizeDependencyId = (dependencyId: string, ownerId: string): string => {
const cleanId = dependencyId.split('?')[0] ?? dependencyId;
Expand Down Expand Up @@ -69,7 +71,7 @@ export default function surimiPlugin(options: SurimiOptions = {}): Plugin[] {
include,
exclude,
resolvedConfig: ctx.resolvedConfig,
userPlugins: ctx.resolvedConfig.plugins.flat(),
userPlugins: ctx.resolvedConfig.plugins.filter(plugin => hostPlugins.has(plugin)),
...(options.pluginFilter ? { pluginFilter: options.pluginFilter } : {}),
});
}
Expand Down Expand Up @@ -169,7 +171,9 @@ export default function surimiPlugin(options: SurimiOptions = {}): Plugin[] {

const corePlugin: Plugin = {
name: 'vite-plugin-surimi',
config(config) {
async config(config) {
hostPlugins = await collectPlugins(config.plugins);

if (config.build?.ssr) {
return {
build: {
Expand Down Expand Up @@ -347,6 +351,16 @@ export default function surimiPlugin(options: SurimiOptions = {}): Plugin[] {
return [corePlugin, createVuePlugin(ctx)];
}

async function collectPlugins(option: PluginOption, into = new Set<Plugin>()): Promise<Set<Plugin>> {
const resolved = await option;
if (Array.isArray(resolved)) {
for (const nested of resolved) await collectPlugins(nested, into);
} else if (resolved) {
into.add(resolved);
}
return into;
}

function collectModulesForInvalidation(
fileId: string,
moduleGraph: EnvironmentModuleGraph,
Expand Down
23 changes: 21 additions & 2 deletions packages/vite-plugin-surimi/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,27 @@ function isDevelopmentSurimiFile(id: string): boolean {
return DEV_SURIMI_PACKAGES.some(pkgPath => id.includes(pkgPath));
}

const MODULE_PIPELINE_HOOKS = ['resolveId', 'load', 'transform'] as const;

/**
* Host plugin instances are already configured by the host. Only their module pipeline hooks may run
* here: re-running lifecycle hooks (`config`, `configResolved`, `buildStart`, ...) with this server's
* serve-mode config clobbers their shared state, e.g. Astro's content store flips to its dev path.
*/
function isolateHostPlugin(plugin: Plugin): Plugin {
const isolated: Plugin = { name: plugin.name };
if (plugin.enforce) isolated.enforce = plugin.enforce;
for (const hook of MODULE_PIPELINE_HOOKS) {
if (plugin[hook]) Object.assign(isolated, { [hook]: plugin[hook] });
Comment thread
janis-me marked this conversation as resolved.
Resolved
}
return isolated;
}

function filterUserPlugins(plugins: Plugin[], pluginFilter: (plugin: Plugin) => boolean): Plugin[] {
return plugins.flat().filter((plugin): plugin is Plugin => pluginFilter(plugin));
return plugins
.flat()
.filter((plugin): plugin is Plugin => pluginFilter(plugin))
.map(isolateHostPlugin);
}

function createPrefixAliasPlugin(prefixAliases: Record<string, string>): Plugin {
Expand Down Expand Up @@ -205,7 +224,7 @@ export class SurimiEvaluator {
const hostConfig = this.options.resolvedConfig;
const prefixAliases = this.options.prefixAliases;
const plugins: Plugin[] = [
...filterUserPlugins(this.options.userPlugins ?? hostConfig?.plugins.flat() ?? [], this.pluginFilter),
...filterUserPlugins(this.options.userPlugins ?? [], this.pluginFilter),
...(prefixAliases && Object.keys(prefixAliases).length > 0 ? [createPrefixAliasPlugin(prefixAliases)] : []),
createSurimiCssTsResolvePlugin(),
createVirtualSourcesPlugin(this.virtualSources),
Expand Down
5 changes: 3 additions & 2 deletions packages/vite-plugin-surimi/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ export interface SurimiOptions {

/**
* Decide which host plugins are seeded into surimi's owned evaluation server.
* The default keeps every plugin except surimi's own. Use this to drop plugins that misbehave
* inside a secondary, websocket-less server (middleware/websocket-dependent plugins, etc.).
* The default keeps every plugin registered by you or your framework except surimi's own (Vite's
* internal plugins are never seeded). Seeded plugins only contribute their `resolveId`, `load`
* and `transform` hooks. Use this to drop plugins whose transforms misbehave there.
*/
pluginFilter?: (plugin: Plugin) => boolean;
}
Loading