javascript 如何向puppeteer公开一个包含一堆函数定义(或方法)的对象?

ffx8fchx  于 2023-02-18  发布在  Java
关注(0)|答案(2)|浏览(95)

问题

我如何向puppeteer公开一个包含一堆方法的对象?如果可能的话,我尝试在page.evaluate中保留父对象和方法(即foo.one)的定义。换句话说,我正在寻找console.log(foo.one('world')),这样类型化,以返回world

背景

foo是一个库容器,它返回一大堆(相对而言)纯函数。这些函数在主脚本上下文中和puppeteer浏览器中都需要。我不希望在page.evaluate中重新定义它们,而是将整个"包"传递给page.evaluate以实现存储库可读性/维护。尽管如此,正如下面的一个答案所建议的,从foo中迭代方法并将它们单独暴露给不同名称的puppeteer并不是一个糟糕的选择,只是需要在page.evaluate中重新定义,这是我试图避免的。

预期与实际

让我们假设一个立即调用的函数返回一个带有一系列函数定义作为属性的对象。当试图将这个IIFE(或对象)传递给puppeteer页面时,我收到了以下错误:

import puppeteer from 'puppeteer'

const foo = (()=>{
    const one = (msg) => console.log('1) ' + msg)
    const two = (msg) => console.log('2) ' + msg)
    const three = (msg) => console.log('3) ' + msg)
    return {one, two, three}
})()

const browser = await puppeteer.launch().catch(err => `Browser not launched properly: ${err}`)
const page = await browser.newPage()
page.on('console', (msg) => console.log('PUPPETEER:', msg._text)); // Pipe puppeteer console to local console

await page.evaluate((foo)=>{
    console.log('hello')
    console.log(foo.one('world'))
},foo)

browser.close()

// Error: Evaluation failed: TypeError: foo.one is not a function

当我尝试使用page.exposeFunction时收到一个错误。这是意料之中的,因为foo是一个对象。

page.exposeFunction('foo',foo)

// Error: Failed to add page binding with name foo: [object Object] is not a function or a module with a default export.

定义浏览器页面内函数的控件用例按预期工作:

import puppeteer from 'puppeteer'

const browser = await puppeteer.launch().catch(err => `Browser not launched properly: ${err}`)
const page = await browser.newPage()
page.on('console', (msg) => console.log('PUPPETEER:', msg._text)); // Pipe puppeteer console to local console

await page.evaluate(()=>{
    const bar = (()=>{
        const one = (msg) => console.log('1) ' + msg)
        const two = (msg) => console.log('2) ' + msg)
        const three = (msg) => console.log('3) ' + msg)
        return {one, two, three}
    })()
    console.log('hello')
    console.log(bar.one('world'))
})
browser.close()

// PUPPETEER: hello
// PUPPETEER: 1) world

更新(2022年5月19日)

  • 根据我的使用情形测试以下解决方案后添加快速更新 *

提醒:我正在尝试将一个外部定义的utilities.js库传递给浏览器,以便它可以有条件地与页面数据交互并相应地导航。
我欢迎任何意见或反馈!

添加脚本标记()

不幸的是,在我的情况下,传递实用函数的node.js模块非常困难,当模块包含export语句或对象时,addScriptTag()失败。
在这个例子中,我得到了Error: Evaluation failed: ReferenceError: {x} is not defined。我创建了一个中间函数来删除export语句。这很混乱,但似乎起作用了。然而,我的一些函数是IIFE,它返回一个带有方法的对象。而且至少可以说,对象被证明很难通过addScriptTag()来处理。

冗余代码

我认为对于较小的项目来说,最简单和最好的选择是在puppeteer上下文中重新声明对象/函数。我讨厌重新定义东西,但它能按预期工作。

导入()

正如@ggorlen所建议的,我可以在另一台服务器上托管实用程序函数,这可以从node.js和puppeteer环境中获得,但我仍然必须导入库两次:一次是在node.js环境中,一次是在浏览器上下文中,但在我的情况下,这可能比重新声明几十个函数和对象要好。

8ehkhllq

8ehkhllq1#

调用时可能会重复,但您可以迭代对象并为每个对象调用page.exposeFunction

page.exposeFunction('fooOne', foo.one);
// ...

for (const [fnName, fn] of Object.entries(foo)) {
  page.exposeFunction(fnName, fn);
}

如果这些函数都可以在页面的上下文中执行,那么在page.evaluate中简单地定义它们也可以。

page.evaluate(() => {
    window.foo = (()=>{
        const one = (msg) => console.log('1) ' + msg)
        const two = (msg) => console.log('2) ' + msg)
        const three = (msg) => console.log('3) ' + msg)
        return {one, two, three}
    })();
});

如果您 * 必须 * 在页面上下文中只有一个包含函数的对象,则可以首先使用page.evaluate将对象放在窗口上,然后在主脚本中,对对象的键和值进行串行异步循环,该对象:

  • 使用函数调用page.exposeFunction('fnToMove'
  • 调用page.evaluatepage.evaluatefnToMove赋给先前创建的对象上的所需属性

但这有点复杂。除非你真的需要,否则我不会推荐它。

yb3bgrhw

yb3bgrhw2#

这是一种推测,因为用例在这里非常重要。例如,exposeFunction意味着代码在Node上下文中运行,因此这涉及到进程间通信和数据序列化和反序列化,这似乎不适合完全在浏览器中处理数据的用例。不过,如果有特定于Node的任务,如读取文件或发出跨源请求,这是合适的。
另一方面,如果您希望添加代码以供浏览器在控制台上下文中调用,则一种可伸缩的方法是将库放入脚本中,然后使用page.addScriptTag("./your-lib.js")将其附加到窗口。使用捆绑器构建库以实现浏览器兼容性,或者手动附加它。如果您还希望将其导入到Node中,请使用module.exports
例如:

foo.js

;(function () {
  var foo = {
    one: function () { return 1; },
    two: function () { return 2; },
    // ...
  };

  if (typeof module === "object" &&
      typeof module.exports === "object") {
    module.exports = foo;
  }

  if (typeof window === "object") {
    window.foo = foo;
  }
})();

一米四分一秒

const puppeteer = require("puppeteer"); // ^13.5.1
const foo = require("./foo"); // also use it in Node if you want...

let browser;
(async () => {
  browser = await puppeteer.launch({headless: true});
  const [page] = await browser.pages();
  await page.addScriptTag({path: "./foo.js"});
  console.log(foo.one()); // => 1
  console.log(await page.evaluate(() => foo.two())); // => 2
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close())
;

addScriptTag也适用于模块和原始JS字符串。例如,这也适用:

await page.addScriptTag({content: `
  window.foo = {two() { return 2; }};
`});
console.log(await page.evaluate(() => foo.two())); // => 2

一个比较有技巧的方法是将Node中的函数对象字符串化,我不推荐这样做,但这是可能的:

const foo = {
  one() { return 1; },
  two() { return 2; },
};
const fooToWindow = `window.foo = {
  ${Object.values(foo).map(fn => fn.toString())}
}`;
await page.addScriptTag({content: fooToWindow});
console.log(await page.evaluate(() => foo.two())); // => 2

另请参见是否有方法在Puppeteer evaluate中使用类?

相关问题