devops: custom watch for bundle esbuild steps (#35914)

This commit is contained in:
Yury Semikhatsky 2025-05-09 12:19:44 -07:00 committed by GitHub
parent 9de2bd49e4
commit c5c9cca5ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 275 additions and 79 deletions

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
// @ts-check
const path = require('path');
const esbuild = require('esbuild');
/**
* @param {boolean} watchMode
* @returns {import('esbuild').BuildOptions}
*/
function esbuildOptions(watchMode) {
return {
entryPoints: [path.join(__dirname, 'src/index.ts')],
bundle: true,
outdir: path.join(__dirname, 'lib'),
format: 'cjs',
platform: 'node',
target: 'ES2019',
sourcemap: watchMode,
};
}
async function main() {
const watchMode = process.argv.includes('--watch');
const ctx = await esbuild.context(esbuildOptions(watchMode));
await ctx.rebuild();
if (watchMode)
await ctx.watch();
else
await ctx.dispose();
}
module.exports = { esbuildOptions };
if (require.main === module)
main();

View File

@ -25,8 +25,9 @@
"./package.json": "./package.json" "./package.json": "./package.json"
}, },
"scripts": { "scripts": {
"build": "esbuild ./src/index.ts --outdir=lib --format=cjs --bundle --platform=node --target=ES2019", "esbuild": "node build.js",
"watch": "esbuild ./src/index.ts --outdir=lib --format=cjs --bundle --platform=node --target=ES2019 --watch" "build": "npm run esbuild",
"watch": "npm run esbuild -- --watch"
}, },
"dependencies": { "dependencies": {
"playwright-core": "1.53.0-next" "playwright-core": "1.53.0-next"

View File

@ -21,31 +21,47 @@ const fs = require('fs');
const outdir = path.join(__dirname, '../../lib/utilsBundleImpl'); const outdir = path.join(__dirname, '../../lib/utilsBundleImpl');
if (!fs.existsSync(outdir)) function copyXdgOpen() {
fs.mkdirSync(outdir); if (!fs.existsSync(outdir))
fs.mkdirSync(outdir, { recursive: true });
{
// 'open' package requires 'xdg-open' binary to be present, which does not get bundled by esbuild. // 'open' package requires 'xdg-open' binary to be present, which does not get bundled by esbuild.
fs.copyFileSync(path.join(__dirname, 'node_modules/open/xdg-open'), path.join(outdir, 'xdg-open')); fs.copyFileSync(path.join(__dirname, 'node_modules/open/xdg-open'), path.join(outdir, 'xdg-open'));
console.log('==== Copied xdg-open to', path.join(outdir, 'xdg-open'));
} }
(async () => { /**
const ctx = await esbuild.context({ * @param {boolean} watchMode
* @returns {import('esbuild').BuildOptions}
*/
function esbuildOptions(watchMode) {
return {
entryPoints: [path.join(__dirname, 'src/utilsBundleImpl.ts')], entryPoints: [path.join(__dirname, 'src/utilsBundleImpl.ts')],
bundle: true, bundle: true,
outfile: path.join(outdir, 'index.js'), outfile: path.join(outdir, 'index.js'),
format: 'cjs', format: 'cjs',
platform: 'node', platform: 'node',
target: 'ES2019', target: 'ES2019',
sourcemap: process.argv.includes('--sourcemap'), sourcemap: watchMode,
minify: process.argv.includes('--minify'), minify: !watchMode,
}); };
}
async function main() {
copyXdgOpen();
const watchMode = process.argv.includes('--watch');
const ctx = await esbuild.context(esbuildOptions(watchMode));
await ctx.rebuild(); await ctx.rebuild();
if (process.argv.includes('--watch')) if (watchMode)
await ctx.watch(); await ctx.watch();
else else
await ctx.dispose(); await ctx.dispose();
})().catch(error => { }
console.error(error);
process.exit(1); module.exports = {
}); beforeEsbuild: copyXdgOpen,
esbuildOptions,
};
if (require.main === module)
main();

