Node 6 support (#484)
This patch: - introduces a transpiler which substitutes async/await logic with generators. - starts using the transpiler to generate a node6-compatible version of puppeteer - introduces a runtime-check to decide which version of code to use Fixes #316.
This commit is contained in:
parent
46115f9182
commit
9212863b92
@ -1,2 +1,3 @@
|
|||||||
third_party/*
|
third_party/*
|
||||||
utils/doclint/check_public_api/test/
|
utils/doclint/check_public_api/test/
|
||||||
|
node6/*
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
.vscode
|
.vscode
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
/node6
|
||||||
|
18
.travis.yml
18
.travis.yml
@ -1,13 +1,10 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
|
||||||
- "7"
|
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
# This is required to run new chrome on old trusty
|
# This is required to run new chrome on old trusty
|
||||||
- libnss3
|
- libnss3
|
||||||
env:
|
|
||||||
cache:
|
cache:
|
||||||
yarn: true
|
yarn: true
|
||||||
directories:
|
directories:
|
||||||
@ -16,6 +13,15 @@ install:
|
|||||||
- yarn install
|
- yarn install
|
||||||
# puppeteer's install script downloads Chrome
|
# puppeteer's install script downloads Chrome
|
||||||
script:
|
script:
|
||||||
- yarn run lint
|
- 'if [ "$NODE7" = "true" ]; then yarn run lint; fi'
|
||||||
- yarn run coverage
|
- 'if [ "$NODE7" = "true" ]; then yarn run coverage; fi'
|
||||||
- yarn run test-phantom
|
- 'if [ "$NODE7" = "true" ]; then yarn run test-phantom; fi'
|
||||||
|
- 'if [ "$NODE6" = "true" ]; then yarn run node6; fi'
|
||||||
|
- 'if [ "$NODE6" = "true" ]; then yarn run test-node6; fi'
|
||||||
|
- 'if [ "$NODE6" = "true" ]; then yarn run node6-sanity; fi'
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- node_js: "7"
|
||||||
|
env: NODE7=true
|
||||||
|
- node_js: "6.4.0"
|
||||||
|
env: NODE6=true
|
||||||
|
10
index.js
10
index.js
@ -14,4 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = require('./lib/Puppeteer');
|
// If node does not support async await, use the compiled version.
|
||||||
|
let folder = 'lib';
|
||||||
|
try {
|
||||||
|
new Function('async function test(){await 1}');
|
||||||
|
} catch (error) {
|
||||||
|
folder = 'node6';
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = require(`./${folder}/Puppeteer`);
|
||||||
|
10
package.json
10
package.json
@ -5,18 +5,22 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "github:GoogleChrome/puppeteer",
|
"repository": "github:GoogleChrome/puppeteer",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=7.10.0"
|
"node": ">=6.4.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"unit": "jasmine test/test.js",
|
"unit": "jasmine test/test.js",
|
||||||
"debug-unit": "DEBUG_TEST=true node --inspect-brk ./node_modules/.bin/jasmine test/test.js",
|
"debug-unit": "DEBUG_TEST=true node --inspect-brk ./node_modules/.bin/jasmine test/test.js",
|
||||||
"test-phantom": "python third_party/phantomjs/test/run-tests.py",
|
"test-phantom": "python third_party/phantomjs/test/run-tests.py",
|
||||||
"test-doclint": "jasmine utils/doclint/check_public_api/test/test.js && jasmine utils/doclint/preprocessor/test.js",
|
"test-doclint": "jasmine utils/doclint/check_public_api/test/test.js && jasmine utils/doclint/preprocessor/test.js",
|
||||||
"test": "npm run lint --silent && npm run coverage && npm run test-phantom && npm run test-doclint",
|
"test": "npm run lint --silent && npm run coverage && npm run test-phantom && npm run test-doclint && npm run test-node6",
|
||||||
"install": "node install.js",
|
"install": "node install.js",
|
||||||
"lint": "([ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .) && npm run doc",
|
"lint": "([ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .) && npm run doc",
|
||||||
"doc": "node utils/doclint/cli.js",
|
"doc": "node utils/doclint/cli.js",
|
||||||
"coverage": "COVERAGE=true npm run unit"
|
"coverage": "COVERAGE=true npm run unit",
|
||||||
|
"node6": "node utils/node6-transform/index.js",
|
||||||
|
"test-node6": "jasmine utils/node6-transform/test/test.js",
|
||||||
|
"build": "npm run node6",
|
||||||
|
"node6-sanity": "jasmine test/sanity.js"
|
||||||
},
|
},
|
||||||
"author": "The Chromium Authors",
|
"author": "The Chromium Authors",
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
|
31
test/sanity.js
Normal file
31
test/sanity.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('Puppeteer Sanity', function() {
|
||||||
|
it('should not be insane', function(done) {
|
||||||
|
const puppeteer = require('..');
|
||||||
|
puppeteer.launch().then(browser => {
|
||||||
|
browser.newPage().then(page => {
|
||||||
|
page.goto('data:text/html,hello').then(() => {
|
||||||
|
page.evaluate(() => document.body.textContent).then(content => {
|
||||||
|
expect(content).toBe('hello');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -85,7 +85,7 @@ module.exports = {
|
|||||||
* @param {?function(number, number)} progressCallback
|
* @param {?function(number, number)} progressCallback
|
||||||
* @return {!Promise}
|
* @return {!Promise}
|
||||||
*/
|
*/
|
||||||
downloadRevision: async function(platform, revision, progressCallback) {
|
downloadRevision: function(platform, revision, progressCallback) {
|
||||||
let url = downloadURLs[platform];
|
let url = downloadURLs[platform];
|
||||||
console.assert(url, `Unsupported platform: ${platform}`);
|
console.assert(url, `Unsupported platform: ${platform}`);
|
||||||
url = util.format(url, revision);
|
url = util.format(url, revision);
|
||||||
@ -93,15 +93,15 @@ module.exports = {
|
|||||||
const folderPath = getFolderPath(platform, revision);
|
const folderPath = getFolderPath(platform, revision);
|
||||||
if (fs.existsSync(folderPath))
|
if (fs.existsSync(folderPath))
|
||||||
return;
|
return;
|
||||||
try {
|
if (!fs.existsSync(DOWNLOADS_FOLDER))
|
||||||
if (!fs.existsSync(DOWNLOADS_FOLDER))
|
fs.mkdirSync(DOWNLOADS_FOLDER);
|
||||||
fs.mkdirSync(DOWNLOADS_FOLDER);
|
return downloadFile(url, zipPath, progressCallback)
|
||||||
await downloadFile(url, zipPath, progressCallback);
|
.then(() => extractZip(zipPath, folderPath))
|
||||||
await extractZip(zipPath, folderPath);
|
.catch(err => err)
|
||||||
} finally {
|
.then(() => {
|
||||||
if (fs.existsSync(zipPath))
|
if (fs.existsSync(zipPath))
|
||||||
fs.unlinkSync(zipPath);
|
fs.unlinkSync(zipPath);
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,12 +117,13 @@ module.exports = {
|
|||||||
/**
|
/**
|
||||||
* @param {string} platform
|
* @param {string} platform
|
||||||
* @param {string} revision
|
* @param {string} revision
|
||||||
|
* @return {!Promise}
|
||||||
*/
|
*/
|
||||||
removeRevision: async function(platform, revision) {
|
removeRevision: function(platform, revision) {
|
||||||
console.assert(downloadURLs[platform], `Unsupported platform: ${platform}`);
|
console.assert(downloadURLs[platform], `Unsupported platform: ${platform}`);
|
||||||
const folderPath = getFolderPath(platform, revision);
|
const folderPath = getFolderPath(platform, revision);
|
||||||
console.assert(fs.existsSync(folderPath));
|
console.assert(fs.existsSync(folderPath));
|
||||||
await new Promise(fulfill => removeRecursive(folderPath, fulfill));
|
return new Promise(fulfill => removeRecursive(folderPath, fulfill));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const esprima = require('esprima');
|
const esprima = require('esprima');
|
||||||
const ESTreeWalker = require('./ESTreeWalker');
|
const ESTreeWalker = require('../../ESTreeWalker');
|
||||||
const Documentation = require('./Documentation');
|
const Documentation = require('./Documentation');
|
||||||
|
|
||||||
class JSOutline {
|
class JSOutline {
|
||||||
|
114
utils/node6-transform/TransformAsyncFunctions.js
Normal file
114
utils/node6-transform/TransformAsyncFunctions.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const esprima = require('esprima');
|
||||||
|
const ESTreeWalker = require('../ESTreeWalker');
|
||||||
|
|
||||||
|
// This is converted from Babel's "transform-async-to-generator"
|
||||||
|
// https://babeljs.io/docs/plugins/transform-async-to-generator/
|
||||||
|
const asyncToGenerator = fn => {
|
||||||
|
const gen = fn.call(this);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
function step(key, arg) {
|
||||||
|
let info, value;
|
||||||
|
try {
|
||||||
|
info = gen[key](arg);
|
||||||
|
value = info.value;
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (info.done) {
|
||||||
|
resolve(value);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(value).then(
|
||||||
|
value => {
|
||||||
|
step('next', value);
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
step('throw', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return step('next');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
function transformAsyncFunctions(text) {
|
||||||
|
const edits = [];
|
||||||
|
|
||||||
|
const ast = esprima.parseScript(text, {range: true});
|
||||||
|
const walker = new ESTreeWalker(node => {
|
||||||
|
if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression')
|
||||||
|
onFunction(node);
|
||||||
|
else if (node.type === 'AwaitExpression')
|
||||||
|
onAwait(node);
|
||||||
|
});
|
||||||
|
walker.walk(ast);
|
||||||
|
|
||||||
|
edits.sort((a, b) => b.from - a.from);
|
||||||
|
for (const {replacement, from, to} of edits)
|
||||||
|
text = text.substring(0, from) + replacement + text.substring(to);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ESTree.Node} node
|
||||||
|
*/
|
||||||
|
function onFunction(node) {
|
||||||
|
if (!node.async) return;
|
||||||
|
|
||||||
|
let range;
|
||||||
|
if (node.parent.type === 'MethodDefinition')
|
||||||
|
range = node.parent.range;
|
||||||
|
else
|
||||||
|
range = node.range;
|
||||||
|
const index = text.substring(range[0], range[1]).indexOf('async') + range[0];
|
||||||
|
insertText(index, index + 'async'.length, '/* async */');
|
||||||
|
|
||||||
|
let before = `{return (${asyncToGenerator.toString()})(function*()`;
|
||||||
|
let after = `);}`;
|
||||||
|
if (node.body.type !== 'BlockStatement') {
|
||||||
|
before += `{ return `;
|
||||||
|
after = `; }` + after;
|
||||||
|
}
|
||||||
|
insertText(node.body.range[0], node.body.range[0], before);
|
||||||
|
insertText(node.body.range[1], node.body.range[1], after);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ESTree.Node} node
|
||||||
|
*/
|
||||||
|
function onAwait(node) {
|
||||||
|
const index = text.substring(node.range[0], node.range[1]).indexOf('await') + node.range[0];
|
||||||
|
insertText(index, index + 'await'.length, '(yield');
|
||||||
|
insertText(node.range[1], node.range[1], ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} from
|
||||||
|
* @param {number} to
|
||||||
|
* @param {string} replacement
|
||||||
|
*/
|
||||||
|
function insertText(from, to, replacement) {
|
||||||
|
edits.push({from, to, replacement});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = transformAsyncFunctions;
|
36
utils/node6-transform/index.js
Normal file
36
utils/node6-transform/index.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const removeRecursive = require('rimraf').sync;
|
||||||
|
const transformAsyncFunctions = require('./TransformAsyncFunctions');
|
||||||
|
|
||||||
|
const dirPath = path.join(__dirname, '..', '..', 'lib');
|
||||||
|
const outPath = path.join(__dirname, '..', '..', 'node6');
|
||||||
|
const fileNames = fs.readdirSync(dirPath);
|
||||||
|
const filePaths = fileNames.filter(fileName => fileName.endsWith('.js'));
|
||||||
|
|
||||||
|
if (fs.existsSync(outPath))
|
||||||
|
removeRecursive(outPath);
|
||||||
|
fs.mkdirSync(outPath);
|
||||||
|
|
||||||
|
filePaths.forEach(filePath => {
|
||||||
|
const content = fs.readFileSync(path.join(dirPath, filePath), 'utf8');
|
||||||
|
const output = transformAsyncFunctions(content);
|
||||||
|
fs.writeFileSync(path.resolve(outPath, filePath), output);
|
||||||
|
});
|
||||||
|
|
67
utils/node6-transform/test/test.js
Normal file
67
utils/node6-transform/test/test.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
const transformAsyncFunctions = require('../TransformAsyncFunctions');
|
||||||
|
|
||||||
|
describe('TransformAsyncFunctions', function() {
|
||||||
|
it('should convert a function expression', function(done) {
|
||||||
|
const input = `(async function(){ return 123 })()`;
|
||||||
|
const output = eval(transformAsyncFunctions(input));
|
||||||
|
expect(output instanceof Promise).toBe(true);
|
||||||
|
output.then(result => expect(result).toBe(123)).then(done);
|
||||||
|
});
|
||||||
|
it('should convert an arrow function', function(done) {
|
||||||
|
const input = `(async () => 123)()`;
|
||||||
|
const output = eval(transformAsyncFunctions(input));
|
||||||
|
expect(output instanceof Promise).toBe(true);
|
||||||
|
output.then(result => expect(result).toBe(123)).then(done);
|
||||||
|
});
|
||||||
|
it('should convert an arrow function with curly braces', function(done) {
|
||||||
|
const input = `(async () => { return 123 })()`;
|
||||||
|
const output = eval(transformAsyncFunctions(input));
|
||||||
|
expect(output instanceof Promise).toBe(true);
|
||||||
|
output.then(result => expect(result).toBe(123)).then(done);
|
||||||
|
});
|
||||||
|
it('should convert a function declaration', function(done) {
|
||||||
|
const input = `async function f(){ return 123; } f();`;
|
||||||
|
const output = eval(transformAsyncFunctions(input));
|
||||||
|
expect(output instanceof Promise).toBe(true);
|
||||||
|
output.then(result => expect(result).toBe(123)).then(done);
|
||||||
|
});
|
||||||
|
it('should convert await', function(done) {
|
||||||
|
const input = `async function f(){ return 23 + await Promise.resolve(100); } f();`;
|
||||||
|
const output = eval(transformAsyncFunctions(input));
|
||||||
|
expect(output instanceof Promise).toBe(true);
|
||||||
|
output.then(result => expect(result).toBe(123)).then(done);
|
||||||
|
});
|
||||||
|
it('should convert method', function(done) {
|
||||||
|
const input = `class X{async f() { return 123 }} (new X()).f();`;
|
||||||
|
const output = eval(transformAsyncFunctions(input));
|
||||||
|
expect(output instanceof Promise).toBe(true);
|
||||||
|
output.then(result => expect(result).toBe(123)).then(done);
|
||||||
|
});
|
||||||
|
it('should pass arguments', function(done) {
|
||||||
|
const input = `(async function(a, b){ return await a + await b })(Promise.resolve(100), 23)`;
|
||||||
|
const output = eval(transformAsyncFunctions(input));
|
||||||
|
expect(output instanceof Promise).toBe(true);
|
||||||
|
output.then(result => expect(result).toBe(123)).then(done);
|
||||||
|
});
|
||||||
|
it('should still work across eval', function(done) {
|
||||||
|
const input = `var str = (async function(){ return 123; }).toString(); eval('(' + str + ')')();`;
|
||||||
|
const output = eval(transformAsyncFunctions(input));
|
||||||
|
expect(output instanceof Promise).toBe(true);
|
||||||
|
output.then(result => expect(result).toBe(123)).then(done);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user