pugkit 1.0.2 → 1.1.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/cli/develop.mjs +1 -2
- package/cli/logger.mjs +5 -9
- package/config/defaults.mjs +1 -2
- package/core/cache.mjs +4 -5
- package/core/watcher.mjs +167 -295
- package/generate/page.mjs +1 -1
- package/package.json +1 -1
- package/tasks/copy.mjs +15 -0
- package/tasks/sass.mjs +1 -1
package/cli/develop.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { createBuilder } from '../index.mjs'
|
|
|
2
2
|
import { logger } from './logger.mjs'
|
|
3
3
|
|
|
4
4
|
export async function develop(options = {}) {
|
|
5
|
-
const { root = process.cwd(), port, host
|
|
5
|
+
const { root = process.cwd(), port, host } = options
|
|
6
6
|
|
|
7
7
|
logger.info('pugkit', 'starting dev server...')
|
|
8
8
|
|
|
@@ -10,7 +10,6 @@ export async function develop(options = {}) {
|
|
|
10
10
|
|
|
11
11
|
if (port) builder.context.config.server.port = port
|
|
12
12
|
if (host) builder.context.config.server.host = host
|
|
13
|
-
if (open) builder.context.config.server.open = open
|
|
14
13
|
|
|
15
14
|
await builder.watch()
|
|
16
15
|
}
|
package/cli/logger.mjs
CHANGED
|
@@ -6,26 +6,22 @@ import path from 'node:path'
|
|
|
6
6
|
const __filename = fileURLToPath(import.meta.url)
|
|
7
7
|
const __dirname = path.dirname(__filename)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const pkgPath = path.join(__dirname, '../package.json')
|
|
11
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
12
|
-
return pkg.version
|
|
13
|
-
}
|
|
9
|
+
const version = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8')).version
|
|
14
10
|
|
|
15
11
|
export const logger = {
|
|
16
12
|
info(name, message) {
|
|
17
|
-
console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${
|
|
13
|
+
console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${version}`)} ${message}`)
|
|
18
14
|
},
|
|
19
15
|
|
|
20
16
|
success(name, message) {
|
|
21
|
-
console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${
|
|
17
|
+
console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${version}`)} ${message}`)
|
|
22
18
|
},
|
|
23
19
|
|
|
24
20
|
warn(name, message) {
|
|
25
|
-
console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${
|
|
21
|
+
console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${version}`)} ${message}`)
|
|
26
22
|
},
|
|
27
23
|
|
|
28
24
|
error(name, message) {
|
|
29
|
-
console.error(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${
|
|
25
|
+
console.error(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${version}`)} ${message}`)
|
|
30
26
|
}
|
|
31
27
|
}
|
package/config/defaults.mjs
CHANGED
package/core/cache.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
|
-
import {
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
3
|
import { existsSync } from 'node:fs'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -76,13 +76,12 @@ export class CacheManager {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
79
|
+
* ハッシュ計算(ファイル内容のみ)
|
|
80
80
|
*/
|
|
81
81
|
async computeHash(filePath) {
|
|
82
82
|
try {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
return createHash('md5').update(content).update(stats.mtime.toISOString()).digest('hex')
|
|
83
|
+
const content = await readFile(filePath)
|
|
84
|
+
return createHash('md5').update(content).digest('hex')
|
|
86
85
|
} catch {
|
|
87
86
|
return `error-${Math.random()}`
|
|
88
87
|
}
|
package/core/watcher.mjs
CHANGED
|
@@ -17,7 +17,7 @@ export async function watcherTask(context, options = {}) {
|
|
|
17
17
|
class FileWatcher {
|
|
18
18
|
constructor(context) {
|
|
19
19
|
this.context = context
|
|
20
|
-
this.
|
|
20
|
+
this.watcher = null
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
async start() {
|
|
@@ -29,327 +29,208 @@ class FileWatcher {
|
|
|
29
29
|
await this.context.taskRegistry.pug(this.context)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
this.watcher = chokidar
|
|
33
|
+
.watch([paths.src, paths.public], {
|
|
34
|
+
ignoreInitial: true,
|
|
35
|
+
ignored: [/(^|[\/\\])\./, /node_modules/, /\.git/],
|
|
36
|
+
persistent: true,
|
|
37
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
38
|
+
})
|
|
39
|
+
.on('change', filePath => this.handleChange(filePath))
|
|
40
|
+
.on('add', filePath => this.handleAdd(filePath))
|
|
41
|
+
.on('unlink', filePath => this.handleUnlink(filePath))
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
logger.info('watch', 'File watching started')
|
|
44
|
+
}
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
// ---- ルーティング ----
|
|
47
|
+
handleChange(filePath) {
|
|
48
|
+
if (this.isPublic(filePath)) return this.onPublicChange(filePath, 'change')
|
|
49
|
+
if (filePath.endsWith('.pug')) return this.onPugChange(filePath)
|
|
50
|
+
if (filePath.endsWith('.scss')) return this.onSassChange(filePath)
|
|
51
|
+
if (this.isScript(filePath)) return this.onScriptChange(filePath)
|
|
52
|
+
if (filePath.endsWith('.svg') && !this.isIcons(filePath)) return this.onSvgChange(filePath, 'change')
|
|
53
|
+
if (/\.(jpg|jpeg|png|gif)$/i.test(filePath)) return this.onImageChange(filePath, 'change')
|
|
54
|
+
}
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
this.
|
|
56
|
+
handleAdd(filePath) {
|
|
57
|
+
if (this.isPublic(filePath)) return this.onPublicChange(filePath, 'add')
|
|
58
|
+
if (filePath.endsWith('.svg') && !this.isIcons(filePath)) return this.onSvgChange(filePath, 'add')
|
|
59
|
+
if (/\.(jpg|jpeg|png|gif)$/i.test(filePath)) return this.onImageChange(filePath, 'add')
|
|
60
|
+
}
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
this.
|
|
62
|
+
handleUnlink(filePath) {
|
|
63
|
+
if (this.isPublic(filePath)) return this.onPublicUnlink(filePath)
|
|
64
|
+
if (filePath.endsWith('.pug')) return this.onPugUnlink(filePath)
|
|
65
|
+
if (filePath.endsWith('.scss')) return this.onSassUnlink(filePath)
|
|
66
|
+
if (this.isScript(filePath)) return this.onScriptUnlink(filePath)
|
|
67
|
+
if (filePath.endsWith('.svg') && !this.isIcons(filePath)) return this.onSvgUnlink(filePath)
|
|
68
|
+
if (/\.(jpg|jpeg|png|gif)$/i.test(filePath)) return this.onImageUnlink(filePath)
|
|
69
|
+
}
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
this.watchPublic(paths.public)
|
|
71
|
+
// ---- 判定ヘルパー ----
|
|
49
72
|
|
|
50
|
-
|
|
73
|
+
isPublic(filePath) {
|
|
74
|
+
return filePath.startsWith(this.context.paths.public)
|
|
51
75
|
}
|
|
52
76
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
watchPug(basePath) {
|
|
57
|
-
const watcher = chokidar.watch(basePath, {
|
|
58
|
-
ignoreInitial: true,
|
|
59
|
-
ignored: [
|
|
60
|
-
/(^|[\/\\])\../, // 隠しファイル
|
|
61
|
-
/node_modules/,
|
|
62
|
-
/\.git/
|
|
63
|
-
],
|
|
64
|
-
persistent: true,
|
|
65
|
-
awaitWriteFinish: {
|
|
66
|
-
stabilityThreshold: 100,
|
|
67
|
-
pollInterval: 50
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
watcher.on('change', async path => {
|
|
72
|
-
// .pugファイルのみ処理
|
|
73
|
-
if (!path.endsWith('.pug')) {
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
const relPath = relative(basePath, path)
|
|
77
|
-
logger.info('change', `pug: ${relPath}`)
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
// パーシャルの場合、依存する親ファイルも再ビルド
|
|
81
|
-
const affectedFiles = this.context.graph.getAffectedParents(path)
|
|
82
|
-
|
|
83
|
-
if (affectedFiles.length > 0) {
|
|
84
|
-
logger.info('pug', `Rebuilding ${affectedFiles.length} affected file(s)`)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// キャッシュ無効化
|
|
88
|
-
this.context.cache.invalidatePugTemplate(path)
|
|
89
|
-
affectedFiles.forEach(f => this.context.cache.invalidatePugTemplate(f))
|
|
90
|
-
|
|
91
|
-
// Pugタスクを実行(変更されたファイルのみ)
|
|
92
|
-
if (this.context.taskRegistry?.pug) {
|
|
93
|
-
await this.context.taskRegistry.pug(this.context, {
|
|
94
|
-
files: [path, ...affectedFiles]
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this.reload()
|
|
99
|
-
} catch (error) {
|
|
100
|
-
logger.error('watch', `Pug build failed: ${error.message}`)
|
|
101
|
-
}
|
|
102
|
-
})
|
|
77
|
+
isScript(filePath) {
|
|
78
|
+
return (filePath.endsWith('.ts') || filePath.endsWith('.js')) && !filePath.endsWith('.d.ts')
|
|
79
|
+
}
|
|
103
80
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
81
|
+
isIcons(filePath) {
|
|
82
|
+
// Windows 対応: セパレータを正規化
|
|
83
|
+
return filePath.replace(/\\/g, '/').includes('/icons/')
|
|
84
|
+
}
|
|
107
85
|
|
|
108
|
-
|
|
109
|
-
this.context.cache.invalidatePugTemplate(path)
|
|
110
|
-
this.context.graph.clearDependencies(path)
|
|
86
|
+
// ---- Pug ----
|
|
111
87
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
88
|
+
async onPugChange(filePath) {
|
|
89
|
+
const { paths, graph, cache, taskRegistry } = this.context
|
|
90
|
+
const relPath = relative(paths.src, filePath)
|
|
91
|
+
logger.info('change', `pug: ${relPath}`)
|
|
92
|
+
try {
|
|
93
|
+
const affectedFiles = graph.getAffectedParents(filePath)
|
|
94
|
+
if (affectedFiles.length > 0) logger.info('pug', `Rebuilding ${affectedFiles.length} affected file(s)`)
|
|
95
|
+
cache.invalidatePugTemplate(filePath)
|
|
96
|
+
affectedFiles.forEach(f => cache.invalidatePugTemplate(f))
|
|
97
|
+
if (taskRegistry?.pug) {
|
|
98
|
+
await taskRegistry.pug(this.context, { files: [filePath, ...affectedFiles] })
|
|
115
99
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.watchers.push(watcher)
|
|
100
|
+
this.reload()
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logger.error('watch', `Pug build failed: ${error.message}`)
|
|
103
|
+
}
|
|
121
104
|
}
|
|
122
105
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
watcher.on('change', async path => {
|
|
135
|
-
// .scssファイルのみ処理
|
|
136
|
-
if (!path.endsWith('.scss')) {
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
const relPath = relative(basePath, path)
|
|
140
|
-
logger.info('change', `sass: ${relPath}`)
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
// Sassタスクを実行
|
|
144
|
-
if (this.context.taskRegistry?.sass) {
|
|
145
|
-
await this.context.taskRegistry.sass(this.context)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
this.injectCSS()
|
|
149
|
-
} catch (error) {
|
|
150
|
-
logger.error('watch', `Sass build failed: ${error.message}`)
|
|
151
|
-
}
|
|
152
|
-
})
|
|
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
|
-
|
|
165
|
-
this.watchers.push(watcher)
|
|
106
|
+
async onPugUnlink(filePath) {
|
|
107
|
+
const { paths, cache, graph } = this.context
|
|
108
|
+
const relPath = relative(paths.src, filePath)
|
|
109
|
+
cache.invalidatePugTemplate(filePath)
|
|
110
|
+
graph.clearDependencies(filePath)
|
|
111
|
+
if (basename(filePath).startsWith('_')) {
|
|
112
|
+
logger.info('unlink', relPath)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
const distPath = resolve(paths.dist, relPath.replace(/\.pug$/, '.html'))
|
|
116
|
+
await this.deleteDistFile(distPath, relPath)
|
|
166
117
|
}
|
|
167
118
|
|
|
168
|
-
|
|
169
|
-
* Script監視
|
|
170
|
-
*/
|
|
171
|
-
watchScript(basePath) {
|
|
172
|
-
const watcher = chokidar.watch(basePath, {
|
|
173
|
-
ignoreInitial: true,
|
|
174
|
-
ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/, /\.d\.ts$/],
|
|
175
|
-
persistent: true,
|
|
176
|
-
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
watcher.on('change', async path => {
|
|
180
|
-
// .ts/.jsファイルのみ処理(.d.tsを除く)
|
|
181
|
-
if (!(path.endsWith('.ts') || path.endsWith('.js')) || path.endsWith('.d.ts')) {
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
const relPath = relative(basePath, path)
|
|
185
|
-
logger.info('change', `script: ${relPath}`)
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
// Scriptタスクを実行
|
|
189
|
-
if (this.context.taskRegistry?.script) {
|
|
190
|
-
await this.context.taskRegistry.script(this.context)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
this.reload()
|
|
194
|
-
} catch (error) {
|
|
195
|
-
logger.error('watch', `Script build failed: ${error.message}`)
|
|
196
|
-
}
|
|
197
|
-
})
|
|
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
|
-
})
|
|
119
|
+
// ---- Sass ----
|
|
209
120
|
|
|
210
|
-
|
|
121
|
+
async onSassChange(filePath) {
|
|
122
|
+
const relPath = relative(this.context.paths.src, filePath)
|
|
123
|
+
logger.info('change', `sass: ${relPath}`)
|
|
124
|
+
try {
|
|
125
|
+
if (this.context.taskRegistry?.sass) await this.context.taskRegistry.sass(this.context)
|
|
126
|
+
this.injectCSS()
|
|
127
|
+
} catch (error) {
|
|
128
|
+
logger.error('watch', `Sass build failed: ${error.message}`)
|
|
129
|
+
}
|
|
211
130
|
}
|
|
212
131
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/, /icons/], // iconsはスプライト用なので除外
|
|
220
|
-
persistent: true,
|
|
221
|
-
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
const handleSvgChange = async (path, event) => {
|
|
225
|
-
// .svgファイルのみ処理(iconsディレクトリは除外)
|
|
226
|
-
// Windows対応: パスセパレータを正規化
|
|
227
|
-
const normalizedPath = path.replace(/\\/g, '/')
|
|
228
|
-
if (!path.endsWith('.svg') || normalizedPath.includes('/icons/')) {
|
|
229
|
-
return
|
|
230
|
-
}
|
|
231
|
-
const relPath = relative(basePath, path)
|
|
232
|
-
logger.info(event, `svg: ${relPath}`)
|
|
233
|
-
|
|
234
|
-
try {
|
|
235
|
-
// SVGタスクを実行(変更されたファイルのみ)
|
|
236
|
-
if (this.context.taskRegistry?.svg) {
|
|
237
|
-
await this.context.taskRegistry.svg(this.context, {
|
|
238
|
-
files: [path]
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
this.reload()
|
|
243
|
-
} catch (error) {
|
|
244
|
-
logger.error('watch', `SVG processing failed: ${error.message}`)
|
|
245
|
-
}
|
|
132
|
+
async onSassUnlink(filePath) {
|
|
133
|
+
const { paths } = this.context
|
|
134
|
+
const relPath = relative(paths.src, filePath)
|
|
135
|
+
if (basename(filePath).startsWith('_')) {
|
|
136
|
+
logger.info('unlink', relPath)
|
|
137
|
+
return
|
|
246
138
|
}
|
|
139
|
+
const distPath = resolve(paths.dist, relPath.replace(/\.scss$/, '.css'))
|
|
140
|
+
await this.deleteDistFile(distPath, relPath)
|
|
141
|
+
}
|
|
247
142
|
|
|
248
|
-
|
|
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
|
-
})
|
|
143
|
+
// ---- Script ----
|
|
258
144
|
|
|
259
|
-
|
|
145
|
+
async onScriptChange(filePath) {
|
|
146
|
+
const relPath = relative(this.context.paths.src, filePath)
|
|
147
|
+
logger.info('change', `script: ${relPath}`)
|
|
148
|
+
try {
|
|
149
|
+
if (this.context.taskRegistry?.script) await this.context.taskRegistry.script(this.context)
|
|
150
|
+
this.reload()
|
|
151
|
+
} catch (error) {
|
|
152
|
+
logger.error('watch', `Script build failed: ${error.message}`)
|
|
153
|
+
}
|
|
260
154
|
}
|
|
261
155
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/],
|
|
269
|
-
persistent: true,
|
|
270
|
-
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
const handleImageChange = async (path, event) => {
|
|
274
|
-
// 画像ファイルのみ処理
|
|
275
|
-
if (!/\.(jpg|jpeg|png|gif)$/i.test(path)) {
|
|
276
|
-
return
|
|
277
|
-
}
|
|
278
|
-
const relPath = relative(basePath, path)
|
|
279
|
-
logger.info(event, `image: ${relPath}`)
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
// 追加・変更時: 画像を処理
|
|
283
|
-
if (this.context.taskRegistry?.image) {
|
|
284
|
-
await this.context.taskRegistry.image(this.context, {
|
|
285
|
-
files: [path]
|
|
286
|
-
})
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
this.reload()
|
|
290
|
-
} catch (error) {
|
|
291
|
-
logger.error('watch', `Image processing failed: ${error.message}`)
|
|
292
|
-
}
|
|
156
|
+
async onScriptUnlink(filePath) {
|
|
157
|
+
const { paths } = this.context
|
|
158
|
+
const relPath = relative(paths.src, filePath)
|
|
159
|
+
if (basename(filePath).startsWith('_')) {
|
|
160
|
+
logger.info('unlink', relPath)
|
|
161
|
+
return
|
|
293
162
|
}
|
|
163
|
+
const distPath = resolve(paths.dist, relPath.replace(/\.ts$/, '.js'))
|
|
164
|
+
await this.deleteDistFile(distPath, relPath)
|
|
165
|
+
}
|
|
294
166
|
|
|
295
|
-
|
|
296
|
-
watcher.on('add', path => handleImageChange(path, 'add'))
|
|
167
|
+
// ---- SVG ----
|
|
297
168
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
})
|
|
169
|
+
async onSvgChange(filePath, event) {
|
|
170
|
+
const relPath = relative(this.context.paths.src, filePath)
|
|
171
|
+
logger.info(event, `svg: ${relPath}`)
|
|
172
|
+
try {
|
|
173
|
+
if (this.context.taskRegistry?.svg) {
|
|
174
|
+
await this.context.taskRegistry.svg(this.context, { files: [filePath] })
|
|
175
|
+
}
|
|
176
|
+
this.reload()
|
|
177
|
+
} catch (error) {
|
|
178
|
+
logger.error('watch', `SVG processing failed: ${error.message}`)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
307
181
|
|
|
308
|
-
|
|
182
|
+
async onSvgUnlink(filePath) {
|
|
183
|
+
const relPath = relative(this.context.paths.src, filePath)
|
|
184
|
+
const distPath = resolve(this.context.paths.dist, relPath)
|
|
185
|
+
await this.deleteDistFile(distPath, relPath)
|
|
309
186
|
}
|
|
310
187
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
const handlePublicChange = async (path, event) => {
|
|
323
|
-
const relPath = relative(basePath, path)
|
|
324
|
-
logger.info(event, `public: ${relPath}`)
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
// Copyタスクを実行
|
|
328
|
-
if (this.context.taskRegistry?.copy) {
|
|
329
|
-
await this.context.taskRegistry.copy(this.context)
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
this.reload()
|
|
333
|
-
} catch (error) {
|
|
334
|
-
logger.error('watch', `Copy failed: ${error.message}`)
|
|
188
|
+
// ---- Image ----
|
|
189
|
+
|
|
190
|
+
async onImageChange(filePath, event) {
|
|
191
|
+
const relPath = relative(this.context.paths.src, filePath)
|
|
192
|
+
logger.info(event, `image: ${relPath}`)
|
|
193
|
+
try {
|
|
194
|
+
if (this.context.taskRegistry?.image) {
|
|
195
|
+
await this.context.taskRegistry.image(this.context, { files: [filePath] })
|
|
335
196
|
}
|
|
197
|
+
this.reload()
|
|
198
|
+
} catch (error) {
|
|
199
|
+
logger.error('watch', `Image processing failed: ${error.message}`)
|
|
336
200
|
}
|
|
201
|
+
}
|
|
337
202
|
|
|
338
|
-
|
|
339
|
-
|
|
203
|
+
async onImageUnlink(filePath) {
|
|
204
|
+
const { paths, config } = this.context
|
|
205
|
+
const relPath = relative(paths.src, filePath)
|
|
206
|
+
const useWebp = config.build.imageOptimization === 'webp'
|
|
207
|
+
const ext = extname(filePath)
|
|
208
|
+
const destRelPath = useWebp ? relPath.replace(new RegExp(`\\${ext}$`, 'i'), '.webp') : relPath
|
|
209
|
+
const distPath = resolve(paths.dist, destRelPath)
|
|
210
|
+
await this.deleteDistFile(distPath, relPath)
|
|
211
|
+
}
|
|
340
212
|
|
|
341
|
-
|
|
342
|
-
const relPath = relative(basePath, path)
|
|
343
|
-
const distPath = resolve(this.context.paths.dist, relPath)
|
|
344
|
-
await this.deleteDistFile(distPath, relPath)
|
|
345
|
-
})
|
|
213
|
+
// ---- Public ----
|
|
346
214
|
|
|
347
|
-
|
|
215
|
+
async onPublicChange(filePath, event) {
|
|
216
|
+
const relPath = relative(this.context.paths.public, filePath)
|
|
217
|
+
logger.info(event, `public: ${relPath}`)
|
|
218
|
+
try {
|
|
219
|
+
if (this.context.taskRegistry?.copy) await this.context.taskRegistry.copy(this.context, { files: [filePath] })
|
|
220
|
+
this.reload()
|
|
221
|
+
} catch (error) {
|
|
222
|
+
logger.error('watch', `Copy failed: ${error.message}`)
|
|
223
|
+
}
|
|
348
224
|
}
|
|
349
225
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
226
|
+
async onPublicUnlink(filePath) {
|
|
227
|
+
const relPath = relative(this.context.paths.public, filePath)
|
|
228
|
+
const distPath = resolve(this.context.paths.dist, relPath)
|
|
229
|
+
await this.deleteDistFile(distPath, relPath)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---- 共通ヘルパー ----
|
|
233
|
+
|
|
353
234
|
reload() {
|
|
354
235
|
if (this.context.server) {
|
|
355
236
|
setTimeout(() => {
|
|
@@ -358,9 +239,6 @@ class FileWatcher {
|
|
|
358
239
|
}
|
|
359
240
|
}
|
|
360
241
|
|
|
361
|
-
/**
|
|
362
|
-
* dist内のファイルを削除してブラウザをリロード
|
|
363
|
-
*/
|
|
364
242
|
async deleteDistFile(distPath, relPath) {
|
|
365
243
|
try {
|
|
366
244
|
await rm(distPath, { force: true })
|
|
@@ -371,20 +249,14 @@ class FileWatcher {
|
|
|
371
249
|
}
|
|
372
250
|
}
|
|
373
251
|
|
|
374
|
-
/**
|
|
375
|
-
* CSSインジェクション
|
|
376
|
-
*/
|
|
377
252
|
injectCSS() {
|
|
378
253
|
if (this.context.server) {
|
|
379
254
|
this.context.server.reloadCSS()
|
|
380
255
|
}
|
|
381
256
|
}
|
|
382
257
|
|
|
383
|
-
/**
|
|
384
|
-
* 監視停止
|
|
385
|
-
*/
|
|
386
258
|
async stop() {
|
|
387
|
-
|
|
259
|
+
if (this.watcher) await this.watcher.close()
|
|
388
260
|
logger.info('watch', 'File watching stopped')
|
|
389
261
|
}
|
|
390
262
|
}
|
package/generate/page.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { ensureFileDir } from '../utils/file.mjs'
|
|
|
4
4
|
|
|
5
5
|
export async function generatePage(filePath, html, paths) {
|
|
6
6
|
const relativePath = relative(paths.src, filePath)
|
|
7
|
-
const outputPath = resolve(paths.dist, relativePath.replace(
|
|
7
|
+
const outputPath = resolve(paths.dist, relativePath.replace(/\.pug$/, '.html'))
|
|
8
8
|
|
|
9
9
|
await ensureFileDir(outputPath)
|
|
10
10
|
await writeFile(outputPath, html, 'utf8')
|
package/package.json
CHANGED
package/tasks/copy.mjs
CHANGED
|
@@ -10,6 +10,21 @@ import { ensureFileDir } from '../utils/file.mjs'
|
|
|
10
10
|
export async function copyTask(context, options = {}) {
|
|
11
11
|
const { paths } = context
|
|
12
12
|
|
|
13
|
+
// 特定のファイルが指定されている場合(watch時)
|
|
14
|
+
if (options.files && Array.isArray(options.files)) {
|
|
15
|
+
logger.info('copy', `Copying ${options.files.length} file(s)`)
|
|
16
|
+
await Promise.all(
|
|
17
|
+
options.files.map(async file => {
|
|
18
|
+
const relativePath = relative(paths.public, file)
|
|
19
|
+
const outputPath = resolve(paths.dist, relativePath)
|
|
20
|
+
await ensureFileDir(outputPath)
|
|
21
|
+
await copyFile(file, outputPath)
|
|
22
|
+
})
|
|
23
|
+
)
|
|
24
|
+
logger.success('copy', `Copied ${options.files.length} file(s)`)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
const files = await glob('**/*', {
|
|
14
29
|
cwd: paths.public,
|
|
15
30
|
absolute: true,
|
package/tasks/sass.mjs
CHANGED
|
@@ -78,7 +78,7 @@ async function compileSassFile(filePath, context, isDebugMode) {
|
|
|
78
78
|
)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
const outputRelativePath = relative(paths.src, filePath).replace(
|
|
81
|
+
const outputRelativePath = relative(paths.src, filePath).replace(/\.scss$/, '.css')
|
|
82
82
|
const outputPath = resolve(paths.dist, outputRelativePath)
|
|
83
83
|
|
|
84
84
|
const postcssResult = await postcss(postcssPlugins).process(css, {
|