@stylewx/preview 0.1.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/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/preview.test.d.ts +2 -0
- package/dist/preview.test.d.ts.map +1 -0
- package/dist/preview.test.js +19 -0
- package/dist/preview.test.js.map +1 -0
- package/package.json +31 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface PreviewOptions {
|
|
2
|
+
/** 视口宽度(默认 390,即常见 iPhone 逻辑宽度)。 */
|
|
3
|
+
width?: number;
|
|
4
|
+
/** 视口高度(默认 844)。 */
|
|
5
|
+
height?: number;
|
|
6
|
+
/** 设备像素比(默认 2,retina 更清晰)。 */
|
|
7
|
+
deviceScaleFactor?: number;
|
|
8
|
+
/** 是否整页截图(默认 true)。 */
|
|
9
|
+
fullPage?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface PreviewResult {
|
|
12
|
+
/** PNG 字节。 */
|
|
13
|
+
png: Buffer;
|
|
14
|
+
/** 实际使用的视口。 */
|
|
15
|
+
viewport: {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
deviceScaleFactor: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 把 HTML 渲染为 PNG 截图。
|
|
23
|
+
* @param html 已内联样式的 HTML
|
|
24
|
+
* @param opts 视口等选项
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderHtmlToPng(html: string, opts?: PreviewOptions): Promise<PreviewResult>;
|
|
27
|
+
/** 模拟 iPhone 视口(390px 宽)的微信预览截图。 */
|
|
28
|
+
export declare function renderIphonePreview(html: string): Promise<PreviewResult>;
|
|
29
|
+
/** 关闭并释放浏览器实例(进程退出前调用,可选)。 */
|
|
30
|
+
export declare function closePreviewBrowser(): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8BAA8B;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,uBAAuB;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc;IACd,GAAG,EAAE,MAAM,CAAA;IACX,eAAe;IACf,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAA;CACvE;AAmBD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,aAAa,CAAC,CAqBxB;AAED,oCAAoC;AACpC,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAO9E;AAED,8BAA8B;AAC9B,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CASzD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright 截图预览。
|
|
3
|
+
* 把渲染后的内联样式 HTML 在模拟 iPhone 视口(390px 宽)下截图,输出 PNG Buffer。
|
|
4
|
+
* 该包 Node-only(依赖 Playwright / Chromium)。
|
|
5
|
+
*/
|
|
6
|
+
import { chromium } from 'playwright';
|
|
7
|
+
let browserPromise = null;
|
|
8
|
+
/** 惰性启动浏览器实例(多进程共享,避免重复启动开销)。 */
|
|
9
|
+
async function getBrowser() {
|
|
10
|
+
if (!browserPromise) {
|
|
11
|
+
browserPromise = chromium
|
|
12
|
+
.launch({ headless: true })
|
|
13
|
+
.catch((error) => {
|
|
14
|
+
browserPromise = null;
|
|
15
|
+
throw new Error(`无法启动 Playwright Chromium:${error instanceof Error ? error.message : String(error)}。请先运行 pnpm --filter @stylewx/preview exec playwright install chromium 安装浏览器。`);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return browserPromise;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 把 HTML 渲染为 PNG 截图。
|
|
22
|
+
* @param html 已内联样式的 HTML
|
|
23
|
+
* @param opts 视口等选项
|
|
24
|
+
*/
|
|
25
|
+
export async function renderHtmlToPng(html, opts = {}) {
|
|
26
|
+
const width = opts.width ?? 390;
|
|
27
|
+
const height = opts.height ?? 844;
|
|
28
|
+
const deviceScaleFactor = opts.deviceScaleFactor ?? 2;
|
|
29
|
+
const fullPage = opts.fullPage ?? true;
|
|
30
|
+
const browser = await getBrowser();
|
|
31
|
+
const page = await browser.newPage({
|
|
32
|
+
viewport: { width, height },
|
|
33
|
+
deviceScaleFactor,
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
await page.setContent(`<html><head><meta charset="utf-8"></head><body style="margin:0">${html}</body></html>`, {
|
|
37
|
+
waitUntil: 'networkidle',
|
|
38
|
+
timeout: 10000,
|
|
39
|
+
});
|
|
40
|
+
const png = await page.screenshot({ fullPage, type: 'png' });
|
|
41
|
+
return { png: Buffer.from(png), viewport: { width, height, deviceScaleFactor } };
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
await page.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** 模拟 iPhone 视口(390px 宽)的微信预览截图。 */
|
|
48
|
+
export async function renderIphonePreview(html) {
|
|
49
|
+
return renderHtmlToPng(html, {
|
|
50
|
+
width: 390,
|
|
51
|
+
height: 844,
|
|
52
|
+
deviceScaleFactor: 2,
|
|
53
|
+
fullPage: true,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** 关闭并释放浏览器实例(进程退出前调用,可选)。 */
|
|
57
|
+
export async function closePreviewBrowser() {
|
|
58
|
+
if (browserPromise) {
|
|
59
|
+
try {
|
|
60
|
+
const browser = await browserPromise;
|
|
61
|
+
await browser.close();
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
browserPromise = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAqBrC,IAAI,cAAc,GAA4B,IAAI,CAAA;AAElD,iCAAiC;AACjC,KAAK,UAAU,UAAU;IACvB,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,QAAQ;aACtB,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;aAC1B,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxB,cAAc,GAAG,IAAI,CAAA;YACrB,MAAM,IAAI,KAAK,CACb,4BAA4B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,8EAA8E,CACjK,CAAA;QACH,CAAC,CAAC,CAAA;IACN,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAY,EACZ,OAAuB,EAAE;IAEzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAA;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAA;IACjC,MAAM,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,IAAI,CAAC,CAAA;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAA;IAEtC,MAAM,OAAO,GAAG,MAAM,UAAU,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;QACjC,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;QAC3B,iBAAiB;KAClB,CAAC,CAAA;IACF,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,UAAU,CAAC,mEAAmE,IAAI,gBAAgB,EAAE;YAC7G,SAAS,EAAE,aAAa;YACxB,OAAO,EAAE,KAAK;SACf,CAAC,CAAA;QACF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,EAAE,CAAA;IAClF,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;IACpB,CAAC;AACH,CAAC;AAED,oCAAoC;AACpC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAY;IACpD,OAAO,eAAe,CAAC,IAAI,EAAE;QAC3B,KAAK,EAAE,GAAG;QACV,MAAM,EAAE,GAAG;QACX,iBAAiB,EAAE,CAAC;QACpB,QAAQ,EAAE,IAAI;KACf,CAAC,CAAA;AACJ,CAAC;AAED,8BAA8B;AAC9B,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,cAAc,CAAA;YACpC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA;QACvB,CAAC;gBAAS,CAAC;YACT,cAAc,GAAG,IAAI,CAAA;QACvB,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.test.d.ts","sourceRoot":"","sources":["../src/preview.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { renderHtmlToPng, renderIphonePreview, closePreviewBrowser } from './index.js';
|
|
3
|
+
describe('preview exports', () => {
|
|
4
|
+
it('暴露截图函数', () => {
|
|
5
|
+
expect(typeof renderHtmlToPng).toBe('function');
|
|
6
|
+
expect(typeof renderIphonePreview).toBe('function');
|
|
7
|
+
expect(typeof closePreviewBrowser).toBe('function');
|
|
8
|
+
});
|
|
9
|
+
it('renderHtmlToPng 在 Chromium 不可用时给出清晰错误(不静默)', async () => {
|
|
10
|
+
// 不真正启动浏览器;此处仅验证错误为「明确说明需要安装浏览器」。
|
|
11
|
+
// 若本机已安装 chromium,则跳过该断言。
|
|
12
|
+
const error = await renderHtmlToPng('<p>x</p>').catch((e) => e);
|
|
13
|
+
if (error instanceof Error) {
|
|
14
|
+
expect(error.message).toContain('playwright install');
|
|
15
|
+
}
|
|
16
|
+
await closePreviewBrowser().catch(() => undefined);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=preview.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.test.js","sourceRoot":"","sources":["../src/preview.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAEtF,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QAChB,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC/C,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACnD,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,kCAAkC;QAClC,0BAA0B;QAC1B,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;QACxE,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAA;QACvD,CAAC;QACD,MAAM,mBAAmB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stylewx/preview",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "stylewx preview: render styled HTML to a PNG screenshot via Playwright (headless Chromium). iPhone-like viewport (390px) for WeChat previews.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"type-check": "tsc -p tsconfig.json --noEmit",
|
|
23
|
+
"prepublishOnly": "pnpm build"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"playwright": "^1.49.0"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
}
|
|
31
|
+
}
|