fix: resolve collect-i18n babel transformation issues

- Created configurable babel-plugin-inject-globals to inject browser setup
- Added setup-browser-globals.mjs for browser environment mocking
- Moved babel plugin files to scripts directory for better organization
- Removed dependency on import order by using babel transformation
- Made plugin options configurable (filenamePattern, setupFile)
- Updated tsconfig.json to include playwright config and scripts

This fixes the ReferenceError: location is not defined issue that occurred
when running pnpm collect-i18n, ensuring the command works reliably
regardless of import auto-sorting.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
snomiao
2025-09-24 04:08:33 +00:00
parent c4cb3592ae
commit c3e2588ae0
8 changed files with 67 additions and 29 deletions

View File

@@ -0,0 +1,30 @@
module.exports = function(babel) {
const { types: t } = babel;
return {
visitor: {
Program(path, state) {
// Get options from plugin configuration
const opts = state.opts || {};
const filenamePattern = opts.filenamePattern || DIE('filenamePattern option is required');
const setupFile = opts.setupFile || DIE('setupFile option is required');
// Only inject the setup for matching test files
if (state.filename?.match(filenamePattern)) {
// Create an import statement for the setup file
const importDeclaration = t.importDeclaration(
[],
t.stringLiteral(setupFile)
);
// Insert the import at the beginning of the file
path.node.body.unshift(importDeclaration);
}
}
}
};
};
function DIE(msg) {
throw new Error(msg);
}

View File

@@ -0,0 +1,32 @@
module.exports = function(babel) {
const { types: t } = babel;
return {
visitor: {
ImportDeclaration(path) {
const source = path.node.source.value;
// Handle Vue files
if (source.endsWith('.vue')) {
const specifiers = path.node.specifiers;
if (specifiers.length > 0 && specifiers[0].type === 'ImportDefaultSpecifier') {
const name = specifiers[0].local.name;
// Replace with a variable declaration
path.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(name),
t.objectExpression([])
)
])
);
}
}
// Handle CSS files - just remove the import
else if (source.endsWith('.css') || source.endsWith('.scss') || source.endsWith('.less')) {
path.remove();
}
}
}
};
};

View File

@@ -7,7 +7,6 @@ import { SERVER_CONFIG_ITEMS } from '../src/constants/serverConfig'
import type { ComfyCommandImpl } from '../src/stores/commandStore'
import type { FormItem, SettingParams } from '../src/platform/settings/types'
import { formatCamelCase, normalizeI18nKey } from '../src/utils/formatUtil'
import './setup-browser-globals.js'
const localePath = './src/locales/en/main.json'
const commandsPath = './src/locales/en/commands.json'

View File

@@ -1,4 +1,3 @@
// Setup browser globals before any other imports that might use them
import * as fs from 'fs'
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
@@ -6,7 +5,6 @@ import type { ComfyNodeDef } from '../src/schemas/nodeDefSchema'
import type { ComfyApi } from '../src/scripts/api'
import { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'
import { normalizeI18nKey } from '../src/utils/formatUtil'
import './setup-browser-globals.js'
const localePath = './src/locales/en/main.json'
const nodeDefsPath = './src/locales/en/nodeDefs.json'

View File

@@ -1,32 +1,34 @@
// Setup browser-like globals for Node.js environment
import { Window } from 'happy-dom'
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const { Window } = require('happy-dom');
// Set build-time constants
global.__USE_PROD_CONFIG__ = false
global.__USE_LOCAL_SERVER__ = true
global.__RUN_TESTS__ = true
global.__USE_PROD_CONFIG__ = false;
global.__USE_LOCAL_SERVER__ = true;
global.__RUN_TESTS__ = true;
const window = new Window({
url: 'http://localhost:5173',
width: 1024,
height: 768
})
});
global.window = window
global.document = window.document
global.location = window.location
global.window = window;
global.document = window.document;
global.location = window.location;
// Don't set navigator if it's read-only
if (!global.navigator || Object.getOwnPropertyDescriptor(global, 'navigator')?.set) {
global.navigator = window.navigator
global.navigator = window.navigator;
}
global.HTMLElement = window.HTMLElement
global.Element = window.Element
global.CustomEvent = window.CustomEvent
global.requestAnimationFrame = window.requestAnimationFrame
global.HTMLElement = window.HTMLElement;
global.Element = window.Element;
global.CustomEvent = window.CustomEvent;
global.requestAnimationFrame = window.requestAnimationFrame;
// Use happy-dom's storage implementations
global.localStorage = window.localStorage
global.sessionStorage = window.sessionStorage
global.localStorage = window.localStorage;
global.sessionStorage = window.sessionStorage;
// Mock fetch if not available
if (!global.fetch) {
@@ -37,7 +39,7 @@ if (!global.fetch) {
blob: () => Promise.resolve(new Blob()),
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
headers: new Map()
})
});
}
// Mock ResizeObserver
@@ -45,7 +47,7 @@ global.ResizeObserver = class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
}
};
// Mock IntersectionObserver
global.IntersectionObserver = class IntersectionObserver {
@@ -53,10 +55,10 @@ global.IntersectionObserver = class IntersectionObserver {
observe() {}
unobserve() {}
disconnect() {}
}
};
// Mock getComputedStyle
global.getComputedStyle = window.getComputedStyle
global.getComputedStyle = window.getComputedStyle;
// Mock createRange
global.document.createRange = () => ({
@@ -71,4 +73,4 @@ global.document.createRange = () => ({
height: 0
}),
getClientRects: () => []
})
});