w-flowchart 1.0.3 → 1.0.5

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/src/p2/gen.mjs CHANGED
@@ -3,24 +3,11 @@
3
3
  // 版面通用化: dir 取自 data.dir; 子群組方向自動取補方向; classDef/顏色取自 palette;
4
4
  // 無逐圖魔術數字, ELK 全自動排版。
5
5
  import { chromium } from 'playwright'
6
- import fs from 'fs'
7
6
  import { PALETTE, isGroupCls } from '../common/palette.mjs'
8
- import { pkgFile } from '../common/pkg.mjs'
9
-
10
- // mermaid@11 與 layout-elk 為 chunk 式 ESM(無 UMD 可內聯), 改以虛擬 origin 提供本機 node_modules 檔案
11
- // (page.route 攔截 https://pkg.local/**, fs 直讀回應; 相對 chunk import 亦循同 origin 解析; 斷網環境可用)
12
- const PKG_ORIGIN = 'https://pkg.local/'
13
- async function routeLocalPkgs(page) {
14
- await page.route(PKG_ORIGIN + '**', (route) => {
15
- const rel = decodeURIComponent(route.request().url().slice(PKG_ORIGIN.length).split('?')[0])
16
- try {
17
- route.fulfill({ body: fs.readFileSync(pkgFile(rel)), contentType: rel.endsWith('.css') ? 'text/css' : 'text/javascript' })
18
- }
19
- catch (err) {
20
- route.fulfill({ status: 404, body: String(err && err.message || err) })
21
- }
22
- })
23
- }
7
+ import { cdnUrl } from '../common/cdn.mjs'
8
+
9
+ // mermaid@11 與 layout-elk 為 chunk 式 ESM, jsDelivr CDN import() 動態載入(免安裝; 版本鎖定見 common/cdn.mjs)
10
+ // (相對 chunk import jsDelivr 循同 origin 自動解析)
24
11
 
25
12
  const FM = `---\nconfig:\n layout: elk\n---\n`
26
13
 
@@ -151,8 +138,8 @@ const setup = `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
151
138
  <div id="box" style="display:inline-block;padding:24px"></div>
152
139
  <script type="module">
153
140
  try {
154
- const mermaid = (await import('https://pkg.local/mermaid11/dist/mermaid.esm.min.mjs')).default
155
- const elk = (await import('https://pkg.local/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs')).default
141
+ const mermaid = (await import('${cdnUrl('mermaid11-esm')}')).default
142
+ const elk = (await import('${cdnUrl('layout-elk-esm')}')).default
156
143
  mermaid.registerLayoutLoaders(elk)
157
144
  mermaid.initialize({ startOnLoad:false, theme:'base', flowchart:{ useMaxWidth:false }, themeVariables:{ fontFamily:"'微軟正黑體','Microsoft JhengHei',sans-serif", fontSize:'17px', primaryColor:'#eef4fb', primaryBorderColor:'#3f6fb0', lineColor:'#44505a', primaryTextColor:'#1c2b36' } })
158
145
  window.renderFig = async (code, idx) => {
@@ -208,7 +195,6 @@ async function renderFigPage(data) {
208
195
  const browser = await chromium.launch()
209
196
  try {
210
197
  const page = await browser.newPage({ deviceScaleFactor: 2 })
211
- await routeLocalPkgs(page)
212
198
  await page.setContent(setup, { waitUntil: 'load' })
213
199
  await page.waitForFunction(() => window.__ready || window.__setupErr, { timeout: 90000 }).catch(() => {})
214
200
  const setupErr = await page.evaluate(() => window.__setupErr)
package/src/p3/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 技術核心
4
4
 
5
- - **繪圖庫**:nomnoml(`nomnoml/dist/nomnoml.js`),與其相依 `graphre`(`graphre/dist/graphre.js`)由本機 `node_modules` 讀出後內聯注入頁面 `<script>`(`common/pkg.mjs` `pkgScript()`),非透過 CDN,斷網環境亦可渲染;nomnoml UMD 模組依賴全域 `graphre`,注入順序須 graphre 先於 nomnoml。npm 版 nomnoml 之佈局由套件自宣告相依的 graphre 驅動,與舊版 CDN bundle(`nomnoml.web.js`,內部改捆 `@dagrejs/dagre`)佈局引擎不同,兩者排版結果可能略有差異。
5
+ - **繪圖庫**:nomnoml(`nomnoml/dist/nomnoml.js`),與其相依 `graphre`(`graphre/dist/graphre.js`)改由 jsDelivr CDN 載入(免安裝),於頁面內以外部 `<script src>` 標籤(`cdnScript()`,版本鎖定於 `src/common/cdn.mjs`)依序載入;nomnoml UMD 模組依賴全域 `graphre`,載入順序須 graphre 先於 nomnoml。npm 版 nomnoml 之佈局由套件自宣告相依的 graphre 驅動,與舊版 CDN bundle(`nomnoml.web.js`,內部改捆 `@dagrejs/dagre`)佈局引擎不同,兩者排版結果可能略有差異。
6
6
  - **渲染方式**:於頁面注入 `window.renderAndDetect(src)` 函式,呼叫 `window.nomnoml.renderSvg(src)` 將 DSL 字串渲染為 SVG,並將結果寫入 `#box` div 的 innerHTML。
