現象
単体のCLIから起動すると成功するコードを、NuxtのserverMiddlewareとしてリクエスト経由で起動するとpage.goto
が動かない。
実行環境
- Windows7
- Node.js 11.0.0 (に上げたが変わらず)
- Puppeteer 1.9.0 (および
@next
も同様) - Nuxt.js 2.2.0
エラー出力
TypeError: Cannot read property 'on' of undefined
at Function.addEventListener (~\node_modules\puppeteer\lib\helper.js:165:13)
at new NavigatorWatcher (~\node_modules\puppeteer\lib\FrameManager.js:1146:14)
at FrameManager.navigateFrame (~\node_modules\puppeteer\lib\FrameManager.js:75:21)
at Frame.goto (~\node_modules\puppeteer\lib\FrameManager.js:404:37)
at Frame.<anonymous> (~\node_modules\puppeteer\lib\helper.js:145:23)
at Page.goto (~\node_modules\puppeteer\lib\Page.js:579:49)
at Page.<anonymous> (~\node_modules\puppeteer\lib\helper.js:145:23)
(中略: 呼び出し元スクリプト)
at process.internalTickCallback (internal/process/next_tick.js:77:7)
エラー箇所の原因
puppeteer\lib\Connection.js
の
/**
* @param {!CDPSession} session
* @return {!Connection}
*/
static fromSession(session) {
let connection = session._connection;
// TODO(lushnikov): move to flatten protocol to avoid this.
while (connection instanceof CDPSession)
connection = connection._connection;
return connection;
}
このループ文が、Nuxt経由の場合は、connectionがConnectionのインスタンスにも関わらず条件がtrueとなり掘り続けて、undefinedまで潜って終わるため。
console.log
で見るとConnectionなのにinstanceof CDPSession
が通る理由はprototypeに明るくないのでよくわからない…
コードの意味を汲み取れないが、潜り続ける問題を場当たり的に回避したコードを書くと、Nuxt経由でもうまく動いた。
/**
* @param {!CDPSession} session
* @return {!Connection}
*/
static fromSession(session) {
let connection = session._connection;
// TODO(lushnikov): move to flatten protocol to avoid this.
while (connection instanceof CDPSession) {
if (connection._connection) {
connection = connection._connection;
} else {
break;
}
}
return connection;
}
これで少なくとも返される値はundefinedではなくなる。
でもこれじゃnode_modulesのコードをいじるのでデプロイできないんですよね。
コード例
module.exports = async (req, res, next) => {
const puppeteer = require('puppeteer')
const browser = await puppeteer.launch({ headless: true, })
const page = await browser.newPage()
await page.goto('https://www.google.com/')
}
const serveStatic = require('serve-static')
module.exports = {
serverMiddleware: [{
path: '/api', handler: '~/api.js'
},]
}
nuxt で http://localhost:3000
に立てhttp://localhost:3000/api
にアクセスすると、api.jsが実行される。
調査
エラー的に似てそうなのはこれなのだが関係ないと考えている。
ただ、Nuxtが裏でどう取りまとめているのかを理解していないのでなにか無理解が原因の可能性もあるのではと思っている。
zero configurationで動くのは本当にすごい1がエラーが出たときにこまることを痛感。
どうする?
expressとpuppeteerの連携はいくつか見かけたので、Nuxtからexpressに乗り換えるかserverMiddlewareをexpressにしてそこからpuppeteer起動でなんとかならないかと考えている。
しかしすごく遠回りなのでモチベーションがまったく出ない。
追記
serverMiddlewareをexpressにしたが、こちらでも同様のエラーが出た。
そのままexpressのコードでexpress単体でサーバーを立てリクエストしたら成功したので…
nuxtとpuppeteerを使う場合はexpressをメインでnuxtをミドルウェアとして実装するのが正解っぽい。
こっちの方法を調べて移植してみる。
追記の追記
expressメインで動かしました。
components/
express.js
node_modules/
package.json
package-lock.json
pages/
store/
こういう構成で、基本はNuxtのもの。
const express = require('express')
const { Nuxt, Builder } = require('nuxt')
const puppeteer = require('puppeteer')
// Create express instnace
const app = express()
const bodyParser = require('body-parser')
app.use(bodyParser.json())
const config = require('./nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
const nuxt = new Nuxt(config)
if (config.dev) {
const builder = new Builder(nuxt)
builder.build()
}
app.post('/api', async (req, res) => {
console.log(req.body.id)
const browser = await puppeteer.launch({ headless: true, })
const page = await browser.newPage()
await page.goto('https://www.google.com/')
res.send('success')
})
// 後ろに持ってこないとexpressのルーティングが効かない?
app.use(nuxt.render)
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
ルーティングルールをまだ理解しきれてないが、望む動作は成功。
リクエストのparamを使いたい場合は、ちゃんとbodyParserを設定する。
app.use(nuxt.render)
を先に持ってくると/api
が404になってしまった。
-
serverMiddleware
を使わなければnuxt.config.js
も不要 ↩