# 2024년 5월 9일 작업일지

### SWC 버그 수정 1

* [https://github.com/swc-project/swc/pull/8938](https://github.com/swc-project/swc/pull/8938)
    

간단해보였는데 regression 때문에 생각보단 복잡했다. 최대한 regrssion을 줄인 뒤 리뷰 기다리기로 했다.

### 터보팩 Tree shaking PR

* [https://github.com/vercel/turbo/pull/7986](https://github.com/vercel/turbo/pull/7986)
    

일단 `uri_of_module` 대신

```rust

const TURBOPACK_PART_IMPORT_SOURCE: &str = "__TURBOPACK_PART__";
```

를 임포트 경로로 쓰게 바꿨고, `SplitResult`의 `url_of_module: Atom`을 `asset_ident: Vc<AssetIdent>` 로 바꿨다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715219159199/db2c077e-05bb-4cf8-acd3-f19e21faf5d7.png align="center")

당연히 `__TURBOPACK__PART__`를 찾을 수 없다는 에러가 떴고, `resolve` 관련 로직에 로그 찍는 코드를 추가했다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715228630402/bffd0792-79b2-4143-9166-f6e2d9df4ba5.png align="center")

로그를 몇번 찍어보면서 처리 로직을 넣기 적당해보이는 곳을 찾아서 거기다가 `__TURBOPACK_PART__` 처리 코드를 넣었다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715228645830/7611316d-eb5f-4665-a18d-9afa38944678.png align="center")

그런데 `turbopack-core`에 넣는 것보다는 `turbopack` 에 넣는 게 맞는 것 같아서 위치를 바꿨다. 근데 여기서 `EcmascriptModuleAsset`을 가져오는 게 쉽지 않아보여서 아예 접근을 다르게 하기로 했다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715228672622/9983cd73-fbc9-4b73-b5aa-eb6c9c7b2dc4.png align="center")

`__TURBOPACK_PART__` 를 경로로 가지는 Reference 는 `EsmAssetReference` 였기에 이 타입이 `ModuleReference`로 바뀌는 부분을 패치해줬다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715225717090/113ef6ef-fb94-41e1-a4d4-0b4e1b3b7900.png align="center")

결과는 반쯤 성공적이었다. 이제 서로 다른 모듈의 Module splitting 결과물이 섞이는 버그는 더 이상 발생하지 않기에 `panic!`이나 `bail!` 이 발생하지 않았고, 대신 자바스크립트 실행 결과물로 exception이 발생했다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715226112504/99a8471d-0871-43fe-b655-76622632ba0e.png align="center")

`<module evaluation>` 모듈에서 exception이 떴는데, 보니까 module splitting 패스의 전역 변수 처리에 버그가 있는 것 같았다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715226308160/79cde67b-2bd9-4027-b8dd-ff61d62b373b.png align="center")

터지는 코드를 보면 `exports`가 정의되지 않았다는 것을 알 수 있다.

저 파일의 원본을 찾기 위해 `runtime.js` 파일을 vscode를 이용해서 찾아봤는데 존재하지 않아서 `runtime.ts`로 찾아보니까 있었다.

```typescript
import RefreshRuntime from 'react-refresh/runtime'
import RefreshHelpers from './internal/helpers'

export type RefreshRuntimeGlobals = {
  $RefreshReg$: (type: unknown, id: string) => void
  $RefreshSig$: () => (type: unknown) => unknown
  $RefreshInterceptModuleExecution$: (moduleId: string) => () => void
  $RefreshHelpers$: typeof RefreshHelpers
}

declare const self: Window & RefreshRuntimeGlobals

// Hook into ReactDOM initialization
RefreshRuntime.injectIntoGlobalHook(self)

// Register global helpers
self.$RefreshHelpers$ = RefreshHelpers

// Register a helper for module execution interception
self.$RefreshInterceptModuleExecution$ = function (webpackModuleId) {
  var prevRefreshReg = self.$RefreshReg$
  var prevRefreshSig = self.$RefreshSig$

  self.$RefreshReg$ = function (type, id) {
    RefreshRuntime.register(type, webpackModuleId + ' ' + id)
  }
  self.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform

  // Modeled after `useEffect` cleanup pattern:
  // https://react.dev/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed
  return function () {
    self.$RefreshReg$ = prevRefreshReg
    self.$RefreshSig$ = prevRefreshSig
  }
}
```

