pugkit 1.0.0 → 1.0.2
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 +137 -76
- package/core/cache.mjs +2 -2
- package/core/context.mjs +1 -6
- package/core/server.mjs +41 -25
- package/core/watcher.mjs +93 -18
- package/generate/asset.mjs +2 -0
- package/package.json +1 -1
- package/tasks/pug.mjs +2 -4
- package/tasks/script.mjs +3 -3
- package/tasks/svg-sprite.mjs +0 -4
- package/tasks/svg.mjs +1 -7
- package/utils/file.mjs +2 -7
package/README.md
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
1
|
# pugkit
|
|
2
2
|
|
|
3
|
+
<p>
|
|
4
|
+
<a aria-label="NPM version" href="https://www.npmjs.com/package/pugkit">
|
|
5
|
+
<img alt="" src="https://img.shields.io/npm/v/pugkit.svg?style=for-the-badge&labelColor=212121">
|
|
6
|
+
</a>
|
|
7
|
+
<a aria-label="License" href="https://github.com/mfxgu2i/pugkit/blob/main/LICENSE">
|
|
8
|
+
<img alt="" src="https://img.shields.io/npm/l/pugkit.svg?style=for-the-badge&labelColor=212121">
|
|
9
|
+
</a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
3
12
|
## About
|
|
4
13
|
|
|
5
14
|
pugkitは静的サイト制作に特化したビルドツールです。
|
|
6
15
|
納品向きの綺麗なHTMLと、ファイル構成に制約のないアセットファイルを出力可能です。
|
|
7
16
|
|
|
8
|
-
##
|
|
17
|
+
## How To Use
|
|
9
18
|
|
|
10
|
-
```
|
|
11
|
-
npm install pugkit
|
|
19
|
+
```sh
|
|
20
|
+
$ npm install --save-dev pugkit
|
|
21
|
+
$ touch ./src/index.pug
|
|
12
22
|
```
|
|
13
23
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
### Development Mode
|
|
17
|
-
|
|
18
|
-
ファイルの変更を監視し、ブラウザの自動リロードを行います。
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
pugkit
|
|
22
|
-
# or
|
|
23
|
-
pugkit dev
|
|
24
|
-
# or
|
|
25
|
-
pugkit watch
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Production Build
|
|
29
|
-
|
|
30
|
-
製品用ファイルを生成します。
|
|
24
|
+
`package.json` にスクリプトを追加します。
|
|
31
25
|
|
|
32
|
-
```
|
|
33
|
-
|
|
26
|
+
```json
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "pugkit",
|
|
29
|
+
"build": "pugkit build",
|
|
30
|
+
"sprite": "pugkit sprite"
|
|
31
|
+
}
|
|
34
32
|
```
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
アイコン用のSVGスプライトを生成します。
|
|
34
|
+
## Commands
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
| コマンド | 内容 |
|
|
37
|
+
| --------------- | ----------------------------- |
|
|
38
|
+
| `pugkit` | 開発モード(Ctrl + C で停止) |
|
|
39
|
+
| `pugkit build` | 本番ビルド |
|
|
40
|
+
| `pugkit sprite` | SVGスプライト生成 |
|
|
43
41
|
|
|
44
42
|
## Configuration
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
プロジェクトルートに`pugkit.config.mjs`を配置することで、ビルド設定をカスタマイズできます。
|
|
47
45
|
|
|
48
46
|
```js
|
|
49
47
|
// pugkit.config.mjs
|
|
50
|
-
|
|
48
|
+
import { defineConfig } from 'pugkit'
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
51
|
siteUrl: 'https://example.com/',
|
|
52
|
-
subdir: '
|
|
52
|
+
subdir: '',
|
|
53
53
|
debug: false,
|
|
54
54
|
server: {
|
|
55
55
|
port: 5555,
|
|
@@ -57,42 +57,30 @@ export default {
|
|
|
57
57
|
startPath: '/'
|
|
58
58
|
},
|
|
59
59
|
build: {
|
|
60
|
-
imageOptimization: 'webp'
|
|
61
|
-
imageOptions: {
|
|
62
|
-
webp: {
|
|
63
|
-
quality: 90,
|
|
64
|
-
effort: 6
|
|
65
|
-
},
|
|
66
|
-
jpeg: {
|
|
67
|
-
quality: 75,
|
|
68
|
-
progressive: true
|
|
69
|
-
},
|
|
70
|
-
png: {
|
|
71
|
-
quality: 85,
|
|
72
|
-
compressionLevel: 6
|
|
73
|
-
}
|
|
74
|
-
}
|
|
60
|
+
imageOptimization: 'webp'
|
|
75
61
|
}
|
|
76
|
-
}
|
|
62
|
+
})
|
|
77
63
|
```
|
|
78
64
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
| `debug` | デバッグモード(開発時のみ有効) | `false` |
|
|
84
|
-
| `server.port` | 開発サーバーのポート番号
|
|
85
|
-
| `server.host` | 開発サーバーのホスト
|
|
86
|
-
| `server.startPath` | サーバー起動時に開くパス
|
|
87
|
-
| `server.open` | サーバー起動時にブラウザを開く
|
|
88
|
-
| `build.imageOptimization` | 画像最適化の方式
|
|
89
|
-
| `build.imageOptions`
|
|
65
|
+
| Option | Description | Type / Values | Default |
|
|
66
|
+
| ------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------- | ------------- |
|
|
67
|
+
| `siteUrl` | サイトのベースURL(`Builder.url` に使用) | `string` | `''` |
|
|
68
|
+
| `subdir` | サブディレクトリのパス | `string` | `''` |
|
|
69
|
+
| `debug` | デバッグモード(開発時のみ有効) | `boolean` | `false` |
|
|
70
|
+
| `server.port` | 開発サーバーのポート番号 | `number` | `5555` |
|
|
71
|
+
| `server.host` | 開発サーバーのホスト | `string` | `'localhost'` |
|
|
72
|
+
| `server.startPath` | サーバー起動時に開くパス | `string` | `'/'` |
|
|
73
|
+
| `server.open` | サーバー起動時にブラウザを開く | `boolean` | `false` |
|
|
74
|
+
| `build.imageOptimization` | 画像最適化の方式 | `'webp'` \| `'compress'` \| `false` | `'webp'` |
|
|
75
|
+
| `build.imageOptions.webp` | WebP変換オプション([Sharp WebP options](https://sharp.pixelplumbing.com/api-output#webp)) | `object` | - |
|
|
76
|
+
| `build.imageOptions.jpeg` | JPEG圧縮オプション([Sharp JPEG options](https://sharp.pixelplumbing.com/api-output#jpeg)) | `object` | - |
|
|
77
|
+
| `build.imageOptions.png` | PNG圧縮オプション([Sharp PNG options](https://sharp.pixelplumbing.com/api-output#png)) | `object` | - |
|
|
90
78
|
|
|
91
79
|
## Features
|
|
92
80
|
|
|
93
81
|
### Pug Templates
|
|
94
82
|
|
|
95
|
-
Pug
|
|
83
|
+
Pugテンプレート内では `Builder` オブジェクトと `imageInfo()` 関数が使用できます。
|
|
96
84
|
|
|
97
85
|
#### Builder Object
|
|
98
86
|
|
|
@@ -122,8 +110,6 @@ meta(property='og:url', content=Builder.url.href)
|
|
|
122
110
|
img(src=info.src width=info.width height=info.height alt='')
|
|
123
111
|
```
|
|
124
112
|
|
|
125
|
-
**返り値**
|
|
126
|
-
|
|
127
113
|
| Property | Type | Description |
|
|
128
114
|
| -------- | -------------------------------- | ----------------------------------------------------- |
|
|
129
115
|
| `src` | `string` | 最適化設定に応じたパス(webpモード時は `.webp` パス) |
|
|
@@ -134,21 +120,102 @@ img(src=info.src width=info.width height=info.height alt='')
|
|
|
134
120
|
| `retina` | `{ src: string } \| null` | `@2x` 画像が存在する場合に自動検出 |
|
|
135
121
|
| `sp` | `{ src, width, height } \| null` | `_sp` 画像が存在する場合に自動検出 |
|
|
136
122
|
|
|
137
|
-
>
|
|
123
|
+
> `imageInfo()` は `src/` 配下の画像のみ対応しています。`public/` 配下の画像は非対応です。
|
|
124
|
+
|
|
125
|
+
### Sass
|
|
126
|
+
|
|
127
|
+
`src/` 配下の `.scss` ファイルをコンパイルして出力します。ベンダープレフィックスの自動付与と圧縮も行われます。
|
|
128
|
+
|
|
129
|
+
> ブラウザターゲットを指定する場合は、プロジェクトルートに `.browserslistrc` を配置してください。
|
|
130
|
+
|
|
131
|
+
### JavaScript / TypeScript
|
|
132
|
+
|
|
133
|
+
`src/` 配下の `.js` / `.ts` ファイルをバンドルして出力します。
|
|
134
|
+
|
|
135
|
+
esbuild がTypeScriptをネイティブ処理するため、`tsconfig.json` は不要です。ただし型チェックは行わずトランスパイルのみ行います。
|
|
136
|
+
|
|
137
|
+
#### TypeScript 型チェックを追加する(オプション)
|
|
138
|
+
|
|
139
|
+
型チェックが必要な場合は`typescript`を追加し、`tsc --noEmit`を組み合わせて使用します。
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
npm install --save-dev typescript
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`tsconfig.json`をプロジェクトルートに作成します。
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"compilerOptions": {
|
|
150
|
+
"target": "ES2022",
|
|
151
|
+
"module": "ESNext",
|
|
152
|
+
"moduleResolution": "bundler",
|
|
153
|
+
"strict": true,
|
|
154
|
+
"noEmit": true,
|
|
155
|
+
"skipLibCheck": true
|
|
156
|
+
},
|
|
157
|
+
"include": ["src/**/*.ts"]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`package.json` にスクリプトを追加します。
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
"scripts": {
|
|
165
|
+
"start": "pugkit",
|
|
166
|
+
"build": "tsc --noEmit && pugkit build",
|
|
167
|
+
"sprite": "pugkit sprite",
|
|
168
|
+
}
|
|
169
|
+
```
|
|
138
170
|
|
|
139
171
|
### Image Optimization
|
|
140
172
|
|
|
141
|
-
|
|
173
|
+
ビルド時に `src/` 配下の画像(JPEG・PNG)を自動的に最適化します。
|
|
174
|
+
|
|
175
|
+
- `'webp'` - PNG/JPEGをWebPに変換
|
|
176
|
+
- `'compress'` - 元の形式を維持したまま圧縮
|
|
177
|
+
- `false` - 最適化を無効化
|
|
178
|
+
|
|
179
|
+
### SVG Sprite
|
|
180
|
+
|
|
181
|
+
`src/` 配下の `icons/` ディレクトリに配置したSVGを1つのスプライトファイルにまとめます。
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
src/assets/icons/arrow.svg → dist/assets/icons.svg#arrow
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<svg><use href="assets/icons.svg#arrow"></use></svg>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
- SVG ファイル名がそのまま `<symbol id>` になります
|
|
192
|
+
- `fill` / `stroke` は自動的に `currentColor` に変換されます
|
|
193
|
+
|
|
194
|
+
### SVG Optimization
|
|
195
|
+
|
|
196
|
+
`icons/` 以外に配置した SVG ファイルは SVGO で自動最適化されて出力されます。
|
|
197
|
+
|
|
198
|
+
### Public Directory
|
|
199
|
+
|
|
200
|
+
`public/` に置いたファイルはそのまま `dist/` のルートにコピーされます。faviconやOGP画像など最適化不要なファイルの置き場として使用します。
|
|
201
|
+
|
|
202
|
+
### Debug Mode
|
|
142
203
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
204
|
+
`debug: true` のとき、開発モードでのみ以下の出力に切り替わります。
|
|
205
|
+
|
|
206
|
+
| 対象 | 通常 | debug: true |
|
|
207
|
+
| ---- | ---------------------------- | ------------------------------ |
|
|
208
|
+
| CSS | minify済み | expanded + ソースマップ |
|
|
209
|
+
| JS | minify済み・`console.*` 削除 | ソースマップ・`console.*` 保持 |
|
|
146
210
|
|
|
147
211
|
### File Naming Rules
|
|
148
212
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
213
|
+
`_`(アンダースコア)で始まるファイル・ディレクトリはビルド対象外です。それ以外のファイルは `src/` 配下のディレクトリ構成を維持したまま `dist/` に出力されます。
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
src/foo/style.scss → dist/foo/style.css
|
|
217
|
+
src/foo/bar/script.js → dist/foo/bar/script.js
|
|
218
|
+
```
|
|
152
219
|
|
|
153
220
|
## Directory Structure
|
|
154
221
|
|
|
@@ -178,10 +245,4 @@ project-root/
|
|
|
178
245
|
- [Sharp](https://sharp.pixelplumbing.com/) - 画像最適化
|
|
179
246
|
- [SVGO](https://svgo.dev/) - SVG最適化
|
|
180
247
|
- [Chokidar](https://github.com/paulmillr/chokidar) - ファイル監視
|
|
181
|
-
- [sirv](https://github.com/lukeed/sirv) - 開発サーバー
|
|
182
|
-
- SSE(Server-Sent Events) - ライブリロード
|
|
183
|
-
- [Prettier](https://prettier.io/) - HTML整形
|
|
184
|
-
|
|
185
|
-
## License
|
|
186
|
-
|
|
187
|
-
MIT
|
|
248
|
+
- [sirv](https://github.com/lukeed/sirv) + SSE(Server-Sent Events) - 開発サーバー
|
package/core/cache.mjs
CHANGED
|
@@ -83,8 +83,8 @@ export class CacheManager {
|
|
|
83
83
|
const [stats, content] = await Promise.all([stat(filePath), readFile(filePath)])
|
|
84
84
|
|
|
85
85
|
return createHash('md5').update(content).update(stats.mtime.toISOString()).digest('hex')
|
|
86
|
-
} catch
|
|
87
|
-
return
|
|
86
|
+
} catch {
|
|
87
|
+
return `error-${Math.random()}`
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
package/core/context.mjs
CHANGED
|
@@ -23,12 +23,7 @@ export class BuildContext {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async runTask(taskName, taskFn, options = {}) {
|
|
26
|
-
|
|
27
|
-
await taskFn(this, options)
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error(`Task failed: ${taskName}`)
|
|
30
|
-
throw error
|
|
31
|
-
}
|
|
26
|
+
await taskFn(this, options)
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
async runParallel(tasks) {
|
package/core/server.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import http from 'node:http'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import { existsSync
|
|
4
|
-
import { mkdir } from 'node:fs/promises'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { mkdir, readFile } from 'node:fs/promises'
|
|
5
5
|
import sirv from 'sirv'
|
|
6
6
|
import { logger } from '../utils/logger.mjs'
|
|
7
7
|
|
|
@@ -17,7 +17,6 @@ const liveReloadScript = `<script>
|
|
|
17
17
|
location.reload();
|
|
18
18
|
});
|
|
19
19
|
es.addEventListener('css-update', function() {
|
|
20
|
-
// 同一オリジンの <link rel="stylesheet"> のみ再読み込み(外部フォント等は除外)
|
|
21
20
|
document.querySelectorAll('link[rel="stylesheet"]').forEach(function(link) {
|
|
22
21
|
var url = new URL(link.href);
|
|
23
22
|
if (url.origin !== location.origin) return;
|
|
@@ -29,6 +28,9 @@ const liveReloadScript = `<script>
|
|
|
29
28
|
es.close();
|
|
30
29
|
setTimeout(function() { location.reload(); }, 1000);
|
|
31
30
|
};
|
|
31
|
+
window.addEventListener('beforeunload', function() {
|
|
32
|
+
es.close();
|
|
33
|
+
});
|
|
32
34
|
})();
|
|
33
35
|
</script>`
|
|
34
36
|
|
|
@@ -42,15 +44,17 @@ export async function serverTask(context, options = {}) {
|
|
|
42
44
|
await mkdir(paths.dist, { recursive: true })
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
const port = config.server?.port ??
|
|
47
|
+
const port = config.server?.port ?? 5555
|
|
46
48
|
const host = config.server?.host ?? 'localhost'
|
|
47
49
|
const subdir = config.subdir ? '/' + config.subdir.replace(/^\/|\/$/g, '') : ''
|
|
48
50
|
const startPath = (config.server?.startPath || '/').replace(/^\//, '')
|
|
49
51
|
const fullStartPath = subdir ? `${subdir}/${startPath}` : `/${startPath}`
|
|
50
52
|
|
|
53
|
+
const serveRoot = path.resolve(config.root, 'dist')
|
|
54
|
+
|
|
51
55
|
const clients = new Set()
|
|
52
56
|
|
|
53
|
-
const staticServe = sirv(
|
|
57
|
+
const staticServe = sirv(serveRoot, {
|
|
54
58
|
dev: true,
|
|
55
59
|
extensions: ['html'],
|
|
56
60
|
setHeaders(res, filePath) {
|
|
@@ -73,36 +77,44 @@ export async function serverTask(context, options = {}) {
|
|
|
73
77
|
})
|
|
74
78
|
res.write('retry: 1000\n\n')
|
|
75
79
|
clients.add(res)
|
|
76
|
-
|
|
80
|
+
|
|
81
|
+
const cleanup = () => clients.delete(res)
|
|
82
|
+
req.on('close', cleanup)
|
|
83
|
+
req.socket.on('close', cleanup)
|
|
84
|
+
res.on('error', cleanup)
|
|
77
85
|
return
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
// ── HTML へのライブリロードスクリプト注入 ───────────
|
|
81
89
|
const decoded = decodeURIComponent(urlPath)
|
|
82
90
|
const candidates = [
|
|
83
|
-
path.join(
|
|
84
|
-
path.join(
|
|
85
|
-
path.join(
|
|
91
|
+
path.join(serveRoot, decoded === '/' ? 'index.html' : decoded.replace(/\/$/, '') + '/index.html'),
|
|
92
|
+
path.join(serveRoot, decoded === '/' ? 'index.html' : decoded + '.html'),
|
|
93
|
+
path.join(serveRoot, decoded)
|
|
86
94
|
]
|
|
87
95
|
const htmlFile = candidates.find(p => p.endsWith('.html') && existsSync(p))
|
|
88
96
|
|
|
89
97
|
if (htmlFile) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
readFile(htmlFile, 'utf-8')
|
|
99
|
+
.then(html => {
|
|
100
|
+
html = html.includes('</body>')
|
|
101
|
+
? html.replace('</body>', liveReloadScript + '</body>')
|
|
102
|
+
: html + liveReloadScript
|
|
103
|
+
const buf = Buffer.from(html, 'utf-8')
|
|
104
|
+
res.writeHead(200, {
|
|
105
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
106
|
+
'Content-Length': buf.length,
|
|
107
|
+
'Cache-Control': 'no-cache'
|
|
108
|
+
})
|
|
109
|
+
res.end(buf)
|
|
100
110
|
})
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
.catch(() => {
|
|
112
|
+
staticServe(req, res, () => {
|
|
113
|
+
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' })
|
|
114
|
+
res.end('404 Not Found')
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
return
|
|
106
118
|
}
|
|
107
119
|
|
|
108
120
|
// ── sirv で静的ファイルを配信 ───────────────────────
|
|
@@ -115,7 +127,11 @@ export async function serverTask(context, options = {}) {
|
|
|
115
127
|
function broadcast(event, data = '') {
|
|
116
128
|
const msg = `event: ${event}\ndata: ${data}\n\n`
|
|
117
129
|
for (const res of clients) {
|
|
118
|
-
|
|
130
|
+
try {
|
|
131
|
+
res.write(msg)
|
|
132
|
+
} catch {
|
|
133
|
+
clients.delete(res)
|
|
134
|
+
}
|
|
119
135
|
}
|
|
120
136
|
}
|
|
121
137
|
|
package/core/watcher.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chokidar from 'chokidar'
|
|
2
|
-
import {
|
|
2
|
+
import { rm } from 'node:fs/promises'
|
|
3
|
+
import { relative, resolve, basename, extname } from 'node:path'
|
|
3
4
|
import { logger } from '../utils/logger.mjs'
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -100,6 +101,22 @@ class FileWatcher {
|
|
|
100
101
|
}
|
|
101
102
|
})
|
|
102
103
|
|
|
104
|
+
watcher.on('unlink', async path => {
|
|
105
|
+
if (!path.endsWith('.pug')) return
|
|
106
|
+
const relPath = relative(basePath, path)
|
|
107
|
+
|
|
108
|
+
// キャッシュとグラフをクリア
|
|
109
|
+
this.context.cache.invalidatePugTemplate(path)
|
|
110
|
+
this.context.graph.clearDependencies(path)
|
|
111
|
+
|
|
112
|
+
if (basename(path).startsWith('_')) {
|
|
113
|
+
logger.info('unlink', relPath)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
const distPath = resolve(this.context.paths.dist, relPath.replace(/\.pug$/, '.html'))
|
|
117
|
+
await this.deleteDistFile(distPath, relPath)
|
|
118
|
+
})
|
|
119
|
+
|
|
103
120
|
this.watchers.push(watcher)
|
|
104
121
|
}
|
|
105
122
|
|
|
@@ -111,7 +128,7 @@ class FileWatcher {
|
|
|
111
128
|
ignoreInitial: true,
|
|
112
129
|
ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/],
|
|
113
130
|
persistent: true,
|
|
114
|
-
awaitWriteFinish: { stabilityThreshold:
|
|
131
|
+
awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 25 }
|
|
115
132
|
})
|
|
116
133
|
|
|
117
134
|
watcher.on('change', async path => {
|
|
@@ -128,14 +145,23 @@ class FileWatcher {
|
|
|
128
145
|
await this.context.taskRegistry.sass(this.context)
|
|
129
146
|
}
|
|
130
147
|
|
|
131
|
-
|
|
132
|
-
const cssUrlPath = '/' + relPath.replace(/\\/g, '/').replace(/\.scss$/, '.css')
|
|
133
|
-
this.injectCSS(cssUrlPath)
|
|
148
|
+
this.injectCSS()
|
|
134
149
|
} catch (error) {
|
|
135
150
|
logger.error('watch', `Sass build failed: ${error.message}`)
|
|
136
151
|
}
|
|
137
152
|
})
|
|
138
153
|
|
|
154
|
+
watcher.on('unlink', async path => {
|
|
155
|
+
if (!path.endsWith('.scss')) return
|
|
156
|
+
const relPath = relative(basePath, path)
|
|
157
|
+
if (basename(path).startsWith('_')) {
|
|
158
|
+
logger.info('unlink', relPath)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
const distPath = resolve(this.context.paths.dist, relPath.replace(/\.scss$/, '.css'))
|
|
162
|
+
await this.deleteDistFile(distPath, relPath)
|
|
163
|
+
})
|
|
164
|
+
|
|
139
165
|
this.watchers.push(watcher)
|
|
140
166
|
}
|
|
141
167
|
|
|
@@ -170,6 +196,17 @@ class FileWatcher {
|
|
|
170
196
|
}
|
|
171
197
|
})
|
|
172
198
|
|
|
199
|
+
watcher.on('unlink', async path => {
|
|
200
|
+
if (!(path.endsWith('.ts') || path.endsWith('.js')) || path.endsWith('.d.ts')) return
|
|
201
|
+
const relPath = relative(basePath, path)
|
|
202
|
+
if (basename(path).startsWith('_')) {
|
|
203
|
+
logger.info('unlink', relPath)
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
const distPath = resolve(this.context.paths.dist, relPath.replace(/\.ts$/, '.js'))
|
|
207
|
+
await this.deleteDistFile(distPath, relPath)
|
|
208
|
+
})
|
|
209
|
+
|
|
173
210
|
this.watchers.push(watcher)
|
|
174
211
|
}
|
|
175
212
|
|
|
@@ -184,7 +221,7 @@ class FileWatcher {
|
|
|
184
221
|
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
185
222
|
})
|
|
186
223
|
|
|
187
|
-
const handleSvgChange = async path => {
|
|
224
|
+
const handleSvgChange = async (path, event) => {
|
|
188
225
|
// .svgファイルのみ処理(iconsディレクトリは除外)
|
|
189
226
|
// Windows対応: パスセパレータを正規化
|
|
190
227
|
const normalizedPath = path.replace(/\\/g, '/')
|
|
@@ -192,7 +229,7 @@ class FileWatcher {
|
|
|
192
229
|
return
|
|
193
230
|
}
|
|
194
231
|
const relPath = relative(basePath, path)
|
|
195
|
-
logger.info(
|
|
232
|
+
logger.info(event, `svg: ${relPath}`)
|
|
196
233
|
|
|
197
234
|
try {
|
|
198
235
|
// SVGタスクを実行(変更されたファイルのみ)
|
|
@@ -208,8 +245,16 @@ class FileWatcher {
|
|
|
208
245
|
}
|
|
209
246
|
}
|
|
210
247
|
|
|
211
|
-
watcher.on('change', handleSvgChange)
|
|
212
|
-
watcher.on('add', handleSvgChange)
|
|
248
|
+
watcher.on('change', path => handleSvgChange(path, 'change'))
|
|
249
|
+
watcher.on('add', path => handleSvgChange(path, 'add'))
|
|
250
|
+
|
|
251
|
+
watcher.on('unlink', async path => {
|
|
252
|
+
const normalizedPath = path.replace(/\\/g, '/')
|
|
253
|
+
if (!path.endsWith('.svg') || normalizedPath.includes('/icons/')) return
|
|
254
|
+
const relPath = relative(basePath, path)
|
|
255
|
+
const distPath = resolve(this.context.paths.dist, relPath)
|
|
256
|
+
await this.deleteDistFile(distPath, relPath)
|
|
257
|
+
})
|
|
213
258
|
|
|
214
259
|
this.watchers.push(watcher)
|
|
215
260
|
}
|
|
@@ -225,13 +270,13 @@ class FileWatcher {
|
|
|
225
270
|
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
226
271
|
})
|
|
227
272
|
|
|
228
|
-
const handleImageChange = async path => {
|
|
273
|
+
const handleImageChange = async (path, event) => {
|
|
229
274
|
// 画像ファイルのみ処理
|
|
230
275
|
if (!/\.(jpg|jpeg|png|gif)$/i.test(path)) {
|
|
231
276
|
return
|
|
232
277
|
}
|
|
233
278
|
const relPath = relative(basePath, path)
|
|
234
|
-
logger.info(
|
|
279
|
+
logger.info(event, `image: ${relPath}`)
|
|
235
280
|
|
|
236
281
|
try {
|
|
237
282
|
// 追加・変更時: 画像を処理
|
|
@@ -247,8 +292,18 @@ class FileWatcher {
|
|
|
247
292
|
}
|
|
248
293
|
}
|
|
249
294
|
|
|
250
|
-
watcher.on('change', handleImageChange)
|
|
251
|
-
watcher.on('add', handleImageChange)
|
|
295
|
+
watcher.on('change', path => handleImageChange(path, 'change'))
|
|
296
|
+
watcher.on('add', path => handleImageChange(path, 'add'))
|
|
297
|
+
|
|
298
|
+
watcher.on('unlink', async path => {
|
|
299
|
+
if (!/\.(jpg|jpeg|png|gif)$/i.test(path)) return
|
|
300
|
+
const relPath = relative(basePath, path)
|
|
301
|
+
const useWebp = this.context.config.build.imageOptimization === 'webp'
|
|
302
|
+
const ext = extname(path)
|
|
303
|
+
const destRelPath = useWebp ? relPath.replace(new RegExp(`\\${ext}$`, 'i'), '.webp') : relPath
|
|
304
|
+
const distPath = resolve(this.context.paths.dist, destRelPath)
|
|
305
|
+
await this.deleteDistFile(distPath, relPath)
|
|
306
|
+
})
|
|
252
307
|
|
|
253
308
|
this.watchers.push(watcher)
|
|
254
309
|
}
|
|
@@ -264,9 +319,9 @@ class FileWatcher {
|
|
|
264
319
|
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
265
320
|
})
|
|
266
321
|
|
|
267
|
-
|
|
322
|
+
const handlePublicChange = async (path, event) => {
|
|
268
323
|
const relPath = relative(basePath, path)
|
|
269
|
-
logger.info(
|
|
324
|
+
logger.info(event, `public: ${relPath}`)
|
|
270
325
|
|
|
271
326
|
try {
|
|
272
327
|
// Copyタスクを実行
|
|
@@ -278,6 +333,15 @@ class FileWatcher {
|
|
|
278
333
|
} catch (error) {
|
|
279
334
|
logger.error('watch', `Copy failed: ${error.message}`)
|
|
280
335
|
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
watcher.on('change', path => handlePublicChange(path, 'change'))
|
|
339
|
+
watcher.on('add', path => handlePublicChange(path, 'add'))
|
|
340
|
+
|
|
341
|
+
watcher.on('unlink', async path => {
|
|
342
|
+
const relPath = relative(basePath, path)
|
|
343
|
+
const distPath = resolve(this.context.paths.dist, relPath)
|
|
344
|
+
await this.deleteDistFile(distPath, relPath)
|
|
281
345
|
})
|
|
282
346
|
|
|
283
347
|
this.watchers.push(watcher)
|
|
@@ -294,14 +358,25 @@ class FileWatcher {
|
|
|
294
358
|
}
|
|
295
359
|
}
|
|
296
360
|
|
|
361
|
+
/**
|
|
362
|
+
* dist内のファイルを削除してブラウザをリロード
|
|
363
|
+
*/
|
|
364
|
+
async deleteDistFile(distPath, relPath) {
|
|
365
|
+
try {
|
|
366
|
+
await rm(distPath, { force: true })
|
|
367
|
+
logger.info('unlink', relPath)
|
|
368
|
+
this.reload()
|
|
369
|
+
} catch (error) {
|
|
370
|
+
logger.error('watch', `Failed to delete ${relPath}: ${error.message}`)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
297
374
|
/**
|
|
298
375
|
* CSSインジェクション
|
|
299
376
|
*/
|
|
300
377
|
injectCSS() {
|
|
301
378
|
if (this.context.server) {
|
|
302
|
-
|
|
303
|
-
this.context.server.reloadCSS()
|
|
304
|
-
}, 100)
|
|
379
|
+
this.context.server.reloadCSS()
|
|
305
380
|
}
|
|
306
381
|
}
|
|
307
382
|
|
package/generate/asset.mjs
CHANGED
|
@@ -7,6 +7,8 @@ export async function generateAsset(outputPath, data) {
|
|
|
7
7
|
|
|
8
8
|
if (typeof data === 'string' || Buffer.isBuffer(data)) {
|
|
9
9
|
await writeFile(outputPath, data)
|
|
10
|
+
} else {
|
|
11
|
+
throw new TypeError(`generateAsset: data must be a string or Buffer, got ${typeof data}`)
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
return outputPath
|
package/package.json
CHANGED
package/tasks/pug.mjs
CHANGED
|
@@ -57,10 +57,8 @@ async function processFile(filePath, context) {
|
|
|
57
57
|
if (!template) {
|
|
58
58
|
const result = await compilePugFile(filePath, { basedir: paths.src })
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
result.dependencies.forEach(dep => graph.addDependency(filePath, dep))
|
|
63
|
-
}
|
|
60
|
+
graph.clearDependencies(filePath)
|
|
61
|
+
result.dependencies.forEach(dep => graph.addDependency(filePath, dep))
|
|
64
62
|
|
|
65
63
|
template = result.template
|
|
66
64
|
cache.setPugTemplate(filePath, template)
|
package/tasks/script.mjs
CHANGED
|
@@ -29,7 +29,7 @@ export async function scriptTask(context, options = {}) {
|
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
31
|
// 2. esbuild設定
|
|
32
|
-
const
|
|
32
|
+
const esbuildConfig = {
|
|
33
33
|
entryPoints: scriptFiles,
|
|
34
34
|
outdir: paths.dist,
|
|
35
35
|
outbase: paths.src,
|
|
@@ -54,12 +54,12 @@ export async function scriptTask(context, options = {}) {
|
|
|
54
54
|
|
|
55
55
|
// debugモードでない場合はconsole/debuggerを削除
|
|
56
56
|
if (!isDebugMode) {
|
|
57
|
-
|
|
57
|
+
esbuildConfig.drop = ['console', 'debugger']
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// 3. ビルド実行
|
|
61
61
|
await ensureDir(paths.dist)
|
|
62
|
-
const result = await esbuild.build(
|
|
62
|
+
const result = await esbuild.build(esbuildConfig)
|
|
63
63
|
|
|
64
64
|
if (result.errors && result.errors.length > 0) {
|
|
65
65
|
throw new Error(`esbuild errors: ${result.errors.length}`)
|
package/tasks/svg-sprite.mjs
CHANGED
package/tasks/svg.mjs
CHANGED
|
@@ -52,13 +52,7 @@ async function optimizeSvg(filePath, context) {
|
|
|
52
52
|
// SVGOで最適化
|
|
53
53
|
const result = optimize(content, {
|
|
54
54
|
path: filePath,
|
|
55
|
-
plugins: [
|
|
56
|
-
'preset-default',
|
|
57
|
-
{
|
|
58
|
-
name: 'removeViewBox',
|
|
59
|
-
active: false
|
|
60
|
-
}
|
|
61
|
-
]
|
|
55
|
+
plugins: ['preset-default']
|
|
62
56
|
})
|
|
63
57
|
|
|
64
58
|
// 出力
|
package/utils/file.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { mkdir, rm } from 'node:fs/promises'
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
3
2
|
import { dirname } from 'node:path'
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -10,9 +9,7 @@ import { dirname } from 'node:path'
|
|
|
10
9
|
* ディレクトリを作成(再帰的)
|
|
11
10
|
*/
|
|
12
11
|
export async function ensureDir(dirPath) {
|
|
13
|
-
|
|
14
|
-
await mkdir(dirPath, { recursive: true })
|
|
15
|
-
}
|
|
12
|
+
await mkdir(dirPath, { recursive: true })
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
/**
|
|
@@ -26,9 +23,7 @@ export async function ensureFileDir(filePath) {
|
|
|
26
23
|
* ディレクトリをクリーンアップ
|
|
27
24
|
*/
|
|
28
25
|
export async function cleanDir(dirPath) {
|
|
29
|
-
|
|
30
|
-
await rm(dirPath, { recursive: true, force: true })
|
|
31
|
-
}
|
|
26
|
+
await rm(dirPath, { recursive: true, force: true })
|
|
32
27
|
await mkdir(dirPath, { recursive: true })
|
|
33
28
|
}
|
|
34
29
|
|