@browserless/function

中文文档
14.1.0发布于 2026/10/28MITCommonJS近 30 天下载 4246

Execute arbitrary JavaScript code in a secure browser sandbox with access to page DOM and network.

原始使用文档
文档目录15 节
  1. Install
  2. About
  3. What this package does
  4. Usage
  5. Accessing the page
  6. Available context
  7. Result object
  8. Teardown
  9. extendPage
  10. hostPage
  11. Options
  12. Examples
  13. How it fits in the monorepo
  14. Dependencies
  15. License
browserless browserless

Powered by microlink.io Last version Coverage Status NPM Status

@browserless/function: Run arbitrary JavaScript inside a browser sandbox.

See the function section on our website for more information.

Install

Using npm:

npm install @browserless/function --save

About

This package provides a secure sandbox for running arbitrary JavaScript code with runtime access to a browser page. It executes user-provided functions in an isolated VM environment, with optional access to Puppeteer's page API.

What this package does

The @browserless/function package allows you to:

  • Execute arbitrary JavaScript in a secure, isolated VM sandbox
  • Access the browser page from within the sandbox for DOM manipulation
  • Capture console output and execution profiling data
  • Pass custom data to the sandboxed function at runtime
  • Handle errors gracefully with structured result objects

Usage

First, create a function instance by calling the factory:

const createFunction = require('@browserless/function')()

You can optionally pass a tmpdir for the sandbox working directory:

const createFunction = require('@browserless/function')({ tmpdir: '/tmp/functions' })

Then use it to run arbitrary JavaScript:

// Simple function without page access
const code = ({ query }) => query.value * 2

const myFn = createFunction(code)
const result = await myFn('https://example.com', { query: { value: 21 } })

console.log(result)
// => { isFulfilled: true, value: 42, profiling: {...}, logging: {...} }

Accessing the page

When your code references page, browserless automatically provides access to the Puppeteer page:

const createFunction = require('@browserless/function')()

// Function with page access
const code = async ({ page }) => {
  const title = await page.title()
  const content = await page.evaluate(() => document.body.innerText)
  return { title, content }
}

const scraper = createFunction(code)
const result = await scraper('https://example.com')

console.log(result)
// => { isFulfilled: true, value: { title: 'Example', content: '...' }, ... }

Available context

The sandboxed function receives these properties:

PropertyDescription
urlTarget URL passed to the function (no browser required)
pagePuppeteer Page object (if referenced in code)
deviceDevice descriptor with userAgent and viewport
...optsAny custom options passed at runtime

Pass values meant for your function under args. They are kept apart from the runtime options, so a value named code, source or vmOpts reaches your function instead of changing how it runs:

const myFn = createFunction(({ code }) => code, { args: { code: 'abc' } })
await myFn('https://example.com') // => { isFulfilled: true, value: 'abc', ... }

// per-call args merge over the ones given at creation
await myFn('https://example.com', { args: { code: 'xyz' } }) // value: 'xyz'

args win over other custom options with the same name. response and url come from the runtime and win over args. So does page, when the function runs with one; a function that does not use page still receives args.page. device comes from the runtime, and args cannot set it.

Result object

The function returns a structured result:

PropertyDescription
isFulfilledtrue if execution succeeded, false if error
valueReturn value (success) or error object (failure)
profilingExecution timing and resource usage (see below)
loggingCaptured console output (log, warn, error, etc.)

Profiling

The profiling object contains phased timing and resource data:

PropertyDescription
phases.compileTime to compile the sandbox script (ms)
phases.spawnTime to spawn the child process (ms)
phases.runTime executing the user function (ms)
phases.totalTotal wall-clock time (ms)
cpuCPU time consumed (ms)
memoryPeak memory usage (bytes)

Teardown

Call .teardown() on the factory instance to clean up sandbox resources:

const createFunction = require('@browserless/function')({ tmpdir: '/tmp/functions' })

// ... use createFunction ...

await createFunction.teardown()

extendPage

Attach extra methods on page. JSON values become async getters. Functions are inlined as async methods (this is the page; use a function, not an arrow, when you need this). If the user function only uses these methods, Chromium is not started:

