npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

unplugin-derive

v0.6.1

Published

Generate derived artifacts from your project files with a flexible, unplugin-based abstraction.

Readme

unplugin-derive

基于 unplugin 的通用派生引擎:

  • 监听 watch 文件变化
  • 触发 derive(event)full/patch
  • 根据返回的 files 写入或删除目标文件

安装

pnpm add -D unplugin-derive

用法(Vite)

import { defineConfig } from 'vite'
import Derive from 'unplugin-derive/vite'

export default defineConfig({
  plugins: [
    Derive({
      watch: ['src/api/**/*.js'],
      load: 'text',
      async derive(event) {
        const count = event.changes.length
        const content = `// generated from ${event.type}, files: ${count}\n`
        return {
          files: [{ path: 'src/generated.txt', content }]
        }
      },
      verbose: true
    })
  ]
})

其它构建工具(折叠)

Rollup

import Derive from 'unplugin-derive/rollup'

export default {
  plugins: [
    Derive({
      watch: ['src/api/**/*.js'],
      async derive() {
        return {
          files: [{ path: 'src/generated.txt', content: 'from rollup\n' }]
        }
      }
    })
  ]
}

Webpack

const Derive = require('unplugin-derive/webpack').default

module.exports = {
  plugins: [
    Derive({
      watch: ['src/api/**/*.js'],
      async derive() {
        return {
          files: [{ path: 'src/generated.txt', content: 'from webpack\n' }]
        }
      }
    })
  ]
}

esbuild

import { build } from 'esbuild'
import Derive from 'unplugin-derive/esbuild'

await build({
  entryPoints: ['src/index.ts'],
  bundle: true,
  outfile: 'dist/index.js',
  plugins: [
    Derive({
      watch: ['src/api/**/*.js'],
      async derive() {
        return {
          files: [{ path: 'src/generated.txt', content: 'from esbuild\n' }]
        }
      }
    })
  ]
})

配置总览

  • 通用配置: rootverbose
  • 监听触发: watchderiveWhen
  • 派生输出: derive
  • 内容加载: load
  • 输出加工: banner
  • 后续处理: gitignore

执行流程

一次 derive 任务的执行顺序如下:

  1. 收集变更(full 扫描 watchpatch 使用传入变更并做路径归一化)
  2. 依次执行 load,为每个 change 补充 content / loader(可选)
  3. 调用 derive(event),拿到要写入/删除的 files
  4. 按配置维护 .gitignore(如果启用,基于 derive 返回的 files 计算)
  5. 最终执行文件写入/删除(包含 banner 合并与渲染;在 emit 阶段会跳过越界路径与命中 watch 的输出)

按执行顺序的配置详解

