pugkit 1.0.0-beta.3 → 1.0.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 +22 -8
- package/core/server.mjs +2 -0
- package/core/watcher.mjs +4 -3
- package/package.json +1 -1
- package/tasks/pug.mjs +3 -2
- package/transform/image-size.mjs +81 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## About
|
|
4
4
|
|
|
5
5
|
pugkitは静的サイト制作に特化したビルドツールです。
|
|
6
|
-
納品向きの綺麗なHTML
|
|
6
|
+
納品向きの綺麗なHTMLと、ファイル構成に制約のないアセットファイルを出力可能です。
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
@@ -27,7 +27,7 @@ pugkit watch
|
|
|
27
27
|
|
|
28
28
|
### Production Build
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
製品用ファイルを生成します。
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
pugkit build
|
|
@@ -92,7 +92,7 @@ export default {
|
|
|
92
92
|
|
|
93
93
|
### Pug Templates
|
|
94
94
|
|
|
95
|
-
Pugテンプレート内では、`Builder
|
|
95
|
+
Pugテンプレート内では、`Builder` オブジェクトと `imageInfo()` 関数が使用できます。
|
|
96
96
|
|
|
97
97
|
#### Builder Object
|
|
98
98
|
|
|
@@ -113,15 +113,29 @@ meta(property='og:url', content=Builder.url.href)
|
|
|
113
113
|
| `Builder.url.pathname` | 現在のページのパス | `/about/` |
|
|
114
114
|
| `Builder.url.href` | 完全なURL | `https://example.com/subdirectory/about/` |
|
|
115
115
|
|
|
116
|
-
####
|
|
116
|
+
#### imageInfo()
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
`src/` 配下の画像のメタデータを取得します。`imageOptimization` の設定に応じて `src` が最適化後のパスに変換され、`@2x`/`_sp` 画像が存在する場合も自動的に解決されます。
|
|
119
119
|
|
|
120
120
|
```pug
|
|
121
|
-
- const
|
|
122
|
-
img(src=
|
|
121
|
+
- const info = imageInfo('/assets/img/hero.jpg')
|
|
122
|
+
img(src=info.src width=info.width height=info.height alt='')
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
**返り値**
|
|
126
|
+
|
|
127
|
+
| Property | Type | Description |
|
|
128
|
+
| -------- | -------------------------------- | ----------------------------------------------------- |
|
|
129
|
+
| `src` | `string` | 最適化設定に応じたパス(webpモード時は `.webp` パス) |
|
|
130
|
+
| `width` | `number \| undefined` | 画像の幅(px) |
|
|
131
|
+
| `height` | `number \| undefined` | 画像の高さ(px) |
|
|
132
|
+
| `format` | `string \| undefined` | 画像フォーマット(`'jpg'` / `'png'` / `'svg'` など) |
|
|
133
|
+
| `isSvg` | `boolean` | SVG かどうか |
|
|
134
|
+
| `retina` | `{ src: string } \| null` | `@2x` 画像が存在する場合に自動検出 |
|
|
135
|
+
| `sp` | `{ src, width, height } \| null` | `_sp` 画像が存在する場合に自動検出 |
|
|
136
|
+
|
|
137
|
+
> **Note:** `imageInfo()`は`src/`配下の画像のみ対応しています。`public/`配下の画像は非対応です。
|
|
138
|
+
|
|
125
139
|
### Image Optimization
|
|
126
140
|
|
|
127
141
|
ビルド時に自動的に画像を最適化します。
|
|
@@ -132,7 +146,7 @@ img(src='/assets/img/photo.jpg', width=size.width, height=size.height, alt='')
|
|
|
132
146
|
|
|
133
147
|
### File Naming Rules
|
|
134
148
|
|
|
135
|
-
- `_
|
|
149
|
+
- `_`(アンダースコア)で始まるファイルはテンプレートとして扱われます
|
|
136
150
|
- `_`で始まるディレクトリ内のファイルもビルド対象外です
|
|
137
151
|
- 通常のファイル名のみがビルドされます
|
|
138
152
|
|
package/core/server.mjs
CHANGED
|
@@ -17,8 +17,10 @@ const liveReloadScript = `<script>
|
|
|
17
17
|
location.reload();
|
|
18
18
|
});
|
|
19
19
|
es.addEventListener('css-update', function() {
|
|
20
|
+
// 同一オリジンの <link rel="stylesheet"> のみ再読み込み(外部フォント等は除外)
|
|
20
21
|
document.querySelectorAll('link[rel="stylesheet"]').forEach(function(link) {
|
|
21
22
|
var url = new URL(link.href);
|
|
23
|
+
if (url.origin !== location.origin) return;
|
|
22
24
|
url.searchParams.set('t', Date.now());
|
|
23
25
|
link.href = url.toString();
|
|
24
26
|
});
|
package/core/watcher.mjs
CHANGED
|
@@ -128,8 +128,9 @@ class FileWatcher {
|
|
|
128
128
|
await this.context.taskRegistry.sass(this.context)
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
// CSS
|
|
132
|
-
|
|
131
|
+
// CSSはインジェクション
|
|
132
|
+
const cssUrlPath = '/' + relPath.replace(/\\/g, '/').replace(/\.scss$/, '.css')
|
|
133
|
+
this.injectCSS(cssUrlPath)
|
|
133
134
|
} catch (error) {
|
|
134
135
|
logger.error('watch', `Sass build failed: ${error.message}`)
|
|
135
136
|
}
|
|
@@ -294,7 +295,7 @@ class FileWatcher {
|
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
/**
|
|
297
|
-
* CSS
|
|
298
|
+
* CSSインジェクション
|
|
298
299
|
*/
|
|
299
300
|
injectCSS() {
|
|
300
301
|
if (this.context.server) {
|
package/package.json
CHANGED
package/tasks/pug.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { basename } from 'node:path'
|
|
|
3
3
|
import { compilePugFile } from '../transform/pug.mjs'
|
|
4
4
|
import { formatHtml } from '../transform/html.mjs'
|
|
5
5
|
import { createBuilderVars } from '../transform/builder-vars.mjs'
|
|
6
|
-
import { createImageSizeHelper } from '../transform/image-size.mjs'
|
|
6
|
+
import { createImageSizeHelper, createImageInfoHelper } from '../transform/image-size.mjs'
|
|
7
7
|
import { generatePage } from '../generate/page.mjs'
|
|
8
8
|
import { logger } from '../utils/logger.mjs'
|
|
9
9
|
|
|
@@ -68,8 +68,9 @@ async function processFile(filePath, context) {
|
|
|
68
68
|
|
|
69
69
|
const builderVars = createBuilderVars(filePath, paths, config)
|
|
70
70
|
const imageSize = createImageSizeHelper(filePath, paths, logger)
|
|
71
|
+
const imageInfo = createImageInfoHelper(filePath, paths, logger, config)
|
|
71
72
|
|
|
72
|
-
const html = template({ Builder: builderVars, imageSize })
|
|
73
|
+
const html = template({ Builder: builderVars, imageSize, imageInfo })
|
|
73
74
|
const formatted = await formatHtml(html)
|
|
74
75
|
await generatePage(filePath, formatted, paths)
|
|
75
76
|
} catch (error) {
|
package/transform/image-size.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs'
|
|
2
|
-
import { resolve, dirname, basename } from 'node:path'
|
|
2
|
+
import { resolve, dirname, basename, extname } from 'node:path'
|
|
3
3
|
import sizeOf from 'image-size'
|
|
4
4
|
|
|
5
5
|
export function createImageSizeHelper(filePath, paths, logger) {
|
|
@@ -36,3 +36,83 @@ export function createImageSizeHelper(filePath, paths, logger) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
export function createImageInfoHelper(filePath, paths, logger, config) {
|
|
41
|
+
const useWebp = config?.build?.imageOptimization === 'webp'
|
|
42
|
+
return src => {
|
|
43
|
+
const resolveImagePath = (imageSrc, baseDir) => {
|
|
44
|
+
if (imageSrc.startsWith('/')) {
|
|
45
|
+
return resolve(paths.src, imageSrc.slice(1))
|
|
46
|
+
}
|
|
47
|
+
return resolve(baseDir, imageSrc)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const findImageFile = resolvedPath => {
|
|
51
|
+
if (existsSync(resolvedPath)) return resolvedPath
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fallback = {
|
|
56
|
+
src,
|
|
57
|
+
width: undefined,
|
|
58
|
+
height: undefined,
|
|
59
|
+
format: undefined,
|
|
60
|
+
isSvg: false,
|
|
61
|
+
retina: null,
|
|
62
|
+
sp: null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const pageDir = dirname(filePath)
|
|
67
|
+
const resolvedPath = resolveImagePath(src, pageDir)
|
|
68
|
+
const foundPath = findImageFile(resolvedPath)
|
|
69
|
+
|
|
70
|
+
if (!foundPath) {
|
|
71
|
+
logger?.warn('pug', `Image not found: ${basename(resolvedPath)}`)
|
|
72
|
+
return fallback
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const buffer = readFileSync(foundPath)
|
|
76
|
+
const { width, height, type: format } = sizeOf(buffer)
|
|
77
|
+
|
|
78
|
+
const ext = extname(src)
|
|
79
|
+
const isSvg = ext.toLowerCase() === '.svg'
|
|
80
|
+
const base = src.slice(0, -ext.length)
|
|
81
|
+
// webp モード時は src 自体を .webp パスに変換(SVG は除外)
|
|
82
|
+
const resolvedSrc = useWebp && !isSvg ? `${base}.webp` : src
|
|
83
|
+
|
|
84
|
+
// 2x retina 画像の自動検出
|
|
85
|
+
let retina = null
|
|
86
|
+
if (!isSvg) {
|
|
87
|
+
const retinaSrc = `${base}@2x${ext}`
|
|
88
|
+
const retinaResolvedPath = resolveImagePath(retinaSrc, pageDir)
|
|
89
|
+
const retinaFoundPath = findImageFile(retinaResolvedPath)
|
|
90
|
+
if (retinaFoundPath) {
|
|
91
|
+
retina = { src: useWebp ? `${base}@2x.webp` : retinaSrc }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// SP 画像の自動検出
|
|
96
|
+
let sp = null
|
|
97
|
+
if (!isSvg) {
|
|
98
|
+
const spSrc = `${base}_sp${ext}`
|
|
99
|
+
const spResolvedPath = resolveImagePath(spSrc, pageDir)
|
|
100
|
+
const spFoundPath = findImageFile(spResolvedPath)
|
|
101
|
+
if (spFoundPath) {
|
|
102
|
+
const spBuffer = readFileSync(spFoundPath)
|
|
103
|
+
const { width: spWidth, height: spHeight } = sizeOf(spBuffer)
|
|
104
|
+
sp = {
|
|
105
|
+
src: useWebp ? `${base}_sp.webp` : spSrc,
|
|
106
|
+
width: spWidth,
|
|
107
|
+
height: spHeight
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { src: resolvedSrc, width, height, format, isSvg, retina, sp }
|
|
113
|
+
} catch {
|
|
114
|
+
logger?.warn('pug', `Failed to get image info: ${basename(src)}`)
|
|
115
|
+
return fallback
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|