pugkit 1.0.0-beta.2 → 1.0.0-beta.3

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
@@ -164,7 +164,8 @@ project-root/
164
164
  - [Sharp](https://sharp.pixelplumbing.com/) - 画像最適化
165
165
  - [SVGO](https://svgo.dev/) - SVG最適化
166
166
  - [Chokidar](https://github.com/paulmillr/chokidar) - ファイル監視
167
- - [BrowserSync](https://browsersync.io/) - 開発サーバー
167
+ - [sirv](https://github.com/lukeed/sirv) - 開発サーバー
168
+ - SSE(Server-Sent Events) - ライブリロード
168
169
  - [Prettier](https://prettier.io/) - HTML整形
169
170
 
170
171
  ## License
package/core/server.mjs CHANGED
@@ -1,77 +1,142 @@
1
- import browser from 'browser-sync'
2
- import { resolve } from 'node:path'
3
- import { existsSync } from 'node:fs'
1
+ import http from 'node:http'
2
+ import path from 'node:path'
3
+ import { existsSync, readFileSync } from 'node:fs'
4
4
  import { mkdir } from 'node:fs/promises'
5
+ import sirv from 'sirv'
5
6
  import { logger } from '../utils/logger.mjs'
6
7
 
8
+ const SSE_PATH = '/__pugkit_sse'
9
+
10
+ /**
11
+ * HTMLに挿入するライブリロードクライアントスクリプト。
12
+ */
13
+ const liveReloadScript = `<script>
14
+ (function() {
15
+ var es = new EventSource('${SSE_PATH}');
16
+ es.addEventListener('reload', function() {
17
+ location.reload();
18
+ });
19
+ es.addEventListener('css-update', function() {
20
+ document.querySelectorAll('link[rel="stylesheet"]').forEach(function(link) {
21
+ var url = new URL(link.href);
22
+ url.searchParams.set('t', Date.now());
23
+ link.href = url.toString();
24
+ });
25
+ });
26
+ es.onerror = function() {
27
+ es.close();
28
+ setTimeout(function() { location.reload(); }, 1000);
29
+ };
30
+ })();
31
+ </script>`
32
+
7
33
  /**
8
- * 開発サーバータスク
34
+ * 開発サーバータスク(SSE + sirv)
9
35
  */
10
36
  export async function serverTask(context, options = {}) {
11
37
  const { paths, config } = context
12
- const distRoot = paths.dist
13
38
 
14
- // distディレクトリの確認
15
- if (!existsSync(distRoot)) {
16
- await mkdir(distRoot, { recursive: true })
39
+ if (!existsSync(paths.dist)) {
40
+ await mkdir(paths.dist, { recursive: true })
17
41
  }
18
42
 
19
- // BrowserSyncインスタンス作成
20
- const bs = browser.create()
21
- context.server = bs
22
-
43
+ const port = config.server?.port ?? 3000
44
+ const host = config.server?.host ?? 'localhost'
23
45
  const subdir = config.subdir ? '/' + config.subdir.replace(/^\/|\/$/g, '') : ''
24
- const startPath = (config.server.startPath || '/').replace(/^\//, '')
46
+ const startPath = (config.server?.startPath || '/').replace(/^\//, '')
25
47
  const fullStartPath = subdir ? `${subdir}/${startPath}` : `/${startPath}`
26
48
 
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
- }
49
+ const clients = new Set()
50
+
51
+ const staticServe = sirv(paths.dist, {
52
+ dev: true,
53
+ extensions: ['html'],
54
+ setHeaders(res, filePath) {
55
+ if (filePath.endsWith('.css') || filePath.endsWith('.js')) {
56
+ res.setHeader('Cache-Control', 'no-cache')
73
57
  }
74
- )
58
+ }
59
+ })
60
+
61
+ const httpServer = http.createServer((req, res) => {
62
+ const urlPath = req.url?.split('?')[0] ?? '/'
63
+
64
+ // ── SSE エンドポイント ──────────────────────────────
65
+ if (urlPath === SSE_PATH) {
66
+ res.writeHead(200, {
67
+ 'Content-Type': 'text/event-stream',
68
+ 'Cache-Control': 'no-cache',
69
+ Connection: 'keep-alive',
70
+ 'X-Accel-Buffering': 'no'
71
+ })
72
+ res.write('retry: 1000\n\n')
73
+ clients.add(res)
74
+ req.on('close', () => clients.delete(res))
75
+ return
76
+ }
77
+
78
+ // ── HTML へのライブリロードスクリプト注入 ───────────
79
+ const decoded = decodeURIComponent(urlPath)
80
+ const candidates = [
81
+ path.join(paths.dist, decoded === '/' ? 'index.html' : decoded.replace(/\/$/, '') + '/index.html'),
82
+ path.join(paths.dist, decoded === '/' ? 'index.html' : decoded + '.html'),
83
+ path.join(paths.dist, decoded)
84
+ ]
85
+ const htmlFile = candidates.find(p => p.endsWith('.html') && existsSync(p))
86
+
87
+ if (htmlFile) {
88
+ try {
89
+ let html = readFileSync(htmlFile, 'utf-8')
90
+ html = html.includes('</body>')
91
+ ? html.replace('</body>', liveReloadScript + '</body>')
92
+ : html + liveReloadScript
93
+ const buf = Buffer.from(html, 'utf-8')
94
+ res.writeHead(200, {
95
+ 'Content-Type': 'text/html; charset=utf-8',
96
+ 'Content-Length': buf.length,
97
+ 'Cache-Control': 'no-cache'
98
+ })
99
+ res.end(buf)
100
+ return
101
+ } catch {
102
+ // 読み込み失敗時は sirv にフォールスルー
103
+ }
104
+ }
105
+
106
+ // ── sirv で静的ファイルを配信 ───────────────────────
107
+ staticServe(req, res, () => {
108
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' })
109
+ res.end('404 Not Found')
110
+ })
111
+ })
112
+
113
+ function broadcast(event, data = '') {
114
+ const msg = `event: ${event}\ndata: ${data}\n\n`
115
+ for (const res of clients) {
116
+ res.write(msg)
117
+ }
118
+ }
119
+
120
+ context.server = {
121
+ reload() {
122
+ broadcast('reload')
123
+ },
124
+ reloadCSS() {
125
+ broadcast('css-update')
126
+ },
127
+ close() {
128
+ for (const res of clients) res.end()
129
+ clients.clear()
130
+ httpServer.close()
131
+ }
132
+ }
133
+
134
+ return new Promise((resolve, reject) => {
135
+ httpServer.listen(port, host, () => {
136
+ logger.success('server', `Running at http://${host}:${port}${fullStartPath}`)
137
+ resolve()
138
+ })
139
+ httpServer.on('error', reject)
75
140
  })
76
141
  }
77
142
 
package/core/watcher.mjs CHANGED
@@ -299,7 +299,7 @@ class FileWatcher {
299
299
  injectCSS() {
300
300
  if (this.context.server) {
301
301
  setTimeout(() => {
302
- this.context.server.reload('*.css')
302
+ this.context.server.reloadCSS()
303
303
  }, 100)
304
304
  }
305
305
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "pugkit",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.3",
4
4
  "description": "A build tool for Pug-based projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "mfxgu2i <mfxgu2i@gmail.com>",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/mfxgu2i/pugkit.git",
10
+ "url": "git+https://github.com/mfxgu2i/pugkit.git",
11
11
  "directory": "packages/pugkit"
12
12
  },
13
13
  "bugs": "https://github.com/mfxgu2i/pugkit/issues",
@@ -19,7 +19,7 @@
19
19
  "access": "public"
20
20
  },
21
21
  "bin": {
22
- "pugkit": "./cli/index.mjs"
22
+ "pugkit": "cli/index.mjs"
23
23
  },
24
24
  "keywords": [
25
25
  "pug",
@@ -43,12 +43,11 @@
43
43
  ],
44
44
  "dependencies": {
45
45
  "autoprefixer": "^10.4.21",
46
- "browser-sync": "^3.0.4",
47
46
  "cac": "^6.7.14",
48
47
  "chokidar": "^4.0.3",
49
48
  "cssnano": "^7.1.1",
50
49
  "esbuild": "^0.25.5",
51
- "glob": "^10.3.10",
50
+ "glob": "^13.0.6",
52
51
  "image-size": "^2.0.2",
53
52
  "picocolors": "^1.1.1",
54
53
  "postcss": "^8.4.49",
@@ -56,6 +55,7 @@
56
55
  "pug": "^3.0.3",
57
56
  "sass": "^1.89.2",
58
57
  "sharp": "^0.34.2",
58
+ "sirv": "^3.0.2",
59
59
  "svgo": "^4.0.0"
60
60
  }
61
61
  }