0) rootverbose(通用项)

  • root: 工程根目录(默认 process.cwd()
  • verbose: 输出运行日志(默认 false

1) watchderiveWhen(何时触发)

  • watch: 监听文件 glob(相对 root
  • 支持否定模式:!pattern(如 ['src/**/*.ts', '!src/**/*.test.ts']
  • deriveWhen.buildStart: full | none(默认 full
  • deriveWhen.watchChange: patch | full | none(默认 patch
  • watchChange: "full" 时,仅在变更路径命中 watch 时触发 full

2) load(如何加载内容)

load 支持 5 种常见配置形态:

单个内置加载器

对所有命中的文件统一使用一个内置加载器('_text' | '_json' | '_buffer' | '_import')。

Derive({
  watch: ['src/**/*.txt'],
  load: '_text',
  async derive(event) {
    return {
      files: [{ path: 'src/generated.txt', content: String(event.changes[0]?.content ?? '') }]
    }
  }
})

单个自定义加载器

直接使用一个自定义加载器函数:(path) => ({ content }) | undefined
path 为相对 root 的路径。

Derive({
  watch: ['src/**/*.md'],
  load(file) {
    if (!file.endsWith('.md')) return undefined
    return { content: '# virtual markdown' }
  },
  async derive(event) {
    return {
      files: [{ path: 'src/generated.txt', content: String(event.changes[0]?.content ?? '') }]
    }
  }
})

组合加载器

使用数组按顺序 fallback,命中即停止。数组项可混用内置与自定义加载器。

Derive({
  watch: ['src/api/**/*'],
  load: ['_json', '_text', file => ({ content: { fallback: file } })],
  async derive(event) {
    return {
      files: [{ path: 'src/generated.txt', content: `event=${event.type}\n` }]
    }
  }
})

动态单个加载器

使用函数按路径动态返回单个内置加载器。

Derive({
  watch: ['src/**/*'],
  load(file) {
    if (file.endsWith('.json')) return '_json'
    return '_text'
  },
  async derive() {
    return { files: [] }
  }
})

动态组合加载器

使用函数按路径动态返回数组链。

Derive({
  watch: ['src/**/*'],
  load(file) {
    if (file.endsWith('.json')) return ['_json', '_text']
    return ['_import', '_text']
  },
  async derive() {
    return { files: [] }
  }
})

补充说明:

  • 兼容旧内置名:'text' | 'json' | 'buffer' | 'import' 在运行时仍可用,但推荐迁移到下划线写法。
  • 不支持 load: () => () => ({ content }) 这类“返回函数”的嵌套形式。
  • change.loader 为可选字段:内置 loader 会自动提供;自定义 loader 如需该信息,请在返回值中自行带上 loader

3) derive(如何产出文件)

derive 是核心回调,签名为 derive(event: DeriveEvent),返回 DeriveResult

  • 通过 event.changes 读取本次输入
  • 返回 files 指定要写入或删除的目标文件
  • 输出路径会在内部做安全过滤(越界路径、命中 watch 的输出会被跳过)

事件和返回值

  • DeriveEvent
    • type: "full" | "patch"
    • changes: Array<{ type, path, content?, loader? }>
  • DeriveResult
    • files: Array<{ path, content, banner? } | { path, type: "delete" }>
    • banner?: DeriveBanner

4) banner(输出加工,可选)

  • 覆盖顺序:DerivePluginOptions.banner -> DeriveResult.banner -> DeriveResultFile.banner(后者覆盖前者)
  • false 也遵循同样规则,表示该层显式禁用
  • data 合并是浅合并:后者覆盖前者的同名 key(嵌套对象不会做深合并)
  • style 可选值:line-slash / line-hash / block-star / block-jsdoc

从简单到复杂,推荐这样使用:

  1. 只用默认模板(最省事)
  • 只要最终合并后的 banner.data.author 存在,就会渲染内置默认模板;否则不输出 banner。
Derive({
  watch: ['src/**/*.ts'],
  banner: {
    data: {
      author: 'team-a',
      source: 'src/**/*.ts',
      overview: {
        description: 'generated stats',
        items: ['files: 12', 'methods: 38']
      }
    }
  },
  async derive() {
    return {
      files: [{ path: 'src/generated.ts', content: 'export const x = 1\n' }]
    }
  }
})
  1. 上层 template 统一渲染(推荐)

设计目的:derive 可以按“文件维度”返回 banner.data,由上层的 template 统一渲染出一致的 banner 样式。

  • 渲染优先级:formatter > template > 默认模板
  • template 的渲染作用域是 { data }(data-only scope):只能通过 <%= data.xxx %> 读取(例如 <%= data.author %><%= data.source %>
Derive({
  watch: ['src/**/*.ts'],
  banner: {
    // 统一模板
    template: 'author=<%= data.author %>, source=<%= data.source %>',
    // 全局默认 data(可被 result/file 覆盖)
    data: { author: 'team-a' }
  },
  async derive() {
    return {
      banner: {
        // 本次任务维度补充/覆盖
        data: { source: 'src/**/*.ts' }
      },
      files: [
        {
          path: 'src/generated.ts',
          content: 'export const x = 1\n',
          banner: {
            // 文件维度补充/覆盖(例如把每个文件的 source 精确到来源文件)
            data: { source: 'src/foo.ts' }
          }
        }
      ]
    }
  }
})
  1. 需要 path/content/style 时,用 formatter(高级)

如果你确实需要读取输出文件路径、内容或 style(例如把路径写进 banner,或根据内容决定是否输出),建议直接提供 formatter 函数。

  • formatter(context) 会收到 { path, content, data, style }
  • context.path 是相对 root 的输出路径(对外不暴露绝对路径)
Derive({
  watch: ['src/**/*.ts'],
  banner: {
    style: 'line-slash',
    formatter: ({ path, data }) => `generated=${path}; author=${data.author ?? ''}`,
    data: { author: 'team-a' }
  },
  async derive() {
    return {
      files: [{ path: 'src/generated.ts', content: 'export const x = 1\n' }]
    }
  }
})

5) gitignore(输出后处理,可选)

  • true: 将本次生成的可写入文件(即带 content 的 file,且路径位于 root 内)写入 .gitignore
  • string / string[]: 直接作为 .gitignore 条目写入
  • (file) => boolean: 按文件相对路径过滤后写入

其它

  • 同一时刻只会执行一个 derive
  • 运行中收到 patch 会合并排队
  • 运行中收到 full 会清空未开始的 patch,并在当前任务结束后优先执行 full

示例

  • 项目特定的 API 解析和 types.d.ts 渲染逻辑已放在 examples/webpack-dts

测试

测试相关说明已迁移到 TESTING.md