ESM이고, `exports` 에 `__esModule` 속성을 추가하는 건 common js 패스가 하는 일이다. `dist` 폴더가 있길래 보니까 `runtime.js`가 있었고, 내용물은

```javascript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const runtime_1 = __importDefault(require("react-refresh/runtime"));
const helpers_1 = __importDefault(require("./internal/helpers"));
// Hook into ReactDOM initialization
runtime_1.default.injectIntoGlobalHook(self);
// Register global helpers
self.$RefreshHelpers$ = helpers_1.default;
// Register a helper for module execution interception
self.$RefreshInterceptModuleExecution$ = function (webpackModuleId) {
    var prevRefreshReg = self.$RefreshReg$;
    var prevRefreshSig = self.$RefreshSig$;
    self.$RefreshReg$ = function (type, id) {
        runtime_1.default.register(type, webpackModuleId + ' ' + id);
    };
    self.$RefreshSig$ = runtime_1.default.createSignatureFunctionForTransform;
    // Modeled after `useEffect` cleanup pattern:
    // https://react.dev/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed
    return function () {
        self.$RefreshReg$ = prevRefreshReg;
        self.$RefreshSig$ = prevRefreshSig;
    };
};
//# sourceMappingURL=runtime.js.map
```

이었다. 이 파일은 CJS 모듈에서 암시적으로 정의되는 `exports` 라는 변수에 의존하는 코드이고, 소스 뷰에서 컴파일된 모듈을 보니까 내용물이

```javascript
"[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/compiled/@next/react-refresh-utils/dist/runtime.js [client] (ecmascript) <module evaluation>": (({ r: __turbopack_require__, f: __turbopack_module_context__, i: __turbopack_import__, s: __turbopack_esm__, v: __turbopack_export_value__, n: __turbopack_export_namespace__, c: __turbopack_cache__, M: __turbopack_modules__, l: __turbopack_load__, j: __turbopack_dynamic__, P: __turbopack_resolve_absolute_path__, U: __turbopack_relative_url__, R: __turbopack_resolve_module_id_path__, g: global, __dirname, k: __turbopack_refresh__ }) => (() => {
"use strict";

__turbopack_esm__({
    "__importDefault": ()=>__importDefault,
    "runtime_1": ()=>runtime_1
});
"module evaluation";
"use strict";
var __importDefault = this && this.__importDefault || function(mod) {
    return mod && mod.__esModule ? mod : {
        "default": mod
    };
};
Object.defineProperty(exports, "__esModule", {
    value: true
});
const runtime_1 = __importDefault(__turbopack_require__("[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/compiled/react-refresh/runtime.js [client] (ecmascript) <facade>"));
// Hook into ReactDOM initialization
runtime_1.default.injectIntoGlobalHook(self);
;
;

})()),
```

이었다. 글로벌 변수에 관련된 유닛 테스트를 만들어둔 게 있었어서 그 테스트는 제대로 도는지 다시 한번 확인했는데, 안 돌았다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715230814742/7a1720bc-2556-4f4f-8242-0e97dae72539.png align="center")

`only: SyntaxContext` 를 통해 top-level 바인딩만 처리하도록 변경한 게 문제라는 걸 깨닫고 unresolved 변수들도 처리할 수 있게 `only: [SyntaxContext; 2]`로 바꾸고 넘기는 인자를 `[top_level_ctxt, unresolved_ctxt]` 로 바꾸었다.

그러니까 유닛 테스트에서 뜨는 에러가 바뀌었는데, `turbopack-node/js/src/ipc/index.ts` 를 제대로 처리 못하는 것 같아서 유닛 테스트를 추가했다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715241095514/7ac5a9cd-eef3-4df2-b4b8-f926e10b6e2b.png align="center")

...

테스트하니까 나온 그래프인데 어차피 못 알아볼 것을 알아서 그냥 무시하고 코드를 비교해서 `createIpc` 이슈를 고쳤다.

```rust

                ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
                    decl: Decl::Fn(f),
                    ..
                }))
                | ModuleItem::Stmt(Stmt::Decl(Decl::Fn(f))) => {
                    let id = ItemId::Item {
                        index,
                        kind: ItemIdItemKind::Normal,
                    };
                    ids.push(id.clone());

                    let vars = ids_used_by(&f.function, [unresolved_ctxt, top_level_ctxt]);
                    let var_decls = {
                        let mut v = IndexSet::with_capacity_and_hasher(1, Default::default());
                        v.insert(f.ident.to_id());
                        v
                    };
                    items.insert(
                        id,
                        ItemData {
                            is_hoisted: true,
                            eventual_read_vars: vars.read,
                            eventual_write_vars: vars.write,
                            // 아랫줄 추가
                            write_vars: var_decls.clone(),
                            var_decls,
                            content: ModuleItem::Stmt(Stmt::Decl(Decl::Fn(f.clone()))),
                            ..Default::default()
                        },
                    );
                }
```

`write_vars`가 올바르지 않은 값을 가지는 것이 문제의 원인이었다. 유닛 테스트에서 뜨는 에러는 바뀌었는데 유닛 테스트 직접 디버깅은 힘들 것 같아서 다시 `app-playground`를 이용해서 디버깅했다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715244184304/41160adb-92af-4238-906b-28be9e1dbfa0.png align="center")

이상한 파싱 실패가 뜨길래 로그 메시지를 추가했고, `Program::Script` 가 파싱 에러로 처리되고 있는 걸 발견해서 `clone` 좀 하더라도 작동은 하게 코드를 수정했다.

```plaintext
 ✓ Compiled /_error in 563ms
 ⨯ TypeError: _interop_require_default._ is not a function
    at /Users/kdy1/projects/app-playground/.next/server/chunks/ssr/node_modules__pnpm_6dc888._.js:424:74
    at [project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/shared/lib/constants.js [ssr] (ecmascript) <module evaluation> (/Users/kdy1/projects/app-playground/.next/server/chunks/ssr/node_modules__pnpm_6dc888._.js:636:3)
```

그러고 나니까 오류가 바뀌었다. 읽어보니까 `@swc/helpers`의 `cjs/interop_require_default`에서 `_`를 가져오지 못했다는 것이었는데, 왜 cjs를 쓰는지는 모르겠지만 cjs 임포트가 실패하는 이유는 알 것 같았다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715245835231/8dc65c9a-a3a5-4f91-9617-9f9666fcc42a.png align="center")

생성된 코드를 확인하니 확실해졌는데, 이해 안 되는 점이 하나 있었다. 분명 임포트 대상은 CJS 모듈인데 ESM Export 바인딩이 생성됐다는 점이었다.

```javascript
"use strict";

exports._ = exports._interop_require_default = _interop_require_default;
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : { default: obj };
}
```

* 원본 코드
    

그래서 원본 코드를 봤고, ESM export는 없는 게 맞았다. 즉, 둘 중 하나다.

* CJS 파일을 렉싱해서 ESM export를 만들어냈다
    
* `package.json`의 값을 읽어와서 ESM export를 만들어냈다.
    

시간도 시간이고 배고파서 집중 안 되기도 해서 오늘 작업은 여기서 중단했다.

### SWC 유니코드 버그 디버깅

* [https://github.com/swc-project/swc/pull/8941](https://github.com/swc-project/swc/pull/8941)
    

`unicode_id_start`의 버그라 메인테이너 CC까지만 했다. 메인테이너가 라이브러리 버그 수정해주면 의존성 업데이트하면서 이슈 닫을 생각이었다. 근데 메인테이너 분이 빠르게 이슈 잡아주셔서 오늘 마무리할 수 있었다.
