fix: Fix i18n collection by handling Vue/CSS imports in Node.js

The collect-i18n Playwright tests were failing because Babel was trying to
parse Vue and CSS files as JavaScript when importing modules in Node.js.

Changes:
- Add Node.js ES module loader to stub Vue components and CSS imports
- Extend litegraph preprocessing to also handle CSS imports and declare keywords in src/scripts
- Simplify ComfyNodeDefImpl usage in i18n collection to avoid Vue dependency chains
- Add mock dialogService to bypass Vue component imports

The loader intercepts .vue and .css file imports and returns appropriate stubs,
while the preprocessing comments out CSS imports in TypeScript files to prevent
Babel parsing errors.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
snomiao
2025-09-12 04:16:02 +00:00
parent 614257a3b1
commit 7444c05564
6 changed files with 186 additions and 22 deletions

View File

@@ -1,6 +1,6 @@
/**
* Setup for i18n collection tests
* Handles preprocessing of litegraph files that contain TypeScript 'declare' keywords
* Handles preprocessing of files that contain TypeScript 'declare' keywords
*/
import { promises as fs } from 'fs'
import { glob } from 'glob'
@@ -10,17 +10,28 @@ import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const rootDir = path.resolve(__dirname, '..')
const litegraphSrcDir = path.join(rootDir, 'src/lib/litegraph/src')
const scriptsSrcDir = path.join(rootDir, 'src/scripts')
const backupMap = new Map<string, string>()
export async function preprocessLitegraph() {
console.log('Preprocessing litegraph files for i18n collection...')
// Search for all .ts files in litegraph src directory
const pattern = path.join(litegraphSrcDir, '**/*.ts')
const files = await glob(pattern, {
ignore: ['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**']
})
// Search for all .ts files in litegraph src directory and scripts directory
const patterns = [
path.join(litegraphSrcDir, '**/*.ts'),
path.join(scriptsSrcDir, '**/*.ts')
]
const files = (
await Promise.all(
patterns.map((pattern) =>
glob(pattern, {
ignore: ['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**']
})
)
)
).flat()
let processedCount = 0
@@ -30,23 +41,41 @@ export async function preprocessLitegraph() {
try {
const originalContent = await fs.readFile(filePath, 'utf-8')
// Check for class property declarations with 'declare' keyword
if (!/^\s*declare\s+/m.test(originalContent)) {
return // Skip files without declare keywords
// Check if file needs any modifications
const hasDeclareKeywords = /^\s*declare\s+/m.test(originalContent)
const hasCssImports = /^(\s*)import\s+['"].*\.css['"];?$/m.test(originalContent)
if (!hasDeclareKeywords && !hasCssImports) {
return // Skip files that don't need modifications
}
// Store original content in memory
backupMap.set(filePath, originalContent)
// Remove 'declare' keyword from class properties
const modifiedContent = originalContent.replace(
/^(\s*)declare\s+/gm,
'$1// @ts-ignore - removed declare for Playwright\n$1'
)
let modifiedContent = originalContent
// Remove 'declare' keyword from class properties if present
if (hasDeclareKeywords) {
modifiedContent = modifiedContent.replace(
/^(\s*)declare\s+/gm,
'$1// @ts-ignore - removed declare for Playwright\n$1'
)
}
// Comment out CSS imports to avoid Babel parsing errors
if (hasCssImports) {
modifiedContent = modifiedContent.replace(
/^(\s*)import\s+['"].*\.css['"];?$/gm,
'$1// CSS import commented for Playwright: $&'
)
}
// Write modified content
await fs.writeFile(filePath, modifiedContent)
console.log(` ✓ Processed ${path.relative(litegraphSrcDir, filePath)}`)
const relativePath = filePath.includes(litegraphSrcDir)
? path.relative(litegraphSrcDir, filePath)
: path.relative(scriptsSrcDir, filePath)
console.log(` ✓ Processed ${relativePath}`)
processedCount++
} catch (error: unknown) {
console.warn(
@@ -73,7 +102,10 @@ export async function restoreLitegraph() {
await Promise.all(
Array.from(backupMap.entries()).map(async ([filePath, originalContent]) => {
await fs.writeFile(filePath, originalContent)
console.log(` ✓ Restored ${path.relative(litegraphSrcDir, filePath)}`)
const relativePath = filePath.includes(litegraphSrcDir)
? path.relative(litegraphSrcDir, filePath)
: path.relative(scriptsSrcDir, filePath)
console.log(` ✓ Restored ${relativePath}`)
})
)

View File

@@ -0,0 +1,15 @@
/**
* Registration script for Vue loader
* Used with --import flag in Node.js to stub Vue files during i18n collection
*/
import { register } from 'node:module'
import { pathToFileURL } from 'node:url'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
// Get the directory of this file
const __dirname = dirname(fileURLToPath(import.meta.url))
// Register the Vue loader
register(pathToFileURL(join(__dirname, 'vueLoader.mjs')))

View File

@@ -0,0 +1,63 @@
/**
* ES Module loader for Vue files and service mocks in Node.js
* Used during i18n collection to stub Vue component imports and services with Vue dependencies
*/
export async function resolve(specifier, context, nextResolve) {
// Mock dialogService to avoid Vue imports
if (specifier.endsWith('/dialogService') || specifier.endsWith('/dialogService.ts')) {
const mockPath = new URL('../scripts/mocks/dialogService.ts', import.meta.url)
return {
url: mockPath.href,
shortCircuit: true
}
}
// Pass through for non-Vue files
if (!specifier.endsWith('.vue')) {
return nextResolve(specifier, context)
}
// Resolve Vue files normally
return nextResolve(specifier, context)
}
export async function load(url, context, nextLoad) {
// Handle mock files
if (url.includes('/scripts/mocks/')) {
return nextLoad(url, context)
}
// Handle CSS files - return empty module
if (url.endsWith('.css')) {
return {
format: 'module',
shortCircuit: true,
source: 'export default {}'
}
}
// Only handle .vue files
if (!url.endsWith('.vue')) {
return nextLoad(url, context)
}
// Return a stub Vue component as JavaScript
const componentName = url.split('/').pop().replace('.vue', '')
const stubComponent = `
export default {
name: '${componentName}',
render: () => null,
props: {},
setup: () => ({}),
template: '<div></div>'
}
`
return {
format: 'module',
shortCircuit: true,
source: stubComponent
}
}