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"
},
"scripts": {
"build": "esbuild ./src/index.ts --outdir=lib --format=cjs --bundle --platform=node --target=ES2019",
"watch": "esbuild ./src/index.ts --outdir=lib --format=cjs --bundle --platform=node --target=ES2019 --watch"
"esbuild": "node build.js",
"build": "npm run esbuild",
"watch": "npm run esbuild -- --watch"
},
"dependencies": {
"playwright-core": "1.53.0-next"

View File

@ -21,31 +21,47 @@ const fs = require('fs');
const outdir = path.join(__dirname, '../../lib/utilsBundleImpl');
if (!fs.existsSync(outdir))
fs.mkdirSync(outdir);
function copyXdgOpen() {
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.
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')],
bundle: true,
outfile: path.join(outdir, 'index.js'),
format: 'cjs',
platform: 'node',
target: 'ES2019',
sourcemap: process.argv.includes('--sourcemap'),
minify: process.argv.includes('--minify'),
});
sourcemap: watchMode,
minify: !watchMode,
};
}
async function main() {
copyXdgOpen();
const watchMode = process.argv.includes('--watch');
const ctx = await esbuild.context(esbuildOptions(watchMode));
await ctx.rebuild();
if (process.argv.includes('--watch'))
if (watchMode)
await ctx.watch();
else
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,
"scripts": {
"esbuild": "node build.js",
"build": "npm run esbuild -- --minify",
"watch": "npm run esbuild -- --watch --sourcemap",
"build": "npm run esbuild",
"watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js"
},
"dependencies": {

View File

@ -18,23 +18,34 @@
const path = require('path');
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')],
bundle: true,
outdir: path.join(__dirname, '../../lib'),
format: 'cjs',
platform: 'node',
target: 'ES2019',
sourcemap: process.argv.includes('--sourcemap'),
minify: process.argv.includes('--minify'),
});
sourcemap: watchMode,
minify: !watchMode,
};
}
async function main() {
const watchMode = process.argv.includes('--watch');
const ctx = await esbuild.context(esbuildOptions(watchMode));
await ctx.rebuild();
if (process.argv.includes('--watch'))
if (watchMode)
await ctx.watch();
else
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,
"scripts": {
"esbuild": "node build.js",
"build": "npm run esbuild -- --minify",
"watch": "npm run esbuild -- --watch --sourcemap",
"build": "npm run esbuild",
"watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js"
},
"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",
"private": true,
"scripts": {
"esbuild": "esbuild ./src/babelBundleImpl.ts --bundle --outdir=../../lib/transform --format=cjs --platform=node --target=ES2019 --external:playwright",
"build": "npm run esbuild -- --minify",
"watch": "npm run esbuild -- --watch --sourcemap",
"esbuild": "node build.js",
"build": "npm run esbuild",
"watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js"
},
"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",
"private": true,
"scripts": {
"esbuild": "esbuild ./src/expectBundleImpl.ts --bundle --outdir=../../lib/common --format=cjs --platform=node --target=ES2019",
"build": "npm run esbuild -- --minify",
"watch": "npm run esbuild -- --watch --sourcemap",
"esbuild": "node build.js",
"build": "npm run esbuild",
"watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js"
},
"dependencies": {

View File

@ -18,8 +18,12 @@
const path = require('path');
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')],
external: ['fsevents'],
bundle: true,
@ -27,15 +31,22 @@ const esbuild = require('esbuild');
format: 'cjs',
platform: 'node',
target: 'ES2019',
sourcemap: process.argv.includes('--sourcemap'),
minify: process.argv.includes('--minify'),
});
sourcemap: watchMode,
minify: !watchMode,
};
}
async function main() {
const watchMode = process.argv.includes('--watch');
const ctx = await esbuild.context(esbuildOptions(watchMode));
await ctx.rebuild();
if (process.argv.includes('--watch'))
if (watchMode)
await ctx.watch();
else
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,
"scripts": {
"esbuild": "node build.js",
"build": "npm run esbuild -- --minify",
"watch": "npm run esbuild -- --watch --sourcemap",
"build": "npm run esbuild",
"watch": "npm run esbuild -- --watch",
"generate-license": "node ../../../../utils/generate_third_party_notice.js"
},
"dependencies": {

View File

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