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 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`オブジェクトと`imageSize()`関数が使用できます。
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
- #### imageSize() Function
116
+ #### imageInfo()
117
117
 
118
- 画像ファイルのサイズを自動取得し、CLSを防ぎます。
118
+ `src/` 配下の画像のメタデータを取得します。`imageOptimization` の設定に応じて `src` が最適化後のパスに変換され、`@2x`/`_sp` 画像が存在する場合も自動的に解決されます。
119
119
 
120
120
  ```pug
121
- - const size = imageSize('/assets/img/photo.jpg')
122
- img(src='/assets/img/photo.jpg', width=size.width, height=size.height, alt='')
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
- this.injectCSS()
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pugkit",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0",
4
4
  "description": "A build tool for Pug-based projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
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) {
@@ -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
+ }