FlutterのWebアプリ開発をオフラインで実行する方法

Flutterアプリを開発している際、まずはWeb（Chrome）を使っているという方も多いのではないでしょうか。個人的には色々な環境で動かしたいので、まずWebで作ってからiOSやAndroid向けの調整を行っています。

そんな中、完全にネットワークがない状況下（飛行機やネットワークが非常に弱いところ）だとアプリが表示されるのに時間がかかったり、エラーが出てしまいます。原因としては下記のエラーです。

```
canvaskit.js:1 Failed to load resource: net::ERR_NAME_NOT_RESOLVED
```

これはFlutterのWebアプリがcanvaskit-wasmをUNPKG経由で取得しているのに起因して発生するエラーです。移動中も快適に開発するためには、ネットワーク要素を排除して完全オフラインにするのが肝要です。もちろんUNPKGが運悪く落ちている時に開発が停滞してしまうのも避けられるでしょう。

他にもフォントが絡むので、この2つを解決します。

## canvaskit-wasmの解決

### 基本

まずcanvaskit-wasmのファイルを `web/js` フォルダ（新しく作成）以下に配置します。ファイルは[UNPKG \- canvaskit\-wasm](https://unpkg.com/browse/canvaskit-wasm@latest/)からダウンロードできます。

必要なのは以下の4つのファイルです。

- bin/canvaskit.js
- bin/canvaskit-wasm
- bin/profiling/canvaskit.js
- bin/profiling/canvaskit.wasm

このファイルを `web/js` フォルダ以下に配置します。

```
web/js
├── canvaskit.js
├── canvaskit.wasm
└── profiling
    ├── canvaskit.js
    └── canvaskit.wasm
```

### 駄目だった方法

ネットで調べてみると、実行時のコマンドライン引数で指定する方法が挙げられています。

```
flutter run \
   -d chrome \
   --web-renderer=canvaskit \
   --dart-define=FLUTTER_WEB_CANVASKIT_URL=./js/
```

実際、[Flutterのリポジトリ](https://github.com/flutter/engine/blob/7b2db6dbe1c49a8004ff865607988eec36868ccc/lib/web_ui/lib/src/engine/canvaskit/initialization.dart#L51)にも、そのように書かれています。しかし、これをlaunch.jsonに記述して、VS CodeでFlutterアプリを起動しても有効になりませんでした。

### 成功した方法

成功した方法は、 `web/index.html` を編集する方法です。JavaScriptを以下のように編集します。

```html
<script>
	// The value below is injected by flutter build, do not touch.
	var serviceWorkerVersion = null;
	// 以下を追加
	window.flutterConfiguration = {
		canvasKitBaseUrl: "/js/"
	};
</script>
```

これで、canvaskit-wasmがローカル経由で読み込まれるようになります。

## フォントの適用

これで終わったかと思ったのですが、ChromeのDev Toolsで見ると、フォントもGoogleのサイト経由でダウンロードされていました。CanvasKitで描画している場合、フォントがないと豆腐だらけになってしまうはずです。

そこで、以下の2つのフォントをダウンロードします。これは利用しているフォントに依りますのでご注意ください。恐らくこの2つのフォントはデフォルトで利用されると思います。

- [Roboto \- Google Fonts](https://fonts.google.com/specimen/Roboto)
- [Noto Sans Japanese \- Google Fonts](https://fonts.google.com/noto/specimen/Noto+Sans+JP)

ダウンロード&展開したら、 `fonts` フォルダ（新規作成）以下に配置します。

```
fonts
├── noto_sans
│   ├── NotoSansJP-Black.otf
│   ├── NotoSansJP-Bold.otf
│   ├── NotoSansJP-Light.otf
│   ├── NotoSansJP-Medium.otf
│   ├── NotoSansJP-Regular.otf
│   ├── NotoSansJP-Thin.otf
│   └── OFL.txt
└── roboto
    ├── LICENSE.txt
    ├── Roboto-Black.ttf
    ├── Roboto-BlackItalic.ttf
    ├── Roboto-Bold.ttf
    ├── Roboto-BoldItalic.ttf
    ├── Roboto-Italic.ttf
    ├── Roboto-Light.ttf
    ├── Roboto-LightItalic.ttf
    ├── Roboto-Medium.ttf
    ├── Roboto-MediumItalic.ttf
    ├── Roboto-Regular.ttf
    ├── Roboto-Thin.ttf
    └── Roboto-ThinItalic.ttf
```

そして、 pubspec.yaml に設定を追加します。

```yaml
flutter:
  fonts:
    - family: Noto Sans JP
      fonts:
        - asset: fonts/noto_sans/NotoSansJP-Black.otf
          weight: 900
        - asset: fonts/noto_sans/NotoSansJP-Bold.otf
          weight: 700
        - asset: fonts/noto_sans/NotoSansJP-Light.otf
          weight: 300
        - asset: fonts/noto_sans/NotoSansJP-Medium.otf
          weight: 500
        - asset: fonts/noto_sans/NotoSansJP-Regular.otf
          weight: 400
        - asset: fonts/noto_sans/NotoSansJP-Thin.otf
          weight: 100
    - family: Roboto
      fonts:
        - asset: fonts/roboto/Roboto-Thin.ttf
          weight: 100
        - asset: fonts/roboto/Roboto-Light.ttf
          weight: 300
        - asset: fonts/roboto/Roboto-Regular.ttf
          weight: 400
        - asset: fonts/roboto/Roboto-Medium.ttf
          weight: 500
        - asset: fonts/roboto/Roboto-Bold.ttf
          weight: 700
        - asset: fonts/roboto/Roboto-Black.ttf
          weight: 900
```

最後に、main.dartなりでアプリを立ち上げる際にフォントを指定します。これは中華フォントの異なる漢字が適用されるのも防止できます。

```dart
return MaterialApp(
	title: 'Flutter Demo',
	theme: ThemeData(primarySwatch: Colors.blue, fontFamily: 'Noto Sans JP'),
	home: const MyAppPage(),
);
```

## まとめ

これで必要なファイルがlocalhost経由で取得できるようになり、オフラインでも開発が進められるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/2e1a61ae-bdfa-da60-64b3-10c7993ea44b.png)


[ニフクラ mobile backend](https://mbaas.nifcloud.com/)や[Hexabase](https://www.hexabase.com/)のようなクラウドサービスをアプリ内で使っている場合には、そこはネットワーク必須になってしまうのですが…UI調整などはオフラインで進められるでしょう。