dsh-design-studio 0.4.1 → 0.6.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/lib/serve.js CHANGED
@@ -25,7 +25,7 @@ const MAX_HTML = 8 * 1024 * 1024 // 要注入脚本的 HTML 最多读这么大
25
25
  const UP_HOSTS = ['127.0.0.1', '::1'] // 开发服务器可能只听其中一个
26
26
  const DEBOUNCE_MS = 120
27
27
 
28
- const MIME = {
28
+ export const MIME = {
29
29
  '.html': 'text/html; charset=utf-8',
30
30
  '.htm': 'text/html; charset=utf-8',
31
31
  '.css': 'text/css; charset=utf-8',
@@ -56,8 +56,9 @@ const MIME = {
56
56
  }
57
57
 
58
58
  // 改动通知里不算的:版本库、依赖、系统垃圾
59
- const EXPORTS = /^(exports([\\/][^\\/]+\.png)?|refs([\\/][^\\/]+\.(png|jpe?g|gif|webp))?)$/i // 导出的图片、参考图(和第一次建的 exports / refs 目录):不算设计改了
59
+ const EXPORTS = /^(exports([\\/][^\\/]+\.(png|pdf|pptx))?|refs([\\/][^\\/]+\.(png|jpe?g|gif|webp))?)$/i // 导出的图片 / PDF / PPT、参考图(和第一次建的 exports / refs 目录):不算设计改了
60
60
  const IGNORED = /(^|[\\/])(\.git|node_modules|\.DS_Store|\.next|\.cache|\.turbo)([\\/]|$)/
61
+ const HANDOFF = /^handoff([\\/]|$)/i // 转成代码的交接包(说明和截图):不算设计改了,也别在截图时把页面刷掉
61
62
 
62
63
  /** 只认本机名 + 自己的端口(防 DNS 重绑定:外站把域名解析到 127.0.0.1 也进不来) */
63
64
  export function hostOk(host, port) {
@@ -65,11 +66,26 @@ export function hostOk(host, port) {
65
66
  return h === `127.0.0.1:${port}` || h === `localhost:${port}` || h === `[::1]:${port}`
66
67
  }
67
68
 
68
- /** 在 </body> 前(没有就在末尾)插入选取脚本 */
69
+ /**
70
+ * 最先跑的一小段:记下页面脚本报错、没载出来的样式表 / 脚本(设计体检报给模型)。
71
+ * picker 在 </body> 前,等它装上,前面脚本的报错早过去了
72
+ */
73
+ export const ERR_CATCHER = '<script>(()=>{if(window.__dshErrs)return;const L=window.__dshErrs=[];const add=(x)=>{if(L.length<30)L.push(x)};' +
74
+ 'addEventListener("error",(e)=>{const t=e.target;if(t&&t!==window&&t.tagName){add({res:1,tag:t.tagName.toLowerCase(),src:String(t.currentSrc||t.src||t.href||"").slice(0,300)})}' +
75
+ 'else add({msg:String(e.message||"").slice(0,300),src:String(e.filename||"").slice(0,300),line:e.lineno||0})},true);' +
76
+ 'addEventListener("unhandledrejection",(e)=>{const r=e.reason;add({msg:String((r&&r.message)||r).slice(0,300),src:"",line:0})})})()</script>'
77
+
78
+ /** 放进 <head> 最前面;没写 <head> 就放在 <html> / <!doctype> 后面(放到 doctype 前面页面会进怪异模式) */
79
+ export function headInsert(html, tag) {
80
+ const m = /<head\b[^>]*>/i.exec(html) || /<html\b[^>]*>/i.exec(html) || /^\s*<!doctype\b[^>]*>/i.exec(html)
81
+ return m ? html.slice(0, m.index + m[0].length) + tag + html.slice(m.index + m[0].length) : tag + html
82
+ }
83
+
84
+ /** 在 </body> 前(没有就在末尾)插入选取脚本,<head> 最前面放报错记录 */
69
85
  export function injectPicker(html) {
70
86
  const tag = `<script src="${INTERNAL}picker.js"></script>`
71
87
  const i = html.toLowerCase().lastIndexOf('</body>')
72
- return i >= 0 ? html.slice(0, i) + tag + html.slice(i) : html + tag
88
+ return headInsert(i >= 0 ? html.slice(0, i) + tag + html.slice(i) : html + tag, ERR_CATCHER)
73
89
  }
74
90
 
75
91
  /** 真实路径在不在根目录(也取真实路径)里面 */
@@ -142,7 +158,7 @@ export function trustedOrigins(hosts) {
142
158
 
143
159
  /**
144
160
  * @param opts.log 出错时写日志
145
- * @param opts.onChange (key, file) 目录里有文件改了(file 目标)
161
+ * @param opts.onChange (key, file, files) 目录里有文件改了(file 目标;files:这一批的相对路径)
146
162
  * @param opts.history 设计版本(lib/history.js):/__dsh_design__/v/<版本>/<文件> 给出那一版的文件
147
163
  * @param opts.parents () → 还有哪些 DSH 页面的源能嵌预览(DSH 网页端的地址)
148
164
  */
@@ -256,15 +272,16 @@ export function createHub({ log = () => {}, onChange = () => {}, history = null,
256
272
  try {
257
273
  entry.watcher = fs.watch(entry.root, { recursive: true }, (_type, name) => {
258
274
  const rel = String(name || '')
259
- if (IGNORED.test(rel) || /(^|[\\/])\./.test(rel) || EXPORTS.test(rel)) return
275
+ if (IGNORED.test(rel) || /(^|[\\/])\./.test(rel) || EXPORTS.test(rel) || HANDOFF.test(rel)) return
260
276
  last = rel
261
277
  batch.add(rel.split(path.sep).join('/'))
262
278
  clearTimeout(timer)
263
279
  timer = setTimeout(() => {
264
- const files = [...batch].slice(0, 50)
280
+ // 一批改了太多文件就不列了(空的 = 不知道改了啥,页面整屏刷新),列一半会漏掉这一屏用到的
281
+ const files = batch.size > 50 ? [] : [...batch]
265
282
  batch.clear()
266
283
  for (const c of entry.clients) c.write(`event: change\ndata: ${JSON.stringify({ file: last, files })}\n\n`)
267
- try { onChange(entry.key, path.join(entry.root, last)) } catch (e) { log(e) }
284
+ try { onChange(entry.key, path.join(entry.root, last), files) } catch (e) { log(e) }
268
285
  }, DEBOUNCE_MS)
269
286
  })
270
287
  entry.watcher.on('error', (e) => log(e))