const myFn = createFunction(({ page }) => page.html(), {
  extendPage: { url, html }
})

const result = await myFn('https://example.com')
// => { isFulfilled: true, value: html, ... }

hostPage

Attach methods on page that the host resolves when the user function calls them, rather than values decided before it runs. Work the function never reaches costs nothing:

const myFn = createFunction(({ page }) => page.content(), {
  hostPage: { content: () => fetchThePage(url) }
})

const result = await myFn('https://example.com')
// => { isFulfilled: true, value: html, ... }, and fetchThePage ran once

fetchThePage runs because the function awaited content. A function returning 420 without touching page never triggers it, and neither does a branch it does not take:

const myFn = createFunction('async ({ page }) => (false ? await page.content() : "skipped")', {
  hostPage: { content: () => fetchThePage(url) }
})

const result = await myFn('https://example.com')
// => { isFulfilled: true, value: 'skipped', ... }, and fetchThePage never ran

Unlike extendPage, the method itself stays on the host rather than being serialized into the isolate; the call travels over the channel at the moment it is made. Its arguments and its result still cross that channel, so both have to be values the channel can carry: a BigInt, for instance, rejects the call rather than resolving it.

Both kinds can sit on the same page, and either counts as satisfying that method, so a function using only these does not start Chromium. A name present on both is the extendPage value, and that host method is not reachable.

The same method and arguments resolve once per run. 32 distinct calls are allowed per run; vmOpts.maxHostCalls changes the cap. The isolate runs untrusted code and can reach the channel directly, so treat every argument as untrusted input. Every hostPage value has to be a function.

Options

const myFn = createFunction(code, {
  // Browserless instance factory
  getBrowserless: () => require('browserless')(),
  
  // Attempts on the `getPage` path, where there is no context to replace and
  // this is the only retry. The default path takes its retry count from the
  // browserless context instead.
  retry: 2,
  
  // Execution timeout in milliseconds
  timeout: 30000,
  
  // Extra methods on `page`. JSON values become async getters; functions
  // are inlined as async methods. If the function only uses these methods,
  // Chromium is not started.
  extendPage: {
    url,
    html
  },

  // Methods on `page` the host resolves when the function calls them. The
  // method stays on the host and one the function never reaches is never
  // resolved, though arguments and results still cross the channel and must
  // be values it can carry. Counts as satisfying that method, like extendPage.
  hostPage: {
    content: () => fetchThePage(url)
  },

  // Options passed to browserless.goto()
  gotoOpts: {
    scripts: ['https://cdn.example.com/library.js'],
    waitUntil: 'networkidle0'
  },
  
  // VM sandbox options (passed to isolated-function)
  vmOpts: { /* ... */ },

  // Values for your function, spread into its argument. Never read as a
  // runtime option. Must be a plain object.
  args: { code: 'abc' },

  // Run against a page that is already navigated, instead of creating a
  // context and navigating. It replaces that path, so `getBrowserless` is not
  // consulted when it is set. No `goto` happens and the page is never closed:
  // whoever supplied it owns its lifetime. Pass `response` when you have it,
  // so the function still sees `_response`. `timeout` bounds how long the
  // caller waits. It leaves the page open, and the snippet plus its isolate
  // subprocess keep running until the snippet returns or the page is closed.
  // `device` and `response` are optional. Without a device, the `{ userAgent,
  // viewport }` a snippet sees is read off the page, so it matches what the
  // default path reports; pass your own to override it.
  getPage: async () => ({ page, device, response })
})

Reusing a page the caller already loaded, so the target is fetched once. getPage replaces the context-and-navigate path, so getBrowserless is not consulted on this call:

const { page, response } = await somethingThatAlreadyNavigated(url)

const result = await createFunction(code, {
  getPage: async () => ({ page, response })
})(url)

await page.close()

The snippet runs in an isolate connected over browserWSEndpoint, so page.browser() reaches every page on that browser, not only the one it was handed. strictTarget decides which page that is, not what it can reach. This was already true of the navigating path, and it matters more once one context is shared across tasks: treat sharing as a trust boundary this package cannot enforce for you.

