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

@@ -18,7 +18,10 @@ const config: KnipConfig = {
'@primeuix/utils',
'@primevue/icons',
// Dev
'@trivago/prettier-plugin-sort-imports'
'@trivago/prettier-plugin-sort-imports',
// Used by playwright.i18n.config.ts babel plugins
'@babel/plugin-transform-typescript',
'babel-plugin-module-resolver'
],
ignore: [
// Auto generated manager types

View File

@@ -8,7 +8,6 @@ const config: any = defineConfig({
testDir: './scripts',
use: {
baseURL: 'http://localhost:5173',
headless: true
},
reporter: 'list',
timeout: 60000,
@@ -26,8 +25,8 @@ const config: any = defineConfig({
// Configure babel plugins for TypeScript with declare fields and module resolution
config['@playwright/test'] = {
babelPlugins: [
// Stub Vue and CSS imports first to prevent parsing errors
[path.join(__dirname, 'babel-plugin-stub-vue-imports.cjs')],
// Stub Vue and CSS imports to prevent parsing errors
[path.join(__dirname, 'scripts/babel-plugin-stub-vue-imports.cjs')],
// Module resolver to handle @ alias
[
'babel-plugin-module-resolver',
@@ -38,14 +37,19 @@ config['@playwright/test'] = {
}
}
],
// Then TypeScript transformation with declare field support
// TypeScript transformation with declare field support
[
'@babel/plugin-transform-typescript',
{
allowDeclareFields: true,
onlyRemoveTypeImports: true
}
]
],
// Inject browser globals AFTER TypeScript transformation
[path.join(__dirname, 'scripts/babel-plugin-inject-globals.cjs'), {
filenamePattern: 'collect-i18n-',
setupFile: './setup-browser-globals.mjs'
}]
]
}

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

@@ -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: () => []
})
});

View File

@@ -33,6 +33,8 @@
"eslint.config.ts",
"global.d.ts",
"knip.config.ts",
"playwright.i18n.config.ts",
"scripts/collect-i18n-*.ts",
"src/**/*.vue",
"src/**/*",
"src/types/**/*.d.ts",