7
7
  - **截圖目標**:以 Playwright locator `#box svg` 精確擷取 SVG 元素本身,排除頁面邊距干擾。
8
8
  - **解析度**:啟動 `browser.newPage({ deviceScaleFactor: 2 })`,輸出為 2× 實體像素的高解析度 PNG。
@@ -95,5 +95,5 @@ nomnoml 不為邊標籤畫底色(邊標籤背景透明),緊湊間距下邊
95
95
  - **無原生群組容器框**:改以「容器→成員」關聯邊表達歸屬(見上),視覺上無實體容器外框,僅靠共用色相辨識群組邊界,不若原生 compound graph 直觀。
96
96
  - **線性鏈被拉長**:對節點數多且為線性序列的圖(如長流程鏈),nomnoml 可能把畫布拉得很長,碰撞偵測的間距調整無法改善長寬比。
97
97
  - **碰撞偵測上限**:掃描序列最大值為 85px,若在此間距下仍有碰撞(如標籤極長或節點極密),會以殘留碰撞數最少者輸出。
98
- - **本機套件相依**:nomnoml/graphre 由本機 `node_modules` 讀出內聯注入,需先完成 `npm install`;執行環境本身不需連網。
98
+ - **CDN 相依**:nomnoml/graphre 改由 jsDelivr CDN 載入,免 `npm install`;執行環境須連網始能渲染。
99
99
  </content>
package/src/p3/gen.mjs CHANGED
@@ -15,12 +15,10 @@
15
15
  // 參照 → 單一連通圖、零重複節點。
16
16
  import { chromium } from 'playwright'
17
17
  import { PALETTE, EDGE } from '../common/palette.mjs'
18
- import { pkgScript } from '../common/pkg.mjs'
18
+ import { cdnScript } from '../common/cdn.mjs'
19
19
 
20
- // nomnoml(與其相依 graphre)由本機 node_modules 內聯注入(取代 CDN nomnoml.web.js, 斷網環境可用)
20
+ // nomnoml(與其相依 graphre) jsDelivr CDN 載入(免安裝; 版本鎖定見 common/cdn.mjs)
21
21
  // nomnoml UMD 依賴全域 graphre, 故 graphre 先載
22
- const GRAPHRE_JS = pkgScript('graphre/dist/graphre.js')
23
- const NOMNOML_JS = pkgScript('nomnoml/dist/nomnoml.js')
24
22
 
25
23
  // cls → nomnoml classifier 別名(純小寫字母 [a-z]+, 否則 nomnoml 視為字面文字而外洩)
26
24
  // 逐字映射保唯一: 大寫 G → 'g'、數字 2 → 'b'(其餘非 [a-z] 字元一律 → 'g'),
@@ -108,8 +106,8 @@ ${edgeDsls}`
108
106
  // 對策不加大間距, 而以繪製順序解: 將每個邊標籤 <text> append 至 svg 尾端(最後繪製),
109
107
  // 使其永遠疊在容器填色/節點 rect 之上, 任何文字皆不被區塊遮住。
110
108
  const setup = `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
111
- <script>${GRAPHRE_JS}</script>
112
- <script>${NOMNOML_JS}</script>
109
+ ${cdnScript('graphre')}
110
+ ${cdnScript('nomnoml')}
113
111
  </head><body style="margin:0;background:#fff;font-family:'Microsoft JhengHei',sans-serif">
114
112
  <div id="box" style="display:inline-block;padding:20px"></div>
115
113
  <script>
package/src/p4/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 技術核心
4
4
 
5
- - **繪圖庫**:Cytoscape.js(`cytoscape/dist/cytoscape.min.js`)搭配 dagre 排版引擎(`dagre/dist/dagre.min.js`)與橋接外掛(`cytoscape-dagre/dist/cytoscape-dagre.js`),三者由本機 `node_modules` 讀出後內聯注入頁面 `<script>`(`common/pkg.mjs` 的 `pkgScript()`),非透過 CDN,斷網環境亦可渲染。
5
+ - **繪圖庫**:Cytoscape.js(`cytoscape/dist/cytoscape.min.js`)搭配 dagre 排版引擎(`dagre/dist/dagre.min.js`)與橋接外掛(`cytoscape-dagre/dist/cytoscape-dagre.js`),三者改由 jsDelivr CDN 載入(免安裝),於頁面內以外部 `<script src>` 標籤(`cdnScript()`,版本鎖定於 `src/common/cdn.mjs`)依序載入;渲染時需連網存取 CDN
6
6
  - **渲染環境**:由 Playwright `chromium.launch()` 開啟無頭瀏覽器,`page.setContent()` 注入含三段內聯 script 的靜態 HTML,頁面掛載一個 `1400×1400px` 的 `<div id="cy">` 作為 Cytoscape 容器。
7
7
  - **截圖與匯出**:排版完成後呼叫 `cy.png({ full:true, scale:2, bg:'#ffffff', output:'base64' })` 取得 base64 字串,存入全域變數 `window.__png`,再由 `page.evaluate()` 讀回 Node 端,以 `Buffer.from(b64, 'base64')` 轉為 PNG Buffer 回傳。`full:true` 使輸出範圍自動裁切至實際元素範圍,`scale:2` 使最終 PNG 解析度為實際佈局尺寸的 2 倍;`deviceScaleFactor` 設為 `1`(由 `browser.newPage({ deviceScaleFactor:1 })` 指定),縮放完全由 `cy.png` 的 `scale:2` 控制。
8
8
  - **呼叫方式**:`genPng(data, opt)` 為單張渲染函式,接受正規化繪圖數據 `{ dir, nodes, edges }`,內部自建 browser/page 並於結束時關閉,回傳 PNG 的 Node Buffer(不落地檔案),由 `src/WFlowchart.mjs` 統一調用(`mode: 'p4'`);同結構之 `genSvg(data, opt)` 經 `cytoscape-svg` 外掛將同一份佈局/樣式匯出為 SVG 字串(canvas 引擎重繪為 SVG,經像素級忠實度驗證)。
@@ -80,5 +80,5 @@ dagre layout 使用三個通用常數,**不因圖而異**:
80
80
  - **diamond 節點在群組容器內偏大**:`diamond` cls 固定使用 `padding:34px` 以撐開菱形可視範圍,在小型群組容器內可能撐爆容器邊界。
81
81
  - **跨容器邊路由**:Cytoscape compound node 下,跨群組的邊由 dagre 自動路由,部分情況下路由線會穿越容器框線,視覺上不夠乾淨,但功能正確。
82
82
  - **無碰撞偵測迴圈**:版面為單次 dagre 計算,若節點數量極多、標籤極長,可能出現節點標籤截斷(超出 `text-max-width:360px`)或節點互疊,無自動調整機制。
83
- - **本機套件相依**:cytoscape/dagre/cytoscape-dagre 由本機 `node_modules` 讀出內聯注入,需先完成 `npm install`;執行環境本身不需連網。
83
+ - **CDN 相依**:cytoscape/dagre/cytoscape-dagre 改由 jsDelivr CDN 載入,免 `npm install`;執行環境須連網始能渲染。
84
84
  </content>
package/src/p4/gen.mjs CHANGED
@@ -2,13 +2,9 @@
2
2
  // genPng(data): 正規化繪圖數據 → 轉 cytoscape els → dagre 自動排版渲染 → 回傳 PNG Buffer
3
3
  // 版面通用化: rankDir 取自數據 dir; nodeSep/rankSep 為通用常數; full:true + 自動尺寸, 無逐圖魔術數字。
4
4
  import { chromium } from 'playwright'
5
- import { pkgScript } from '../common/pkg.mjs'
5
+ import { cdnScript } from '../common/cdn.mjs'
6
6
 
7
- // cytoscape + dagre + cytoscape-dagre + cytoscape-svg 由本機 node_modules 內聯注入(取代 CDN, 斷網環境可用)
8
- const CYTOSCAPE_JS = pkgScript('cytoscape/dist/cytoscape.min.js')
9
- const DAGRE_JS = pkgScript('dagre/dist/dagre.min.js')
10
- const CYTOSCAPE_DAGRE_JS = pkgScript('cytoscape-dagre/dist/cytoscape-dagre.js')
11
- const CYTOSCAPE_SVG_JS = pkgScript('cytoscape-svg/cytoscape-svg.js')
7
+ // cytoscape + dagre + cytoscape-dagre + cytoscape-svg jsDelivr CDN 載入(免安裝; 版本鎖定見 common/cdn.mjs)
12
8
 
13
9
  // 正規化數據 → cytoscape elements(節點帶 parent=group; 邊 dashed→classes)
