@ywal123456/jskim 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -23
- package/docs/configuration.md +48 -3
- package/docs/create-jskim.md +13 -13
- package/docs/publishing.md +32 -52
- package/package.json +1 -1
- package/scripts/commands/dev-command.js +10 -109
- package/scripts/commands/watch-command.js +15 -25
- package/scripts/lib/create-watch-runtime.js +516 -0
package/README.md
CHANGED
|
@@ -25,14 +25,15 @@ Nunjucks ソースを設定に従って静的 HTML にレンダリングし、as
|
|
|
25
25
|
|
|
26
26
|
- ブラウザ自動起動
|
|
27
27
|
- HMR / CSS だけのホット更新
|
|
28
|
-
- HTML 移行
|
|
29
28
|
- JSON データの自動読み込み
|
|
30
29
|
- API / Mock API
|
|
31
|
-
- 実際の npm registry publish(手順は準備済み)
|
|
32
|
-
- config の hot reload
|
|
33
30
|
- 増分ビルド
|
|
34
31
|
- SPA fallback / proxy
|
|
35
32
|
|
|
33
|
+
既存 HTML の自動 import / migration は JSKim core の責任範囲外です。
|
|
34
|
+
既存 source の移行は利用者が project に合わせて行います。
|
|
35
|
+
将来必要になった場合も、JSKim 本体ではなく独立した tool / package として検討します。
|
|
36
|
+
|
|
36
37
|
## パッケージの役割
|
|
37
38
|
|
|
38
39
|
| 名称 | 種類 | 役割 |
|
|
@@ -43,28 +44,38 @@ Nunjucks ソースを設定に従って静的 HTML にレンダリングし、as
|
|
|
43
44
|
|
|
44
45
|
製品名は **JSKim**、CLI binary は **`jskim`**、npm engine package は **`@ywal123456/jskim`** です。package 名と binary 名を混同しないでください。
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
`@ywal123456/jskim` と `create-jskim` は npm registry で公開済みの MIT package です。
|
|
47
48
|
|
|
48
49
|
詳細:
|
|
49
50
|
|
|
50
51
|
- [docs/create-jskim.md](docs/create-jskim.md)
|
|
51
|
-
- [docs/publishing.md](docs/publishing.md)
|
|
52
|
-
|
|
53
|
-
## パッケージとしての利用
|
|
52
|
+
- [docs/publishing.md](docs/publishing.md)(maintainer 向け release 手順)
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
## インストール
|
|
56
55
|
|
|
57
56
|
```bash
|
|
58
57
|
npm install --save-dev @ywal123456/jskim
|
|
59
58
|
```
|
|
60
59
|
|
|
60
|
+
## 新規 project の作成
|
|
61
|
+
|
|
61
62
|
```bash
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
jskim serve sample
|
|
65
|
-
jskim dev sample
|
|
63
|
+
npm create jskim@latest
|
|
64
|
+
```
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
代替:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx create-jskim my-project
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## CLI
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
jskim build <project>
|
|
76
|
+
jskim watch <project>
|
|
77
|
+
jskim serve <project>
|
|
78
|
+
jskim dev <project>
|
|
68
79
|
```
|
|
69
80
|
|
|
70
81
|
`package.json` の scripts 例:
|
|
@@ -83,11 +94,11 @@ npx jskim build sample
|
|
|
83
94
|
要点:
|
|
84
95
|
|
|
85
96
|
- npm package 名は `@ywal123456/jskim`(scoped)
|
|
86
|
-
- インストール後の CLI binary 名は `jskim
|
|
97
|
+
- インストール後の CLI binary 名は `jskim`
|
|
87
98
|
- コマンドは実行したディレクトリの `process.cwd()` をプロジェクトルートとして扱う
|
|
88
99
|
- パッケージのインストール先(`node_modules/@ywal123456/jskim`)を作業空間とはみなさない
|
|
89
100
|
- プロジェクトルートに `jskim.config.js` が必要
|
|
90
|
-
|
|
101
|
+
|
|
91
102
|
ヘルプ / バージョン:
|
|
92
103
|
|
|
93
104
|
```bash
|
|
@@ -97,18 +108,13 @@ jskim --version
|
|
|
97
108
|
|
|
98
109
|
## プロジェクト生成(create-jskim)
|
|
99
110
|
|
|
100
|
-
公開後の例:
|
|
101
|
-
|
|
102
111
|
```bash
|
|
103
112
|
npm create jskim@latest
|
|
104
113
|
# または
|
|
105
114
|
npx create-jskim my-project
|
|
106
115
|
```
|
|
107
116
|
|
|
108
|
-
ローカル / 開発時:
|
|
109
|
-
|
|
110
117
|
```bash
|
|
111
|
-
create-jskim my-project
|
|
112
118
|
cd my-project
|
|
113
119
|
npm install
|
|
114
120
|
npm run dev
|
|
@@ -116,7 +122,6 @@ npm run dev
|
|
|
116
122
|
|
|
117
123
|
- 自動で `npm install` / `git init` は実行しません
|
|
118
124
|
- 空ではない既存ディレクトリは上書きしません
|
|
119
|
-
- 実際の registry インストールは publish 後に利用できます
|
|
120
125
|
|
|
121
126
|
## このリポジトリでの開発コマンド
|
|
122
127
|
|
|
@@ -167,9 +172,24 @@ jskim watch sample
|
|
|
167
172
|
|
|
168
173
|
- 関連する変更はすべて **全体ビルド** です(増分 render / 単一 asset copy なし)。
|
|
169
174
|
- `build.clean: true` のとき、再ビルドで outputDir を消して作り直すため、ソース削除も結果に反映されます。
|
|
170
|
-
- `jskim.config.js`
|
|
175
|
+
- `jskim.config.js` の変更は **watch / dev が検知** し、正常なら監視対象を更新して全体ビルドします。
|
|
176
|
+
- 設定の読み込み / 検証に失敗しても process は終了せず、以前の正常な設定を継続します。
|
|
177
|
+
- `serve` は config を監視しません。設定変更後は serve process の再起動が必要です。
|
|
171
178
|
- 終了: `Ctrl+C`(SIGINT)または SIGTERM → `[JSKim] ウォッチを停止しました。`
|
|
172
179
|
|
|
180
|
+
#### config hot reload(watch / dev)
|
|
181
|
+
|
|
182
|
+
- 対象ファイル: ワークスペースルートの `jskim.config.js`
|
|
183
|
+
- 正常な変更: watcher 再構成 → 全体ビルド
|
|
184
|
+
- 不正な設定: 以前の正常な設定を維持し、修正後に自動で再試行
|
|
185
|
+
- `watch` では `outputDir` 変更も適用できます(以前の outputDir は自動削除しません)
|
|
186
|
+
- `dev` では次の変更は process 再起動が必要です:
|
|
187
|
+
- `outputDir`
|
|
188
|
+
- `serve.host`
|
|
189
|
+
- `serve.port`
|
|
190
|
+
- `dev.liveReload`
|
|
191
|
+
- `dev` で対応できる設定変更が成功し、build も成功した場合だけ browser reload を 1 回送ります
|
|
192
|
+
|
|
173
193
|
debounce の既定値は `watch.debounce: 150`(ms)で、プロジェクトごとに上書きできます。詳細は [docs/configuration.md](docs/configuration.md) を参照してください。
|
|
174
194
|
|
|
175
195
|
## 静的サーバー(serve)
|
|
@@ -234,7 +254,8 @@ http://127.0.0.1:3000/
|
|
|
234
254
|
|
|
235
255
|
`dev.liveReload: false` にすると SSE endpoint と script 注入を無効化できます。
|
|
236
256
|
|
|
237
|
-
|
|
257
|
+
`jskim.config.js` の build / watch 関連設定は hot reload されます。
|
|
258
|
+
`outputDir` / `serve.host` / `serve.port` / `dev.liveReload` を変えた場合は `dev` を再起動してください。
|
|
238
259
|
|
|
239
260
|
終了: `Ctrl+C` → `[JSKim] 開発サーバーを停止しました。`
|
|
240
261
|
|
package/docs/configuration.md
CHANGED
|
@@ -185,8 +185,9 @@ projects: {
|
|
|
185
185
|
|
|
186
186
|
注意:
|
|
187
187
|
|
|
188
|
-
-
|
|
189
|
-
-
|
|
188
|
+
- `watch` / `dev` 実行中は `jskim.config.js` 自体も監視します
|
|
189
|
+
- 正常な設定変更は監視対象の再構成と全体ビルドに反映されます
|
|
190
|
+
- 設定の読み込み / 検証エラー時は以前の正常な設定を継続します
|
|
190
191
|
|
|
191
192
|
## serve.host
|
|
192
193
|
|
|
@@ -295,7 +296,51 @@ projects: {
|
|
|
295
296
|
|
|
296
297
|
`dev.host` / `dev.port` / `dev.debounce` は用意しません。
|
|
297
298
|
|
|
298
|
-
|
|
299
|
+
## config hot reload
|
|
300
|
+
|
|
301
|
+
`watch` と `dev` はワークスペースルートの `jskim.config.js` を監視します。
|
|
302
|
+
|
|
303
|
+
### コマンド別
|
|
304
|
+
|
|
305
|
+
| コマンド | 動作 |
|
|
306
|
+
|----------|------|
|
|
307
|
+
| `build` | 実行時に config を 1 回読み込む。監視しない |
|
|
308
|
+
| `serve` | 実行時に config を 1 回読み込む。監視しない。変更後は process 再起動が必要 |
|
|
309
|
+
| `watch` | config 変更を hot apply。`outputDir` 変更も可。以前の outputDir は自動削除しない |
|
|
310
|
+
| `dev` | build / watch 関連設定を hot apply。一部 runtime 設定は再起動が必要 |
|
|
311
|
+
|
|
312
|
+
### 正常な reload
|
|
313
|
+
|
|
314
|
+
1. config 変更を検知
|
|
315
|
+
2. fresh load(CommonJS require cache を `jskim.config.js` について削除)
|
|
316
|
+
3. merge / project resolve / validation
|
|
317
|
+
4. watcher 再構成
|
|
318
|
+
5. 全体ビルド
|
|
319
|
+
|
|
320
|
+
### エラー時
|
|
321
|
+
|
|
322
|
+
| 状況 | 動作 |
|
|
323
|
+
|------|------|
|
|
324
|
+
| config load / resolve / validation エラー | 以前の正常な設定を維持。process は終了しない |
|
|
325
|
+
| 新しい設定適用後の build エラー | 新しい設定 / watcher を維持。process は終了しない。修正後に再試行 |
|
|
326
|
+
| config 一時削除(unlink) | 以前の設定を継続。再作成後に reload |
|
|
327
|
+
|
|
328
|
+
### `dev` で再起動が必要な設定
|
|
329
|
+
|
|
330
|
+
次の値が変わった candidate config は適用しません。
|
|
331
|
+
|
|
332
|
+
- `outputDir`
|
|
333
|
+
- `serve.host`
|
|
334
|
+
- `serve.port`
|
|
335
|
+
- `dev.liveReload`
|
|
336
|
+
|
|
337
|
+
理由: server bind と serve root を安全に切り替えるには process 再起動が必要で、部分適用すると build output と serve root がずれるためです。
|
|
338
|
+
|
|
339
|
+
### 制限
|
|
340
|
+
|
|
341
|
+
- config hot reload は常に **全体ビルド** です(増分 build ではありません)
|
|
342
|
+
- `jskim.config.js` が require した別 helper module だけの変更は自動反映しません。config を保存し直すか process を再起動してください
|
|
343
|
+
- `node_modules` や JSKim package 自身の require cache は一括削除しません
|
|
299
344
|
|
|
300
345
|
## パス基準のまとめ
|
|
301
346
|
|
package/docs/create-jskim.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`create-jskim` は JSKim の作業空間を新規作成する **独立 scaffold package** です。
|
|
4
4
|
|
|
5
|
-
エンジン CLI(`jskim build` など)とは別 package として管理します。
|
|
5
|
+
エンジン CLI(`jskim build` など)とは別 package として管理します。npm registry で公開済みです。
|
|
6
6
|
|
|
7
7
|
## 役割
|
|
8
8
|
|
|
@@ -18,7 +18,14 @@
|
|
|
18
18
|
| `jskim` | CLI binary |
|
|
19
19
|
| `@ywal123456/jskim` | npm engine package |
|
|
20
20
|
| `create-jskim` | npm creator package |
|
|
21
|
-
| `npm create jskim@latest` |
|
|
21
|
+
| `npm create jskim@latest` | ユーザー向け生成コマンド |
|
|
22
|
+
|
|
23
|
+
## 使い方
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm create jskim@latest
|
|
27
|
+
npx create-jskim my-project
|
|
28
|
+
```
|
|
22
29
|
|
|
23
30
|
## 生成結果
|
|
24
31
|
|
|
@@ -32,12 +39,12 @@ project/
|
|
|
32
39
|
```
|
|
33
40
|
|
|
34
41
|
`package.json` の scripts は bin 名 `jskim` を使います(package name ではありません)。
|
|
35
|
-
既定の engine dependency は `@ywal123456/jskim`(`^0.
|
|
42
|
+
既定の engine dependency は `@ywal123456/jskim`(`^0.2.0`)です。
|
|
36
43
|
|
|
37
44
|
```json
|
|
38
45
|
{
|
|
39
46
|
"devDependencies": {
|
|
40
|
-
"@ywal123456/jskim": "^0.
|
|
47
|
+
"@ywal123456/jskim": "^0.2.0"
|
|
41
48
|
},
|
|
42
49
|
"scripts": {
|
|
43
50
|
"build": "jskim build sample",
|
|
@@ -63,18 +70,11 @@ create package の `package.json` に `jskimEngine` metadata を持ち、生成
|
|
|
63
70
|
{
|
|
64
71
|
"jskimEngine": {
|
|
65
72
|
"packageName": "@ywal123456/jskim",
|
|
66
|
-
"version": "^0.
|
|
73
|
+
"version": "^0.2.0"
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
```
|
|
70
77
|
|
|
71
78
|
生成プロジェクトへ License は強制しません。
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
npm create jskim@latest
|
|
77
|
-
npx create-jskim my-project
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Package 名・CLI 構造は上記に合わせて準備済みです。registry への実際の publish は別手順です([publishing.md](./publishing.md))。
|
|
80
|
+
Maintainer 向けの release / publish 手順は [publishing.md](./publishing.md) を参照してください。
|
package/docs/publishing.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
# 公開手順(publishing)
|
|
2
2
|
|
|
3
|
-
この文書は
|
|
3
|
+
この文書は **maintainer 向けの release / publish 手順** です。
|
|
4
4
|
開発リポジトリの通常作業では `npm publish` を実行しません。
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## 現在公開中
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
```text
|
|
9
|
+
engine: @ywal123456/jskim
|
|
10
|
+
creator: create-jskim
|
|
11
|
+
CLI binary: jskim / create-jskim
|
|
12
|
+
license: MIT
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
初回公開(v0.1.0)は完了しています。以降の patch / minor release でも、同じ手順で engine を先に、creator を後に公開します。
|
|
12
16
|
|
|
13
17
|
### 過去の失敗(参考)
|
|
14
18
|
|
|
15
|
-
unscoped
|
|
19
|
+
unscoped 名での初回 engine publish は、npm の package name similarity ポリシーにより拒否されました。現在の engine 公開名は `@ywal123456/jskim` です。
|
|
16
20
|
|
|
17
21
|
### 確定済み metadata
|
|
18
22
|
|
|
@@ -38,15 +42,7 @@ Root `package.json` の `publishConfig`:
|
|
|
38
42
|
}
|
|
39
43
|
```
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
### 残りの運用判断
|
|
44
|
-
|
|
45
|
-
```text
|
|
46
|
-
- 実際の npm publish 時点
|
|
47
|
-
- npm アカウントのログイン状態
|
|
48
|
-
- npm 2FA 状態
|
|
49
|
-
```
|
|
45
|
+
scoped engine の publish では手動コマンドの `--access public` を省略しないでください。
|
|
50
46
|
|
|
51
47
|
## 認証とセキュリティ
|
|
52
48
|
|
|
@@ -55,51 +51,41 @@ Root `package.json` の `publishConfig`:
|
|
|
55
51
|
- token をソースコードや文書に記録しないでください
|
|
56
52
|
- `.npmrc` の認証情報を repository に含めないでください
|
|
57
53
|
- publish 直前に `npm whoami` でログイン状態を確認してください
|
|
58
|
-
-
|
|
54
|
+
- 公開作業の前に会社固有情報・secret・内部 URL が残っていないことを確認してください
|
|
59
55
|
|
|
60
|
-
##
|
|
56
|
+
## 推奨順序(patch / minor)
|
|
61
57
|
|
|
62
58
|
Engine を先に publish します。生成プロジェクトの `package.json` が `@ywal123456/jskim` を参照するため、creator だけ先に公開すると生成直後の `npm install` が失敗し得ます。
|
|
63
59
|
|
|
64
|
-
1.
|
|
65
|
-
2.
|
|
66
|
-
3.
|
|
67
|
-
4.
|
|
68
|
-
5.
|
|
69
|
-
6. **engine を先に publish** する(`--access public
|
|
60
|
+
1. version を決定する
|
|
61
|
+
2. リポジトリ root で `npm test` を実行する
|
|
62
|
+
3. engine / creator の `npm pack` を確認する
|
|
63
|
+
4. `npm publish --dry-run` を確認する
|
|
64
|
+
5. Git commit / push する
|
|
65
|
+
6. **engine を先に publish** する(`--access public`)
|
|
70
66
|
7. registry から engine をインストールできることを確認する
|
|
71
67
|
8. creator を publish する
|
|
72
68
|
9. `npm create jskim@latest` を検証する
|
|
69
|
+
10. Git tag と GitHub Release を作成する
|
|
73
70
|
|
|
74
|
-
|
|
71
|
+
同じ name / version を再 publish できると仮定しないでください。
|
|
75
72
|
|
|
76
|
-
|
|
73
|
+
## コマンド例(PowerShell)
|
|
74
|
+
|
|
75
|
+
Engine(リポジトリ root):
|
|
77
76
|
|
|
78
77
|
```powershell
|
|
79
78
|
npm.cmd whoami --registry=https://registry.npmjs.org
|
|
80
|
-
npm.cmd view @ywal123456/jskim name version --registry=https://registry.npmjs.org
|
|
81
|
-
npm.cmd view create-jskim name version --registry=https://registry.npmjs.org
|
|
82
79
|
npm.cmd test
|
|
83
80
|
npm.cmd publish --dry-run --access public --registry=https://registry.npmjs.org
|
|
84
81
|
npm.cmd publish --access public --registry=https://registry.npmjs.org
|
|
85
82
|
```
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```text
|
|
90
|
-
+ @ywal123456/jskim@0.1.0
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Registry 確認:
|
|
84
|
+
Registry / インストール確認:
|
|
94
85
|
|
|
95
86
|
```powershell
|
|
96
|
-
npm.cmd view @ywal123456/jskim
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
外部インストール:
|
|
100
|
-
|
|
101
|
-
```powershell
|
|
102
|
-
npm.cmd install --save-dev @ywal123456/jskim@0.1.0 --registry=https://registry.npmjs.org
|
|
87
|
+
npm.cmd view @ywal123456/jskim name version dist-tags --registry=https://registry.npmjs.org
|
|
88
|
+
npm.cmd install --save-dev @ywal123456/jskim --registry=https://registry.npmjs.org
|
|
103
89
|
```
|
|
104
90
|
|
|
105
91
|
Creator:
|
|
@@ -110,25 +96,19 @@ npm.cmd publish --dry-run --registry=https://registry.npmjs.org
|
|
|
110
96
|
npm.cmd publish --registry=https://registry.npmjs.org
|
|
111
97
|
```
|
|
112
98
|
|
|
113
|
-
期待する成功出力:
|
|
114
|
-
|
|
115
|
-
```text
|
|
116
|
-
+ create-jskim@0.1.0
|
|
117
|
-
```
|
|
118
|
-
|
|
119
99
|
最終ユーザー検証:
|
|
120
100
|
|
|
121
101
|
```powershell
|
|
122
102
|
npm.cmd create jskim@latest generated-project
|
|
123
|
-
npx create-jskim my-project
|
|
124
103
|
```
|
|
125
104
|
|
|
126
105
|
## 注意
|
|
127
106
|
|
|
128
|
-
- unscoped engine
|
|
129
|
-
- lifecycle script(`prepublishOnly`
|
|
107
|
+
- unscoped engine 向けの旧 package 名では publish しません
|
|
108
|
+
- lifecycle script(`prepublishOnly` など)は必須化していません
|
|
130
109
|
- publish 前チェックリストとして **手動の `npm test`** を必須とします
|
|
131
110
|
- token 生成・login・owner / dist-tag 変更は運用者が責任を持って行います
|
|
132
111
|
- 生成プロジェクトへ License を強制しません(ユーザー側で決定)
|
|
133
112
|
- creator は unscoped public package のため、creator `package.json` に `access: public` は追加していません
|
|
134
|
-
-
|
|
113
|
+
- 文書のみの修正でも npm package README を更新する場合は patch version を使います
|
|
114
|
+
- 既存の公開 tag(例: `v0.1.0`)を移動または再作成しません
|
package/package.json
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
const { resolveProject } = require('../lib/resolve-project');
|
|
5
|
-
const { createProjectWatcher } = require('../lib/create-project-watcher');
|
|
6
|
-
const { createStaticServer } = require('../lib/create-static-server');
|
|
7
|
-
const { createLiveReload } = require('../lib/create-live-reload');
|
|
3
|
+
const { createWatchRuntime } = require('../lib/create-watch-runtime');
|
|
8
4
|
const { registerShutdown } = require('./register-shutdown');
|
|
9
|
-
const { toDisplayPath } = require('./path-display');
|
|
10
|
-
const { formatListenError } = require('./serve-errors');
|
|
11
5
|
|
|
12
6
|
/**
|
|
13
7
|
* dev コマンドを実行します。
|
|
@@ -22,51 +16,15 @@ async function runDevCommand(options = {}) {
|
|
|
22
16
|
const usageLine =
|
|
23
17
|
options.usageLine || 'npm run dev -- <project-name>';
|
|
24
18
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
config,
|
|
19
|
+
const runtime = createWatchRuntime({
|
|
20
|
+
mode: 'dev',
|
|
28
21
|
workspaceRoot,
|
|
29
22
|
projectName: options.projectName,
|
|
30
23
|
commandName: 'dev',
|
|
31
24
|
usageLine,
|
|
32
25
|
});
|
|
33
26
|
|
|
34
|
-
const host = project.serve.host.trim();
|
|
35
|
-
const port = project.serve.port;
|
|
36
|
-
const liveReloadEnabled = project.dev.liveReload;
|
|
37
|
-
const outputDisplay = toDisplayPath(project.outputDir, workspaceRoot);
|
|
38
|
-
|
|
39
|
-
const liveReload = createLiveReload({
|
|
40
|
-
projectName: project.name,
|
|
41
|
-
enabled: liveReloadEnabled,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const staticServer = createStaticServer({
|
|
45
|
-
rootDir: project.outputDir,
|
|
46
|
-
host,
|
|
47
|
-
port,
|
|
48
|
-
projectName: project.name,
|
|
49
|
-
handleInternalRequest: (req, res, meta) =>
|
|
50
|
-
liveReload.handleRequest(req, res, meta),
|
|
51
|
-
transformHtml: liveReloadEnabled
|
|
52
|
-
? (html) => liveReload.injectHtml(html)
|
|
53
|
-
: undefined,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const projectWatcher = createProjectWatcher(project, {
|
|
57
|
-
runInitialBuild: true,
|
|
58
|
-
logChanges: true,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// 成功した再ビルドのあとだけ reload(初回はクライアント未接続のため不要)
|
|
62
|
-
projectWatcher.on('build:success', ({ initial }) => {
|
|
63
|
-
if (!initial && liveReloadEnabled) {
|
|
64
|
-
liveReload.broadcastReload();
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
27
|
let stopping = false;
|
|
69
|
-
let watcherStarted = false;
|
|
70
28
|
|
|
71
29
|
async function shutdown() {
|
|
72
30
|
if (stopping) {
|
|
@@ -75,21 +33,7 @@ async function runDevCommand(options = {}) {
|
|
|
75
33
|
stopping = true;
|
|
76
34
|
|
|
77
35
|
try {
|
|
78
|
-
|
|
79
|
-
await projectWatcher.close();
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
// 終了時エラーは無視
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
liveReload.close();
|
|
87
|
-
} catch {
|
|
88
|
-
// 終了時エラーは無視
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
await staticServer.stop();
|
|
36
|
+
await runtime.close();
|
|
93
37
|
} catch {
|
|
94
38
|
// 終了時エラーは無視
|
|
95
39
|
}
|
|
@@ -98,65 +42,22 @@ async function runDevCommand(options = {}) {
|
|
|
98
42
|
process.exit(0);
|
|
99
43
|
}
|
|
100
44
|
|
|
101
|
-
|
|
45
|
+
registerShutdown(shutdown);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await runtime.start();
|
|
49
|
+
} catch (err) {
|
|
102
50
|
const message = err && err.message ? err.message : String(err);
|
|
103
51
|
console.error(message);
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
if (watcherStarted) {
|
|
107
|
-
await projectWatcher.close();
|
|
108
|
-
}
|
|
109
|
-
} catch {
|
|
110
|
-
// ignore
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
liveReload.close();
|
|
115
|
-
} catch {
|
|
116
|
-
// ignore
|
|
117
|
-
}
|
|
118
|
-
|
|
119
52
|
try {
|
|
120
|
-
await
|
|
53
|
+
await runtime.close();
|
|
121
54
|
} catch {
|
|
122
55
|
// ignore
|
|
123
56
|
}
|
|
124
|
-
|
|
125
57
|
// cleanup 完了後に終了する。
|
|
126
58
|
// IPC 接続があると exitCode だけではプロセスが残るため明示終了する。
|
|
127
59
|
process.exit(1);
|
|
128
60
|
}
|
|
129
|
-
|
|
130
|
-
registerShutdown(shutdown);
|
|
131
|
-
|
|
132
|
-
// 初回 build → server → watcher
|
|
133
|
-
await projectWatcher.start({ watchFiles: false });
|
|
134
|
-
watcherStarted = true;
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
await staticServer.start();
|
|
138
|
-
} catch (err) {
|
|
139
|
-
await failStartup(
|
|
140
|
-
formatListenError(err, {
|
|
141
|
-
projectName: project.name,
|
|
142
|
-
host,
|
|
143
|
-
port,
|
|
144
|
-
kind: '開発',
|
|
145
|
-
})
|
|
146
|
-
);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
await projectWatcher.startWatching();
|
|
151
|
-
|
|
152
|
-
console.log('[JSKim] 開発サーバーを起動しました。');
|
|
153
|
-
console.log(`プロジェクト: ${project.name}`);
|
|
154
|
-
console.log(`ルート: ${outputDisplay}`);
|
|
155
|
-
console.log(`URL: ${staticServer.url}`);
|
|
156
|
-
console.log(
|
|
157
|
-
`ライブリロード: ${liveReloadEnabled ? '有効' : '無効'}`
|
|
158
|
-
);
|
|
159
|
-
console.log('終了するには Ctrl+C を押してください。');
|
|
160
61
|
}
|
|
161
62
|
|
|
162
63
|
module.exports = {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
const { resolveProject } = require('../lib/resolve-project');
|
|
5
|
-
const { createProjectWatcher } = require('../lib/create-project-watcher');
|
|
3
|
+
const { createWatchRuntime } = require('../lib/create-watch-runtime');
|
|
6
4
|
const { registerShutdown } = require('./register-shutdown');
|
|
7
5
|
|
|
8
6
|
/**
|
|
@@ -18,33 +16,14 @@ async function runWatchCommand(options = {}) {
|
|
|
18
16
|
const usageLine =
|
|
19
17
|
options.usageLine || 'npm run watch -- <project-name>';
|
|
20
18
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
config,
|
|
19
|
+
const runtime = createWatchRuntime({
|
|
20
|
+
mode: 'watch',
|
|
24
21
|
workspaceRoot,
|
|
25
22
|
projectName: options.projectName,
|
|
26
23
|
commandName: 'watch',
|
|
27
24
|
usageLine,
|
|
28
25
|
});
|
|
29
26
|
|
|
30
|
-
const projectWatcher = createProjectWatcher(project, {
|
|
31
|
-
runInitialBuild: true,
|
|
32
|
-
logChanges: true,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
projectWatcher.on('ready', ({ displayPaths, debounceMs }) => {
|
|
36
|
-
console.log(`[JSKim] プロジェクトを監視しています: ${project.name}`);
|
|
37
|
-
console.log('パス:');
|
|
38
|
-
for (const display of displayPaths) {
|
|
39
|
-
console.log(`- ${display}`);
|
|
40
|
-
}
|
|
41
|
-
console.log('');
|
|
42
|
-
console.log(`Debounce: ${debounceMs}ms`);
|
|
43
|
-
console.log('終了するには Ctrl+C を押してください。');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
await projectWatcher.start();
|
|
47
|
-
|
|
48
27
|
let stopping = false;
|
|
49
28
|
|
|
50
29
|
async function shutdown() {
|
|
@@ -54,7 +33,7 @@ async function runWatchCommand(options = {}) {
|
|
|
54
33
|
stopping = true;
|
|
55
34
|
|
|
56
35
|
try {
|
|
57
|
-
await
|
|
36
|
+
await runtime.close();
|
|
58
37
|
} catch {
|
|
59
38
|
// 終了時エラーは無視
|
|
60
39
|
}
|
|
@@ -64,6 +43,17 @@ async function runWatchCommand(options = {}) {
|
|
|
64
43
|
}
|
|
65
44
|
|
|
66
45
|
registerShutdown(shutdown);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await runtime.start();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
try {
|
|
51
|
+
await runtime.close();
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore
|
|
54
|
+
}
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
67
57
|
}
|
|
68
58
|
|
|
69
59
|
module.exports = {
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const chokidar = require('chokidar');
|
|
6
|
+
const { loadConfig, CONFIG_FILENAME } = require('./load-config');
|
|
7
|
+
const { resolveProject } = require('./resolve-project');
|
|
8
|
+
const { createProjectWatcher } = require('./create-project-watcher');
|
|
9
|
+
const { createStaticServer } = require('./create-static-server');
|
|
10
|
+
const { createLiveReload } = require('./create-live-reload');
|
|
11
|
+
const { formatListenError } = require('../commands/serve-errors');
|
|
12
|
+
|
|
13
|
+
const DEV_RESTART_KEYS = [
|
|
14
|
+
'outputDir',
|
|
15
|
+
'serve.host',
|
|
16
|
+
'serve.port',
|
|
17
|
+
'dev.liveReload',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* watch / dev 共通の実行ランタイムです。
|
|
22
|
+
* jskim.config.js の hot reload と project watcher 再構成を担当します。
|
|
23
|
+
*
|
|
24
|
+
* @param {object} options
|
|
25
|
+
* @param {'watch'|'dev'} options.mode
|
|
26
|
+
* @param {string} options.workspaceRoot
|
|
27
|
+
* @param {string|undefined} options.projectName
|
|
28
|
+
* @param {string} [options.commandName]
|
|
29
|
+
* @param {string} [options.usageLine]
|
|
30
|
+
* @returns {{ start: Function, close: Function }}
|
|
31
|
+
*/
|
|
32
|
+
function createWatchRuntime(options) {
|
|
33
|
+
const mode = options.mode;
|
|
34
|
+
const workspaceRoot = options.workspaceRoot || process.cwd();
|
|
35
|
+
const projectName = options.projectName;
|
|
36
|
+
const commandName = options.commandName || mode;
|
|
37
|
+
const usageLine =
|
|
38
|
+
options.usageLine || `npm run ${commandName} -- <project-name>`;
|
|
39
|
+
|
|
40
|
+
let currentProject = null;
|
|
41
|
+
let configPath = null;
|
|
42
|
+
let projectWatcher = null;
|
|
43
|
+
let configWatcher = null;
|
|
44
|
+
let liveReload = null;
|
|
45
|
+
let staticServer = null;
|
|
46
|
+
let liveReloadEnabled = false;
|
|
47
|
+
|
|
48
|
+
let stopping = false;
|
|
49
|
+
let started = false;
|
|
50
|
+
let configDebounceTimer = null;
|
|
51
|
+
let configReloadPromise = null;
|
|
52
|
+
let pendingConfigReload = false;
|
|
53
|
+
let sourceBuilding = false;
|
|
54
|
+
|
|
55
|
+
function resolveCurrentProject() {
|
|
56
|
+
const loaded = loadConfig(workspaceRoot);
|
|
57
|
+
const project = resolveProject({
|
|
58
|
+
config: loaded.config,
|
|
59
|
+
workspaceRoot,
|
|
60
|
+
projectName,
|
|
61
|
+
commandName,
|
|
62
|
+
usageLine,
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
project,
|
|
66
|
+
configPath: loaded.configPath,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function start() {
|
|
71
|
+
if (started) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
started = true;
|
|
75
|
+
|
|
76
|
+
const resolved = resolveCurrentProject();
|
|
77
|
+
currentProject = resolved.project;
|
|
78
|
+
configPath = resolved.configPath;
|
|
79
|
+
|
|
80
|
+
if (mode === 'dev') {
|
|
81
|
+
await startDevSession(currentProject, { initial: true });
|
|
82
|
+
} else {
|
|
83
|
+
await startWatchSession(currentProject, { initial: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
beginConfigWatching();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function startWatchSession(project, { initial }) {
|
|
90
|
+
projectWatcher = createProjectWatcher(project, {
|
|
91
|
+
runInitialBuild: true,
|
|
92
|
+
logChanges: true,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
wireSourceBuildTracking(projectWatcher);
|
|
96
|
+
|
|
97
|
+
if (initial) {
|
|
98
|
+
projectWatcher.on('ready', ({ displayPaths, debounceMs }) => {
|
|
99
|
+
console.log(`[JSKim] プロジェクトを監視しています: ${project.name}`);
|
|
100
|
+
console.log('パス:');
|
|
101
|
+
for (const display of displayPaths) {
|
|
102
|
+
console.log(`- ${display}`);
|
|
103
|
+
}
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log(`Debounce: ${debounceMs}ms`);
|
|
106
|
+
console.log('終了するには Ctrl+C を押してください。');
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await projectWatcher.start();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function startDevSession(project, { initial }) {
|
|
114
|
+
liveReloadEnabled = project.dev.liveReload;
|
|
115
|
+
const host = project.serve.host.trim();
|
|
116
|
+
const port = project.serve.port;
|
|
117
|
+
const outputDisplay = toDisplayPath(project.outputDir, workspaceRoot);
|
|
118
|
+
|
|
119
|
+
liveReload = createLiveReload({
|
|
120
|
+
projectName: project.name,
|
|
121
|
+
enabled: liveReloadEnabled,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
staticServer = createStaticServer({
|
|
125
|
+
rootDir: project.outputDir,
|
|
126
|
+
host,
|
|
127
|
+
port,
|
|
128
|
+
projectName: project.name,
|
|
129
|
+
handleInternalRequest: (req, res, meta) =>
|
|
130
|
+
liveReload.handleRequest(req, res, meta),
|
|
131
|
+
transformHtml: liveReloadEnabled
|
|
132
|
+
? (html) => liveReload.injectHtml(html)
|
|
133
|
+
: undefined,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
projectWatcher = createProjectWatcher(project, {
|
|
137
|
+
runInitialBuild: true,
|
|
138
|
+
logChanges: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
wireSourceBuildTracking(projectWatcher);
|
|
142
|
+
wireDevSourceReload(projectWatcher);
|
|
143
|
+
|
|
144
|
+
await projectWatcher.start({ watchFiles: false });
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
await staticServer.start();
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const formatted = formatListenError(err, {
|
|
150
|
+
projectName: project.name,
|
|
151
|
+
host,
|
|
152
|
+
port,
|
|
153
|
+
kind: '開発',
|
|
154
|
+
});
|
|
155
|
+
await cleanupDevComponents();
|
|
156
|
+
throw formatted;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await projectWatcher.startWatching();
|
|
160
|
+
|
|
161
|
+
if (initial) {
|
|
162
|
+
console.log('[JSKim] 開発サーバーを起動しました。');
|
|
163
|
+
console.log(`プロジェクト: ${project.name}`);
|
|
164
|
+
console.log(`ルート: ${outputDisplay}`);
|
|
165
|
+
console.log(`URL: ${staticServer.url}`);
|
|
166
|
+
console.log(
|
|
167
|
+
`ライブリロード: ${liveReloadEnabled ? '有効' : '無効'}`
|
|
168
|
+
);
|
|
169
|
+
console.log('終了するには Ctrl+C を押してください。');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function wireSourceBuildTracking(watcher) {
|
|
174
|
+
watcher.on('build:start', () => {
|
|
175
|
+
sourceBuilding = true;
|
|
176
|
+
});
|
|
177
|
+
watcher.on('build:success', () => {
|
|
178
|
+
sourceBuilding = false;
|
|
179
|
+
maybeRunPendingConfigReload();
|
|
180
|
+
});
|
|
181
|
+
watcher.on('build:failure', () => {
|
|
182
|
+
sourceBuilding = false;
|
|
183
|
+
maybeRunPendingConfigReload();
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function wireDevSourceReload(watcher) {
|
|
188
|
+
watcher.on('build:success', ({ initial }) => {
|
|
189
|
+
if (!initial && liveReloadEnabled && liveReload) {
|
|
190
|
+
liveReload.broadcastReload();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function beginConfigWatching() {
|
|
196
|
+
if (configWatcher || stopping) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
configWatcher = chokidar.watch(configPath, {
|
|
201
|
+
ignoreInitial: true,
|
|
202
|
+
persistent: true,
|
|
203
|
+
awaitWriteFinish: {
|
|
204
|
+
stabilityThreshold: 50,
|
|
205
|
+
pollInterval: 25,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
configWatcher.on('all', (eventName) => {
|
|
210
|
+
if (
|
|
211
|
+
eventName === 'add' ||
|
|
212
|
+
eventName === 'change' ||
|
|
213
|
+
eventName === 'unlink'
|
|
214
|
+
) {
|
|
215
|
+
queueConfigReload(eventName);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
configWatcher.on('error', (err) => {
|
|
220
|
+
const message = err && err.message ? err.message : String(err);
|
|
221
|
+
console.error(`[JSKim] 設定ファイルの監視エラー: ${message}`);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function queueConfigReload(eventName) {
|
|
226
|
+
if (stopping) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (eventName === 'unlink') {
|
|
231
|
+
console.log(
|
|
232
|
+
`[JSKim] 設定ファイルが一時的に削除されました。以前の正常な設定を継続します。`
|
|
233
|
+
);
|
|
234
|
+
} else {
|
|
235
|
+
console.log('[JSKim] 設定ファイルの変更を検出しました。');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (configDebounceTimer) {
|
|
239
|
+
clearTimeout(configDebounceTimer);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const debounceMs =
|
|
243
|
+
currentProject && currentProject.watch
|
|
244
|
+
? currentProject.watch.debounce
|
|
245
|
+
: 150;
|
|
246
|
+
|
|
247
|
+
configDebounceTimer = setTimeout(() => {
|
|
248
|
+
configDebounceTimer = null;
|
|
249
|
+
requestConfigReload();
|
|
250
|
+
}, debounceMs);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function requestConfigReload() {
|
|
254
|
+
if (stopping) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (configReloadPromise || sourceBuilding) {
|
|
259
|
+
pendingConfigReload = true;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
configReloadPromise = (async () => {
|
|
264
|
+
try {
|
|
265
|
+
await performConfigReload();
|
|
266
|
+
} finally {
|
|
267
|
+
configReloadPromise = null;
|
|
268
|
+
if (!stopping && pendingConfigReload) {
|
|
269
|
+
pendingConfigReload = false;
|
|
270
|
+
requestConfigReload();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
})();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function maybeRunPendingConfigReload() {
|
|
277
|
+
if (!stopping && pendingConfigReload && !configReloadPromise) {
|
|
278
|
+
pendingConfigReload = false;
|
|
279
|
+
requestConfigReload();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function performConfigReload() {
|
|
284
|
+
if (stopping) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!fs.existsSync(configPath)) {
|
|
289
|
+
// unlink 後まだ復元されていない
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let candidate;
|
|
294
|
+
try {
|
|
295
|
+
const loaded = loadConfig(workspaceRoot);
|
|
296
|
+
candidate = resolveProject({
|
|
297
|
+
config: loaded.config,
|
|
298
|
+
workspaceRoot,
|
|
299
|
+
projectName,
|
|
300
|
+
commandName,
|
|
301
|
+
usageLine,
|
|
302
|
+
});
|
|
303
|
+
} catch (err) {
|
|
304
|
+
const message = err && err.message ? err.message : String(err);
|
|
305
|
+
console.error('[JSKim] 設定ファイルの再読み込みに失敗しました。');
|
|
306
|
+
console.error(message);
|
|
307
|
+
console.error('[JSKim] 以前の正常な設定を継続します。');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (mode === 'dev') {
|
|
312
|
+
const restartKeys = getRestartRequiredChanges(currentProject, candidate);
|
|
313
|
+
if (restartKeys.length > 0) {
|
|
314
|
+
console.warn(
|
|
315
|
+
'[JSKim] 次の設定変更を反映するにはdev processの再起動が必要です:'
|
|
316
|
+
);
|
|
317
|
+
for (const key of restartKeys) {
|
|
318
|
+
console.warn(`- ${key}`);
|
|
319
|
+
}
|
|
320
|
+
console.warn('[JSKim] 以前の正常な設定を継続します。');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
console.log('[JSKim] 設定を再読み込みしました。');
|
|
326
|
+
|
|
327
|
+
let buildSucceeded = false;
|
|
328
|
+
try {
|
|
329
|
+
await replaceProjectWatcher(candidate, {
|
|
330
|
+
onInitialBuildSuccess: () => {
|
|
331
|
+
buildSucceeded = true;
|
|
332
|
+
},
|
|
333
|
+
onInitialBuildFailure: () => {
|
|
334
|
+
buildSucceeded = false;
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
currentProject = candidate;
|
|
338
|
+
console.log('[JSKim] 監視対象を更新しました。');
|
|
339
|
+
if (mode === 'watch') {
|
|
340
|
+
console.log('パス:');
|
|
341
|
+
for (const display of projectWatcher.displayPaths) {
|
|
342
|
+
console.log(`- ${display}`);
|
|
343
|
+
}
|
|
344
|
+
console.log(`Debounce: ${projectWatcher.debounceMs}ms`);
|
|
345
|
+
}
|
|
346
|
+
} catch (err) {
|
|
347
|
+
const message = err && err.message ? err.message : String(err);
|
|
348
|
+
console.error('[JSKim] 設定の再読み込み後に監視の更新に失敗しました。');
|
|
349
|
+
console.error(message);
|
|
350
|
+
console.error('[JSKim] 以前の正常な設定を継続します。');
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!buildSucceeded) {
|
|
355
|
+
console.error(
|
|
356
|
+
'[JSKim] 設定の再読み込み後にbuildが失敗しました。監視は新しい設定で継続します。'
|
|
357
|
+
);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (mode === 'dev' && liveReloadEnabled && liveReload) {
|
|
362
|
+
liveReload.broadcastReload();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function replaceProjectWatcher(project, hooks = {}) {
|
|
367
|
+
if (projectWatcher) {
|
|
368
|
+
try {
|
|
369
|
+
await projectWatcher.close();
|
|
370
|
+
} catch {
|
|
371
|
+
// reload 時の close エラーは無視
|
|
372
|
+
}
|
|
373
|
+
projectWatcher = null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
projectWatcher = createProjectWatcher(project, {
|
|
377
|
+
runInitialBuild: true,
|
|
378
|
+
logChanges: true,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
wireSourceBuildTracking(projectWatcher);
|
|
382
|
+
if (mode === 'dev') {
|
|
383
|
+
wireDevSourceReload(projectWatcher);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const onSuccess = ({ initial }) => {
|
|
387
|
+
if (initial && hooks.onInitialBuildSuccess) {
|
|
388
|
+
hooks.onInitialBuildSuccess();
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
const onFailure = ({ initial }) => {
|
|
392
|
+
if (initial && hooks.onInitialBuildFailure) {
|
|
393
|
+
hooks.onInitialBuildFailure();
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
projectWatcher.on('build:success', onSuccess);
|
|
397
|
+
projectWatcher.on('build:failure', onFailure);
|
|
398
|
+
|
|
399
|
+
await projectWatcher.start();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function cleanupDevComponents() {
|
|
403
|
+
if (projectWatcher) {
|
|
404
|
+
try {
|
|
405
|
+
await projectWatcher.close();
|
|
406
|
+
} catch {
|
|
407
|
+
// ignore
|
|
408
|
+
}
|
|
409
|
+
projectWatcher = null;
|
|
410
|
+
}
|
|
411
|
+
if (liveReload) {
|
|
412
|
+
try {
|
|
413
|
+
liveReload.close();
|
|
414
|
+
} catch {
|
|
415
|
+
// ignore
|
|
416
|
+
}
|
|
417
|
+
liveReload = null;
|
|
418
|
+
}
|
|
419
|
+
if (staticServer) {
|
|
420
|
+
try {
|
|
421
|
+
await staticServer.stop();
|
|
422
|
+
} catch {
|
|
423
|
+
// ignore
|
|
424
|
+
}
|
|
425
|
+
staticServer = null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function close() {
|
|
430
|
+
if (stopping) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
stopping = true;
|
|
434
|
+
|
|
435
|
+
if (configDebounceTimer) {
|
|
436
|
+
clearTimeout(configDebounceTimer);
|
|
437
|
+
configDebounceTimer = null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (configWatcher) {
|
|
441
|
+
try {
|
|
442
|
+
await configWatcher.close();
|
|
443
|
+
} catch {
|
|
444
|
+
// ignore
|
|
445
|
+
}
|
|
446
|
+
configWatcher = null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (configReloadPromise) {
|
|
450
|
+
try {
|
|
451
|
+
await configReloadPromise;
|
|
452
|
+
} catch {
|
|
453
|
+
// ignore
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (mode === 'dev') {
|
|
458
|
+
await cleanupDevComponents();
|
|
459
|
+
} else if (projectWatcher) {
|
|
460
|
+
try {
|
|
461
|
+
await projectWatcher.close();
|
|
462
|
+
} catch {
|
|
463
|
+
// ignore
|
|
464
|
+
}
|
|
465
|
+
projectWatcher = null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
start,
|
|
471
|
+
close,
|
|
472
|
+
get project() {
|
|
473
|
+
return currentProject;
|
|
474
|
+
},
|
|
475
|
+
get configFileName() {
|
|
476
|
+
return CONFIG_FILENAME;
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* @param {object} previous
|
|
483
|
+
* @param {object} next
|
|
484
|
+
* @returns {string[]}
|
|
485
|
+
*/
|
|
486
|
+
function getRestartRequiredChanges(previous, next) {
|
|
487
|
+
const changes = [];
|
|
488
|
+
if (previous.outputDir !== next.outputDir) {
|
|
489
|
+
changes.push('outputDir');
|
|
490
|
+
}
|
|
491
|
+
if (previous.serve.host !== next.serve.host) {
|
|
492
|
+
changes.push('serve.host');
|
|
493
|
+
}
|
|
494
|
+
if (previous.serve.port !== next.serve.port) {
|
|
495
|
+
changes.push('serve.port');
|
|
496
|
+
}
|
|
497
|
+
if (previous.dev.liveReload !== next.dev.liveReload) {
|
|
498
|
+
changes.push('dev.liveReload');
|
|
499
|
+
}
|
|
500
|
+
return changes;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function toDisplayPath(abs, workspaceRoot) {
|
|
504
|
+
const resolved = path.resolve(abs);
|
|
505
|
+
const rel = path.relative(workspaceRoot, resolved);
|
|
506
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
507
|
+
return resolved.split(path.sep).join('/');
|
|
508
|
+
}
|
|
509
|
+
return rel.split(path.sep).join('/');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
module.exports = {
|
|
513
|
+
createWatchRuntime,
|
|
514
|
+
getRestartRequiredChanges,
|
|
515
|
+
DEV_RESTART_KEYS,
|
|
516
|
+
};
|