← 전체 글로

AI 사용기

Phaser is not defined: Scene을 import했는데 Math에서 멈출 때

Phaser 4 npm 모듈에서 타입 검사는 통과하지만 브라우저가 멈추는 최소 예제를 재현했다. import 누락을 고치는 두 방법과 실제 콜백의 오류를 확인하는 순서를 정리한다.

10월 8일 조사·작성한 원고를 10월 9일 공개했습니다.

이 글의 목적

Phaser를 설치했는데도 브라우저에서 이름 참조가 실패하는 개발자가 오류 파일의 import부터 확인하도록 돕는다.

핵심 내용

Scene만 가져오면 Phaser라는 로컬 값은 생기지 않는다. Phaser.Math를 쓰는 파일에서 namespace를 가져오거나 Math를 직접 가져온 뒤 실제 실패 경로를 다시 실행한다.

읽고 나서

브라우저 콘솔의 첫 Phaser is not defined 위치로 이동해 그 파일의 import와 Phaser 참조를 대조한다.

목차

먼저 답하면

Phaser is not defined가 Phaser.Math.Between(...) 줄에서 나고 그 파일에는 import { Scene } from 'phaser'만 있다면, 오류가 난 파일에서 Phaser라는 값을 가져왔는지 확인한다. Scene을 가져오는 것과 Phaser를 가져오는 것은 다른 일이다.

Phaser 4 npm 모듈에서 Phaser.* 형태를 유지하려면 import를 아래처럼 바꾼다. 기존에 가져온 Scene을 쓰던 클래스도 함께 맞춘다.

import * as Phaser from 'phaser';

class Demo extends Phaser.Scene {
  sample() {
    return Phaser.Math.Between(7, 7);
  }
}

Math만 필요하면 다음 방법도 있다. 이때 호출 이름은 Phaser.Math가 아니라 PhaserMath다.

import { Scene, Math as PhaserMath } from 'phaser';

class Demo extends Scene {
  sample() {
    return PhaserMath.Between(7, 7);
  }
}

이번 최소 시험에서 두 수정 예제는 모두 7을 출력했고 pageerror는 없었다. 자기 프로젝트에서는 원래 오류가 난 버튼·콜백·장면 전환까지 다시 실행해야 한다. 다른 파일에 namespace import를 추가하는 것만으로 오류 파일의 로컬 바인딩이 생기지는 않는다.

타입 검사는 통과했지만 화면은 멈춘 예제

2026년 10월 8일 macOS 26.6.2 arm64, Node v26.7.0, Phaser 4.0.0, TypeScript 5.7.3, Vite 6.3.1, Chromium 154.0.8037.98에서 새로 만든 최소 코드를 실행했다. 실제 게임 전체를 재실행한 결과가 아니다. 캔버스를 만들지 않고 Scene 인스턴스의 메서드를 호출해 모듈 이름 참조만 시험했다.

실패한 broken.ts는 다음과 같다.

import { Scene } from 'phaser';

class Demo extends Scene {
  sample() { return Phaser.Math.Between(7, 7); }
}

Promise.resolve().then(() => {
  document.body.textContent = String(new Demo().sample());
});

Vite 프로젝트의 HTML에는 <body>pending<script type="module" src="/broken.ts"></script></body>를 넣었다. <script type="module">로 실행하는 npm 모듈 예제다. 타입 검사 설정은 다음과 같았다.

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["*.ts"]
}

새 폴더에서 세 예제 실행하기

기존 프로젝트를 수정하기 전에 별도의 빈 폴더에서 비교하려면 Node와 npm이 설치된 터미널을 사용한다. 다음 명령으로 폴더를 만든다.

mkdir phaser-import-demo
cd phaser-import-demo

아래 내용으로 package.json을 저장한다. 시험한 버전을 고정했으며 최신 버전을 설치하는 명령이 아니다.

{
  "private": true,
  "type": "module",
  "dependencies": { "phaser": "4.0.0" },
  "devDependencies": { "typescript": "5.7.3", "vite": "6.3.1" },
  "scripts": {
    "typecheck": "tsc -p tsconfig.json",
    "dev": "vite"
  }
}

이 폴더에 앞의 타입 검사 JSON을 tsconfig.json으로, 실패 코드를 broken.ts로 저장한다. fixed.ts에는 첫 namespace 수정 코드와 다음 호출을 함께 넣는다.

Promise.resolve().then(() => {
  document.body.textContent = String(new Demo().sample());
});

named.ts에는 두 번째 named import 수정 코드와 다음 호출을 함께 넣는다.

document.body.textContent = String(new Demo().sample());

각각의 HTML 파일도 같은 폴더에 저장한다. broken.html 전체 내용은 다음과 같다.

<!doctype html>
<html lang="en">
  <head><meta charset="UTF-8"><title>broken</title></head>
  <body>pending<script type="module" src="/broken.ts"></script></body>
</html>

이를 복사해 fixed.html, named.html을 만들고 각 파일의 title과 script 경로를 fixed·/fixed.ts, named·/named.ts로 바꾼다. 최종 파일 배치는 다음과 같다.

phaser-import-demo/
  package.json
  tsconfig.json
  broken.ts   broken.html
  fixed.ts    fixed.html
  named.ts    named.html

같은 폴더의 터미널에서 실행한다.

npm install
npm run typecheck
npm run dev -- --host 127.0.0.1 --port 4319 --strictPort

마지막 명령은 개발 서버를 켠 채 유지한다. 브라우저에서 http://127.0.0.1:4319/broken.html, http://127.0.0.1:4319/fixed.html, http://127.0.0.1:4319/named.html을 각각 열어 아래 표와 대조한다. 시험 후 터미널에서 Ctrl+C로 서버를 종료한다. 포트가 이미 사용 중이면 임의로 기존 프로세스를 종료하지 말고 다른 빈 포트로 시작해 세 주소의 포트도 함께 바꾼다. npm 설치나 서버 시작부터 실패하면 아직 이 글의 모듈 참조 시험까지 도달한 것이 아니다.

이 설치·타입 검사·서버 시작 순서를 별도의 빈 폴더에서 다시 실행했고, 세 페이지의 결과도 아래 표와 일치했다.

tsc -p tsconfig.json은 종료 코드 0으로 끝났지만, 브라우저는 Phaser is not defined를 냈다. sample()이 실패해 본문의 pending은 7로 바뀌지 않았다. Promise 콜백 안에서 발생한 오류이므로 겉으로는 다음 동작이 진행되지 않는 현상만 보일 수 있다. 타입 검사 종료 코드가 성공이어도 그 콜백이 성공했다는 뜻은 아니다.

가져온 값과 호출 브라우저 본문 수집한 pageerror
Scene만 가져오고 Phaser.Math 호출 pending 유지 Phaser is not defined
namespace를 가져오고 Phaser.Math 호출 7 없음
Scene, Math as PhaserMath를 가져오고 PhaserMath 호출 7 없음

수정한 namespace 예제에는 첫 코드 블록 뒤에 위 Promise.resolve().then(...)을 그대로 붙여 같은 경로를 실행했다. named import 예제는 document.body.textContent = String(new Demo().sample());로 메서드를 실행했다. 두 결과를 같은 브라우저에서 확인했다. Between(7, 7)은 랜덤 범위를 같은 값으로 고정해 출력이 바뀌지 않도록 선택한 입력이다.

타입 선언과 실행 값은 다르다

설치한 Phaser 4.0.0의 타입 파일에는 declare namespace Phaser가 있었다. 이 선언은 타입 정보이지, 브라우저에서 실행되어 Phaser 값을 만드는 코드가 아니다. 이 예제에서는 타입 검사기가 이름을 받아들였지만, Scene만 가져온 실행 모듈에는 Phaser를 사용할 바인딩이 없었다. 타입 검사와 런타임 실패를 함께 본 이유다. 다른 TypeScript 설정에서도 반드시 검사에 통과한다는 뜻은 아니다.

설치본의 ES 모듈은 Scene, Math를 각각 export했다. 그래서 두 번째 수정은 Math를 PhaserMath라는 로컬 이름으로 가져올 수 있었다. import type은 런타임 값을 가져오는 용도로 쓰지 않는다.

Phaser의 공식 업데이트 안내는 Phaser 4의 npm default import 대신 wildcard import를 안내한다. 동시에 CDN 스크립트로 전역을 쓰는 경우에는 그 변경이 적용되지 않는다고 구분한다. 여기서 직접 시험한 문제는 Scene만 가져온 파일의 참조 누락이다. 공식 글의 default import 변경과 같은 오류라고 섞어 설명하면 진단이 흐려진다.

고친 뒤 무엇을 확인하나

  1. 브라우저 개발자 도구 콘솔에서 첫 오류와 파일·줄을 확인한다. 이 글의 대상은 Phaser is not defined다. npm 패키지를 찾지 못한다는 빌드 오류와 구분한다.
  2. 그 파일의 Phaser.* 사용과 import를 대조한다. namespace를 유지할지, 필요한 export를 별도 이름으로 가져올지 선택한다. 앞의 두 방식을 섞어 참조 이름만 남기지 않는다.
  3. 타입 검사를 다시 실행하고 페이지를 새로 연다. 원래 실패한 동작도 수행한다. 첫 화면이 열렸다는 것만으로 뒤의 콜백까지 확인한 것은 아니다.
  4. 예제는 본문이 7인지와 pageerror가 없는지를 함께 본다. 실제 게임에서는 기대한 HUD 변경·다음 상태와 콘솔 오류를 함께 확인한다. 이 글에서는 그 게임 동작을 시험하지 않았다.

Playwright를 이미 쓰는 프로젝트라면 페이지를 열기 전에 오류 수집을 붙일 수 있다. 다음은 기존 page 객체를 쓰는 진단 코드다. 이 코드만 붙여 실행되는 독립 스크립트는 아니다.

const errors: string[] = [];
page.on('pageerror', error => errors.push(error.message));
await page.goto('http://127.0.0.1:4319/broken.html'); // 시험 페이지 또는 자기 오류 페이지
// 여기서 원래 실패한 버튼·콜백·장면을 실행하고 결과를 기다린다.
console.log(errors);

위 수집 방식으로 이번 최소 예제의 실패 원문과 수정 뒤 빈 오류 목록을 확인했다. 오류 목록만 비었다면 코드가 아예 실행되지 않았을 가능성도 있으므로 기대 결과 확인을 함께 한다.

이 수정으로 해결하지 못하는 경우

Cannot read properties of undefined (reading 'Between')처럼 다른 오류가 난다면 이 글의 재현과 같다고 단정하지 않는다. 객체를 잘못 덮어썼는지, 실제 가져온 export와 설치 버전이 무엇인지 오류 위치에서 확인한다. window is not defined가 서버 실행 중에 난다면 브라우저 모듈 참조 문제와 구분해야 한다.

CDN 전역 로딩, SSR, 다른 번들러·Phaser 버전·TypeScript 설정, 실제 게임의 전체 초기화는 시험하지 않았다. 패키지 업데이트나 전역 변수 주입으로 먼저 덮기보다, 정확한 오류 문구와 실행 위치가 이 최소 예제의 조건에 맞는지 확인한다.

AI가 공식 자료 조사, 격리된 브라우저 재현, 초안 작성과 별도 자동 검토를 수행했습니다. 아래 결과는 새 최소 예제의 실행이며 과거 게임 플레이나 사람 독자 시험으로 서술하지 않습니다. 공개 전 출처와 실행 결과를 대조합니다.