timeout rejects the call and leaves the page open. The snippet and its isolate subprocess keep running, so treat the page as busy: leave it alone until that work finishes, or close it. Closing the page ends the snippet.

Retaining a page from browserless requires keepPage, since evaluate closes its page as soon as it resolves:

let page
await context.evaluate(
  currentPage => {
    page = currentPage
    return currentPage.content()
  },
  { keepPage: true }
)(url)

Retaining a page restarts its close watchdog. The new window is the evaluate / withPage timeout, measured from handover. Finish or close the page before that window ends. A later getPage call has its own timeout; the watchdog still closes the page when the evaluate window ends, including while that call is in progress. Set the evaluate timeout long enough to cover the follow-up work.

Examples

Interact with page elements

const createFunction = require('@browserless/function')()

const code = async ({ page }) => {
  await page.waitForSelector('button.submit')
  await page.type('input', '[email protected]', { delay: 200 })
  await page.click('button.submit')
  await page.waitForNavigation()
  return page.title()
}

const clickAndGetTitle = createFunction(code)
const result = await clickAndGetTitle('https://example.com')

Inject external scripts

const createFunction = require('@browserless/function')()

const code = ({ page }) => page.evaluate('jQuery.fn.jquery')

const getjQueryVersion = createFunction(code, {
  gotoOpts: {
    scripts: ['https://code.jquery.com/jquery-3.6.0.min.js']
  }
})

const result = await getjQueryVersion('https://example.com')
// => { isFulfilled: true, value: '3.6.0', ... }

Use npm modules in sandbox

const createFunction = require('@browserless/function')()

const code = async ({ page }) => {
  const _ = require('lodash')
  const text = await page.evaluate(() => document.body.innerText)
  return _.words(text).length
}

const countWords = createFunction(code)
const result = await countWords('https://example.com')

Handle errors

const createFunction = require('@browserless/function')()

const code = () => {
  throw new Error('Something went wrong')
}

const myFn = createFunction(code)
const result = await myFn('https://example.com')

console.log(result.isFulfilled) // => false
console.log(result.value.message) // => 'Something went wrong'

How it fits in the monorepo

This is an extended functionality package. It is not wired into browserless core — install it when you need a sandbox that can touch a page.

Dependencies

PackagePurpose
@browserless/errorsError normalization and typed errors
isolated-functionSecure sandboxed execution via child processes
require-one-ofAuto-detects browserless installation
acorn / acorn-walkAST parsing to detect page usage in code

License

@browserless/function © Microlink, released under the MIT License.
Authored and maintained by Microlink with help from contributors.

The logo has been designed by xinh studio.

microlink.io · GitHub microlinkhq · X @microlinkhq

版本与发布记录

@browserless/function 共发布 235 个版本,首次发布于 2021/03/18;过去 12 个月发布了 74 个版本。

版本发布日期距今
14.1.0latest2026/10/2811 天前
14.0.302026/10/2316 天前
14.0.292026/10/2316 天前
14.0.252026/10/1425 天前
14.0.242026/10/1425 天前
14.0.222026/10/1326 天前

关于 @browserless/function 的常见问题

@browserless/function 是什么?

Execute arbitrary JavaScript code in a secure browser sandbox with access to page DOM and network.

如何安装 @browserless/function?

在项目目录运行 npm install @browserless/function,或使用 pnpm add / yarn add / bun add。

@browserless/function 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/browserless__function 是否存在。

@browserless/function 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@browserless/function 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >= 20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@browserless/function 可以商用吗?

@browserless/function 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@browserless/function 的下载量有多少?

根据 npm 官方下载统计,@browserless/function 在 2026/10/09 – 2026/11/07 这 30 天内被下载 4,246 次,日均约 142 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@browserless/function 还在维护吗?

最新版本 14.1.0 发布于 2026/10/28(11 天前);至今共发布 235 个版本,其中过去 12 个月发布了 74 个;首次发布于 2021/03/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。