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

vant4-testid-webpack-plugin

v1.0.3

Published

Webpack 5 plugin to auto-inject data-testid into Vant 4 (Vue 3) component DOM for E2E testing

Readme

vant4-testid-webpack-plugin

English

一个 Webpack 5 插件,为 Vue 3 + Vant 4 项目的 DOM 元素自动注入 data-testid 属性,让 Playwright / Cypress / Testing Library 等 E2E 测试更稳定、更易维护。

兼容: Vue 3.2+ · Vant 4.x · Webpack 5.x

环境要求: Node.js >=18.0.0

本包是 vant-testid-webpack-plugin (面向 Vue 2 + Vant 2 + Webpack 4)的 Webpack 5 / Vue 3(Vant 4)对应版本,沿用其「铁三角」三层注入架构。

动机

在大型 Vue 3 应用中手动为每个 Vant 组件添加 data-testid 既繁琐又容易遗漏。随着 UI 演进,选择器会失效、测试变脆。本插件通过三层自动注入解决这一问题:

  • 编译期:Vue 3 nodeTransforms 模块生成稳定、模板作用域内的 testid(van-button-0van-field-1 …)。
  • Vue 插件桥接createTestIdBridge() 从 VNode props 读取编译期 testid 并写入根 DOM 元素,绕过 inheritAttrs: false
  • 运行时:基于 MutationObserver 的注入器补全弹层、teleport 入口以及编译期无法触及的子元素(ActionSheet 项、Picker 列、Slider 手柄等)。

安装

pnpm add -D vant4-testid-webpack-plugin
# 或
npm install -D vant4-testid-webpack-plugin
# 或
yarn add -D vant4-testid-webpack-plugin

Peer 依赖: vue >=3.2.0, webpack >=5.0.0

快速开始

推荐:编译期 + Vue 插件 + 运行时

可获得跨 re-render 稳定的 testid,并具备完整运行时覆盖。

// webpack.config.js
const { Vant4TestIdWebpackPlugin, testIdTransforms } = require('vant4-testid-webpack-plugin')

module.exports = {
  module: {
    rules: [{
      test: /\.vue$/,
      loader: 'vue-loader',
      options: {
        compilerOptions: {
          nodeTransforms: testIdTransforms()
        }
      }
    }]
  },
  plugins: [new Vant4TestIdWebpackPlugin({ compileTime: true })]
}
// main.ts
import { createApp } from 'vue'
import { createTestIdBridge } from 'vant4-testid-webpack-plugin/vue-plugin'
import { setupVantTestIds } from 'vant4-testid-webpack-plugin/runtime'
import App from './App.vue'

const app = createApp(App)
app.use(createTestIdBridge())   // 编译器 testid → DOM
app.mount('#app')
setupVantTestIds()              // 面板 / 子元素 / 兜底

compileTime: true 时,插件会自动把 testIdTransforms() 注入到 vue-loader 的 compilerOptions.nodeTransforms,无需手动在每条 rule 上配置。你也可以手动添加 (如上所示),插件会去重合并。

纯运行时

最简单,零配置,但 testid 在运行时由全局计数器生成。

// webpack.config.js
const { Vant4TestIdWebpackPlugin } = require('vant4-testid-webpack-plugin')

module.exports = {
  plugins: [new Vant4TestIdWebpackPlugin()]
}
// main.ts
import { createApp } from 'vue'
import { setupVantTestIds } from 'vant4-testid-webpack-plugin/runtime'
import App from './App.vue'

createApp(App).mount('#app')
setupVantTestIds()

仅编译期

无运行时开销,但弹层/对话框面板及子元素不会获得 testid。

// webpack.config.js
const { testIdTransforms } = require('vant4-testid-webpack-plugin')

module.exports = {
  module: {
    rules: [{
      test: /\.vue$/,
      loader: 'vue-loader',
      options: {
        compilerOptions: {
          nodeTransforms: testIdTransforms()
        }
      }
    }]
  }
}
// main.ts
import { createApp } from 'vue'
import { createTestIdBridge } from 'vant4-testid-webpack-plugin/vue-plugin'
import App from './App.vue'

const app = createApp(App)
app.use(createTestIdBridge())
app.mount('#app')

在 Vue CLI(vue.config.js)中使用

Vue CLI(Vue 3 项目底层使用 Webpack 5)通过 vue.config.js 提供 webpack 配置入口。有两种等价方式接入本插件。

方式 A —— 让插件自动注入(推荐)

compileTime: true 时,插件的 apply() 会遍历最终生成的 module.rules,找到 vue-loader 规则,并自动把 testIdTransforms() 合并进其 compilerOptions.nodeTransforms,无需手动修改 vue-loader 的 options。

// vue.config.js
const { Vant4TestIdWebpackPlugin } = require('vant4-testid-webpack-plugin')

module.exports = {
  // configureWebpack 接受的对象会与最终 webpack 配置合并。
  // 插件被加入 plugins 数组,其 apply() 会自动向 vue-loader 注入 nodeTransforms。
  configureWebpack: {
    plugins: [
      new Vant4TestIdWebpackPlugin({ compileTime: true }),
    ],
  },
}

方式 B —— 显式使用 chainWebpack(需要完全掌控时)

当项目对 vue-loader 做了大量自定义,或上面的自动注入未能定位到 loader 时使用。

// vue.config.js
const {
  Vant4TestIdWebpackPlugin,
  testIdTransforms,
} = require('vant4-testid-webpack-plugin')

module.exports = {
  chainWebpack: (config) => {
    // 1) 显式向 vue-loader 添加编译期 nodeTransforms
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap((options = {}) => {
        options.compilerOptions = options.compilerOptions || {}
        options.compilerOptions.nodeTransforms = [
          ...(options.compilerOptions.nodeTransforms || []),
          ...testIdTransforms(),
        ]
        return options
      })

    // 2) 注册 webpack 插件,用于全局配置 / 运行时兜底
    config
      .plugin('vant4-testid')
      .use(Vant4TestIdWebpackPlugin, [{ compileTime: false }])
  },
}

方式 B 中第 1 步已手动添加了 nodeTransforms,因此插件 compileTime 设为 false; 若保留 true,插件会再注入一次(合并时自动去重)。

main.ts / main.js 侧(Vue 插件桥接 + 运行时 setupVantTestIds())与上面的快速开始示例完全一致。

API 参考

Vant4TestIdWebpackPlugin(options?)

Webpack 5 插件。存储全局配置,并在 compileTime: true 时自动把 nodeTransforms 注入 vue-loader。

new Vant4TestIdWebpackPlugin({
  attributeName?: string        // 默认 'data-testid'
  prefixCls?: string            // 默认 'van'
  testIdPrefix?: string         // 默认 ''
  compileTime?: boolean         // 默认 false
  compilerModuleOptions?: TransformOptions
  components?: Record<string, string>  // 自定义 CSS 选择器 → 前缀映射
  debug?: boolean               // 默认 false
})

testIdTransforms(options?) / createCompilerModule(options?)

返回 NodeTransform[],用于在编译期注入 testid,添加到 vue-loader 的 compilerOptions.nodeTransformscreateCompilerModule 为兼容 Vue 2 包命名习惯的别名。

testIdTransforms({
  attributeName?: string   // 默认 'data-testid'
  vantPrefix?: string      // 默认 'van-'
  customPrefixes?: string[] // 额外的组件 tag 前缀
  injectForLoops?: boolean // 默认 true
  injectEventElements?: boolean // 默认 true
  testIdPrefix?: string    // 默认 ''
  debug?: boolean          // 默认 false
})

createTestIdBridge(options?) / createVue3TestIdBridge(options?)

Vue 3 插件,从 VNode props 读取编译期 testid 并写入根 DOM 元素。使用编译期注入时必需, 因为部分 Vant 4 组件使用了 inheritAttrs: false

app.use(createTestIdBridge({
  attributeName?: string  // 默认 'data-testid'
}))

setupVantTestIds(options?)

启动运行时 MutationObserver 注入器,返回清理函数。

const stop = setupVantTestIds({
  attributeName?: string    // 默认 'data-testid'
  prefixCls?: string        // 默认 'van'
  injectAllElements?: boolean // 默认 true(为所有 HTML 元素注入 testid)
  components?: Record<string, string>
  panels?: Record<string, PanelInjectStrategy | undefined>
  debug?: boolean
})

// 之后停止监听
stop()

injectCurrentPanels(options?)

为已存在或即将出现的面板一次性注入。适用于 SSR/hydration 或不想用 MutationObserver 的场景。

import { injectCurrentPanels } from 'vant4-testid-webpack-plugin/runtime'

// 弹窗打开后调用
injectCurrentPanels()

rescanAllTestIds()

重新扫描整个 document.body 并注入 testid。适用于全页导航、HMR 替换 DOM 等场景。

import { rescanAllTestIds } from 'vant4-testid-webpack-plugin/runtime'
document.addEventListener('some-navigation-event', () => rescanAllTestIds())

deduplicateForLoopTestIds(root?, attrName?)

为无 index 变量的 v-for 产生的重复 testid 去重,对第 2+ 个追加 -{index} 后缀。

import { nextTick } from 'vue'
import { deduplicateForLoopTestIds } from 'vant4-testid-webpack-plugin/runtime'

nextTick(() => deduplicateForLoopTestIds())

工作原理

编译期 transform(三层计数器)

Vue SFC 编译期间,testIdTransforms() 遍历模板 AST,为每个 Vant/自定义组件注入 data-testid,采用三层计数器架构:

  1. v-for 动态注入(最高优先级)— 组件在 v-for 中带有 index 变量(v-for="(item, i) in list")时,生成动态 :data-testid 表达式,利用 index 生成每轮迭代唯一的 testid,无需运行时去重。
  2. 条件块子计数器v-if/v-else-if/v-else/v-show 块拥有独立子计数器,块内元素的增删不影响外部 testid 稳定性。
  3. 全局计数器 — 跨模板共享计数器 + usedIds 集合自动跳过已占用 testid。

事件监听元素使用 {tag}-event-{names}-{n} 格式。

<!-- 源码 -->
<van-button type="primary">Submit</van-button>
<van-field v-model="name" placeholder="Enter name" />

<!-- 编译后(简化) -->
<van-button type="primary" data-testid="van-button-0">Submit</van-button>
<van-field v-model="name" data-testid="van-field-0" placeholder="Enter name" />

Vue 插件桥接

createTestIdBridge() 通过 app.mixin({ mounted() }) 读取 vnode.props['data-testid'] 并显式调用 el.setAttribute(),绕过 van-popupvan-dialogvan-picker 等组件的 inheritAttrs: false

运行时注入器

setupVantTestIds() 通过 MutationObserver 监听 DOM 变化。检测到新的 Vant 组件根时,使用计数器注入 testid,并:

  • 注入子元素:field 控件、stepper 按钮、slider 手柄、rate 星标、tab 项等。
  • 注入弹层:Picker/DatePicker/TimePicker/Area 工具栏、ActionSheet 项、ShareSheet 选项、Dialog 按钮。
  • 为编译期遗漏的组件兜底。

调试

在浏览器控制台设置 globalThis.__VANT4_TESTID_DEBUG = true 查看 testid 注入日志:

globalThis.__VANT4_TESTID_DEBUG = true

或在插件构造函数中开启 debug 模式:

new Vant4TestIdWebpackPlugin({ debug: true })

与 Vue 2 / Webpack 4 版本对比

| | vant4-testid-webpack-plugin | vant-testid-webpack-plugin | |---|---|---| | Vue 版本 | Vue 3.2+ | Vue 2.6+ | | UI 库 | Vant 4 | Vant 2 | | Webpack | Webpack 5 | Webpack 4 | | 编译器 | @vue/compiler-core(nodeTransforms) | vue-template-compiler(modules) | | Vue 插件 | app.mixin + getCurrentInstance | Vue.mixin + $vnode.data.attrs | | 输出格式 | CJS | CJS |

常见报错

nodeTransforms[i] is not a function(或 nodeTransform[xx] is not a function

这是 @vue/compiler-core 在编译模板时,发现 compilerOptions.nodeTransforms 数组里 混入了非函数元素而抛出的 TypeError。本插件注入的 transform 本身都是合法函数, 因此问题几乎都出在配置方式上。两种最常见原因:

  1. 漏写括号(最高频):把工厂函数本身当成数组传了进去:

    // ❌ 错误:testIdTransforms 是函数,nodeTransforms[0] 取到 undefined
    compilerOptions: { nodeTransforms: testIdTransforms }
    // ✅ 正确:调用它,拿到 NodeTransform[]
    compilerOptions: { nodeTransforms: testIdTransforms() }
  2. 已有的 nodeTransforms 里有脏值:例如从别的构建工具 / 自己代码 spread 进来的 某一项是 undefined、字符串或非函数。

解决方式(任选其一):

  • 推荐:使用 compileTime: true,让插件自动把正确的 testIdTransforms() 注入 vue-loader,不要再手动设置 nodeTransforms。插件会自动清洗掉非函数项并打印告警, 不会再让整个构建崩溃。
  • 若必须手动配置:务必写成 nodeTransforms: testIdTransforms()(带括号),并确保你已有的 nodeTransforms 数组每一项都是函数。

许可证

MIT