pugkit 1.1.0 → 1.3.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 +50 -53
- package/config/defaults.mjs +2 -0
- package/config/main.mjs +25 -1
- package/config/patterns.mjs +3 -12
- package/core/builder.mjs +7 -1
- package/core/context.mjs +8 -2
- package/core/graph.mjs +1 -1
- package/core/server.mjs +1 -1
- package/core/watcher.mjs +19 -5
- package/package.json +2 -2
- package/tasks/pug.mjs +1 -1
- package/tasks/sass.mjs +51 -9
- package/tasks/script.mjs +51 -10
- package/transform/html.mjs +13 -9
package/README.md
CHANGED
|
@@ -9,11 +9,6 @@
|
|
|
9
9
|
</a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
-
## About
|
|
13
|
-
|
|
14
|
-
pugkitは静的サイト制作に特化したビルドツールです。
|
|
15
|
-
納品向きの綺麗なHTMLと、ファイル構成に制約のないアセットファイルを出力可能です。
|
|
16
|
-
|
|
17
12
|
## How To Use
|
|
18
13
|
|
|
19
14
|
```sh
|
|
@@ -39,6 +34,34 @@ $ touch ./src/index.pug
|
|
|
39
34
|
| `pugkit build` | 本番ビルド |
|
|
40
35
|
| `pugkit sprite` | SVGスプライト生成 |
|
|
41
36
|
|
|
37
|
+
## Directory Structure
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
project-root/
|
|
41
|
+
├── src/ # ソースファイル
|
|
42
|
+
│ ├── *.pug
|
|
43
|
+
│ ├── *.scss
|
|
44
|
+
│ ├── *.ts
|
|
45
|
+
│ ├── *.js
|
|
46
|
+
│ ├── *.jpg
|
|
47
|
+
│ ├── *.png
|
|
48
|
+
│ └── *.svg
|
|
49
|
+
├── public/ # 静的ファイル
|
|
50
|
+
│ ├── ogp.jpg
|
|
51
|
+
│ └── favicon.ico
|
|
52
|
+
├── dist/ # ビルド出力先
|
|
53
|
+
└── pugkit.config.mjs # ビルド設定ファイル
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### File Naming Rules
|
|
57
|
+
|
|
58
|
+
`_`(アンダースコア)で始まるファイル・ディレクトリはビルド対象外です。それ以外のファイルは `src/` 配下のディレクトリ構成を維持したまま `outDir`(デフォルト: `dist/`)に出力されます。
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
src/foo/style.scss → dist/foo/style.css
|
|
62
|
+
src/foo/bar/script.js → dist/foo/bar/script.js
|
|
63
|
+
```
|
|
64
|
+
|
|
42
65
|
## Configuration
|
|
43
66
|
|
|
44
67
|
プロジェクトルートに`pugkit.config.mjs`を配置することで、ビルド設定をカスタマイズできます。
|
|
@@ -50,6 +73,7 @@ import { defineConfig } from 'pugkit'
|
|
|
50
73
|
export default defineConfig({
|
|
51
74
|
siteUrl: 'https://example.com/',
|
|
52
75
|
subdir: '',
|
|
76
|
+
outDir: 'dist',
|
|
53
77
|
debug: false,
|
|
54
78
|
server: {
|
|
55
79
|
port: 5555,
|
|
@@ -62,19 +86,20 @@ export default defineConfig({
|
|
|
62
86
|
})
|
|
63
87
|
```
|
|
64
88
|
|
|
65
|
-
| Option | Description
|
|
66
|
-
| ------------------------- |
|
|
67
|
-
| `siteUrl` | サイトのベースURL(`Builder.url` に使用)
|
|
68
|
-
| `subdir` | サブディレクトリのパス
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `server.
|
|
72
|
-
| `server.
|
|
73
|
-
| `server.
|
|
74
|
-
| `build.
|
|
75
|
-
| `build.
|
|
76
|
-
| `build.imageOptions.
|
|
77
|
-
| `build.imageOptions.
|
|
89
|
+
| Option | Description | Type / Values | Default |
|
|
90
|
+
| ------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------- | ------------- |
|
|
91
|
+
| `siteUrl` | サイトのベースURL(`Builder.url` に使用) | `string` | `''` |
|
|
92
|
+
| `subdir` | サブディレクトリのパス | `string` | `''` |
|
|
93
|
+
| `outDir` | ビルド出力先ディレクトリ。相対・絶対パス・ネスト(`htdocs/v2`)・上位(`../htdocs`)も指定可 | `string` | `'dist'` |
|
|
94
|
+
| `debug` | デバッグモード(開発時のみ有効) | `boolean` | `false` |
|
|
95
|
+
| `server.port` | 開発サーバーのポート番号 | `number` | `5555` |
|
|
96
|
+
| `server.host` | 開発サーバーのホスト | `string` | `'localhost'` |
|
|
97
|
+
| `server.startPath` | サーバー起動時に開くパス | `string` | `'/'` |
|
|
98
|
+
| `build.clean` | ビルド前に `outDir` をクリーンするか(`false` にすると他リソースと共存可能) | `boolean` | `true` |
|
|
99
|
+
| `build.imageOptimization` | 画像最適化の方式 | `'webp'` \| `'compress'` \| `false` | `'webp'` |
|
|
100
|
+
| `build.imageOptions.webp` | WebP変換オプション([Sharp WebP options](https://sharp.pixelplumbing.com/api-output#webp)) | `object` | - |
|
|
101
|
+
| `build.imageOptions.jpeg` | JPEG圧縮オプション([Sharp JPEG options](https://sharp.pixelplumbing.com/api-output#jpeg)) | `object` | - |
|
|
102
|
+
| `build.imageOptions.png` | PNG圧縮オプション([Sharp PNG options](https://sharp.pixelplumbing.com/api-output#png)) | `object` | - |
|
|
78
103
|
|
|
79
104
|
## Features
|
|
80
105
|
|
|
@@ -176,12 +201,16 @@ npm install --save-dev typescript
|
|
|
176
201
|
- `'compress'` - 元の形式を維持したまま圧縮
|
|
177
202
|
- `false` - 最適化を無効化
|
|
178
203
|
|
|
204
|
+
### SVG Optimization
|
|
205
|
+
|
|
206
|
+
`icons/`以外に配置した SVG ファイルはSVGOで自動最適化されて出力されます。
|
|
207
|
+
|
|
179
208
|
### SVG Sprite
|
|
180
209
|
|
|
181
|
-
`src
|
|
210
|
+
`src/`配下の`icons/`ディレクトリに配置したSVGを1つのスプライトファイルにまとめます。
|
|
182
211
|
|
|
183
212
|
```
|
|
184
|
-
src/assets/icons/arrow.svg →
|
|
213
|
+
`src/assets/icons/arrow.svg → <outDir>/assets/icons.svg#arrow`
|
|
185
214
|
```
|
|
186
215
|
|
|
187
216
|
```html
|
|
@@ -191,13 +220,9 @@ src/assets/icons/arrow.svg → dist/assets/icons.svg#arrow
|
|
|
191
220
|
- SVG ファイル名がそのまま `<symbol id>` になります
|
|
192
221
|
- `fill` / `stroke` は自動的に `currentColor` に変換されます
|
|
193
222
|
|
|
194
|
-
### SVG Optimization
|
|
195
|
-
|
|
196
|
-
`icons/` 以外に配置した SVG ファイルは SVGO で自動最適化されて出力されます。
|
|
197
|
-
|
|
198
223
|
### Public Directory
|
|
199
224
|
|
|
200
|
-
`public/` に置いたファイルはそのまま `
|
|
225
|
+
`public/` に置いたファイルはそのまま `outDir` のルートにコピーされます。faviconやOGP画像など最適化不要なファイルの置き場として使用します。
|
|
201
226
|
|
|
202
227
|
### Debug Mode
|
|
203
228
|
|
|
@@ -208,34 +233,6 @@ src/assets/icons/arrow.svg → dist/assets/icons.svg#arrow
|
|
|
208
233
|
| CSS | minify済み | expanded + ソースマップ |
|
|
209
234
|
| JS | minify済み・`console.*` 削除 | ソースマップ・`console.*` 保持 |
|
|
210
235
|
|
|
211
|
-
### File Naming Rules
|
|
212
|
-
|
|
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
|
-
```
|
|
219
|
-
|
|
220
|
-
## Directory Structure
|
|
221
|
-
|
|
222
|
-
```
|
|
223
|
-
project-root/
|
|
224
|
-
├── src/ # ソースファイル
|
|
225
|
-
│ ├── *.pug
|
|
226
|
-
│ ├── *.scss
|
|
227
|
-
│ ├── *.ts
|
|
228
|
-
│ ├── *.js
|
|
229
|
-
│ ├── *.jpg
|
|
230
|
-
│ ├── *.png
|
|
231
|
-
│ └── *.svg
|
|
232
|
-
├── public/ # 静的ファイル
|
|
233
|
-
│ ├── ogp.jpg
|
|
234
|
-
│ └── favicon.ico
|
|
235
|
-
├── dist/ # ビルド出力先
|
|
236
|
-
└── pugkit.config.mjs # ビルド設定ファイル
|
|
237
|
-
```
|
|
238
|
-
|
|
239
236
|
## Tech Stack
|
|
240
237
|
|
|
241
238
|
- [Pug](https://pugjs.org/) - HTMLテンプレートエンジン
|
package/config/defaults.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const defaultConfig = {
|
|
2
2
|
siteUrl: '',
|
|
3
3
|
subdir: '',
|
|
4
|
+
outDir: 'dist',
|
|
4
5
|
debug: false,
|
|
5
6
|
server: {
|
|
6
7
|
port: 5555,
|
|
@@ -8,6 +9,7 @@ export const defaultConfig = {
|
|
|
8
9
|
startPath: '/'
|
|
9
10
|
},
|
|
10
11
|
build: {
|
|
12
|
+
clean: true,
|
|
11
13
|
imageOptimization: 'webp',
|
|
12
14
|
imageOptions: {
|
|
13
15
|
webp: {
|
package/config/main.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolve } from 'node:path'
|
|
1
|
+
import { resolve, isAbsolute, relative } from 'node:path'
|
|
2
2
|
import { existsSync } from 'node:fs'
|
|
3
3
|
import { defaultConfig } from './defaults.mjs'
|
|
4
4
|
|
|
@@ -20,11 +20,13 @@ function mergeConfig(defaults, user) {
|
|
|
20
20
|
return {
|
|
21
21
|
siteUrl: user.siteUrl || defaults.siteUrl,
|
|
22
22
|
subdir: user.subdir || defaults.subdir,
|
|
23
|
+
outDir: user.outDir !== undefined ? user.outDir : defaults.outDir,
|
|
23
24
|
debug: user.debug !== undefined ? user.debug : defaults.debug,
|
|
24
25
|
server: { ...defaults.server, ...(user.server || {}) },
|
|
25
26
|
build: {
|
|
26
27
|
...defaults.build,
|
|
27
28
|
...(user.build || {}),
|
|
29
|
+
clean: user.build?.clean !== undefined ? user.build.clean : defaults.build.clean,
|
|
28
30
|
imageOptions: {
|
|
29
31
|
webp: { ...defaults.build.imageOptions.webp, ...(user.build?.imageOptions?.webp || {}) },
|
|
30
32
|
jpeg: { ...defaults.build.imageOptions.jpeg, ...(user.build?.imageOptions?.jpeg || {}) },
|
|
@@ -34,10 +36,32 @@ function mergeConfig(defaults, user) {
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
function validateConfig(config) {
|
|
40
|
+
const root = config.root
|
|
41
|
+
const outDir = config.outDir
|
|
42
|
+
const resolvedOutDir = isAbsolute(outDir) ? outDir : resolve(root, outDir)
|
|
43
|
+
|
|
44
|
+
// Vite同様、outDirがrootと同一またはrootの親ディレクトリの場合に警告
|
|
45
|
+
// relative() を使うことでWindows(バックスラッシュ)でも正しく動作する
|
|
46
|
+
const isSameAsRoot = resolvedOutDir === root
|
|
47
|
+
const relToRoot = relative(resolvedOutDir, root)
|
|
48
|
+
const isParentOfRoot = relToRoot !== '' && !relToRoot.startsWith('..')
|
|
49
|
+
|
|
50
|
+
if (isSameAsRoot || isParentOfRoot) {
|
|
51
|
+
console.warn(
|
|
52
|
+
`[pugkit] outDir "${outDir}" はプロジェクトルートと同じか親ディレクトリです。` +
|
|
53
|
+
`ソースファイルが上書きされる可能性があるため、別のディレクトリを指定してください。`
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return config
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
export async function loadConfig(root = process.cwd()) {
|
|
38
61
|
const userConfig = await loadUserConfig(root)
|
|
39
62
|
const config = mergeConfig(defaultConfig, userConfig)
|
|
40
63
|
config.root = root
|
|
64
|
+
validateConfig(config)
|
|
41
65
|
return config
|
|
42
66
|
}
|
|
43
67
|
|
package/config/patterns.mjs
CHANGED
|
@@ -11,20 +11,11 @@ export function createGlobPatterns(srcPath) {
|
|
|
11
11
|
ignore: ['**/*.d.ts', '**/node_modules/**']
|
|
12
12
|
},
|
|
13
13
|
images: {
|
|
14
|
-
optimize: [
|
|
15
|
-
|
|
16
|
-
`!${srcPath}/**/sprites_*/*`,
|
|
17
|
-
`!${srcPath}/**/_inline*/*`,
|
|
18
|
-
`!${srcPath}/**/icons*/*`
|
|
19
|
-
],
|
|
20
|
-
webp: {
|
|
21
|
-
src: [`${srcPath}/**/*.{png,jpg,jpeg,gif}`],
|
|
22
|
-
ignore: [`!${srcPath}/**/favicons/*`, `!${srcPath}/**/ogp.{png,jpg}`]
|
|
23
|
-
}
|
|
14
|
+
optimize: [`${srcPath}/**/*.{png,jpg,jpeg}`, `!${srcPath}/**/icons/*`],
|
|
15
|
+
webp: [`${srcPath}/**/*.{png,jpg,jpeg,gif}`, `!${srcPath}/**/icons/*`]
|
|
24
16
|
},
|
|
25
17
|
svg: {
|
|
26
|
-
src: [`${srcPath}/**/*.svg
|
|
27
|
-
ignore: [`!${srcPath}/**/sprites_*/*`, `!${srcPath}/**/_inline*/*`, `!${srcPath}/**/icons*/*`]
|
|
18
|
+
src: [`${srcPath}/**/*.svg`, `!${srcPath}/**/icons/*`]
|
|
28
19
|
}
|
|
29
20
|
}
|
|
30
21
|
}
|
package/core/builder.mjs
CHANGED
|
@@ -34,11 +34,17 @@ export class Builder {
|
|
|
34
34
|
const { context } = this
|
|
35
35
|
const startTime = Date.now()
|
|
36
36
|
|
|
37
|
+
const shouldClean = context.config.build.clean
|
|
38
|
+
|
|
37
39
|
logger.info('build', `Building in ${context.mode} mode`)
|
|
38
40
|
|
|
39
41
|
try {
|
|
40
42
|
// 1. クリーンアップ
|
|
41
|
-
|
|
43
|
+
if (shouldClean) {
|
|
44
|
+
await this.clean()
|
|
45
|
+
} else {
|
|
46
|
+
logger.info('build', 'Skipping clean (clean: false)')
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
// 2. 並列ビルド(軽量タスク + スプライト)
|
|
44
50
|
const parallelTasks = []
|
package/core/context.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolve } from 'node:path'
|
|
1
|
+
import { resolve, isAbsolute } from 'node:path'
|
|
2
2
|
import { CacheManager } from './cache.mjs'
|
|
3
3
|
import { DependencyGraph } from './graph.mjs'
|
|
4
4
|
import { createGlobPatterns } from '../config/index.mjs'
|
|
@@ -9,11 +9,17 @@ export class BuildContext {
|
|
|
9
9
|
this.mode = mode
|
|
10
10
|
this.cache = new CacheManager(mode)
|
|
11
11
|
this.graph = new DependencyGraph()
|
|
12
|
+
this.sassGraph = new DependencyGraph()
|
|
13
|
+
this.scriptGraph = new DependencyGraph()
|
|
14
|
+
|
|
15
|
+
const outDir = config.outDir ?? 'dist'
|
|
16
|
+
const resolvedOutDir = isAbsolute(outDir) ? outDir : resolve(config.root, outDir)
|
|
12
17
|
|
|
13
18
|
this.paths = {
|
|
14
19
|
root: config.root,
|
|
15
20
|
src: resolve(config.root, 'src'),
|
|
16
|
-
|
|
21
|
+
outDir: resolvedOutDir,
|
|
22
|
+
dist: config.subdir ? resolve(resolvedOutDir, config.subdir) : resolvedOutDir,
|
|
17
23
|
public: resolve(config.root, 'public')
|
|
18
24
|
}
|
|
19
25
|
|
package/core/graph.mjs
CHANGED
package/core/server.mjs
CHANGED
|
@@ -50,7 +50,7 @@ export async function serverTask(context, options = {}) {
|
|
|
50
50
|
const startPath = (config.server?.startPath || '/').replace(/^\//, '')
|
|
51
51
|
const fullStartPath = subdir ? `${subdir}/${startPath}` : `/${startPath}`
|
|
52
52
|
|
|
53
|
-
const serveRoot =
|
|
53
|
+
const serveRoot = paths.outDir
|
|
54
54
|
|
|
55
55
|
const clients = new Set()
|
|
56
56
|
|
package/core/watcher.mjs
CHANGED
|
@@ -25,9 +25,18 @@ class FileWatcher {
|
|
|
25
25
|
|
|
26
26
|
// 初回ビルド(依存関係グラフ構築のため)
|
|
27
27
|
logger.info('watch', 'Building initial dependency graph...')
|
|
28
|
+
|
|
29
|
+
const initialTasks = []
|
|
28
30
|
if (this.context.taskRegistry?.pug) {
|
|
29
|
-
|
|
31
|
+
initialTasks.push(this.context.taskRegistry.pug(this.context))
|
|
32
|
+
}
|
|
33
|
+
if (this.context.taskRegistry?.sass) {
|
|
34
|
+
initialTasks.push(this.context.taskRegistry.sass(this.context))
|
|
30
35
|
}
|
|
36
|
+
if (this.context.taskRegistry?.script) {
|
|
37
|
+
initialTasks.push(this.context.taskRegistry.script(this.context))
|
|
38
|
+
}
|
|
39
|
+
await Promise.all(initialTasks)
|
|
31
40
|
|
|
32
41
|
this.watcher = chokidar
|
|
33
42
|
.watch([paths.src, paths.public], {
|
|
@@ -55,6 +64,9 @@ class FileWatcher {
|
|
|
55
64
|
|
|
56
65
|
handleAdd(filePath) {
|
|
57
66
|
if (this.isPublic(filePath)) return this.onPublicChange(filePath, 'add')
|
|
67
|
+
if (filePath.endsWith('.pug')) return this.onPugChange(filePath)
|
|
68
|
+
if (filePath.endsWith('.scss')) return this.onSassChange(filePath)
|
|
69
|
+
if (this.isScript(filePath)) return this.onScriptChange(filePath)
|
|
58
70
|
if (filePath.endsWith('.svg') && !this.isIcons(filePath)) return this.onSvgChange(filePath, 'add')
|
|
59
71
|
if (/\.(jpg|jpeg|png|gif)$/i.test(filePath)) return this.onImageChange(filePath, 'add')
|
|
60
72
|
}
|
|
@@ -122,7 +134,7 @@ class FileWatcher {
|
|
|
122
134
|
const relPath = relative(this.context.paths.src, filePath)
|
|
123
135
|
logger.info('change', `sass: ${relPath}`)
|
|
124
136
|
try {
|
|
125
|
-
if (this.context.taskRegistry?.sass) await this.context.taskRegistry.sass(this.context)
|
|
137
|
+
if (this.context.taskRegistry?.sass) await this.context.taskRegistry.sass(this.context, { files: [filePath] })
|
|
126
138
|
this.injectCSS()
|
|
127
139
|
} catch (error) {
|
|
128
140
|
logger.error('watch', `Sass build failed: ${error.message}`)
|
|
@@ -130,8 +142,9 @@ class FileWatcher {
|
|
|
130
142
|
}
|
|
131
143
|
|
|
132
144
|
async onSassUnlink(filePath) {
|
|
133
|
-
const { paths } = this.context
|
|
145
|
+
const { paths, sassGraph } = this.context
|
|
134
146
|
const relPath = relative(paths.src, filePath)
|
|
147
|
+
sassGraph.clearDependencies(filePath)
|
|
135
148
|
if (basename(filePath).startsWith('_')) {
|
|
136
149
|
logger.info('unlink', relPath)
|
|
137
150
|
return
|
|
@@ -146,7 +159,7 @@ class FileWatcher {
|
|
|
146
159
|
const relPath = relative(this.context.paths.src, filePath)
|
|
147
160
|
logger.info('change', `script: ${relPath}`)
|
|
148
161
|
try {
|
|
149
|
-
if (this.context.taskRegistry?.script) await this.context.taskRegistry.script(this.context)
|
|
162
|
+
if (this.context.taskRegistry?.script) await this.context.taskRegistry.script(this.context, { files: [filePath] })
|
|
150
163
|
this.reload()
|
|
151
164
|
} catch (error) {
|
|
152
165
|
logger.error('watch', `Script build failed: ${error.message}`)
|
|
@@ -154,8 +167,9 @@ class FileWatcher {
|
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
async onScriptUnlink(filePath) {
|
|
157
|
-
const { paths } = this.context
|
|
170
|
+
const { paths, scriptGraph } = this.context
|
|
158
171
|
const relPath = relative(paths.src, filePath)
|
|
172
|
+
scriptGraph.clearDependencies(filePath)
|
|
159
173
|
if (basename(filePath).startsWith('_')) {
|
|
160
174
|
logger.info('unlink', relPath)
|
|
161
175
|
return
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pugkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "A build tool for Pug-based projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"esbuild": "^0.25.5",
|
|
50
50
|
"glob": "^13.0.6",
|
|
51
51
|
"image-size": "^2.0.2",
|
|
52
|
+
"js-beautify": "^1.15.4",
|
|
52
53
|
"picocolors": "^1.1.1",
|
|
53
54
|
"postcss": "^8.4.49",
|
|
54
|
-
"prettier": "^3.8.0",
|
|
55
55
|
"pug": "^3.0.3",
|
|
56
56
|
"sass": "^1.89.2",
|
|
57
57
|
"sharp": "^0.34.2",
|
package/tasks/pug.mjs
CHANGED
|
@@ -69,7 +69,7 @@ async function processFile(filePath, context) {
|
|
|
69
69
|
const imageInfo = createImageInfoHelper(filePath, paths, logger, config)
|
|
70
70
|
|
|
71
71
|
const html = template({ Builder: builderVars, imageSize, imageInfo })
|
|
72
|
-
const formatted =
|
|
72
|
+
const formatted = formatHtml(html)
|
|
73
73
|
await generatePage(filePath, formatted, paths)
|
|
74
74
|
} catch (error) {
|
|
75
75
|
logger.error('pug', `Failed: ${basename(filePath)} - ${error.message}`)
|
package/tasks/sass.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { glob } from 'glob'
|
|
2
2
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
3
3
|
import { relative, resolve, basename, extname } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
4
5
|
import * as sass from 'sass'
|
|
5
6
|
import postcss from 'postcss'
|
|
6
7
|
import autoprefixer from 'autoprefixer'
|
|
@@ -12,36 +13,64 @@ import { ensureFileDir } from '../utils/file.mjs'
|
|
|
12
13
|
* Sassビルドタスク
|
|
13
14
|
*/
|
|
14
15
|
export async function sassTask(context, options = {}) {
|
|
15
|
-
const { paths, config, isProduction } = context
|
|
16
|
+
const { paths, config, isProduction, isDevelopment, sassGraph, cache } = context
|
|
16
17
|
|
|
17
18
|
// debugモードはdevモード時のみ有効
|
|
18
19
|
const isDebugMode = !isProduction && config.debug
|
|
19
20
|
|
|
20
|
-
// 1.
|
|
21
|
-
const
|
|
21
|
+
// 1. ビルド対象ファイルの取得(非パーシャル)
|
|
22
|
+
const allEntryFiles = await glob('**/[^_]*.scss', {
|
|
22
23
|
cwd: paths.src,
|
|
23
24
|
absolute: true,
|
|
24
25
|
ignore: ['**/_*.scss']
|
|
25
26
|
})
|
|
26
27
|
|
|
27
|
-
if (
|
|
28
|
+
if (allEntryFiles.length === 0) {
|
|
28
29
|
logger.skip('sass', 'No files to build')
|
|
29
30
|
return
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
// 2. dev モードでのインクリメンタルビルド
|
|
34
|
+
let filesToBuild = allEntryFiles
|
|
35
|
+
|
|
36
|
+
if (isDevelopment && options.files?.length > 0) {
|
|
37
|
+
const changedFile = options.files[0]
|
|
38
|
+
const isPartial = basename(changedFile).startsWith('_')
|
|
39
|
+
|
|
40
|
+
if (isPartial) {
|
|
41
|
+
// パーシャル変更 → 依存グラフから影響を受けるエントリファイルを特定
|
|
42
|
+
const affected = sassGraph.getAffectedParents(changedFile)
|
|
43
|
+
filesToBuild = affected.filter(f => allEntryFiles.includes(f))
|
|
44
|
+
|
|
45
|
+
if (filesToBuild.length === 0) {
|
|
46
|
+
// グラフにまだ情報がない場合はフルビルド
|
|
47
|
+
filesToBuild = allEntryFiles
|
|
48
|
+
} else {
|
|
49
|
+
logger.info('sass', `Partial changed, rebuilding ${filesToBuild.length} affected file(s)`)
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// 非パーシャル変更 → そのファイルだけリビルド
|
|
53
|
+
filesToBuild = allEntryFiles.filter(f => f === changedFile)
|
|
54
|
+
if (filesToBuild.length === 0) {
|
|
55
|
+
logger.skip('sass', 'Changed file is not a build target')
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
logger.info('sass', `Building ${filesToBuild.length} file(s)`)
|
|
33
62
|
|
|
34
|
-
//
|
|
35
|
-
await Promise.all(
|
|
63
|
+
// 3. 並列コンパイル
|
|
64
|
+
await Promise.all(filesToBuild.map(file => compileSassFile(file, context, isDebugMode)))
|
|
36
65
|
|
|
37
|
-
logger.success('sass', `Built ${
|
|
66
|
+
logger.success('sass', `Built ${filesToBuild.length} file(s)`)
|
|
38
67
|
}
|
|
39
68
|
|
|
40
69
|
/**
|
|
41
70
|
* 個別Sassファイルのコンパイル
|
|
42
71
|
*/
|
|
43
72
|
async function compileSassFile(filePath, context, isDebugMode) {
|
|
44
|
-
const { paths, config, isProduction } = context
|
|
73
|
+
const { paths, config, isProduction, sassGraph } = context
|
|
45
74
|
|
|
46
75
|
try {
|
|
47
76
|
// Sassコンパイル
|
|
@@ -55,6 +84,19 @@ async function compileSassFile(filePath, context, isDebugMode) {
|
|
|
55
84
|
sourceMapIncludeSources: isDebugMode
|
|
56
85
|
})
|
|
57
86
|
|
|
87
|
+
// 依存グラフを更新(loadedUrls からパーシャルの依存関係を構築)
|
|
88
|
+
sassGraph.clearDependencies(filePath)
|
|
89
|
+
if (result.loadedUrls) {
|
|
90
|
+
for (const url of result.loadedUrls) {
|
|
91
|
+
if (url.protocol === 'file:') {
|
|
92
|
+
const depPath = fileURLToPath(url)
|
|
93
|
+
if (depPath !== filePath) {
|
|
94
|
+
sassGraph.addDependency(filePath, depPath)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
58
100
|
let css = result.css
|
|
59
101
|
|
|
60
102
|
// PostCSS処理
|
package/tasks/script.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { glob } from 'glob'
|
|
2
|
-
import { resolve, relative, dirname } from 'node:path'
|
|
2
|
+
import { resolve, relative, dirname, basename } from 'node:path'
|
|
3
3
|
import * as esbuild from 'esbuild'
|
|
4
4
|
import { logger } from '../utils/logger.mjs'
|
|
5
5
|
import { ensureDir } from '../utils/file.mjs'
|
|
@@ -8,29 +8,53 @@ import { ensureDir } from '../utils/file.mjs'
|
|
|
8
8
|
* esbuild(TypeScript/JavaScript)ビルドタスク
|
|
9
9
|
*/
|
|
10
10
|
export async function scriptTask(context, options = {}) {
|
|
11
|
-
const { paths, isProduction, config } = context
|
|
11
|
+
const { paths, isProduction, isDevelopment, config, scriptGraph } = context
|
|
12
12
|
|
|
13
13
|
// 1. ビルド対象ファイルの取得
|
|
14
|
-
const
|
|
14
|
+
const allEntryFiles = await glob('**/[^_]*.{ts,js}', {
|
|
15
15
|
cwd: paths.src,
|
|
16
16
|
absolute: true,
|
|
17
17
|
ignore: ['**/*.d.ts', '**/node_modules/**']
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
if (
|
|
20
|
+
if (allEntryFiles.length === 0) {
|
|
21
21
|
logger.skip('script', 'No files to build')
|
|
22
22
|
return
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// 2. dev モードでのインクリメンタルビルド
|
|
26
|
+
let filesToBuild = allEntryFiles
|
|
27
|
+
|
|
28
|
+
if (isDevelopment && options.files?.length > 0) {
|
|
29
|
+
const changedFile = options.files[0]
|
|
30
|
+
const isPartial = basename(changedFile).startsWith('_')
|
|
31
|
+
|
|
32
|
+
if (isPartial) {
|
|
33
|
+
// パーシャル変更 → 依存グラフから影響を受けるエントリファイルを特定
|
|
34
|
+
const affected = scriptGraph.getAffectedParents(changedFile)
|
|
35
|
+
filesToBuild = affected.filter(f => allEntryFiles.includes(f))
|
|
36
|
+
|
|
37
|
+
if (filesToBuild.length === 0) {
|
|
38
|
+
// グラフにまだ情報がない場合はフルビルド
|
|
39
|
+
filesToBuild = allEntryFiles
|
|
40
|
+
} else {
|
|
41
|
+
logger.info('script', `Partial changed, rebuilding ${filesToBuild.length} affected file(s)`)
|
|
42
|
+
}
|
|
43
|
+
} else if (allEntryFiles.includes(changedFile)) {
|
|
44
|
+
// 非パーシャルのエントリファイル → そのファイルだけリビルド
|
|
45
|
+
filesToBuild = [changedFile]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
logger.info('script', `Building ${filesToBuild.length} file(s)`)
|
|
26
50
|
|
|
27
51
|
// debugモードはdevモード時のみ有効
|
|
28
52
|
const isDebugMode = !isProduction && config.debug
|
|
29
53
|
|
|
30
54
|
try {
|
|
31
|
-
//
|
|
55
|
+
// 3. esbuild設定
|
|
32
56
|
const esbuildConfig = {
|
|
33
|
-
entryPoints:
|
|
57
|
+
entryPoints: filesToBuild,
|
|
34
58
|
outdir: paths.dist,
|
|
35
59
|
outbase: paths.src,
|
|
36
60
|
bundle: true,
|
|
@@ -41,7 +65,7 @@ export async function scriptTask(context, options = {}) {
|
|
|
41
65
|
write: true,
|
|
42
66
|
sourcemap: isDebugMode,
|
|
43
67
|
minify: false,
|
|
44
|
-
metafile:
|
|
68
|
+
metafile: true,
|
|
45
69
|
logLevel: 'error',
|
|
46
70
|
keepNames: false,
|
|
47
71
|
external: [],
|
|
@@ -57,7 +81,7 @@ export async function scriptTask(context, options = {}) {
|
|
|
57
81
|
esbuildConfig.drop = ['console', 'debugger']
|
|
58
82
|
}
|
|
59
83
|
|
|
60
|
-
//
|
|
84
|
+
// 4. ビルド実行
|
|
61
85
|
await ensureDir(paths.dist)
|
|
62
86
|
const result = await esbuild.build(esbuildConfig)
|
|
63
87
|
|
|
@@ -65,7 +89,24 @@ export async function scriptTask(context, options = {}) {
|
|
|
65
89
|
throw new Error(`esbuild errors: ${result.errors.length}`)
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
|
|
92
|
+
// 5. 依存グラフを更新(metafile から依存関係を構築)
|
|
93
|
+
if (result.metafile) {
|
|
94
|
+
for (const [output, meta] of Object.entries(result.metafile.outputs)) {
|
|
95
|
+
if (!meta.entryPoint) continue
|
|
96
|
+
const entryPath = resolve(paths.root, meta.entryPoint)
|
|
97
|
+
|
|
98
|
+
scriptGraph.clearDependencies(entryPath)
|
|
99
|
+
|
|
100
|
+
for (const inputPath of Object.keys(meta.inputs)) {
|
|
101
|
+
const absInputPath = resolve(paths.root, inputPath)
|
|
102
|
+
if (absInputPath !== entryPath) {
|
|
103
|
+
scriptGraph.addDependency(entryPath, absInputPath)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
logger.success('script', `Built ${filesToBuild.length} file(s)`)
|
|
69
110
|
} catch (error) {
|
|
70
111
|
logger.error('script', error.message)
|
|
71
112
|
throw error
|
package/transform/html.mjs
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import pkg from 'js-beautify'
|
|
2
|
+
const { html: beautifyHtml } = pkg
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
+
export function formatHtml(html, options = {}) {
|
|
4
5
|
const cleaned = html.replace(/[\u200B-\u200D\uFEFF]/g, '')
|
|
5
6
|
|
|
6
|
-
return
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
return beautifyHtml(cleaned, {
|
|
8
|
+
indent_size: options.tabWidth || 2,
|
|
9
|
+
indent_with_tabs: options.useTabs || false,
|
|
10
|
+
max_preserve_newlines: 1,
|
|
11
|
+
preserve_newlines: false,
|
|
12
|
+
end_with_newline: true,
|
|
13
|
+
extra_liners: [],
|
|
14
|
+
wrap_line_length: 0,
|
|
15
|
+
inline: [],
|
|
16
|
+
content_unformatted: ['script', 'style', 'pre']
|
|
13
17
|
})
|
|
14
18
|
}
|