Phase 007: migrate every remaining view from naive-ui to Arco Design Vue
The project now has exactly one UI library. Every naive-ui component in
packages/client/src is replaced by its Arco equivalent across 24 views and
roughly 80 components, and naive-ui is dropped from package.json along with
its transitive vueuc / @css-render / vdirs / seemly dependencies.
The migration is rendering-layer only. No store logic, no API contract, and
no /chat-run payload was touched.
Three classes of leftover were invisible to `grep naive-ui` and are now
covered by tests/client/no-naive-ui.test.ts, which asserts all three:
- dead .n-* class names in style blocks
- dead --n-* custom properties
- naive-ui in package.json
Notable behavioural traps handled along the way, recorded in
docs/arco-migration-rules.md:
- a-input-password defaults to masked; passing default-visibility="false"
would have exposed API keys in clear text
- a-upload fires a real XHR unless auto-upload is false
- nested Trigger components fight over the first element child, so the
group-chat settings dropdown was flattened
- Arco renders tooltip default-slot text inline, which was silently
collapsing the chat and group-chat toolbars
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { readdirSync, readFileSync, statSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const SRC_ROOT = 'packages/client/src'
|
||||
|
||||
const listFiles = (dir: string): string[] =>
|
||||
readdirSync(dir).flatMap((name) => {
|
||||
const full = join(dir, name)
|
||||
return statSync(full).isDirectory() ? listFiles(full) : [full]
|
||||
})
|
||||
|
||||
const sourceFiles = listFiles(SRC_ROOT).filter((file) =>
|
||||
/\.(vue|ts|js)$/.test(file),
|
||||
)
|
||||
|
||||
/**
|
||||
* naive-ui 的类名在迁移时最容易漏:`grep naive-ui` 只匹配 import,
|
||||
* 匹配不到样式块里的 `:deep(.n-button__content)` 这种写法,也不匹配
|
||||
* JS 里的字符串字面量(`document.querySelector('.n-modal-mask')`)。
|
||||
* 这两种都会在 Arco 下静默失效,不报错、测试也测不出来。
|
||||
*/
|
||||
const NAIVE_CLASS_PATTERN = /\.n-[a-z][\w-]*/i
|
||||
|
||||
const findOffenders = (pattern: RegExp) =>
|
||||
sourceFiles
|
||||
.filter((file) => pattern.test(readFileSync(file, 'utf8')))
|
||||
.map((file) => file.replace(`${SRC_ROOT}/`, ''))
|
||||
|
||||
describe('naive-ui removal', () => {
|
||||
it('has source files to scan', () => {
|
||||
expect(sourceFiles.length).toBeGreaterThan(100)
|
||||
})
|
||||
|
||||
it('never imports from naive-ui', () => {
|
||||
expect(findOffenders(/from\s+['"]naive-ui['"]/)).toEqual([])
|
||||
})
|
||||
|
||||
it('never imports naive-ui types or helpers by deep path', () => {
|
||||
// packages/client/src 下没有深层导入,但防止将来有人从 @css-render 绕进来
|
||||
expect(
|
||||
findOffenders(/from\s+['"](vueuc|@css-render\/vue3-ssr|vdirs|seemly)['"]/),
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('never references naive-ui CSS class names', () => {
|
||||
// 覆盖样式块里的 :deep(.n-*) 与 JS 字符串字面量两种写法
|
||||
expect(findOffenders(NAIVE_CLASS_PATTERN)).toEqual([])
|
||||
})
|
||||
|
||||
it('never registers naive-ui components globally', () => {
|
||||
const main = readFileSync(join(SRC_ROOT, 'main.ts'), 'utf8')
|
||||
expect(main).toContain('ArcoVue')
|
||||
expect(main).not.toMatch(/N[A-Z][A-Za-z]*Provider/)
|
||||
})
|
||||
|
||||
it('does not depend on naive-ui in package.json', () => {
|
||||
const pkg = JSON.parse(readFileSync('package.json', 'utf8'))
|
||||
const all = { ...pkg.dependencies, ...pkg.devDependencies }
|
||||
expect(Object.keys(all)).not.toContain('naive-ui')
|
||||
for (const dep of Object.keys(all)) {
|
||||
expect(dep).not.toBe('vueuc')
|
||||
expect(dep).not.toBe('vdirs')
|
||||
}
|
||||
})
|
||||
|
||||
it('does not chunk naive-ui in the vite config', () => {
|
||||
const config = readFileSync('vite.config.ts', 'utf8')
|
||||
expect(config).not.toMatch(/['"]naive-ui['"]/)
|
||||
// Arco 的 scope 名含 "vue" 子串,判断必须排在 vue-vendor 之前,
|
||||
// 否则整个 UI 库会被打进 vue chunk。见 vite.config.ts 的注释。
|
||||
const arcoIndex = config.indexOf('@arco-design')
|
||||
const vueIndex = config.indexOf('vue-vendor')
|
||||
expect(arcoIndex).toBeGreaterThan(-1)
|
||||
expect(arcoIndex).toBeLessThan(vueIndex)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user