pugkit 1.0.0-beta.1
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 +172 -0
- package/cli/build.mjs +15 -0
- package/cli/develop.mjs +16 -0
- package/cli/index.mjs +55 -0
- package/cli/logger.mjs +31 -0
- package/cli/sprite.mjs +13 -0
- package/config/defaults.mjs +36 -0
- package/config/define.mjs +3 -0
- package/config/index.mjs +4 -0
- package/config/main.mjs +53 -0
- package/config/patterns.mjs +30 -0
- package/core/builder.mjs +139 -0
- package/core/cache.mjs +98 -0
- package/core/context.mjs +51 -0
- package/core/graph.mjs +97 -0
- package/core/server.mjs +78 -0
- package/core/watcher.mjs +314 -0
- package/generate/asset.mjs +19 -0
- package/generate/page.mjs +13 -0
- package/index.mjs +60 -0
- package/package.json +61 -0
- package/tasks/copy.mjs +40 -0
- package/tasks/image.mjs +93 -0
- package/tasks/pug.mjs +81 -0
- package/tasks/sass.mjs +107 -0
- package/tasks/script.mjs +75 -0
- package/tasks/svg-sprite.mjs +125 -0
- package/tasks/svg.mjs +73 -0
- package/transform/builder-vars.mjs +33 -0
- package/transform/html.mjs +14 -0
- package/transform/image-size.mjs +38 -0
- package/transform/pug.mjs +26 -0
- package/utils/file.mjs +44 -0
- package/utils/logger.mjs +40 -0
package/core/context.mjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { CacheManager } from './cache.mjs'
|
|
3
|
+
import { DependencyGraph } from './graph.mjs'
|
|
4
|
+
import { createGlobPatterns } from '../config/index.mjs'
|
|
5
|
+
|
|
6
|
+
export class BuildContext {
|
|
7
|
+
constructor(config, mode) {
|
|
8
|
+
this.config = config
|
|
9
|
+
this.mode = mode
|
|
10
|
+
this.cache = new CacheManager(mode)
|
|
11
|
+
this.graph = new DependencyGraph()
|
|
12
|
+
|
|
13
|
+
this.paths = {
|
|
14
|
+
root: config.root,
|
|
15
|
+
src: resolve(config.root, 'src'),
|
|
16
|
+
dist: resolve(config.root, 'dist', config.subdir || ''),
|
|
17
|
+
public: resolve(config.root, 'public')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.patterns = createGlobPatterns(this.paths.src)
|
|
21
|
+
this.server = null
|
|
22
|
+
this.taskRegistry = null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async runTask(taskName, taskFn, options = {}) {
|
|
26
|
+
try {
|
|
27
|
+
await taskFn(this, options)
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(`Task failed: ${taskName}`)
|
|
30
|
+
throw error
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async runParallel(tasks) {
|
|
35
|
+
await Promise.all(tasks.map(({ name, fn, options = {} }) => this.runTask(name, fn, options)))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async runSeries(tasks) {
|
|
39
|
+
for (const { name, fn, options = {} } of tasks) {
|
|
40
|
+
await this.runTask(name, fn, options)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get isProduction() {
|
|
45
|
+
return this.mode === 'production'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get isDevelopment() {
|
|
49
|
+
return this.mode === 'development'
|
|
50
|
+
}
|
|
51
|
+
}
|
package/core/graph.mjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 依存関係グラフ
|
|
3
|
+
* Pugのパーシャル依存を管理
|
|
4
|
+
*/
|
|
5
|
+
export class DependencyGraph {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.edges = new Map() // 親 -> Set<依存>
|
|
8
|
+
this.reverseEdges = new Map() // 依存 -> Set<親>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 依存関係を追加
|
|
13
|
+
*/
|
|
14
|
+
addDependency(parent, dependency) {
|
|
15
|
+
// 親 -> 依存
|
|
16
|
+
if (!this.edges.has(parent)) {
|
|
17
|
+
this.edges.set(parent, new Set())
|
|
18
|
+
}
|
|
19
|
+
this.edges.get(parent).add(dependency)
|
|
20
|
+
|
|
21
|
+
// 依存 -> 親(逆引き)
|
|
22
|
+
if (!this.reverseEdges.has(dependency)) {
|
|
23
|
+
this.reverseEdges.set(dependency, new Set())
|
|
24
|
+
}
|
|
25
|
+
this.reverseEdges.get(dependency).add(parent)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* パーシャル変更時に再ビルドが必要な親ファイルを取得
|
|
30
|
+
*/
|
|
31
|
+
getAffectedParents(dependency) {
|
|
32
|
+
const affected = new Set()
|
|
33
|
+
const queue = [dependency]
|
|
34
|
+
const visited = new Set()
|
|
35
|
+
|
|
36
|
+
while (queue.length > 0) {
|
|
37
|
+
const current = queue.shift()
|
|
38
|
+
|
|
39
|
+
if (visited.has(current)) {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
visited.add(current)
|
|
43
|
+
|
|
44
|
+
const parents = this.reverseEdges.get(current)
|
|
45
|
+
|
|
46
|
+
if (parents) {
|
|
47
|
+
parents.forEach(parent => {
|
|
48
|
+
affected.add(parent)
|
|
49
|
+
// 連鎖的な依存もチェック
|
|
50
|
+
queue.push(parent)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return Array.from(affected)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* ファイルの依存関係をクリア
|
|
60
|
+
*/
|
|
61
|
+
clearDependencies(file) {
|
|
62
|
+
// 親としての依存をクリア
|
|
63
|
+
const deps = this.edges.get(file)
|
|
64
|
+
if (deps) {
|
|
65
|
+
deps.forEach(dep => {
|
|
66
|
+
const parents = this.reverseEdges.get(dep)
|
|
67
|
+
if (parents) {
|
|
68
|
+
parents.delete(file)
|
|
69
|
+
if (parents.size === 0) {
|
|
70
|
+
this.reverseEdges.delete(dep)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
this.edges.delete(file)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 依存としての親をクリア
|
|
78
|
+
const parents = this.reverseEdges.get(file)
|
|
79
|
+
if (parents) {
|
|
80
|
+
parents.forEach(parent => {
|
|
81
|
+
const deps = this.edges.get(parent)
|
|
82
|
+
if (deps) {
|
|
83
|
+
deps.delete(file)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
this.reverseEdges.delete(file)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* すべてのグラフをクリア
|
|
92
|
+
*/
|
|
93
|
+
clear() {
|
|
94
|
+
this.edges.clear()
|
|
95
|
+
this.reverseEdges.clear()
|
|
96
|
+
}
|
|
97
|
+
}
|
package/core/server.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import browser from 'browser-sync'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { mkdir } from 'node:fs/promises'
|
|
5
|
+
import { logger } from '../utils/logger.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 開発サーバータスク
|
|
9
|
+
*/
|
|
10
|
+
export async function serverTask(context, options = {}) {
|
|
11
|
+
const { paths, config } = context
|
|
12
|
+
const distRoot = paths.dist
|
|
13
|
+
|
|
14
|
+
// distディレクトリの確認
|
|
15
|
+
if (!existsSync(distRoot)) {
|
|
16
|
+
await mkdir(distRoot, { recursive: true })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// BrowserSyncインスタンス作成
|
|
20
|
+
const bs = browser.create()
|
|
21
|
+
context.server = bs
|
|
22
|
+
|
|
23
|
+
const subdir = config.subdir ? '/' + config.subdir.replace(/^\/|\/$/g, '') : ''
|
|
24
|
+
const startPath = (config.server.startPath || '/').replace(/^\//, '')
|
|
25
|
+
const fullStartPath = subdir ? `${subdir}/${startPath}` : `/${startPath}`
|
|
26
|
+
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
bs.init(
|
|
29
|
+
{
|
|
30
|
+
notify: false,
|
|
31
|
+
server: {
|
|
32
|
+
baseDir: distRoot,
|
|
33
|
+
serveStaticOptions: {
|
|
34
|
+
extensions: ['html'],
|
|
35
|
+
setHeaders: (res, path) => {
|
|
36
|
+
if (path.endsWith('.css') || path.endsWith('.js')) {
|
|
37
|
+
res.setHeader('Cache-Control', 'no-cache')
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
open: false,
|
|
43
|
+
scrollProportionally: false,
|
|
44
|
+
ghostMode: false,
|
|
45
|
+
ui: false,
|
|
46
|
+
startPath: fullStartPath,
|
|
47
|
+
port: config.server.port,
|
|
48
|
+
host: config.server.host,
|
|
49
|
+
socket: {
|
|
50
|
+
namespace: '/browser-sync'
|
|
51
|
+
},
|
|
52
|
+
logLevel: 'silent',
|
|
53
|
+
logFileChanges: false,
|
|
54
|
+
logConnections: false,
|
|
55
|
+
minify: false,
|
|
56
|
+
timestamps: false,
|
|
57
|
+
codeSync: true,
|
|
58
|
+
online: false,
|
|
59
|
+
files: false, // 手動でリロード制御
|
|
60
|
+
injectChanges: true,
|
|
61
|
+
reloadDelay: 0,
|
|
62
|
+
reloadDebounce: 50
|
|
63
|
+
},
|
|
64
|
+
err => {
|
|
65
|
+
if (err) {
|
|
66
|
+
logger.error('server', err.message)
|
|
67
|
+
reject(err)
|
|
68
|
+
} else {
|
|
69
|
+
const urls = bs.getOption('urls')
|
|
70
|
+
logger.success('server', `Running at ${urls.get('local')}`)
|
|
71
|
+
resolve()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default serverTask
|
package/core/watcher.mjs
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import chokidar from 'chokidar'
|
|
2
|
+
import { relative } from 'node:path'
|
|
3
|
+
import { logger } from '../utils/logger.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ファイル監視タスク
|
|
7
|
+
*/
|
|
8
|
+
export async function watcherTask(context, options = {}) {
|
|
9
|
+
const watcher = new FileWatcher(context)
|
|
10
|
+
await watcher.start()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ファイルウォッチャー
|
|
15
|
+
*/
|
|
16
|
+
class FileWatcher {
|
|
17
|
+
constructor(context) {
|
|
18
|
+
this.context = context
|
|
19
|
+
this.watchers = []
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async start() {
|
|
23
|
+
const { paths } = this.context
|
|
24
|
+
|
|
25
|
+
// 初回ビルド(依存関係グラフ構築のため)
|
|
26
|
+
logger.info('watch', 'Building initial dependency graph...')
|
|
27
|
+
if (this.context.taskRegistry?.pug) {
|
|
28
|
+
await this.context.taskRegistry.pug(this.context)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Pug監視
|
|
32
|
+
this.watchPug(paths.src)
|
|
33
|
+
|
|
34
|
+
// Sass監視
|
|
35
|
+
this.watchSass(paths.src)
|
|
36
|
+
|
|
37
|
+
// Script監視
|
|
38
|
+
this.watchScript(paths.src)
|
|
39
|
+
|
|
40
|
+
// SVG監視
|
|
41
|
+
this.watchSvg(paths.src)
|
|
42
|
+
|
|
43
|
+
// 画像監視
|
|
44
|
+
this.watchImages(paths.src)
|
|
45
|
+
|
|
46
|
+
// Public監視
|
|
47
|
+
this.watchPublic(paths.public)
|
|
48
|
+
|
|
49
|
+
logger.info('watch', 'File watching started')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Pug監視(依存関係を考慮)
|
|
54
|
+
*/
|
|
55
|
+
watchPug(basePath) {
|
|
56
|
+
const watcher = chokidar.watch(basePath, {
|
|
57
|
+
ignoreInitial: true,
|
|
58
|
+
ignored: [
|
|
59
|
+
/(^|[\/\\])\../, // 隠しファイル
|
|
60
|
+
/node_modules/,
|
|
61
|
+
/\.git/
|
|
62
|
+
],
|
|
63
|
+
persistent: true,
|
|
64
|
+
awaitWriteFinish: {
|
|
65
|
+
stabilityThreshold: 100,
|
|
66
|
+
pollInterval: 50
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
watcher.on('change', async path => {
|
|
71
|
+
// .pugファイルのみ処理
|
|
72
|
+
if (!path.endsWith('.pug')) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
const relPath = relative(basePath, path)
|
|
76
|
+
logger.info('change', `pug: ${relPath}`)
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// パーシャルの場合、依存する親ファイルも再ビルド
|
|
80
|
+
const affectedFiles = this.context.graph.getAffectedParents(path)
|
|
81
|
+
|
|
82
|
+
if (affectedFiles.length > 0) {
|
|
83
|
+
logger.info('pug', `Rebuilding ${affectedFiles.length} affected file(s)`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// キャッシュ無効化
|
|
87
|
+
this.context.cache.invalidatePugTemplate(path)
|
|
88
|
+
affectedFiles.forEach(f => this.context.cache.invalidatePugTemplate(f))
|
|
89
|
+
|
|
90
|
+
// Pugタスクを実行(変更されたファイルのみ)
|
|
91
|
+
if (this.context.taskRegistry?.pug) {
|
|
92
|
+
await this.context.taskRegistry.pug(this.context, {
|
|
93
|
+
files: [path, ...affectedFiles]
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.reload()
|
|
98
|
+
} catch (error) {
|
|
99
|
+
logger.error('watch', `Pug build failed: ${error.message}`)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
this.watchers.push(watcher)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Sass監視
|
|
108
|
+
*/
|
|
109
|
+
watchSass(basePath) {
|
|
110
|
+
const watcher = chokidar.watch(basePath, {
|
|
111
|
+
ignoreInitial: true,
|
|
112
|
+
ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/],
|
|
113
|
+
persistent: true,
|
|
114
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
watcher.on('change', async path => {
|
|
118
|
+
// .scssファイルのみ処理
|
|
119
|
+
if (!path.endsWith('.scss')) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
const relPath = relative(basePath, path)
|
|
123
|
+
logger.info('change', `sass: ${relPath}`)
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// Sassタスクを実行
|
|
127
|
+
if (this.context.taskRegistry?.sass) {
|
|
128
|
+
await this.context.taskRegistry.sass(this.context)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// CSSはインジェクション(リロードせずに更新)
|
|
132
|
+
this.injectCSS()
|
|
133
|
+
} catch (error) {
|
|
134
|
+
logger.error('watch', `Sass build failed: ${error.message}`)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
this.watchers.push(watcher)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Script監視
|
|
143
|
+
*/
|
|
144
|
+
watchScript(basePath) {
|
|
145
|
+
const watcher = chokidar.watch(basePath, {
|
|
146
|
+
ignoreInitial: true,
|
|
147
|
+
ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/, /\.d\.ts$/],
|
|
148
|
+
persistent: true,
|
|
149
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
watcher.on('change', async path => {
|
|
153
|
+
// .ts/.jsファイルのみ処理(.d.tsを除く)
|
|
154
|
+
if (!(path.endsWith('.ts') || path.endsWith('.js')) || path.endsWith('.d.ts')) {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
const relPath = relative(basePath, path)
|
|
158
|
+
logger.info('change', `script: ${relPath}`)
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
// Scriptタスクを実行
|
|
162
|
+
if (this.context.taskRegistry?.script) {
|
|
163
|
+
await this.context.taskRegistry.script(this.context)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.reload()
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.error('watch', `Script build failed: ${error.message}`)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
this.watchers.push(watcher)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* SVG監視
|
|
177
|
+
*/
|
|
178
|
+
watchSvg(basePath) {
|
|
179
|
+
const watcher = chokidar.watch(basePath, {
|
|
180
|
+
ignoreInitial: true,
|
|
181
|
+
ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/, /icons/], // iconsはスプライト用なので除外
|
|
182
|
+
persistent: true,
|
|
183
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const handleSvgChange = async path => {
|
|
187
|
+
// .svgファイルのみ処理(iconsディレクトリは除外)
|
|
188
|
+
if (!path.endsWith('.svg') || path.includes('/icons/')) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
const relPath = relative(basePath, path)
|
|
192
|
+
logger.info('change', `svg: ${relPath}`)
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// SVGタスクを実行(変更されたファイルのみ)
|
|
196
|
+
if (this.context.taskRegistry?.svg) {
|
|
197
|
+
await this.context.taskRegistry.svg(this.context, {
|
|
198
|
+
files: [path]
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this.reload()
|
|
203
|
+
} catch (error) {
|
|
204
|
+
logger.error('watch', `SVG processing failed: ${error.message}`)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
watcher.on('change', handleSvgChange)
|
|
209
|
+
watcher.on('add', handleSvgChange)
|
|
210
|
+
|
|
211
|
+
this.watchers.push(watcher)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 画像監視
|
|
216
|
+
*/
|
|
217
|
+
watchImages(basePath) {
|
|
218
|
+
const watcher = chokidar.watch(basePath, {
|
|
219
|
+
ignoreInitial: true,
|
|
220
|
+
ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/],
|
|
221
|
+
persistent: true,
|
|
222
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
const handleImageChange = async path => {
|
|
226
|
+
// 画像ファイルのみ処理
|
|
227
|
+
if (!/\.(jpg|jpeg|png|gif)$/i.test(path)) {
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
const relPath = relative(basePath, path)
|
|
231
|
+
logger.info('change', `image: ${relPath}`)
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// 追加・変更時: 画像を処理
|
|
235
|
+
if (this.context.taskRegistry?.image) {
|
|
236
|
+
await this.context.taskRegistry.image(this.context, {
|
|
237
|
+
files: [path]
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.reload()
|
|
242
|
+
} catch (error) {
|
|
243
|
+
logger.error('watch', `Image processing failed: ${error.message}`)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
watcher.on('change', handleImageChange)
|
|
248
|
+
watcher.on('add', handleImageChange)
|
|
249
|
+
|
|
250
|
+
this.watchers.push(watcher)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Public監視
|
|
255
|
+
*/
|
|
256
|
+
watchPublic(basePath) {
|
|
257
|
+
const watcher = chokidar.watch(basePath, {
|
|
258
|
+
ignoreInitial: true,
|
|
259
|
+
ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/],
|
|
260
|
+
persistent: true,
|
|
261
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
watcher.on('change', async path => {
|
|
265
|
+
const relPath = relative(basePath, path)
|
|
266
|
+
logger.info('change', `public: ${relPath}`)
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
// Copyタスクを実行
|
|
270
|
+
if (this.context.taskRegistry?.copy) {
|
|
271
|
+
await this.context.taskRegistry.copy(this.context)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
this.reload()
|
|
275
|
+
} catch (error) {
|
|
276
|
+
logger.error('watch', `Copy failed: ${error.message}`)
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
this.watchers.push(watcher)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* ブラウザリロード
|
|
285
|
+
*/
|
|
286
|
+
reload() {
|
|
287
|
+
if (this.context.server) {
|
|
288
|
+
setTimeout(() => {
|
|
289
|
+
this.context.server.reload()
|
|
290
|
+
}, 100)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* CSSインジェクション(リロードなし)
|
|
296
|
+
*/
|
|
297
|
+
injectCSS() {
|
|
298
|
+
if (this.context.server) {
|
|
299
|
+
setTimeout(() => {
|
|
300
|
+
this.context.server.reload('*.css')
|
|
301
|
+
}, 100)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 監視停止
|
|
307
|
+
*/
|
|
308
|
+
async stop() {
|
|
309
|
+
await Promise.all(this.watchers.map(w => w.close()))
|
|
310
|
+
logger.info('watch', 'File watching stopped')
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export default watcherTask
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { writeFile, copyFile, mkdir } from 'node:fs/promises'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
import { ensureFileDir } from '../utils/file.mjs'
|
|
4
|
+
|
|
5
|
+
export async function generateAsset(outputPath, data) {
|
|
6
|
+
await ensureFileDir(outputPath)
|
|
7
|
+
|
|
8
|
+
if (typeof data === 'string' || Buffer.isBuffer(data)) {
|
|
9
|
+
await writeFile(outputPath, data)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return outputPath
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function copyAsset(srcPath, destPath) {
|
|
16
|
+
await ensureFileDir(destPath)
|
|
17
|
+
await copyFile(srcPath, destPath)
|
|
18
|
+
return destPath
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises'
|
|
2
|
+
import { relative, resolve } from 'node:path'
|
|
3
|
+
import { ensureFileDir } from '../utils/file.mjs'
|
|
4
|
+
|
|
5
|
+
export async function generatePage(filePath, html, paths) {
|
|
6
|
+
const relativePath = relative(paths.src, filePath)
|
|
7
|
+
const outputPath = resolve(paths.dist, relativePath.replace('.pug', '.html'))
|
|
8
|
+
|
|
9
|
+
await ensureFileDir(outputPath)
|
|
10
|
+
await writeFile(outputPath, html, 'utf8')
|
|
11
|
+
|
|
12
|
+
return outputPath
|
|
13
|
+
}
|
package/index.mjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { loadConfig } from './config/index.mjs'
|
|
2
|
+
import { Builder } from './core/builder.mjs'
|
|
3
|
+
import pugTask from './tasks/pug.mjs'
|
|
4
|
+
import sassTask from './tasks/sass.mjs'
|
|
5
|
+
import scriptTask from './tasks/script.mjs'
|
|
6
|
+
import copyTask from './tasks/copy.mjs'
|
|
7
|
+
import imageTask from './tasks/image.mjs'
|
|
8
|
+
import svgTask from './tasks/svg.mjs'
|
|
9
|
+
import spriteTask from './tasks/svg-sprite.mjs'
|
|
10
|
+
import serverTask from './core/server.mjs'
|
|
11
|
+
import watcherTask from './core/watcher.mjs'
|
|
12
|
+
|
|
13
|
+
export async function createBuilder(root = process.cwd(), mode = 'development') {
|
|
14
|
+
const config = await loadConfig(root)
|
|
15
|
+
const builder = new Builder(config, mode)
|
|
16
|
+
|
|
17
|
+
builder.registerTasks({
|
|
18
|
+
pug: pugTask,
|
|
19
|
+
sass: sassTask,
|
|
20
|
+
script: scriptTask,
|
|
21
|
+
image: imageTask,
|
|
22
|
+
svg: svgTask,
|
|
23
|
+
sprite: spriteTask,
|
|
24
|
+
copy: copyTask,
|
|
25
|
+
server: serverTask,
|
|
26
|
+
watch: watcherTask
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
builder.context.taskRegistry = {
|
|
30
|
+
pug: pugTask,
|
|
31
|
+
sass: sassTask,
|
|
32
|
+
script: scriptTask,
|
|
33
|
+
image: imageTask,
|
|
34
|
+
svg: svgTask,
|
|
35
|
+
copy: copyTask
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return builder
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function build(root = process.cwd()) {
|
|
42
|
+
const builder = await createBuilder(root, 'production')
|
|
43
|
+
await builder.build()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function watch(root = process.cwd()) {
|
|
47
|
+
const builder = await createBuilder(root, 'development')
|
|
48
|
+
await builder.watch()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function runTask(taskName, root = process.cwd()) {
|
|
52
|
+
const builder = await createBuilder(root, 'production')
|
|
53
|
+
await builder.runTask(taskName)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { Builder, loadConfig }
|
|
57
|
+
export { BuildContext } from './core/context.mjs'
|
|
58
|
+
export { CacheManager } from './core/cache.mjs'
|
|
59
|
+
export { DependencyGraph } from './core/graph.mjs'
|
|
60
|
+
export { defineConfig } from './config/index.mjs'
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pugkit",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "A build tool for Pug-based projects",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "mfxgu2i <mfxgu2i@gmail.com>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/mfxgu2i/pugkit.git",
|
|
11
|
+
"directory": "packages/pugkit"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/mfxgu2i/pugkit/issues",
|
|
14
|
+
"homepage": "https://github.com/mfxgu2i/pugkit#readme",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.0.0"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"pugkit": "./cli/index.mjs"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"pug",
|
|
26
|
+
"ssg",
|
|
27
|
+
"static-site-generator",
|
|
28
|
+
"frontend-tooling"
|
|
29
|
+
],
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./index.mjs",
|
|
32
|
+
"./config": "./config/index.mjs"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"cli",
|
|
36
|
+
"config",
|
|
37
|
+
"core",
|
|
38
|
+
"generate",
|
|
39
|
+
"index.mjs",
|
|
40
|
+
"tasks",
|
|
41
|
+
"transform",
|
|
42
|
+
"utils"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"autoprefixer": "^10.4.21",
|
|
46
|
+
"browser-sync": "^3.0.4",
|
|
47
|
+
"cac": "^6.7.14",
|
|
48
|
+
"chokidar": "^4.0.3",
|
|
49
|
+
"cssnano": "^7.1.1",
|
|
50
|
+
"esbuild": "^0.25.5",
|
|
51
|
+
"glob": "^10.3.10",
|
|
52
|
+
"image-size": "^2.0.2",
|
|
53
|
+
"picocolors": "^1.1.1",
|
|
54
|
+
"postcss": "^8.4.49",
|
|
55
|
+
"prettier": "^3.8.0",
|
|
56
|
+
"pug": "^3.0.3",
|
|
57
|
+
"sass": "^1.89.2",
|
|
58
|
+
"sharp": "^0.34.2",
|
|
59
|
+
"svgo": "^4.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|