Skip to content

[web-framework] collectDependencyVersions에서 svelte export condition을 가진 패키지 resolve 실패 #169

@clomia

Description

@clomia

요약

@apps-in-toss/web-framework를 사용하여 SvelteKit 기반 웹 앱을 빌드할 때, svelte export condition만 정의된 패키지(예: svelte-easy-crop)가 있으면 npx granite build 패키징 단계에서 실패합니다.

Vite 빌드 자체는 성공하지만, .ait 파일 생성을 위한 의존성 버전 수집 단계에서 esbuild가 해당 패키지를 resolve하지 못합니다.


환경

항목 버전
@apps-in-toss/web-framework 1.5.2
@apps-in-toss/plugins (web-framework 내장)
Node.js 22.x
SvelteKit 2.49.2
Svelte 5.45.8
Vite 7.2.7
OS macOS (Darwin 25.1.0)

재현 방법

1. SvelteKit 프로젝트에 앱인토스 설정

npm install @apps-in-toss/web-framework
npx ait init

2. svelte export condition만 있는 패키지 설치

npm install svelte-easy-crop

3. 해당 패키지를 컴포넌트에서 사용

<script>
  import Cropper from 'svelte-easy-crop';
</script>

<Cropper image={imageUrl} bind:crop bind:zoom aspect={5/8} />

4. 빌드 실행

npx granite build

에러 메시지

> app@0.0.1 build:toss
> BUILD_TARGET=toss VITE_BUILD_TARGET=toss vite build

vite v7.2.7 building for production...
✓ 6428 modules transformed.
✓ built in 15.05s

> Using @sveltejs/adapter-static
  Wrote site to "build"
  ✔ done

◇  Building ios
◇  Building android

Internal Error: Build failed with 1 error:
<stdin>:10:7: ERROR: [plugin: collect-package-version] Could not resolve "svelte-easy-crop"
    at failureErrorWithLog (/path/to/node_modules/@apps-in-toss/plugins/node_modules/esbuild/lib/main.js:1463:15)
    ...

원인 분석

1. svelte-easy-croppackage.json exports 필드

{
  "name": "svelte-easy-crop",
  "version": "5.0.0",
  "type": "module",
  "svelte": "./dist/index.js",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "svelte": "./dist/index.js"
    }
  }
}

exportssvelte condition만 정의되어 있고 default가 없습니다.

2. @apps-in-toss/pluginscollectDependencyVersions 함수

// @apps-in-toss/plugins/dist/index.js

async function collectDependencyVersions(rootDir) {
  const packageJsonPath = path.join(rootDir, "package.json");
  const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
  const [dependencies, devDependencies] = await Promise.all([
    resolvePackageVersions(rootDir, Object.keys(packageJson.dependencies)),
    resolvePackageVersions(rootDir, Object.keys(packageJson.devDependencies)),
  ]);
  return { dependencies, devDependencies };
}

async function resolvePackageVersions(rootDir, packageNames) {
  const results = {};
  await esbuild.build({
    stdin: { contents: createVirtualEntry(packageNames) },
    bundle: true,
    write: false,
    logLevel: "silent",
    plugins: [
      {
        name: "collect-package-version",
        setup(build) {
          // ... resolve 로직
        },
      },
    ],
  });
  return results;
}

이 함수는 package.json의 모든 dependenciesdevDependencies를 esbuild로 resolve하여 버전 정보를 수집합니다.

3. 문제 발생 지점

esbuild는 기본적으로 svelte export condition을 인식하지 못합니다. Vite/SvelteKit은 svelte condition을 지원하므로 빌드가 성공하지만, collectDependencyVersions에서 사용하는 esbuild는 이를 처리하지 못해 resolve 실패가 발생합니다.


영향 범위

다음 조건을 만족하는 모든 패키지에서 동일한 문제가 발생합니다:

  1. exports 필드에 svelte condition만 정의
  2. default condition 없음
  3. 프로젝트의 dependencies 또는 devDependencies에 포함

Svelte 생태계의 많은 패키지들이 이 패턴을 따르고 있어, SvelteKit 기반 앱인토스 앱에서 광범위하게 영향을 받을 수 있습니다.

영향받는 패키지 예시:

  • svelte-easy-crop
  • 기타 svelte condition만 사용하는 Svelte 컴포넌트 라이브러리들

제안하는 해결 방법

방법 1: esbuild conditions 옵션 추가 (권장)

resolvePackageVersions 함수의 esbuild 설정에 conditions 옵션을 추가합니다:

await esbuild.build({
  stdin: { contents: createVirtualEntry(packageNames) },
  bundle: true,
  write: false,
  logLevel: "silent",
  conditions: ['svelte', 'node', 'import', 'default'],  // 추가
  plugins: [...]
});

방법 2: resolve 실패 시 graceful fallback

패키지 resolve가 실패해도 빌드를 중단하지 않고 경고만 출력하는 방식:

build.onResolve({ filter: /.*/ }, async (args) => {
  // ...
  if (result.errors.length) {
    console.warn(
      `Warning: Could not resolve ${args.path}, skipping version collection`
    );
    return { path: args.path, external: true }; // 외부 모듈로 처리
  }
  // ...
});

방법 3: 프레임워크별 conditions 자동 감지

프로젝트의 package.json이나 설정 파일을 분석하여 Svelte/Vue/React 등 프레임워크를 감지하고, 해당 프레임워크의 export condition을 자동으로 추가하는 방식.


현재 워크어라운드

1. postinstall 스크립트로 패키지 패치

// scripts/patch-svelte-easy-crop.js
const fs = require("fs");
const path = require("path");

const pkgPath = path.join(
  __dirname,
  "../node_modules/svelte-easy-crop/package.json"
);
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));

pkg.exports["."].default = pkg.exports["."].svelte;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
// package.json
{
  "scripts": {
    "postinstall": "node scripts/patch-svelte-easy-crop.js"
  }
}

2. 수동 .ait 패키징

Vite 빌드가 성공하므로 build/ 디렉토리를 수동으로 패키징하는 방법도 가능하나, 메타데이터 구조를 정확히 맞춰야 하므로 권장하지 않습니다.


추가 정보

성공하는 단계

  • npm run build:toss (Vite 빌드): ✅ 성공
  • npx granite dev (개발 서버): ✅ 성공
  • Static adapter로 build/ 디렉토리 생성: ✅ 성공
  • iOS/Android 번들 생성 (lebit.ios.js, lebit.android.js): ✅ 성공

실패하는 단계

  • .ait 파일 생성을 위한 collectDependencyVersions: ❌ 실패

빌드 결과물 구조 (실패 전)

build/
├── lebit.android.js
├── lebit.android.js.map
├── lebit.ios.js
├── lebit.ios.js.map
└── web/
    ├── _app/
    ├── index.html
    └── ... (static assets)

번들 자체는 정상적으로 생성되었으며, 의존성 버전 수집 단계에서만 실패합니다.


관련 코드 위치

  • @apps-in-toss/plugins/src/utils/collectDependencyVersions.ts
  • @apps-in-toss/web-framework/src/cli/index.ts (appsInTossCreateArtifact 함수)

참고 링크

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions