【ゴリ押し】TypeScript + webpack製のブラウザゲームやアプリの文字列リソースをバンドルから除外(externals)して更新しやすくする【クソ記事】

ほほ備忘録です。
とりあえず動くレベルのクソ記事のため、ノークレームノーリターンでお願いします。

#### ※そもそも文字列リソースはそんな風に管理しないよ などアドバイスいただけたらとてもうれしいです

## 3行

TypeScript製のブラウザアプリにおける  
文字列リソースを  
ビルドすることなく更新できるようにする

![textresource.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557296/906ec4c5-ff03-2e57-bd97-d76ee02c7be6.gif)

## モチベーション

- TypeScript + webpack + PixiJSでオレオレゲームエンジンを作り始めた
- こんな感じで会話用のリソースを作ってみた

```js:/res/text/index.js
export const FETCH_SCRIPT = {
    ALL: (id) => { return SCRIPT[id] },
    TEXT: (id) => { return SCRIPT[id].map((s) => s.text) },
    SPEAKER: (id) => { return SCRIPT[id].map((s) => s.text) },
    TEXT_SPEAKER: (id) => { return SCRIPT[id].map((s) => ({ text:s.text, speaker:s.speaker })) }
} 

export const SCRIPT = {
    0 : [
        {
            text: "こんにちは。",
            speaker: "キャラ1",
        },
        {
            text: "キャラクターを出してみる。",
            speaker: "キャラ2",
        },
        {
            text: "これは地の文。",
            speaker: null,
        },
        {
            text: "左右に動いてみる。",
            speaker: "長い名前のキャラクター",
        },
        {
            text: "跳ねてみる。",
            speaker: "キャラ2",
        },
        {
            text: "表情を変えてみる。",
            speaker: "キャラ1",
        },
        {
            text: "キャラクターを増やしてみる。",
            speaker: "キャラ3",
        }
    ],
    1 : [
        {
            text: "会話シーン2",
            speaker: 1,
        },
        {
            text: "ほげほげ",
            speaker: null,
        }
    ],
}
```

- このように使う

```ts:/src/ts/index.ts
import {FETCH_SCRIPT} from '../../res/text';
console.log(FETCH_SCRIPT.TEXT(0)[0])    // "こんにちは。"
```

### セリフを更新するたびアプリをゲーム全体をビルドするのめんどくさい!!!!<br>キャラの立ち絵IDやアニメーションも今後ここで管理するから絶対ビルドなしで更新できるほうがいい!!!!!

## どうするか

- 文字列リソースファイルをモジュールではなく通常のJavaScriptファイルにする

```js:/res/text/index.js(外部読み込み版)
const RES_TEXT = {
    FetchScript: {
        ALL: (id) => { return SCRIPT[id] },
        TEXT: (id) => { return SCRIPT[id].map((s) => s.text) },
        SPEAKER: (id) => { return SCRIPT[id].map((s) => s.text) },
        TEXT_SPEAKER: (id) => { return SCRIPT[id].map((s) => ({ text:s.text, speaker:s.speaker })) }
    },
}

const SCRIPT = {
    0 : [
        {
            text: "こんにちは。",
            speaker: "キャラ1",
        },
        {
            text: "キャラクターを出してみる。",
            speaker: "キャラ2",
        },
        {
            text: "これは地の文。",
            speaker: null,
        },
        {
            text: "左右に動いてみる。",
            speaker: "長い名前のキャラクター",
        },
        {
            text: "跳ねてみる。",
            speaker: "キャラ2",
        },
        {
            text: "表情を変えてみる。",
            speaker: "キャラ1",
        },
        {
            text: "キャラクターを増やしてみる。",
            speaker: "キャラ3",
        }
    ],
    1 : [
        {
            text: "会話シーン2",
            speaker: 1,
        },
        {
            text: "ほげほげ",
            speaker: null,
        }
    ],
}
```

- グローバルに読み込む

``` html:index.html
…
    <script src="./res/text/index.js"></script>
    <script src="./js/bundle.js"></script>
</body>

```

- webpackの設定で文字列リソースをexternal(外部読み込み)に指定する

```js:webpack.config.js
module.exports = {
…
    externals: {
        RText: 'RES_TEXT',
    }
}
```

- 使う
```ts:/src/ts/index.ts
import RText from 'RText';
console.log(RText.FetchScript.TEXT_SPEAKER(0)[0])    // "こんにちは。"

```

- 'RText'の型情報がないよ と怒られるので適当に.d.tsを書く

``` ts:/src/ts/@types/RText.d.ts
declare module 'RText' {
    export var FetchText: any;
    export var FetchScript: any;
}
```
(怒られない！！)


*Fin*