View File

@ -4,8 +4,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"esbuild": "node build.js", "esbuild": "node build.js",
"build": "npm run esbuild -- --minify", "build": "npm run esbuild",
"watch": "npm run esbuild -- --watch --sourcemap", "watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js" "generate-license": "node ../../../../utils/generate_third_party_notice.js"
}, },
"dependencies": { "dependencies": {

View File

@ -18,23 +18,34 @@
const path = require('path'); const path = require('path');
const esbuild = require('esbuild'); const esbuild = require('esbuild');
(async () => { /**
const ctx = await esbuild.context({ * @param {boolean} watchMode
* @returns {import('esbuild').BuildOptions}
*/
function esbuildOptions(watchMode) {
return {
entryPoints: [path.join(__dirname, 'src/zipBundleImpl.ts')], entryPoints: [path.join(__dirname, 'src/zipBundleImpl.ts')],
bundle: true, bundle: true,
outdir: path.join(__dirname, '../../lib'), outdir: path.join(__dirname, '../../lib'),
format: 'cjs', format: 'cjs',
platform: 'node', platform: 'node',
target: 'ES2019', target: 'ES2019',
sourcemap: process.argv.includes('--sourcemap'), sourcemap: watchMode,
minify: process.argv.includes('--minify'), minify: !watchMode,
}); };
}
async function main() {
const watchMode = process.argv.includes('--watch');
const ctx = await esbuild.context(esbuildOptions(watchMode));
await ctx.rebuild(); await ctx.rebuild();
if (process.argv.includes('--watch')) if (watchMode)
await ctx.watch(); await ctx.watch();
else else
await ctx.dispose(); await ctx.dispose();
})().catch(error => { }
console.error(error);
process.exit(1); module.exports = { esbuildOptions };
});
if (require.main === module)
main();

View File

@ -4,8 +4,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"esbuild": "node build.js", "esbuild": "node build.js",
"build": "npm run esbuild -- --minify", "build": "npm run esbuild",
"watch": "npm run esbuild -- --watch --sourcemap", "watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js" "generate-license": "node ../../../../utils/generate_third_party_notice.js"
}, },
"dependencies": { "dependencies": {

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
// @ts-check
const path = require('path');
const esbuild = require('esbuild');
/**
* @param {boolean} watchMode
* @returns {import('esbuild').BuildOptions}
*/
function esbuildOptions(watchMode) {
return {
entryPoints: [path.join(__dirname, 'src/babelBundleImpl.ts')],
external: ['playwright'],
bundle: true,
outdir: path.join(__dirname, '../../lib/transform'),
format: 'cjs',
platform: 'node',
target: 'ES2019',
sourcemap: watchMode,
minify: !watchMode,
};
}
async function main() {
const watchMode = process.argv.includes('--watch');
const ctx = await esbuild.context(esbuildOptions(watchMode));
await ctx.rebuild();
if (watchMode)
await ctx.watch();
else
await ctx.dispose();
}
module.exports = { esbuildOptions };
if (require.main === module)
main();

View File

@ -3,9 +3,9 @@
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"esbuild": "esbuild ./src/babelBundleImpl.ts --bundle --outdir=../../lib/transform --format=cjs --platform=node --target=ES2019 --external:playwright", "esbuild": "node build.js",
"build": "npm run esbuild -- --minify", "build": "npm run esbuild",
"watch": "npm run esbuild -- --watch --sourcemap", "watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js" "generate-license": "node ../../../../utils/generate_third_party_notice.js"
}, },
"dependencies": { "dependencies": {

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
// @ts-check
const path = require('path');
const esbuild = require('esbuild');
/**
* @param {boolean} watchMode
* @returns {import('esbuild').BuildOptions}
*/
function esbuildOptions(watchMode) {
return {
entryPoints: [path.join(__dirname, 'src/expectBundleImpl.ts')],
bundle: true,
outdir: path.join(__dirname, '../../lib/common'),
format: 'cjs',
platform: 'node',
target: 'ES2019',
sourcemap: watchMode,
minify: !watchMode,
};
}
async function main() {
const watchMode = process.argv.includes('--watch');
const ctx = await esbuild.context(esbuildOptions(watchMode));
await ctx.rebuild();
if (watchMode)
await ctx.watch();
else
await ctx.dispose();
}
module.exports = { esbuildOptions };
if (require.main === module)
main();

View File

@ -3,9 +3,9 @@
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"esbuild": "esbuild ./src/expectBundleImpl.ts --bundle --outdir=../../lib/common --format=cjs --platform=node --target=ES2019", "esbuild": "node build.js",
"build": "npm run esbuild -- --minify", "build": "npm run esbuild",
"watch": "npm run esbuild -- --watch --sourcemap", "watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js" "generate-license": "node ../../../../utils/generate_third_party_notice.js"
}, },
"dependencies": { "dependencies": {

View File

@ -18,8 +18,12 @@
const path = require('path'); const path = require('path');
const esbuild = require('esbuild'); const esbuild = require('esbuild');
(async () => { /**
const ctx = await esbuild.context({ * @param {boolean} watchMode
* @returns {import('esbuild').BuildOptions}
*/
function esbuildOptions(watchMode) {
return {
entryPoints: [path.join(__dirname, 'src/utilsBundleImpl.ts')], entryPoints: [path.join(__dirname, 'src/utilsBundleImpl.ts')],
external: ['fsevents'], external: ['fsevents'],
bundle: true, bundle: true,
@ -27,15 +31,22 @@ const esbuild = require('esbuild');
format: 'cjs', format: 'cjs',
platform: 'node', platform: 'node',
target: 'ES2019', target: 'ES2019',
sourcemap: process.argv.includes('--sourcemap'), sourcemap: watchMode,
minify: process.argv.includes('--minify'), minify: !watchMode,
}); };
}
async function main() {
const watchMode = process.argv.includes('--watch');
const ctx = await esbuild.context(esbuildOptions(watchMode));
await ctx.rebuild(); await ctx.rebuild();
if (process.argv.includes('--watch')) if (watchMode)
await ctx.watch(); await ctx.watch();
else else
await ctx.dispose(); await ctx.dispose();
})().catch(error => { }
console.error(error);
process.exit(1); module.exports = { esbuildOptions };
});
if (require.main === module)
main();

View File

@ -4,8 +4,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"esbuild": "node build.js", "esbuild": "node build.js",
"build": "npm run esbuild -- --minify", "build": "npm run esbuild",
"watch": "npm run esbuild -- --watch --sourcemap", "watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js" "generate-license": "node ../../../../utils/generate_third_party_notice.js"
}, },
"dependencies": { "dependencies": {

View File

@ -67,8 +67,7 @@ const onChanges = [];
const copyFiles = []; const copyFiles = [];
const watchMode = process.argv.slice(2).includes('--watch'); const watchMode = process.argv.slice(2).includes('--watch');
const lintMode = process.argv.slice(2).includes('--lint'); const withSourceMaps = watchMode;
const withSourceMaps = process.argv.slice(2).includes('--sourcemap') || watchMode;
const installMode = process.argv.slice(2).includes('--install'); const installMode = process.argv.slice(2).includes('--install');
const ROOT = path.join(__dirname, '..', '..'); const ROOT = path.join(__dirname, '..', '..');
@ -292,7 +291,7 @@ class EsbuildStep extends Step {
if (watchMode) { if (watchMode) {
await this._ensureWatching(); await this._ensureWatching();
} else { } else {
console.log('==== Running esbuild', this._options.entryPoints.map(e => path.relative(ROOT, e)).join(', ')); console.log('==== Running esbuild:', this._relativeEntryPoints().join(', '));
const start = Date.now(); const start = Date.now();
await build(this._options); await build(this._options);
console.log('==== Done in', Date.now() - start, 'ms'); console.log('==== Done in', Date.now() - start, 'ms');
@ -311,7 +310,7 @@ class EsbuildStep extends Step {
watcher.on('all', () => this._rebuild()); watcher.on('all', () => this._rebuild());
await this._rebuild(); await this._rebuild();
console.log('==== Esbuild watching', this._options.entryPoints, `(started in ${Date.now() - start}ms)`); console.log('==== Esbuild watching:', this._relativeEntryPoints().join(', '), `(started in ${Date.now() - start}ms)`);
} }
async _rebuild() { async _rebuild() {
@ -332,15 +331,32 @@ class EsbuildStep extends Step {
this._rebuilding = false; this._rebuilding = false;
} while (this._sourcesChanged); } while (this._sourcesChanged);
} }
_relativeEntryPoints() {
return this._options.entryPoints.map(e => path.relative(ROOT, e));
}
}
class CustomCallbackStep extends Step {
constructor(callback) {
super({ concurrent: false });
this._callback = callback;
}
async run() {
await this._callback();
}
} }
// Run esbuild. // Run esbuild.
for (const pkg of workspace.packages()) { for (const pkg of workspace.packages()) {
if (!fs.existsSync(path.join(pkg.path, 'src'))) if (!fs.existsSync(path.join(pkg.path, 'src')))
continue; continue;
// These packages have their own build step. // playwright-client has its own build step.
if (['@playwright/client'].includes(pkg.name)) if (['@playwright/client'].includes(pkg.name)) {
loadBundleEsbuildStep(pkg.path);
continue; continue;
}
steps.push(new EsbuildStep({ steps.push(new EsbuildStep({
entryPoints: [path.join(pkg.path, 'src/**/*.ts')], entryPoints: [path.join(pkg.path, 'src/**/*.ts')],
@ -352,32 +368,19 @@ for (const pkg of workspace.packages()) {
} }
// Build/watch bundles. // Build/watch bundles.
for (const bundle of bundles) { for (const bundle of bundles)
steps.push(new ProgramStep({ loadBundleEsbuildStep(bundle);
command: 'npm',
args: [
'run',
watchMode ? 'watch' : 'build',
...(withSourceMaps ? ['--', '--sourcemap'] : [])
],
shell: true,
cwd: bundle,
concurrent: true,
}));
}
// Build/watch playwright-client. function loadBundleEsbuildStep(bundle) {
steps.push(new ProgramStep({ const buildFile = path.join(bundle, 'build.js');
command: 'npm', if (!fs.existsSync(buildFile))
args: [ throw new Error(`Build file ${buildFile} does not exist`);
'run', const { esbuildOptions, beforeEsbuild } = require(buildFile);
watchMode ? 'watch' : 'build', if (beforeEsbuild)
...(withSourceMaps ? ['--', '--sourcemap'] : []) steps.push(new CustomCallbackStep(beforeEsbuild));
], const options = esbuildOptions(watchMode);
shell: true, steps.push(new EsbuildStep(options));
cwd: path.join(__dirname, '..', '..', 'packages', 'playwright-client'), }
concurrent: true,
}));
// Build/watch trace viewer service worker. // Build/watch trace viewer service worker.
steps.push(new ProgramStep({ steps.push(new ProgramStep({
@ -528,7 +531,7 @@ copyFiles.push({
to: 'packages/playwright-core/lib', to: 'packages/playwright-core/lib',
}); });
if (lintMode) { if (watchMode) {
// Run TypeScript for type checking. // Run TypeScript for type checking.
steps.push(new ProgramStep({ steps.push(new ProgramStep({
command: 'npx', command: 'npx',