@ywal123456/jskim 0.3.0 → 0.4.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 +11 -4
- package/docs/configuration.md +6 -1
- package/docs/create-jskim.md +3 -3
- package/package.json +1 -1
- package/scripts/commands/path-display.js +1 -15
- package/scripts/lib/classify-reload.js +131 -0
- package/scripts/lib/create-live-reload.js +422 -32
- package/scripts/lib/create-nunjucks-env.js +2 -1
- package/scripts/lib/create-project-watcher.js +10 -3
- package/scripts/lib/create-watch-runtime.js +39 -7
- package/scripts/lib/format-diagnostic.js +234 -0
- package/scripts/lib/load-config.js +6 -4
- package/scripts/lib/process-files.js +73 -29
- package/scripts/lib/render-pages.js +23 -13
- package/scripts/lib/resolve-project.js +57 -32
- package/scripts/lib/stylesheet-reload.js +58 -0
- package/scripts/lib/to-display-path.js +28 -0
package/README.md
CHANGED
|
@@ -14,14 +14,16 @@ Nunjucks を使った汎用の静的 HTML ビルド環境です。
|
|
|
14
14
|
- ファイル監視(`watch`)後の全体再ビルド
|
|
15
15
|
- ビルド結果のローカル静的サーバー(`serve`)
|
|
16
16
|
- 開発サーバー(`dev` = build + watch + serve)
|
|
17
|
-
-
|
|
17
|
+
- 成功した再ビルド後のライブリロード(SSE)
|
|
18
|
+
- 再ビルド失敗時の browser error overlay(`dev` + `liveReload`)
|
|
19
|
+
- 安全な CSS 変更時の stylesheet soft reload(`dev` + `liveReload`)
|
|
18
20
|
- CLI binary `jskim`
|
|
19
21
|
- プロジェクト生成 CLI `create-jskim`
|
|
20
22
|
|
|
21
23
|
## 現在の非対応範囲
|
|
22
24
|
|
|
23
25
|
- ブラウザ自動起動
|
|
24
|
-
- HMR /
|
|
26
|
+
- HMR / JavaScript module hot replacement
|
|
25
27
|
- JSON / YAML などの外部データファイル自動読み込み
|
|
26
28
|
- API / Mock API
|
|
27
29
|
- 増分ビルド
|
|
@@ -234,9 +236,14 @@ http://127.0.0.1:3000/
|
|
|
234
236
|
- `serve` は自動で build / watch を実行しません
|
|
235
237
|
- サーバールートは `outputDir`(例: `dist/sample`)
|
|
236
238
|
- `serve` は HTML を変換せず、ライブリロード script を注入しません
|
|
237
|
-
- `
|
|
238
|
-
- `dev`
|
|
239
|
+
- `watch` にも browser overlay / CSS soft reload はありません
|
|
240
|
+
- `dev` は `dev.liveReload: true` のときだけ browser 向け機能を有効にします
|
|
241
|
+
- 再ビルド失敗時は terminal 診断に加え、browser error overlay を表示します(成功するまで full reload / CSS reload は送りません)
|
|
242
|
+
- CSS ファイルだけが安全に変更された再ビルドでは、ページ全体ではなく stylesheet を更新します
|
|
243
|
+
- CSS 以外の変更や判定が不確実な場合は、従来どおりページ全体 reload します
|
|
244
|
+
- `dev` でも `dist` ファイルへ runtime は書き込みません
|
|
239
245
|
- ブラウザ自動起動はサポートしません
|
|
246
|
+
- 厳格な Content Security Policy で inline script または inline style が禁止されている場合、dev の live reload・error overlay・CSS soft reload が動作しないことがあります
|
|
240
247
|
|
|
241
248
|
`/_jskim/live-reload` は `dev.liveReload: true` のときだけ使う内部予約パスです。実際の静的ファイルパスとしては使わないことを推奨します。
|
|
242
249
|
|
package/docs/configuration.md
CHANGED
|
@@ -309,16 +309,21 @@ files mode では出力パスの衝突を検出します。たとえば同じ `f
|
|
|
309
309
|
|
|
310
310
|
- `GET /_jskim/live-reload` で SSE を提供
|
|
311
311
|
- HTML レスポンスにだけ client script を一時注入(`dist` には書き込まない)
|
|
312
|
-
- 成功した再ビルドのあとだけ
|
|
312
|
+
- 成功した再ビルドのあとだけ browser 更新を送信(通常はページ全体 reload)
|
|
313
|
+
- 再ビルド失敗時は browser error overlay を表示(成功するまで reload / CSS 更新は送らない)
|
|
314
|
+
- CSS ファイルだけが安全に変更された場合は、ページ全体ではなく stylesheet を更新
|
|
313
315
|
|
|
314
316
|
`false` のとき:
|
|
315
317
|
|
|
316
318
|
- SSE endpoint は提供しない(404)
|
|
317
319
|
- HTML への script 注入なし
|
|
320
|
+
- error overlay / CSS soft reload も無効
|
|
318
321
|
- build + watch + serve 自体は動作
|
|
319
322
|
|
|
320
323
|
`/_jskim/live-reload` は内部予約パスです。プロジェクトの静的ファイルとしては使わないことを推奨します。
|
|
321
324
|
|
|
325
|
+
厳格な Content Security Policy で inline script または inline style が禁止されている場合、dev の live reload・error overlay・CSS soft reload が動作しないことがあります。`watch` と `serve` にはこれらの browser 機能はありません。
|
|
326
|
+
|
|
322
327
|
## config hot reload
|
|
323
328
|
|
|
324
329
|
`watch` と `dev` はワークスペースルートの `jskim.config.js` を監視します。
|
package/docs/create-jskim.md
CHANGED
|
@@ -54,12 +54,12 @@ project/
|
|
|
54
54
|
|
|
55
55
|
`jskim.config.js` は `files: [{ from: 'pages', to: '' }]` を使います。`.njk` は末尾の `.njk` だけを外してレンダリングし、画像などはそのままコピーします。
|
|
56
56
|
|
|
57
|
-
`package.json` の scripts は bin 名 `jskim` を使います(package name ではありません)。既定の engine dependency は `@ywal123456/jskim`(`^0.
|
|
57
|
+
`package.json` の scripts は bin 名 `jskim` を使います(package name ではありません)。既定の engine dependency は `@ywal123456/jskim`(`^0.4.0`)です。
|
|
58
58
|
|
|
59
59
|
```json
|
|
60
60
|
{
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@ywal123456/jskim": "^0.
|
|
62
|
+
"@ywal123456/jskim": "^0.4.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "jskim build sample",
|
|
@@ -85,7 +85,7 @@ create package の `package.json` に `jskimEngine` metadata を持ち、生成
|
|
|
85
85
|
{
|
|
86
86
|
"jskimEngine": {
|
|
87
87
|
"packageName": "@ywal123456/jskim",
|
|
88
|
-
"version": "^0.
|
|
88
|
+
"version": "^0.4.0"
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
```
|
package/package.json
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* ワークスペース相対の表示用パスへ変換します。
|
|
7
|
-
* @param {string} abs
|
|
8
|
-
* @param {string} workspaceRoot
|
|
9
|
-
* @returns {string}
|
|
10
|
-
*/
|
|
11
|
-
function toDisplayPath(abs, workspaceRoot) {
|
|
12
|
-
const rel = path.relative(workspaceRoot, abs);
|
|
13
|
-
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
14
|
-
return abs.split(path.sep).join('/');
|
|
15
|
-
}
|
|
16
|
-
return rel.split(path.sep).join('/');
|
|
17
|
-
}
|
|
3
|
+
const { toDisplayPath } = require('../lib/to-display-path');
|
|
18
4
|
|
|
19
5
|
module.exports = {
|
|
20
6
|
toDisplayPath,
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* rebuild 単位の watcher events から browser 更新方法を分類します。
|
|
8
|
+
* 不確実な場合は必ず full reload にします。
|
|
9
|
+
*
|
|
10
|
+
* @param {object} options
|
|
11
|
+
* @param {Array<{ event?: string, absolutePath?: string, file?: string }>} options.events
|
|
12
|
+
* @param {string} options.sourceDir
|
|
13
|
+
* @param {string[]} [options.templates]
|
|
14
|
+
* @returns {'css'|'reload'}
|
|
15
|
+
*/
|
|
16
|
+
function classifyReload(options) {
|
|
17
|
+
const events = Array.isArray(options.events) ? options.events : [];
|
|
18
|
+
if (events.length === 0) {
|
|
19
|
+
return 'reload';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const templateRoots = resolveTemplateRoots(
|
|
23
|
+
options.sourceDir,
|
|
24
|
+
options.templates
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
for (const item of events) {
|
|
28
|
+
if (!item || typeof item !== 'object') {
|
|
29
|
+
return 'reload';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const eventName = item.event;
|
|
33
|
+
if (eventName !== 'change') {
|
|
34
|
+
return 'reload';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const absolutePath = resolveEventAbsolutePath(item);
|
|
38
|
+
if (!absolutePath) {
|
|
39
|
+
return 'reload';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isInsideAny(templateRoots, absolutePath)) {
|
|
43
|
+
return 'reload';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!isCssSourcePath(absolutePath)) {
|
|
47
|
+
return 'reload';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return 'css';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {{ absolutePath?: string, file?: string }} item
|
|
56
|
+
* @returns {string|null}
|
|
57
|
+
*/
|
|
58
|
+
function resolveEventAbsolutePath(item) {
|
|
59
|
+
if (item.absolutePath && typeof item.absolutePath === 'string') {
|
|
60
|
+
const abs = path.resolve(item.absolutePath);
|
|
61
|
+
if (path.isAbsolute(abs)) {
|
|
62
|
+
return abs;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 末尾が .css または .css.njk のときだけ true(大文字小文字は無視)。
|
|
70
|
+
* @param {string} absolutePath
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
function isCssSourcePath(absolutePath) {
|
|
74
|
+
const normalized = absolutePath.split(path.sep).join('/').toLowerCase();
|
|
75
|
+
return normalized.endsWith('.css.njk') || normalized.endsWith('.css');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveTemplateRoots(sourceDir, templates) {
|
|
79
|
+
const roots = [];
|
|
80
|
+
if (!sourceDir) {
|
|
81
|
+
return roots;
|
|
82
|
+
}
|
|
83
|
+
for (const rel of Array.isArray(templates) ? templates : []) {
|
|
84
|
+
if (!rel) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const abs = path.resolve(sourceDir, rel);
|
|
88
|
+
try {
|
|
89
|
+
if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
|
|
90
|
+
roots.push(abs);
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// 存在確認失敗は root として扱わない
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return roots;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isInsideAny(roots, candidate) {
|
|
100
|
+
for (const root of roots) {
|
|
101
|
+
if (isInsideOrSame(root, candidate)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isInsideOrSame(parent, child) {
|
|
109
|
+
const p = path.resolve(parent);
|
|
110
|
+
const c = path.resolve(child);
|
|
111
|
+
if (samePath(p, c)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
const rel = path.relative(p, c);
|
|
115
|
+
return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function samePath(a, b) {
|
|
119
|
+
const na = path.resolve(a);
|
|
120
|
+
const nb = path.resolve(b);
|
|
121
|
+
if (process.platform === 'win32') {
|
|
122
|
+
return na.toLowerCase() === nb.toLowerCase();
|
|
123
|
+
}
|
|
124
|
+
return na === nb;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
classifyReload,
|
|
129
|
+
isCssSourcePath,
|
|
130
|
+
resolveTemplateRoots,
|
|
131
|
+
};
|