Skip to content

[전수조사/H] 빌드 플러그인·WASM 바인딩 #686

Description

@owjs3901

vite/webpack/next/rsbuild/bun 플러그인과 WASM 바인딩의 문제들입니다. 확인 예: Bun이 생성한 CSS를 버림, Bun의 사전 검사가 alias·StyleX 파일을 건너뜀, Rsbuild가 오래된 CSS를 제공.

전체 계획과 결정 사항은 추적 이슈(#682)에 있습니다. 조사 기준 커밋: 77daad74 (현재 main d0b84255).

발견 항목

심각도: P0 = 흔한 사용에서 조용히 틀리거나 크래시, P1 = 흔한 패턴이 명시적 에러로 막히거나 드물게 조용히 틀림, P2 = 드문 경우·도구/문서 불일치, P3 = 있으면 좋음.

필수 (4)

  • PLG-01 (P0, WRONG) Bun discards generated CSS during bundling
    • 근거: packages/bun-plugin/src/plugin.ts:74-80,127-138 resolves every generated stylesheet into { contents: '', loader: 'js' }. Bun 1.4.2 officially supports an onLoad result with loader: 'css', and JavaScript CSS imports are combined into emitted .css; the plugin instead turns the import into an empty JS module.
    • 수정 방향: Return current sheet contents with loader:'css' for Bun.build, while using a separate runtime/test behavior if runtime imports truly need an empty module. Add an end-to-end Bun.build assertion for the emitted CSS asset.
  • PLG-02 (P0, CRASH) Bun's pre-parse gate skips every compatibility alias and StyleX
    • 근거: compat-detection-probe.ts: hasDevupUI(...,'@devup-ui/react') returned false for @emotion/styled, @emotion/react/jsx-runtime, classic Emotion css-prop pragma, and @stylexjs/stylex. Direct codeExtract transformed Emotion styled and StyleX successfully, proving the gate—not the extractor—drops them. Published placeholders/aliases then execute at runtime or remain unc…
    • 수정 방향: Make the cheap gate alias-aware (including native StyleX and JSX-runtime/css-prop needles), then parse only candidates. Pass the merged alias table to a gate API or perform the measured substring precheck in TypeScript.
  • PLG-03 (P0, WRONG) Rsbuild serves stale CSS after same-length/shorter edits and across plugin instances
    • 근거: packages/rsbuild-plugin/src/plugin.ts:68 declares module-global globalCss; line 315 updates it only when globalCss.length < css.length; line 199 returns the cached value. red→tan or deleting declarations cannot update it, and a second build in the same process inherits the first build's longest sheet.
    • 수정 방향: Remove the length heuristic/module global. Resolve CSS from getCss(...) at CSS load time and reset build state at setup/build boundaries; compare content, not length.
  • PLG-04 (P0, NONDET) Webpack and Rsbuild can snapshot shared CSS before extraction completes
    • 근거: Webpack css-loader.ts:5-8 returns getCss whenever the generated CSS module is first built; the only final hook (plugin.ts:311-321) writes a disk file after compilation and cannot rewrite emitted assets. Prewarm is conditional and only scans src/, excluding included libraries. Rsbuild returns globalCss from its CSS transform and has no generateBundle equivalent. Vite…
    • 수정 방향: Add a completion coordinator/final asset rewrite, or pre-extract the exact full compiled graph including dynamic and include modules before any CSS module is served.

권장 (26)

  • INF-06 (P1, WRONG) codeExtractWithoutSourceMap generates a source map — 재검증 후 P0→P1: 출력은 정상, source map을 불필요하게 생성
    • 근거: public wrapper at bindings/devup-ui-wasm/src/lib.rs:572-596 calls SourceMapMode::Generate; declaration names it codeExtractWithoutSourceMap (pkg/index.d.ts:27).
    • 수정 방향: pass SourceMapMode::Skip and add a JS-boundary assertion that map is absent.
  • INF-07 (P1, WRONG) Resolver errors are silently converted to unresolved modules
    • 근거: call_module_resolver uses .ok()? for JS call/reflection/conversion (bindings/.../lib.rs:473-490).
    • 수정 방향: distinguish resolver-returned null from thrown/malformed results and propagate contextual JS errors.
  • INF-08 (P1, CRASH) Rust panics can become opaque WASM traps
    • 근거: no installed panic hook or catch boundary; optional console_error_panic_hook is unused.
    • 수정 방향: remove reachable panic paths and install a documented panic hook; do not claim ordinary error conversion for panics.
  • NXT-04 (P1, WRONG) CSS loader reuses one app's coordinator port for every app
    • 근거: css-loader.ts:23-30 stores a single cachedPort, not a map keyed by port-file path. css-port-cache.ts started two coordinators and observed {firstResult:"FIRST",secondResult:"FIRST",expectedSecond:"SECOND"}.
    • 수정 방향: Cache by canonical port-file path, or re-read the tiny port file per compilation with health validation.
  • NXT-05 (P1, CRASH) Source loader cannot recover after coordinator restart
    • 근거: loader.ts:29,43-49 permanently caches the first port for a path; connection errors at loader.ts:121 go straight to the callback. loader-stale-port.ts rewrote the same port file after restart and observed ECONNREFUSED instead of the second coordinator's output.
    • 수정 방향: On connect failure, evict the cached port, re-read/health-check the current file, and retry within a bounded deadline.
  • NXT-06 (P1, ERR-gap) Coordinator HTTP clients can hang a build indefinitely
    • 근거: loader.ts:78-124 and css-loader.ts:33-66 set no request/connect timeout, deadline, or abort handling. The coordinator's internal maxWaitMs cannot help if the process accepts a connection but never responds.
    • 수정 방향: Set bounded request deadlines, destroy timed-out sockets, and return a contextual loader error; retry only idempotent startup/restart failures.
  • NXT-07 (P1, WRONG) One process-global coordinator makes two in-process apps mutually exclusive
    • 근거: coordinator.ts:172,350-359 closes any previous server regardless of app/port path. two-coordinators.ts observed first health 200, then first ECONNREFUSED after starting the second while the second remained 200.
    • 수정 방향: Registry coordinators by normalized project/dist key with reference-counted close; never replace an unrelated instance.
  • NXT-09 (P1, WRONG) Deleted or renamed files can leave active global CSS forever
    • 근거: startup imports old sheet/maps (plugin.ts:240-250). Coordinator exposes only /health, /css, and /extract (coordinator.ts:396-663); there is no remove/invalidate operation or fresh-state rebuild in dev. A deleted file never posts its old filename again, so its stored atoms/globalCss remain.
    • 수정 방향: Track the live module set and delete a file's sheet/map contribution on invalidation/removal; validate persisted state against current files at startup.
  • NXT-12 (P1, PERF) Production prewarm extracts unreachable files and can fail on dead source
    • 근거: prewarm.ts:58-64,73-80 explicitly adds every graph source file, not just route-reachable files; plugin.ts:387-426 synchronously reads/extracts each without a per-file reachability check or fallback. plugin.test.ts:1033-1082 locks this behavior.
    • 수정 방향: Prewarm the proven compiled closure plus specifically discovered MDX/template/package dependencies; add an explicit opt-in for conservative whole-tree prewarm rather than making it the default.
  • NXT-14 (P1, WRONG) Persisted sheet/class/file maps are restored non-transactionally
    • 근거: one try imports sheet, then class map, then file map (plugin.ts:244-250). If class/file JSON is missing or corrupt after sheet import, the catch leaves a mixed in-memory state. Writes are separate files (coordinator.ts:573-578) and are not an atomic multi-file transaction.
    • 수정 방향: Read and parse every snapshot first, validate a shared generation/version, then import all; write via generation directories plus atomic pointer/rename.
  • NXT-15 (P1, WRONG) Coordinator completion fallbacks silently serve partial CSS
    • 근거: incomplete buckets resolve after quiet time or maxWaitMs and still return HTTP 200 (coordinator.ts:319-342); base completion similarly fails open (coordinator.ts:229-259). Tests explicitly expect partial CSS at coordinator.test.ts:1380-1411,1544-1634.
    • 수정 방향: In production, fail the CSS loader/build with missing filenames instead of serving partial output. Keep a separately labeled dev-only fallback if necessary.
  • NXT-19 (P1, WRONG) Turbopack does not transform MDX source files
    • 근거: source rule *.{tsx,ts,jsx,js,mjs} at plugin.ts:573 excludes .mdx; prewarm.ts:7 excludes .mdx. The extra exclusion string passed at plugin.ts:265 does not add a loader rule. Devup components/compat APIs authored directly in MDX can therefore reach runtime placeholders.
    • 수정 방향: Add an MDX-aware post-transform path/rule proven against Next's MDX loader ordering, and include MDX in graph/prewarm discovery.
  • PLG-05 (P1, WRONG) include package names are unsafe raw regular expressions
    • 근거: createNodeModulesExcludeRegex interpolates names without regex escaping. Probe: include:['foo.bar'] incorrectly included node_modules/fooXbar; include:['pkg'] also included node_modules/@scope/pkg; foo+bar failed to include the literal package. Windows and Bun/pnpm store paths otherwise worked.
    • 수정 방향: Parse the package segment after each node_modules, compare exact normalized package names, or escape every regex token and anchor unscoped/scoped package boundaries separately. Precompute once per plugin.
  • PLG-06 (P1, CRASH) Modern module extensions and MDX fall through untransformed
    • 근거: filter-resolver-probe.ts: Vite/webpack/Rsbuild and import-graph accept only ts/tsx/js/jsx/mjs; Bun additionally skips ordinary local .js. All omit .mts, .cts, .cjs, raw .mdx; import graph omits the same modern TS modules. Devup placeholders or aliased APIs remain in emitted code.
    • 수정 방향: Share one explicit extension set across transform filters, graph enumeration, resolver, and entry filtering. Add MDX integration after MDX compilation (not against raw MDX syntax).
  • PLG-07 (P1, WRONG) Removed/renamed modules leave stale styles during development
    • 근거: Vite watchChange handles only devup.json; webpack persists sheet/class/file maps but has no deletion hook; Rsbuild/Bun register no removal handling. codeExtract removes/replaces styles only when called for the same filename; a deleted filename is never called again.
    • 수정 방향: Add an engine remove-file API, call it on delete/rename, invalidate the corresponding generated CSS module, and persist the pruned maps in webpack watch mode.
  • PLG-08 (P1, WRONG) Theme inheritance dependencies are not watched; deletion is ignored
    • 근거: loadDevupConfig* recursively reads extends, but plugins register/watch only devupFile. Vite hot hooks require the top-level file still to exist; webpack captures existence at apply; Rsbuild/Bun have no config watch hook. Editing a base theme or deleting devup.json leaves old CSS/types/default theme.
    • 수정 방향: Return resolved config dependencies from the loader, register all of them, and treat deletion as an empty config followed by CSS/type regeneration and reload.
  • PLG-10 (P1, NONDET) Rsbuild cold-start CSS directory creation races its first write
    • 근거: packages/rsbuild-plugin/src/plugin.ts:96-103 starts mkdir(cssDir) and writeFile(cssDir/devup-ui.css) in the same Promise.all. Vite has the same issue documented and fixed sequentially at vite plugin.ts:248-255.
    • 수정 방향: Await recursive mkdir before starting any write, matching Vite.
  • PLG-11 (P1, WRONG) Resolver and graph ignore inherited tsconfig paths
    • 근거: Probe fixture used tsconfig.json extending a base config containing baseUrl/paths; createModuleResolver('@base/value',...) was unresolved. readPathAliases parses only the immediate JSON (import-graph.ts:867-912). Atom/collapse reach and build-time imported constants therefore diverge from the bundler.
    • 수정 방향: Resolve the tsconfig inheritance chain with cycle detection and TS-compatible merge semantics (or use the installed get-tsconfig/resolver stack).
  • PLG-12 (P1, WRONG) Module resolver selects the wrong conditional-export branch
    • 근거: Probe package exports { browser:'./browser.js', import:'./import.js' }; resolver returned import.js. conditionTarget uses fixed import,module,default,require,node and has no client/server/development/react-server condition input (import-graph.ts:1007-1091).
    • 수정 방향: Accept bundler-provided conditions and preserve exports object order; preferably delegate to each bundler resolver rather than maintain a partial resolver.
  • PLG-13 (P1, WRONG) Atom-hoist graph roots miss real source layouts
    • 근거: Vite detects both src and app for file seeding but atomHoist picks only sourceDirs[0]; webpack and Rsbuild hardcode <cwd>/src. Root-level app/, projects with both roots, custom source roots, and included libraries are omitted.
    • 수정 방향: Build a graph over all bundler entries and their resolved closures, or accept configured source roots; never pick only the first existing conventional directory.
  • PLG-18 (P1, WRONG) Relative options ignore bundler project roots/contexts
    • 근거: defaults are resolved when plugin constructors run; webpack graph/loader use process.cwd() rather than compiler.context; Rsbuild uses process.cwd(); only Vite's resolver/scan uses config.root, while its config/data paths still do not. Monorepo configs launched from a parent read/write the wrong devup.json/df and resolve imports under the wrong project.
    • 수정 방향: Delay normalization until setup/configResolved and resolve every relative option against the bundler's resolved root/context; pass that root to loaders/resolvers explicitly.
  • PLG-19 (P1, ERR-gap) Config errors are either silent or recurse without a bound
    • 근거: malformed JSON returns {} with no diagnostic (load-config.ts:48-57), silently clearing theme output. Recursive extends has no visited set (86-139), so a→b→a recurses until stack exhaustion/promise growth.
    • 수정 방향: Return typed parse/load errors with file paths; track a canonical-path stack and report a readable cycle.
  • PLG-20 (P1, WRONG) Bun ignores imported build-time dependency tracking
    • 근거: Bun loadSourceFile discards codeExtract().dependencies; Vite, webpack and Rsbuild register the same values with their watcher APIs. Changes to imported token/style modules can leave transformed output and CSS stale.
    • 수정 방향: Return/register dependency watch files using Bun's current plugin API, or ensure the imported modules are explicit graph dependencies that invalidate the transformed importer.
  • PLG-21 (P1, WRONG) Rsbuild's shared CSS cache group silently does nothing under default splitChunks
    • 근거: injector initializes optimization but only adds the group when splitChunks is already an object (plugin.ts:227-250). Its unit test explicitly confirms missing/false remains undefined. The option docs promise one shared CSS chunk so atom hoisting deduplicates MPA entries.
    • 수정 방향: Compose a valid splitChunks object when absent; handle false explicitly (error or documented opt-out) instead of silently disabling the promised behavior.
  • PLG-22 (P1, CRASH) Library-mode demo emits runtime placeholders without documenting the consumer requirement
    • 근거: allowed apps/vite-lib build produced dist/index.js importing Box/Flex/Text and rendering them directly because DevupUI({extractCss:false}) skips transform. It works only if every consuming app lists the published library in include; plugin READMEs do not document this library publishing/consuming contract.
    • 수정 방향: Document and test the two supported modes: precompiled library with emitted CSS, or intentionally uncompiled library requiring consumer include. Consider emitting package metadata/helper config to make the latter automatic.
  • PLG-09 (P2, WRONG) Empty themes leave stale generated declaration files
    • 근거: Vite plugin.ts:232-243, webpack plugin.ts:124-132, and Rsbuild plugin.ts:81-91 only write theme.d.ts when interfaceCode is truthy; none removes or overwrites a previous declaration when tokens disappear. Bun always writes and is not affected.
    • 수정 방향: Always overwrite theme.d.ts with the generated empty augmentation, or remove it atomically when no interface is needed.

선택 (11)

  • NXT-10 (P2, MISMATCH) debug is a public option but does nothing under Turbopack
    • 근거: DevupUIBasePluginOptions exposes debug (plugin-utils/shared.ts:96-107), but Turbo destructuring omits it (plugin.ts:134-145) and the loaded WASM functions omit setDebug (plugin.ts:209-228). Webpack receives it via {...options}.
    • 수정 방향: Destructure debug, include it in the setup cache key, and call setDebug(debug ?? false) before imports/extraction.
  • NXT-11 (P2, WRONG) Turbopack does not watch theme data files
    • 근거: coordinator mode returns at loader.ts:145-194 after adding only the port file; theme/state dependencies are added only in non-coordinator mode (loader.ts:209-227). css-loader.ts:83-120 likewise fetches coordinator CSS without adding themeFile. Theme registration occurs only during config setup (plugin.ts:252-257).
    • 수정 방향: Add the resolved config and all extends files as loader dependencies; reload/register theme and invalidate generated CSS when they change.
  • NXT-13 (P2, PERF) Shared RSC/client modules are re-extracted in development
    • 근거: dev has no prewarmed-output cache (plugin.ts:371), and every POST invokes WASM (coordinator.ts:462-483). Next may compile a shared module separately in server and client graphs and does not guarantee order: https://nextjs.org/docs/app/guides/server-and-client-boundary
    • 수정 방향: Add a bounded content-hash transform cache in the coordinator and invalidate by source/options/dependency changes.
  • NXT-18 (P2, WRONG) Graph/prewarm planning failures are silently downgraded
    • 근거: the broad catch at plugin.ts:352-359 discards the exception and silently leaves identity canonical mapping, no atom hoist, and idle completion. No warning reaches Next's terminal/overlay.
    • 수정 방향: Catch only recognized optional-resolution failures; otherwise throw in production or emit a contextual warning naming the file/phase and disabled guarantees.
  • NXT-20 (P2, WRONG) Valid ESM/CJS TypeScript module extensions bypass Turbopack extraction
    • 근거: plugin.ts:573 and prewarm.ts:7 omit .mts, .cts, and .cjs, although imported application modules can use those extensions.
    • 수정 방향: Extend rule, graph parser, and prewarm filters together; add fixtures for each extension to prevent coordinator waits on untransformable graph members.
  • PLG-23 (P2, PERF) Atom-hoist setup reparses every source tree twice (webpack can do a third pass)
    • 근거: plugin-utils explicitly supports one buildStaticImportGraph shared by builders (import-graph.ts:80-116), but Vite/webpack/Rsbuild call buildCanonicalMap then computeFileReach without graph, causing two full list/read/parse/resolve passes. Webpack then listSourceFiles+read+codeExtract prewarm.
    • 수정 방향: Build one graph and pass it to both consumers; reuse its sorted files/external imports for prewarm.
  • PLG-24 (P2, PERF) Bun performs quadratic, blocking stylesheet I/O
    • 근거: every matching module synchronously serializes and writes the entire growing global sheet (bun plugin.ts:92-107). With N modules, early rules are rewritten O(N) times and synchronous disk I/O blocks all module loading.
    • 수정 방향: Serve CSS from the loader and write once in an end/finalization hook; for runtime watch, debounce/coalesce revisions without synchronous writes.
  • PLG-27 (P2, MISMATCH) Best-effort catches silently disable determinism/hoisting
    • 근거: Vite seed and atom prepass, webpack graph/prewarm, and Rsbuild atom prepass use empty catches or comments only. A resolver/parser/I/O defect silently falls back to arrival-order IDs or disables requested atomHoist, producing a successful but different build.
    • 수정 방향: Fail builds for requested atomHoist/setup invariants; for optional seeding fallback, emit one structured warning with the root cause and deterministic impact.
  • PLG-29 (P2, MISMATCH) Bun unconditionally enables debug output
    • 근거: packages/bun-plugin/src/plugin.ts:125 calls setDebug(true) and exposes no debug option, unlike all sibling plugins whose default is false. This changes class naming/output size and makes Bun output differ for identical source.
    • 수정 방향: Add debug?:boolean default false and set it on every initialization.
  • NXT-17 (P3, MAINT) Exit cleanup claims to flush async writes but cannot await them
    • 근거: plugin.ts:488-491 calls synchronous close from process.on('exit'); coordinator.ts:673-680 fire-and-forgets flushPendingWrites(). Node's exit event cannot keep the event loop alive for asynchronous work: https://nodejs.org/api/process.html#event-exit
    • 수정 방향: Await drain during a pre-exit lifecycle hook/build completion path; keep exit cleanup synchronous and best-effort only.
  • PLG-28 (P3, PERF) createNodeModulesExcludeRegex is rebuilt for every transformed module
    • 근거: Vite line 530 and Rsbuild line 260 call the regex factory inside hot transform callbacks although include is immutable.
    • 수정 방향: Compile once when constructing/setuping the plugin (after fixing PLG-05).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions