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/*
|
||||
utils/doclint/check_public_api/test/
|
||||
node6/*
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@
|
||||
*.pyc
|
||||
.vscode
|
||||
package-lock.json
|
||||
/node6
|
||||
|
18
.travis.yml
18
.travis.yml
@ -1,13 +1,10 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "7"
|
||||
dist: trusty
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# This is required to run new chrome on old trusty
|
||||
- libnss3
|
||||
env:
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
@ -16,6 +13,15 @@ install:
|
||||
- yarn install
|
||||
# puppeteer's install script downloads Chrome
|
||||
script:
|
||||
- yarn run lint
|
||||
- yarn run coverage
|
||||
- yarn run test-phantom
|
||||
- 'if [ "$NODE7" = "true" ]; then yarn run lint; fi'
|
||||
- 'if [ "$NODE7" = "true" ]; then yarn run coverage; fi'
|
||||
- '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.
|
||||
*/
|
||||
|
||||
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",
|
||||
"repository": "github:GoogleChrome/puppeteer",
|
||||
"engines": {
|
||||
"node": ">=7.10.0"
|
||||
"node": ">=6.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"unit": "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-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",
|
||||
"lint": "([ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .) && npm run doc",
|
||||
"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",
|
||||
"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
|
||||
* @return {!Promise}
|
||||
*/
|
||||
downloadRevision: async function(platform, revision, progressCallback) {
|
||||
downloadRevision: function(platform, revision, progressCallback) {
|
||||
let url = downloadURLs[platform];
|
||||
console.assert(url, `Unsupported platform: ${platform}`);
|
||||
url = util.format(url, revision);
|
||||
@ -93,15 +93,15 @@ module.exports = {
|
||||
const folderPath = getFolderPath(platform, revision);
|
||||
if (fs.existsSync(folderPath))
|
||||
return;
|
||||
try {
|
||||
if (!fs.existsSync(DOWNLOADS_FOLDER))
|
||||
fs.mkdirSync(DOWNLOADS_FOLDER);
|
||||
await downloadFile(url, zipPath, progressCallback);
|
||||
await extractZip(zipPath, folderPath);
|
||||
} finally {
|
||||
if (fs.existsSync(zipPath))
|
||||
fs.unlinkSync(zipPath);
|
||||
}
|
||||
if (!fs.existsSync(DOWNLOADS_FOLDER))
|
||||
fs.mkdirSync(DOWNLOADS_FOLDER);
|
||||
return downloadFile(url, zipPath, progressCallback)
|
||||
.then(() => extractZip(zipPath, folderPath))
|
||||
.catch(err => err)
|
||||
.then(() => {
|
||||
if (fs.existsSync(zipPath))
|
||||
fs.unlinkSync(zipPath);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -117,12 +117,13 @@ module.exports = {
|
||||
/**
|
||||
* @param {string} platform
|
||||
* @param {string} revision
|
||||
* @return {!Promise}
|
||||
*/
|
||||
removeRevision: async function(platform, revision) {
|
||||
removeRevision: function(platform, revision) {
|
||||
console.assert(downloadURLs[platform], `Unsupported platform: ${platform}`);
|
||||
const folderPath = getFolderPath(platform, revision);
|
||||
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 ESTreeWalker = require('./ESTreeWalker');
|
||||
const ESTreeWalker = require('../../ESTreeWalker');
|
||||
const Documentation = require('./Documentation');
|
||||
|
||||
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