插件 API

Nasti 兼容 Vite / Rollup 的核心模块钩子,并提供 Environment Driver API 接入 Rspeedy 等外部工具链。

基本结构

import type { NastiPlugin } from '@nasti-toolchain/nasti'

function myPlugin(options = {}): NastiPlugin {
  return {
    name: 'my-plugin',      // 必需,唯一标识
    enforce: 'pre',          // 可选: 'pre' | 'post'
    apply: 'build',           // 可选: 'build' | 'serve'

    resolveId(source, importer) {
      // 解析模块标识符
    },
    load(id) {
      // 加载模块源代码
    },
    transform(code, id) {
      // 转换模块代码
    },
  }
}

通用钩子

以下钩子在开发和构建模式下都会执行:

resolveId

resolveId(source: string, importer: string | undefined, options: { isEntry: boolean }): string | null | { id: string; external?: boolean }

自定义模块解析逻辑。返回解析后的 ID,或 null 交给下一个插件处理。

load

load(id: string): string | null | { code: string; map?: any }

自定义模块加载。返回模块源码,或 null 使用默认加载。

transform

transform(code: string, id: string): string | null | { code: string; map?: any }

转换模块代码。代码会按插件顺序依次通过 transform 管道。

buildStart / buildEnd

buildStart(): void
buildEnd(error?: Error): void

构建生命周期钩子。

Nasti / Vite 专有钩子

config

config(config: NastiConfig, env: { mode: string; command: string }): NastiConfig | null

在配置解析前修改配置。

configResolved

configResolved(config: ResolvedConfig): void

读取最终配置。

configureServer

configureServer(server: DevServer): void | (() => void)

自定义开发服务器。返回函数会在内部中间件之后执行。

transformIndexHtml

transformIndexHtml(html: string): string | HtmlTagDescriptor[] | { html: string; tags: HtmlTagDescriptor[] }

转换入口 HTML 文件。

handleHotUpdate

handleHotUpdate(ctx: HmrContext): void | ModuleNode[]

自定义 HMR 更新处理。

插件 setup 与跨插件 API

setup(api) 每次配置解析只执行一次。插件可通过 api.expose() / api.useExposed() 共享能力, 并通过 pre / post 声明 setup 依赖顺序。

const key = Symbol.for('example.config')

const provider = {
  name: 'example:provider',
  setup(api) {
    api.expose(key, { enabled: true })
  },
}

const consumer = {
  name: 'example:consumer',
  pre: ['example:provider'],
  setup(api) {
    const config = api.useExposed(key)
  },
}

Environment Driver:Rspeedy bridge

环境声明 driver 后不进入 Nasti 的 Rolldown 流水线,而由插件提供 build / serve / watchChange / close。这适合 Vue Lynx:插件可以调用 @lynx-js/rspeedycreateRspeedy(),保留其 Rspack layer、CSS extractor、HMR 与 Lynx bundle encoder。

import { defineConfig } from '@nasti-toolchain/nasti'
import { pluginRspeedyBridge } from 'nasti-plugin-rspeedy'

export default defineConfig({
  environments: {
    lynx: {
      consumer: 'client',
      driver: 'rspeedy',
    },
  },
  plugins: [pluginRspeedyBridge()],
})

bridge 插件的最小形态:

const pluginRspeedyBridge = () => ({
  name: 'nasti:rspeedy',
  async createEnvironmentDriver(environment) {
    if (environment.options.driver !== 'rspeedy') return

    const { createRspeedy } = await import('@lynx-js/rspeedy')
    let instance

    return {
      name: 'rspeedy',
      async build({ config }) {
        instance = await createRspeedy({ cwd: config.root })
        // 调用 Rspeedy build,并把产物归一为 EnvironmentBuildResult
        return { output: [] }
      },
      async serve({ config }) {
        // 启动 Rspeedy dev server,返回 localUrls / networkUrls
        return { localUrls: [] }
      },
      watchChange(file, event) {},
      async close() {},
    }
  },
  afterBuildApp(results, api, context) {
    // 通过 context 跨环境读取 entries / manifest / stats 并聚合产物
  },
})

插件顺序

插件按以下顺序执行:

  1. enforce: 'pre' 的插件
  2. 没有 enforce 的普通插件
  3. enforce: 'post' 的插件

在上述稳定顺序之上,pre: ['plugin-name'] 表示列出的插件必须先完成 setup,post 表示列出的插件必须后完成 setup。检测到依赖环时配置解析会失败。

条件应用

使用 apply 让插件只在特定模式下生效:

{
  apply: 'build',    // 仅在 nasti build 时生效
  apply: 'serve',    // 仅在 nasti dev 时生效
  apply: (config, { command }) => command === 'build',  // 函数形式
}

示例:Markdown 插件

function markdownPlugin(): NastiPlugin {
  return {
    name: 'markdown',
    transform(code, id) {
      if (!id.endsWith('.md')) return null
      const html = marked(code)
      return `export default ${JSON.stringify(html)}`
    },
  }
}