From 921d2d35c5b98b2df79f13d80f682a149296a476 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Mon, 16 Mar 2026 14:05:24 +0300 Subject: [PATCH] Export IS_BROWSER from core, replace all raw typeof window checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add IS_BROWSER constant to core/constants.ts, rewrite IS_TAURI to use it - Replace 9 raw 'typeof window' checks across canvaskit, fig-export, figma-api, fig-file, fonts, SafariBanner, use-chat - Promote no-typeof-window-check lint rule from warn to error - Extract array mutation helpers (updateArrayItem, removeArrayItem, toggleArrayVisibility) into useMultiProps — DRY up FillSection and StrokeSection (removes 20+ duplicated for-loops) --- lint/plugin.js | 2 +- oxlint.json | 2 +- packages/core/src/canvaskit.ts | 6 ++- packages/core/src/constants.ts | 3 +- packages/core/src/fig-export.ts | 4 +- packages/core/src/figma-api.ts | 5 +- packages/core/src/fonts.ts | 3 +- packages/core/src/kiwi/fig-file.ts | 3 +- src/components/SafariBanner.vue | 4 +- src/components/properties/FillSection.vue | 40 +++----------- src/components/properties/StrokeSection.vue | 60 +++++---------------- src/composables/use-chat.ts | 5 +- src/composables/use-multi-props.ts | 44 +++++++++++++++ src/constants.ts | 1 + 14 files changed, 86 insertions(+), 96 deletions(-) diff --git a/lint/plugin.js b/lint/plugin.js index 3a56867ed..d0c362b8d 100644 --- a/lint/plugin.js +++ b/lint/plugin.js @@ -223,7 +223,7 @@ const noTypeofWindowCheck = { }, create(context) { const file = context.filename ?? context.getFilename?.() - if (file?.endsWith('constants.ts') || file?.endsWith('fonts.ts')) return {} + if (file?.endsWith('constants.ts')) return {} return { BinaryExpression(node) { diff --git a/oxlint.json b/oxlint.json index 1bcb828c6..2b2693bce 100644 --- a/oxlint.json +++ b/oxlint.json @@ -118,7 +118,7 @@ "open-pencil/no-hand-rolled-color": "error", "open-pencil/no-raw-console-format": "off", "open-pencil/no-silent-catch": "error", - "open-pencil/no-typeof-window-check": "warn" + "open-pencil/no-typeof-window-check": "error" }, "overrides": [ { diff --git a/packages/core/src/canvaskit.ts b/packages/core/src/canvaskit.ts index 228fbae21..f9df230d6 100644 --- a/packages/core/src/canvaskit.ts +++ b/packages/core/src/canvaskit.ts @@ -1,5 +1,7 @@ import CanvasKitInit, { type CanvasKit } from 'canvaskit-wasm' +import { IS_BROWSER } from './constants' + let instance: CanvasKit | null = null export type GpuBackend = 'webgl' | 'webgpu' @@ -16,7 +18,7 @@ export interface CanvasKitOptions { } function detectBackend(): GpuBackend { - if (typeof window === 'undefined') return 'webgl' + if (!IS_BROWSER) return 'webgl' const params = new URLSearchParams(window.location.search) if (params.get('gpu') === 'webgpu' && 'gpu' in navigator) return 'webgpu' return 'webgl' @@ -45,7 +47,7 @@ export async function getCanvasKit(options?: CanvasKitOptions): Promise { - if (typeof window !== 'undefined') return `/${file}` + if (IS_BROWSER) return `/${file}` return file } diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 82dd5d6dd..394563773 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -1,6 +1,7 @@ import type { Color } from './types' -export const IS_TAURI = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window +export const IS_BROWSER = typeof window !== 'undefined' +export const IS_TAURI = IS_BROWSER && '__TAURI_INTERNALS__' in window export const BLACK: Color = { r: 0, g: 0, b: 0, a: 1 } export const TRANSPARENT: Color = { r: 0, g: 0, b: 0, a: 0 } diff --git a/packages/core/src/fig-export.ts b/packages/core/src/fig-export.ts index 8a4724770..7d097304e 100644 --- a/packages/core/src/fig-export.ts +++ b/packages/core/src/fig-export.ts @@ -1,6 +1,6 @@ import { zipSync, deflateSync, type Zippable } from 'fflate' -import { CANVAS_BG_COLOR, IS_TAURI } from './constants' +import { CANVAS_BG_COLOR, IS_BROWSER, IS_TAURI } from './constants' import { sceneNodeToKiwi, fractionalPosition, buildFigKiwi, buildFontDigestMap } from './kiwi-serialize' import { initCodec, getCompiledSchema, getSchemaBytes } from './kiwi/codec' import { stringToGuid } from './kiwi/kiwi-convert' @@ -271,7 +271,7 @@ export function compressFigDataSync( } function canUseWorker(): boolean { - return typeof Worker !== 'undefined' && typeof window !== 'undefined' + return typeof Worker !== 'undefined' && IS_BROWSER } function compressViaWorker( diff --git a/packages/core/src/figma-api.ts b/packages/core/src/figma-api.ts index b290d5261..00d34d366 100644 --- a/packages/core/src/figma-api.ts +++ b/packages/core/src/figma-api.ts @@ -7,6 +7,7 @@ import type { VariableValue } from './scene-graph' import type { Rect, Vector } from './types' +import { IS_BROWSER } from './constants' import { copyFills, copyStrokes, copyEffects } from './copy' import { @@ -337,8 +338,8 @@ export class FigmaAPI implements NodeProxyHost { const padding = 80 const contentW = maxX - minX + padding * 2 const contentH = maxY - minY + padding * 2 - const viewW = typeof window !== 'undefined' ? window.innerWidth : 1280 - const viewH = typeof window !== 'undefined' ? window.innerHeight : 720 + const viewW = IS_BROWSER ? window.innerWidth : 1280 + const viewH = IS_BROWSER ? window.innerHeight : 720 const zoom = Math.min(viewW / contentW, viewH / contentH, 1) this._viewport = { x: (minX + maxX) / 2, y: (minY + maxY) / 2, zoom } } diff --git a/packages/core/src/fonts.ts b/packages/core/src/fonts.ts index b11f5674e..97082c178 100644 --- a/packages/core/src/fonts.ts +++ b/packages/core/src/fonts.ts @@ -2,6 +2,7 @@ import type { CanvasKit, TypefaceFontProvider } from 'canvaskit-wasm' import { DEFAULT_FONT_FAMILY, + IS_BROWSER, CJK_FALLBACK_FAMILIES_MACOS, CJK_FALLBACK_FAMILIES_WINDOWS, CJK_FALLBACK_FAMILIES_LINUX, @@ -17,8 +18,6 @@ export interface FontInfo { postscriptName: string } -const IS_BROWSER = typeof window !== 'undefined' - const loadedFamilies = new Map() let fontProvider: TypefaceFontProvider | null = null diff --git a/packages/core/src/kiwi/fig-file.ts b/packages/core/src/kiwi/fig-file.ts index a3fe5ac8f..ac2cfde2f 100644 --- a/packages/core/src/kiwi/fig-file.ts +++ b/packages/core/src/kiwi/fig-file.ts @@ -1,6 +1,7 @@ import { unzipSync, inflateSync } from 'fflate' import { decompress as zstdDecompress } from 'fzstd' +import { IS_BROWSER } from '../constants' import { importNodeChanges } from './fig-import' import { decodeBinarySchema, compileSchema, ByteBuffer } from './kiwi-schema' import { isZstdCompressed } from './protocol' @@ -129,7 +130,7 @@ function parseViaWorker(buffer: ArrayBuffer): Promise { } export async function parseFigFile(buffer: ArrayBuffer): Promise { - if (typeof Worker !== 'undefined' && typeof window !== 'undefined') { + if (typeof Worker !== 'undefined' && IS_BROWSER) { const copy = buffer.slice(0) try { return await parseViaWorker(buffer) diff --git a/src/components/SafariBanner.vue b/src/components/SafariBanner.vue index 4dfdfd468..0162f293e 100644 --- a/src/components/SafariBanner.vue +++ b/src/components/SafariBanner.vue @@ -1,10 +1,10 @@