14
10
  function toEls(data) {
@@ -39,10 +35,10 @@ const style = `[
39
35
  ]`
40
36
 
41
37
  const html = `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
42
- <script>${CYTOSCAPE_JS}</script>
43
- <script>${DAGRE_JS}</script>
44
- <script>${CYTOSCAPE_DAGRE_JS}</script>
45
- <script>${CYTOSCAPE_SVG_JS}</script>
38
+ ${cdnScript('cytoscape')}
39
+ ${cdnScript('dagre')}
40
+ ${cdnScript('cytoscape-dagre')}
41
+ ${cdnScript('cytoscape-svg')}
46
42
  </head><body style="margin:0;background:#fff">
47
43
  <div id="cy" style="width:1400px;height:1400px;background:#fff"></div>
48
44
  <script>
package/src/p6/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 技術核心
4
4
 
5
- - **繪圖庫**:`@antv/g6@5`,由本機 `node_modules/@antv/g6/dist/g6.min.js`(`common/pkg.mjs` `pkgScript()` 讀取檔案內容)內聯注入 Playwright 頁面的 `<script>` 執行,取代舊版 CDN 載入,斷網環境亦可執行。`@antv/g6` 仍是套件 `dependencies` 之一(供 `node_modules` 內取得 dist 檔),但 Node 執行期不以 `import` 方式使用它,僅頁面內 `<script>` 使用其全域 `G6`。
5
+ - **繪圖庫**:`@antv/g6@5`,改由 jsDelivr CDN 載入(免安裝),於 Playwright 頁面內以外部 `<script src>` 標籤(`cdnScript('g6')`,版本鎖定於 `src/common/cdn.mjs`)載入執行,渲染時需連網存取 CDN。`@antv/g6` 已從套件 `dependencies` 移除,Node 執行期亦不以 `import` 方式使用它,僅頁面內 `<script>` 取用其全域 `G6`。
6
6
  - **渲染方式**:以 `chromium.launch()` 開啟無頭瀏覽器,`page.setContent(html)` 載入內嵌 G6 頁面,於頁面內呼叫 `window.renderFig()`,以 `G6.Graph` + `layout: { type:'antv-dagre' }` 完成排版與渲染。
7
7
  - **截圖/輸出**:呼叫 `graph.toDataURL({ mode:'overall' })` 取得整圖 DataURL(非 viewport 截圖),透過 `window.snap()` 取回後在 Node 端解 base64,組成 `Buffer` 回傳(不寫檔)。
8
8
  - **解析度**:`browser.newPage({ deviceScaleFactor: 2 })`,輸出為 2× 實體像素解析度的 PNG。
package/src/p6/gen.mjs CHANGED
@@ -12,14 +12,11 @@
12
12
  // 若緊湊間距下仍有節點互疊且非 z-order 可解, 則保持緊湊留下重疊(代表此套不適合此圖, 由挑選階段換別套)。
13
13
  import { chromium } from 'playwright'
14
14
  import { colorOf, isGroupCls, EDGE, FONT } from '../common/palette.mjs'
15
- import { pkgScript } from '../common/pkg.mjs'
15
+ import { cdnScript } from '../common/cdn.mjs'
16
16
 
17
- // G6 v5 由本機 node_modules 內聯注入(取代 CDN, 斷網環境可用)
18
- const G6_JS = pkgScript('@antv/g6/dist/g6.min.js')
17
+ // G6 v5 jsDelivr CDN 載入(免安裝; 版本鎖定見 common/cdn.mjs)
19
18
  // genSvg 專用:@antv/g-svg renderer(取代預設 canvas renderer, 產出 SVG DOM)。
20
19
  // 依賴 @antv/g-lite UMD(暴露全域 window.G)須先於 g-svg UMD 載入(g-svg UMD 讀取既有 window.G 掛載 window.G.SVG)。
21
- const G_LITE_JS = pkgScript('@antv/g-lite/dist/index.umd.min.js')
22
- const G_SVG_JS = pkgScript('@antv/g-svg/dist/index.umd.min.js')
23
20
 
24
21
  // ===== 通用排版常數(全圖共用, 無逐圖客製) =====
25
22
  const CANVAS = 2200 // 大畫布:給足夠空間, 配合 autoFit:'view' 自動縮放, 不逐圖設 w/h
@@ -134,7 +131,7 @@ function translate(data) {
134
131
  // 原則2:combo.style.zIndex 墊底、edge.style.zIndex 最上層(於 translate 各元素已設, graph 預設再兜底)。
135
132
  // 原則3:固定緊湊 nodesep/ranksep, 無碰撞偵測掃描放大。
136
133
  const html = `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
137
- <script>${G6_JS}</script>
134
+ ${cdnScript('g6')}
138
135
  </head><body style="margin:0;background:#fff">
139
136
  <div id="g6" style="background:#fff"></div>
140
137
  <script>
@@ -201,9 +198,9 @@ export async function genPng(data, opt = {}) {
201
198
  // 換用 @antv/g-svg renderer(取代預設 canvas renderer)產出 SVG DOM, 其餘 translate/layout/樣式邏輯
202
199
  // (dagre 排版、緊湊間距、z-order、字型)與 genPng 完全共用, 故版面結構/顏色/文字與 PNG 版一致。
203
200
  const htmlSvg = `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
204
- <script>${G_LITE_JS}</script>
205
- <script>${G_SVG_JS}</script>
206
- <script>${G6_JS}</script>
201
+ ${cdnScript('g-lite')}
202
+ ${cdnScript('g-svg')}
203
+ ${cdnScript('g6')}
207
204
  </head><body style="margin:0;background:#fff">
208
205
  <div id="g6" style="background:#fff"></div>
209
206
  <script>
package/src/p7/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  ## 技術核心
4
4
 
5
- - **繪圖庫**:`@joint/core`(JointJS)搭配 `dagre`,由 `common/pkg.mjs` `pkgScript()` 讀取本機 `node_modules/@joint/core/dist/joint.min.js` `node_modules/dagre/dist/dagre.min.js` 內容,內聯注入 Playwright 頁內 `<script>` 標籤(取代 CDN,斷網環境可用)。
6
- - **載入方式**:Node 端以 Playwright 的 `page.setContent(html)` 建立含兩段內聯 `<script>` 的完整 HTML 頁,等候 `window.__ready` 旗標確認兩套庫皆就緒後,再透過 `page.evaluate()` 驅動頁內 JavaScript 函式進行排版與渲染。
5
+ - **繪圖庫**:`@joint/core`(JointJS)搭配 `dagre`,改由 jsDelivr CDN 載入(免安裝),於 Playwright 頁內以外部 `<script src>` 標籤(`cdnScript('joint')`、`cdnScript('dagre')`,版本鎖定於 `src/common/cdn.mjs`)載入;渲染時需連網存取 CDN
6
+ - **載入方式**:Node 端以 Playwright 的 `page.setContent(html)` 建立含兩段外部 `<script src>` 的完整 HTML 頁,等候 `window.__ready` 旗標確認兩套庫皆就緒後,再透過 `page.evaluate()` 驅動頁內 JavaScript 函式進行排版與渲染。
7
7
  - **渲染方式**:排版完成後以 `P.fitToContent({ padding: 28, allowNewOrigin: 'any', useModelGeometry: true })` 讓 JointJS Paper 自適應內容邊界,截圖目標為 `#paper svg`(`page.$('#paper svg').screenshot()`),直接輸出 PNG。
8
8
  - **解析度**:`browser.newPage({ deviceScaleFactor: 2 })` 以 2 倍像素密度開頁,截出的 PNG 實際像素為邏輯尺寸的兩倍,保有高清品質。
9
9
  - **字型**:頁內使用 `Microsoft JhengHei`(微軟正黑體)作為主字型,canvas 量測與 JointJS 渲染均使用同一 `FONT` 字串,確保量測結果與渲染結果一致。
package/src/p7/gen.mjs CHANGED
@@ -3,11 +3,9 @@
3
3
  // 版面通用化: rankDir 取自數據 dir; nodesep/ranksep/GPAD/換行寬度 皆為通用常數或依標籤字寬/行數自動推算, 無逐圖魔術數字。
4
4
  import { chromium } from 'playwright'
5
5
  import { PALETTE } from '../common/palette.mjs'
6
- import { pkgScript } from '../common/pkg.mjs'
6
+ import { cdnScript } from '../common/cdn.mjs'
7
7
 
8
- // @joint/core + dagre 由本機 node_modules 內聯注入(取代 CDN, 斷網環境可用)
9
- const JOINT_JS = pkgScript('@joint/core/dist/joint.min.js')
10
- const DAGRE_JS = pkgScript('dagre/dist/dagre.min.js')
8
+ // @joint/core + dagre jsDelivr CDN 載入(免安裝; 版本鎖定見 common/cdn.mjs)
11
9
 
12
10
  // CLS(JointJS 內部樣式表)由共用色票 PALETTE 推導: fill/stroke/text→color; diamond 帶 shape; *G/*G2 帶 group:true
13
11
  const CLS = Object.fromEntries(Object.entries(PALETTE).map(([cls, p]) => {
@@ -28,8 +26,8 @@ function translate(data) {
28
26
  }
29
27
 
30
28
  const html = `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
31
- <script>${JOINT_JS}</script>
32
- <script>${DAGRE_JS}</script>
29
+ ${cdnScript('joint')}
30
+ ${cdnScript('dagre')}
33
31
  </head><body style="margin:0;background:#fff">
34
32
  <div id="paper" style="background:#fff"></div>
35
33
  <script>
package/src/p8/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  ## 技術核心
4
4
 
5
- - **繪圖庫**:`@logicflow/core`(含 CSS)搭配 `dagre`,由 `common/pkg.mjs` 讀取本機檔案內聯注入 Playwright 頁內(取代 CDN,斷網環境可用):`pkgText('@logicflow/core/dist/index.css')` 內聯進 `<style>`,`pkgScript('@logicflow/core/dist/index.min.js')` 與 `pkgScript('dagre/dist/dagre.min.js')` 各自內聯進 `<script>`。
6
- - **載入方式**:Node 端以 Playwright 建立 Chromium 頁面,將完整 HTML 字串(含內聯 CSS 與兩段內聯 `<script>`)透過 `page.setContent()` 注入;繪圖邏輯全部在頁內 `<script>` 執行,Node 端僅負責驅動與截圖。
5
+ - **繪圖庫**:`@logicflow/core`(含 CSS)搭配 `dagre`,改由 jsDelivr CDN 載入(免安裝):`cdnStyle('logicflow-css')` 以外部 `<link>` 載入 CSS,`cdnScript('logicflow')` 與 `cdnScript('dagre')` 各自以外部 `<script src>` 載入(版本鎖定於 `src/common/cdn.mjs`);渲染時需連網存取 CDN。
6
+ - **載入方式**:Node 端以 Playwright 建立 Chromium 頁面,將完整 HTML 字串(含外部 CSS `<link>` 與兩段外部 `<script src>`)透過 `page.setContent()` 注入;繪圖邏輯全部在頁內 `<script>` 執行,Node 端僅負責驅動與截圖。
7
7
  - **渲染與截圖**:頁內 `window.renderFig(fig)` 依序計算尺寸 → dagre 排版 → `lf.render()` 渲染完後以 `setTimeout(..., 200)` 等待 DOM 穩定,回傳 Promise;Node 端再呼叫 `page.evaluate(() => document.fonts.ready)` 確保字型載入完畢,最後以 `page.locator('#app').screenshot()` 截取 `#app` 元素為 PNG Buffer 回傳(不寫檔)。
8
8
  - **解析度**:`browser.newPage({ deviceScaleFactor: 2 })`,輸出為 2× 實體像素密度(HiDPI),寬高由計算所得邏輯像素乘以 2 輸出。
9
9
  - **調用方式**:本模組匯出 `genPng(data, opt) → Promise<Buffer>` 與 `genSvg(data, opt) → Promise<string>`,輸入皆為正規化繪圖數據 `{ dir, nodes, edges }`;`genSvg` 序列化圖面 svg(`lf-canvas-overlay`),並將 LogicFlow CSS 內嵌進 SVG(文字自動換行之 `foreignObject` 標籤樣式依賴此 CSS);由 `src/WFlowchart.mjs` 統一匯入並依 `p8` 鍵值調用,本身不寫檔、不涉及批次流程。
package/src/p8/gen.mjs CHANGED
@@ -5,12 +5,9 @@
5
5
  // 邊標籤(全域修正 A): 背景透明(不遮轉折線) + 文字加白色光暈(SVG paint-order:stroke + stroke=白 stroke-width≈3.5)。
6
6
  import { chromium } from 'playwright'
7
7
  import { PALETTE, EDGE, FONT, colorOf } from '../common/palette.mjs'
8
- import { pkgScript, pkgText } from '../common/pkg.mjs'
8
+ import { cdnScript, cdnStyle, cdnFetchText } from '../common/cdn.mjs'
9
9
 
10
- // @logicflow/core(js+css)+ dagre 由本機 node_modules 內聯注入(取代 CDN, 斷網環境可用)
11
- const LOGICFLOW_CSS = pkgText('@logicflow/core/dist/index.css')
12
- const LOGICFLOW_JS = pkgScript('@logicflow/core/dist/index.min.js')
13
- const DAGRE_JS = pkgScript('dagre/dist/dagre.min.js')
10
+ // @logicflow/core(js+css)+ dagre jsDelivr CDN 載入(免安裝; 版本鎖定見 common/cdn.mjs)
14
11
 
15
12
  // 正規化數據(dir/nodes/edges) → LF 內部結構: nodes 用 parent 表群組歸屬; edges 用 source/target/dashed。
16
13
  // 套色: 由 cls 查 PALETTE(group 類別自動為淺底容器); 形狀: diamond→決策菱形, 其餘→矩形; dir→dagre rankDir; kind:dashed→虛線。
@@ -27,9 +24,9 @@ const PAL_JSON = JSON.stringify(PAL)
27
24
  const EDGE_JSON = JSON.stringify(EDGE)
28
25
 
29
26
  const html = `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
30
- <style>${LOGICFLOW_CSS}</style>
31
- <script>${LOGICFLOW_JS}</script>
32
- <script>${DAGRE_JS}</script>
27
+ ${cdnStyle('logicflow-css')}
28
+ ${cdnScript('logicflow')}
29
+ ${cdnScript('dagre')}
33
30
  <style>
34
31
  * { font-family: ${FONT}; }
35
32
  html,body{ margin:0; background:#fff; }
@@ -444,8 +441,10 @@ export async function genSvg(data, opt = {}) {
444
441
  .replace('height="100%"', `height="${res.h}"`)
445
442
  openTag = openTag.replace(/>$/, ` viewBox="0 0 ${res.w} ${res.h}">`)
446
443
  svg = openTag + svg.slice(openTagMatch[0].length)
447
- // 嵌入 CSS(LOGICFLOW_CSS + 頁內字型規則) + 白底(比照 #app 背景), 使脫離頁面仍忠實呈現
448
- const inject = `<style>${LOGICFLOW_CSS}\n* { font-family: ${FONT}; }</style>`
444
+ // 嵌入 CSS(LogicFlow CSS + 頁內字型規則) + 白底(比照 #app 背景), 使脫離頁面仍忠實呈現
445
+ // LogicFlow CSS CDN 取回文字內容內嵌(免安裝; 與頁面 <link> 同一版本, 內容一致)
446
+ const logicflowCss = await cdnFetchText('logicflow-css')
447
+ const inject = `<style>${logicflowCss}\n* { font-family: ${FONT}; }</style>`
449
448
  + `<rect x="0" y="0" width="${res.w}" height="${res.h}" fill="#fff"></rect>`
450
449
  svg = svg.replace('<g transform', inject + '<g transform')
451
450
  svg = svg.replace(/&nbsp;/g, '&#160;')
package/src/p9/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ## 技術核心
4
4
 
5
5
  - **繪圖庫**:`@xyflow/react@12`(React Flow)搭配 `@dagrejs/dagre@1` 自動排版引擎。
6
- - **載入方式**:`react`、`react-dom`、`@xyflow/react`、`@dagrejs/dagre` 之 UMD 版本與 `@xyflow/react` 樣式表,皆由本機 `node_modules` 讀出後於 `gen.mjs` 模組載入時內聯注入 `page9.html`(取代 CDN `esm.sh`,斷網環境可用):`page9.html` 內以佔位符 `/*__XYFLOW_CSS__*/`、`/*__REACT_JS__*/`、`/*__REACT_DOM_JS__*/`、`/*__XYFLOW_JS__*/`、`/*__DAGRE_JS__*/` 預留插入點,`gen.mjs` 讀入該檔全文後以 `common/pkg.mjs` 的 `pkgText`/`pkgScript`(向上逐層尋找 `node_modules/<rel>`)取代之,`replace` 用函式形式回傳以避免庫碼內 `$` 序列被誤當替換樣板。`@xyflow/react` UMD 之外部依賴含全域 `jsxRuntime`(React 18 UMD 未提供),`page9.html` 內建等價 shim(以 `React.createElement` 實作 `jsx`/`jsxs`/`Fragment`)補上。
6
+ - **載入方式**:`react`、`react-dom`、`@xyflow/react`、`@dagrejs/dagre` 之 UMD 版本與 `@xyflow/react` 樣式表,改由 jsDelivr CDN 載入(免安裝,版本鎖定於 `src/common/cdn.mjs`):`page9.html` 內以佔位符 `/*__XYFLOW_CSS__*/`、`/*__REACT_JS__*/`、`/*__REACT_DOM_JS__*/`、`/*__XYFLOW_JS__*/`、`/*__DAGRE_JS__*/` 預留插入點,`gen.mjs` 讀入該檔全文後以 `common/cdn.mjs` 的 `cdnScript`/`cdnStyle` 將各佔位標籤整段換成對應之外部 `<script src>`/`<link>` 標籤,`replace` 用函式形式回傳以避免庫碼內 `$` 序列被誤當替換樣板;渲染時需連網存取 CDN。`@xyflow/react` UMD 之外部依賴含全域 `jsxRuntime`(React 18 UMD 未提供),`page9.html` 內建等價 shim(以 `React.createElement` 實作 `jsx`/`jsxs`/`Fragment`)補上。
7
7
  - **渲染流程**:`genPng(data, opt)` 每次呼叫皆啟動一個新的 `chromium` browser 與 page(`finally` 內關閉),以 `page.setContent(html)` 注入內聯好各繪圖庫的 `page9.html` 全文(`html` 於模組載入時讀取並完成佔位符替換一次,跨呼叫重用字串),等待 `document.title === 'READY'` 確認 React 與 dagre 啟動完成後,呼叫 `page.evaluate(s => window.renderFig(s), spec)`(`spec` 為 `translate(data)` 之輸出)觸發單張渲染,渲染函式內 `setTimeout 900ms` 後 resolve,確保 React commit 完成。
8
8
  - **截圖目標**:對 `#shot` 元素截圖(`page.locator('#shot').screenshot()`);`#shot` 為 `display:inline-block` 的 div,內含 `#stage`(React Flow 掛載點),外加 `padding:24px` 白邊,截出的 PNG 即為完整含留白之圖片。
9
9
  - **解析度**:`browser.newPage({ deviceScaleFactor: 2 })` 開啟 2x 縮放,所有量測與佈局座標仍以 CSS px 計算,截出的 PNG 實際像素為畫布尺寸之 2 倍,確保中文字型清晰。
package/src/p9/gen.mjs CHANGED
@@ -8,21 +8,21 @@ import { fileURLToPath } from 'url'
8
8
  import { dirname } from 'path'
9
9
  import fs from 'fs'
10
10
 
11
- import { pkgScript, pkgText } from '../common/pkg.mjs'
11
+ import { cdnScript, cdnStyle } from '../common/cdn.mjs'
12
12
 
13
13
  const __dir = dirname(fileURLToPath(import.meta.url))
14
- // React / ReactDOM / @xyflow/react(UMD)/ @dagrejs/dagre 由本機 node_modules 內聯注入(取代 esm.sh, 斷網環境可用)
15
- // replace 用函式形式回傳, 避免庫碼內 $ 序列被當作替換樣板
14
+ // React / ReactDOM / @xyflow/react(UMD, 含 css)/ @dagrejs/dagre jsDelivr CDN 載入(免安裝; 版本鎖定見 common/cdn.mjs)
15
+ // page9.html 內各佔位標籤整段換成外部 <script src>/<link>(非 async, 保留文件順序執行)
16
16
  // 首次呼叫才讀檔組頁(延遲載入): 模組層即讀會令「僅用其他產線」的載入方也付出讀檔成本
17
17
  let _html = null
18
18
  function getHtml() {
19
19
  if (_html === null) {
20
20
  _html = fs.readFileSync(`${__dir}/page9.html`, 'utf8')
21
- .replace('/*__XYFLOW_CSS__*/', () => pkgText('@xyflow/react/dist/style.css'))
22
- .replace('/*__REACT_JS__*/', () => pkgScript('react/umd/react.production.min.js'))
23
- .replace('/*__REACT_DOM_JS__*/', () => pkgScript('react-dom/umd/react-dom.production.min.js'))
24
- .replace('/*__XYFLOW_JS__*/', () => pkgScript('@xyflow/react/dist/umd/index.js'))
25
- .replace('/*__DAGRE_JS__*/', () => pkgScript('@dagrejs/dagre/dist/dagre.min.js'))
21
+ .replace('<style>/*__XYFLOW_CSS__*/</style>', () => cdnStyle('xyflow-css'))
22
+ .replace('<script>/*__REACT_JS__*/</script>', () => cdnScript('react'))
23
+ .replace('<script>/*__REACT_DOM_JS__*/</script>', () => cdnScript('react-dom'))
24
+ .replace('<script>/*__XYFLOW_JS__*/</script>', () => cdnScript('xyflow'))
25
+ .replace('<script>/*__DAGRE_JS__*/</script>', () => cdnScript('dagrejs-dagre'))
26
26
  }
27
27
  return _html
28
28
  }
package/src/p9/page9.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
2
- <!-- 繪圖庫由本機 node_modules 內聯注入(gen.mjs 讀本檔後替換各佔位符; 取代 esm.sh/CDN, 斷網環境可用) -->
2
+ <!-- 繪圖庫由 jsDelivr CDN 載入(gen.mjs 讀本檔後將各佔位標籤換成外部 <script src>/<link>; 免安裝) -->
3
3
  <style>/*__XYFLOW_CSS__*/</style>
4
4
  <style>
5
5
  html,body{margin:0;background:#fff;font-family:'Microsoft JhengHei',sans-serif}
@@ -1,54 +1,24 @@
1
- import path from 'path'
2
- import rollupFiles from 'w-package-tools/src/rollupFiles.mjs'
3
- import rollupWorker from 'w-package-tools/src/rollupWorker.mjs'
4
-
5
-
6
- let fdSrc = './src'
7
- let fdTar = './dist'
8
-
9
- async function core() {
10
-
11
- await rollupFiles({ //rollupFiles預設會clean folder
12
- fns: 'WFlowchart.mjs',
13
- fdSrc,
14
- fdTar,
15
- nameDistType: 'kebabCase',
16
- globals: {
17
- '@terrastruct/d2': '@terrastruct/d2',
18
- 'playwright': 'playwright',
19
- 'sharp': 'sharp',
20
- },
21
- external: [ //繪圖庫多為 fs 直讀內聯注入頁面(非 import), 仍列 external 保險, 避免任何路徑被打包進 dist
22
- '@terrastruct/d2',
23
- 'playwright',
24
- 'sharp',
25
- 'mermaid10',
26
- 'mermaid11',
27
- '@mermaid-js/layout-elk',
28
- 'nomnoml',
29
- 'graphre',
30
- 'cytoscape',
31
- 'cytoscape-dagre',
32
- 'dagre',
33
- '@antv/g6',
34
- '@antv/g-lite',
35
- '@antv/g-svg',
36
- 'cytoscape-svg',
37
- '@joint/core',
38
- '@logicflow/core',
39
- 'react',
40
- 'react-dom',
41
- '@xyflow/react',
42
- '@dagrejs/dagre',
43
- 'elkjs',
44
- ],
45
- })
46
- .catch((err) => {
47
- console.log(err)
48
- })
49
-
50
- }
51
- core()
52
- .catch((err) => {
53
- console.log(err)
54
- })
1
+ import path from 'path'
2
+ import rollupFiles from 'w-package-tools/src/rollupFiles.mjs'
3
+ import rollupWorker from 'w-package-tools/src/rollupWorker.mjs'
4
+
5
+
6
+ let fdSrc = './src'
7
+ let fdTar = './dist'
8
+
9
+ rollupFiles({ //rollupFiles預設會clean folder
10
+ fns: 'WFlowchart.mjs',
11
+ fdSrc,
12
+ fdTar,
13
+ nameDistType: 'kebabCase',
14
+ globals: {
15
+ '@terrastruct/d2': '@terrastruct/d2',
16
+ 'playwright': 'playwright',
17
+ 'sharp': 'sharp',
18
+ },
19
+ external: [ //Node 端執行期依賴(原生/重, 不打包進 dist); 繪圖庫已改由 CDN 於頁面內載入, 非 import, 無須列此
20
+ '@terrastruct/d2',
21
+ 'playwright',
22
+ 'sharp',
23
+ ],
24
+ })
@@ -1,31 +0,0 @@
1
- // 本機套件檔案定位與讀取: 供各產線把繪圖庫由 CDN 改為 node_modules 內聯注入(斷網環境可用)。
2
- // 以 fs 實體路徑向上逐層找 node_modules/<rel>, 不走 require.resolve(不受套件 exports 白名單限制),
3
- // 亦相容本套件被安裝後 node_modules 被 hoist 至上層的情況。
4
- import fs from 'fs'
5
- import { fileURLToPath } from 'url'
6
- import { dirname, join } from 'path'
7
-
8
- const __dir = dirname(fileURLToPath(import.meta.url))
9
-
10
- // rel 例: 'mermaid10/dist/mermaid.min.js' → 回傳實體絕對路徑
11
- export function pkgFile(rel) {
12
- let dir = __dir
13
- for (let i = 0; i < 10; i++) {
14
- const fp = join(dir, 'node_modules', rel)
15
- if (fs.existsSync(fp)) return fp
16
- const up = dirname(dir)
17
- if (up === dir) break
18
- dir = up
19
- }
20
- throw new Error('找不到本機套件檔案 node_modules/' + rel)
21
- }
22
-
23
- // 讀取套件 JS 內容供 <script> 內聯(轉義 </script> 避免提早關閉標籤)
24
- export function pkgScript(rel) {
25
- return fs.readFileSync(pkgFile(rel), 'utf8').replace(/<\/script/gi, '<\\/script')
26
- }
27
-
28
- // 讀取套件文字內容(CSS 等)
29
- export function pkgText(rel) {
30
- return fs.readFileSync(pkgFile(rel), 'utf8')
31
- }