| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 20 天前 | ||
| 7 天前 | ||
| 7 天前 | ||
| 7 天前 | ||
| 7 天前 | ||
| 7 天前 | ||
| 4 个月前 | ||
| 1 个月前 | ||
| 7 天前 | ||
| 7 天前 | ||
| 7 天前 | ||
| 4 个月前 |
@lynx-js/node-lynx
Run Lynx bundles in Node.js for screenshots, visible macOS preview windows, DebugRouter OpenCard sessions, and test automation.
npm i @lynx-js/node-lynx
The package loads the native addon from a matching optional platform package,
such as @lynx-js/node-lynx-darwin-arm64, or from a local build/ /
platform/ directory during development.
CLI
Use the CLI for one-off screenshots, quick template validation, and preview sessions.
node-lynx --help
Capture a headless screenshot and exit:
node-lynx render \
"https://lynxjs.org/lynx-examples/gallery/dist/GalleryComplete.lynx.bundle" \
--width 268 \
--height 469 \
--dpr 2 \
--output ./gallery.png \
--timeout 30000 \
--screenshot-delay 500 \
--no-debug-router
Open a visible macOS preview window:
node-lynx preview \
"https://lynxjs.org/lynx-examples/gallery/dist/GalleryComplete.lynx.bundle" \
--width 268 \
--height 469 \
--dpr 2
Wait for DebugRouter OpenCard without an initial template:
node-lynx render
node-lynx preview
Rules:
rendercaptures a headless screenshot.previewis macOS-only and opens a visible AppKit window.--template <path>loads a local Lynx bundle.--url <url>loads a remotehttp://orhttps://Lynx bundle URL.- A positional input starting with
http://orhttps://is treated as a remote URL. --widthand--heightare CSS pixel viewport values. They default to390and844.--dpror--device-pixel-ratiodefaults to2; the PNG pixel size iswidth * dprbyheight * dpr.--outputdefaults toscreenshot.pngand parent directories are created recursively.--timeout <ms>is the maximum wait for bundle download, template load, CDP calls, and first-frame submission. It defaults to10000.--screenshot-delay <ms>waits after first-frame submission and before capture. It defaults to100and can be0.- DebugRouter is enabled by default. Use
--no-debug-routerfor one-shot screenshot commands that should exit immediately after writing the PNG. - Without
--no-debug-router,renderwith an initial template writes the PNG and then waits forSIGINTorSIGTERMso DebugRouter stays available. - Without an initial template,
renderandpreviewwait for DebugRouter OpenCard.--no-debug-routerrequires an initial template input. - DebugRouter listens on port
8901, which may be reachable from other hosts. By default OpenCard only loads publichttp://orhttps://URLs and re-checks every redirect. Pass--allow-any-open-card-sourceto also load local files and loopback or private hosts, such as a local dev server, only on a trusted network. - Use
--debug-router-schema <schema>to connect DebugRouter with an explicit schema. - Use
--log-level errorto hide verbose, debug, info, and warning Lynx logs, or--log-level silentto hide all Lynx logger output. When omitted, Lynx keeps its default level. - Prefer passing a real template URL when the bundle uses URL-relative resources.
Headless API
Use HeadlessLynxView for programmatic screenshots, repeated captures, global
props, CDP inspection, and automation.
const fs = require('fs/promises');
const { HeadlessLynxView } = require('@lynx-js/node-lynx');
async function main() {
const view = new HeadlessLynxView({
width: 268,
height: 469,
devicePixelRatio: 2,
timeoutMs: 30000,
});
try {
await view.loadTemplateFromUrl(
'https://lynxjs.org/lynx-examples/gallery/dist/GalleryComplete.lynx.bundle',
{
initialData: { source: 'node-lynx' },
globalProps: { theme: 'light' },
}
);
await fs.writeFile('./gallery.png', await view.screenshot({ settleMs: 500 }));
} finally {
view.destroy();
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
For local buffers, pass a URL so relative resource resolution has context:
const fs = require('fs/promises');
const path = require('path');
const { pathToFileURL } = require('url');
const { HeadlessLynxView } = require('@lynx-js/node-lynx');
async function main() {
const templatePath = path.resolve('./dist/main/template.js');
const view = new HeadlessLynxView({ width: 390, height: 844 });
try {
await view.loadTemplate(await fs.readFile(templatePath), {
url: pathToFileURL(templatePath).toString(),
});
await fs.writeFile('./local.png', await view.screenshot({ settleMs: 500 }));
} finally {
view.destroy();
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
For bundles that synchronously load local JavaScript, pass resourceRootPaths
to search those directories before falling back to the asynchronous resource
fetcher. Views created with the same non-empty groupName share a BTS runtime.
Useful APIs:
loadTemplateFromUrl(url, options)downloads and loads a remote template.loadTemplate(buffer, options)loads a local template buffer.updateData(data, options)updates template data after load.updateGlobalProps(globalProps)updates global props after load.invokeCDPFromSDK(cdpMessage)invokes a CDP method with a JSON string.evaluateScript(url, script)schedules JavaScript in the view's BTS runtime; evaluation failures are reported toonErrorOccurred.waitForFrame()waits until a frame has been submitted.screenshot({ settleMs })returns a PNG buffer.destroy()releases the native view. Always call it in afinallyblock.
Lynx logging is process-wide. Set the minimum level before creating a view:
const { LynxEnv } = require('@lynx-js/node-lynx');
LynxEnv.setLogLevel('error');
Windowed Preview
WindowedLynxView is macOS-only and creates a visible AppKit window.
const fs = require('fs/promises');
const { WindowedLynxView } = require('@lynx-js/node-lynx');
async function main() {
const view = new WindowedLynxView({
width: 268,
height: 469,
devicePixelRatio: 2,
title: 'Node Lynx Preview',
});
try {
await view.loadTemplateFromUrl(
'https://lynxjs.org/lynx-examples/gallery/dist/GalleryComplete.lynx.bundle'
);
await view.waitForFrame();
await fs.writeFile('./windowed.png', await view.screenshot({ settleMs: 500 }));
await view.waitUntilClosed();
} finally {
view.destroy();
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
click(x, y) takes CSS pixel coordinates. typeText(text) commits UTF-8 text
into the focused Lynx input. pressKey(key) supports Backspace, Delete,
Enter, ArrowLeft, ArrowRight, ArrowUp, and ArrowDown.
DebugRouter OpenCard
Use OpenCard managers when the page should be opened by DebugRouter instead of an initial CLI/API template load.
const {
HeadlessOpenCardManager,
WindowedOpenCardManager,
LynxEnv,
} = require('@lynx-js/node-lynx');
LynxEnv.init();
LynxEnv.setAppInfo(['App', 'AppVersion'], ['LynxPlayground', '0.0.2']);
const OpenCardManager =
process.platform === 'darwin' ? WindowedOpenCardManager : HeadlessOpenCardManager;
const openCards = new OpenCardManager({
view: { width: 268, height: 469, devicePixelRatio: 2, timeoutMs: 30000 },
onCardLoaded(card) {
console.log(`opened ${card.url}`);
},
onCardError(error, card) {
console.error(`failed ${card.url}: ${error.message}`);
},
});
openCards.install();
process.once('SIGINT', () => {
openCards.dispose();
process.exit(0);
});
install() registers a global OpenCard callback. Call dispose() when the
session ends so the callback and any current card are cleaned up.
By default install() only opens public http:// or https:// URLs and
re-checks every redirect, because any peer that can reach DebugRouter can send
OpenCard. Pass allowAnySource: true to also open local files and loopback or
private hosts. open(url) is not restricted.
Screenshot Timing
waitForFrame() means a frame was submitted; it does not guarantee every image
resource has decoded or repainted. For image-heavy screenshot checks, pass
screenshot({ settleMs }), use CLI --screenshot-delay, or wait for a
page-level ready signal before capturing.
Development
From the repository root:
pnpm --filter @lynx-js/node-lynx exec tsc --noEmit
pnpm --filter @lynx-js/node-lynx run test
pnpm --filter @lynx-js/node-lynx run test:gallery
On macOS, node test/windowed_lifecycle.js from the package directory checks
rendering during close, direct destruction, pending waiters, and repeated
destruction across 36 windows in three child processes. Set
NODE_LYNX_WINDOWED_LIFECYCLE_ITERATIONS to increase the windows per child.
Synthetic mouse and keyboard input is opt-in because it requires access to
desktop input services. Set NODE_LYNX_WINDOWED_INPUT_TESTS=1 to include it in
the lifecycle test on a desktop session.