Skip to content

Check that esbuild, Vite and webpack 4/5 apps can bundle and render Web Chat - #5977

Merged
Eugene (OEvgeny) merged 27 commits into
mainfrom
OEvgeny/fix-ci-webpack-consumers
Oct 29, 2026
Merged

Eugene (OEvgeny) merged 27 commits into
mainfrom
OEvgeny/fix-ci-webpack-consumers

Conversation

@OEvgeny

@OEvgeny Eugene (OEvgeny) commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Nothing checks that apps can bundle Web Chat: import('react-dom/client') breaks webpack 5 on React 16.

Changelog Entry

Added

  • 👷🏻 Pull request validation: added rendering Web Chat bundled by esbuild, Vite, webpack 4 and 5 with React 16, 18 and 19
  • Docs: added workarounds for webpack 4

Changed

  • botframework-webchat-core connects to Redux DevTools without the @redux-devtools/extension package

Fixed

  • Fixed webpack 4 failing to resolve botframework-webchat/schema.js and @redux-devtools/extension

Description

A new job builds an app per bundler from the tarballs on React 16, 18 or 19 and checks it renders in Chrome.

Web Chat ships a root schema.js and drops @redux-devtools/extension: webpack 4 only needs Babel.

Design

  • Apps set up as in their bundler's docs; Chrome 141 runs in a Selenium Docker image, driven over WebDriver.
  • createStore reads the DevTools extension's global like Redux Toolkit, not breaking.

Specific Changes

  • pull-request-validation.yml: bundler-test job, 12 legs

  • __tests__/bundlers/<bundler>/: the apps (React 16 uses ReactDOM.render); run.mjs checks them in the Dockerfile's Chrome; HOSTING.md has webpack 4's config

  • bundle/schema.js: shim like decorator.js

  • createStore.ts: reads __REDUX_DEVTOOLS_EXTENSION_COMPOSE__, has a spec

  • directlinespeech/babel.config.json: test build uses release targets

  • I have added tests and executed them locally

  • I have added .changelog/<PR#>-<slug>.md

  • I have updated documentation

Review Checklist

This section is for contributors to review your work.

  • Accessibility reviewed (tab order, content readability, alt text, color contrast)
  • Browser and platform compatibilities reviewed
  • CSS styles reviewed (minimal rules, no z-index)
  • Documents reviewed (docs, samples, live demo)
  • Internationalization reviewed (strings, unit formatting)
  • package.json and package-lock.json reviewed
  • Security reviewed (no data URIs, check for nonce leak)
  • Tests reviewed (coverage, legitimacy)

Eugene Olonov added 10 commits October 28, 2026 14:48
Adds a webpack-test job to pull request validation: a consumer app installs the
packed tarballs from the build job with webpack 4.47.0 or 5.107.2 and React
16.8.6 or 18.3.1, bundles them, and loads the bundle in jsdom.

webpack 4 legs do not fail the job yet: "@redux-devtools/extension" and
"botframework-webchat/schema.js" are only resolvable through "exports".
- webpack 4 legs apply the workarounds from docs/HOSTING.md and must pass
- Check names use major versions only
- Drop the unused render() from the consumer app, also import decorator.js
- Resolve dependencies fresh, without --prefer-offline
The test env replaced preset-env with targets: { node: 'current' }, so the
instrumented lib/ that CI packs kept optional chaining, which webpack 4
cannot parse. The test env now only adds babel-plugin-istanbul and inherits
the release preset-env.

@compulim William Wong (compulim) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add esbuild and vite to the mix.

Also add React 19.

Test it in real browser than jsdom.

@OEvgeny Eugene (OEvgeny) changed the title Check that webpack 4 and 5 apps can bundle Web Chat Check that esbuild, Vite and webpack 4/5 apps can bundle and render Web Chat Oct 29, 2026
@OEvgeny

Copy link
Copy Markdown
Collaborator Author

Added esbuild 0.28 and Vite 8 next to webpack 4 and 5, and React 19 next to 16 and 18: 11 legs (all 12 combinations except webpack 5 × React 19), each bundling the packed tarballs and rendering a bot message from a fake chat adapter in the runner's preinstalled Chrome (a small DevTools-protocol script, no new dependencies), failing on uncaught errors, console.error or failed requests. esbuild and Vite need no configuration; webpack 5 + React 19 is excluded for now because react-film imports render from react-dom for its unused retrofit(), until react-film is removed (#5552).

The PR also ships a root schema.js and drops @redux-devtools/extension (core reads the DevTools extension's global directly, like Redux Toolkit), so webpack 4 only needs Babel for a few dependencies.

Comment thread .github/workflows/pull-request-validation.yml Outdated
Comment thread .github/workflows/pull-request-validation.yml Outdated
@OEvgeny
Eugene (OEvgeny) marked this pull request as ready for review October 29, 2026 02:44
Copilot AI balanced review requested due to automatic review settings October 29, 2026 02:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Comment thread __tests__/bundlers/browser.mjs Outdated
// Never leaves Chrome or its profile behind, also when the script throws, times out, or is interrupted.
function killChrome(signal) {
try {
process.kill(-chrome.pid, signal);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why -chrome.pid but not chrome.pid?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detached: true starts Chrome as the leader of its own process group, whose ID is chrome.pid, and a negative PID signals the whole group. So the cleanup also reaches Chrome's renderer, GPU and zygote processes; chrome.pid alone would signal only the browser process, and after the SIGKILL in the exit handler it can't take its children down, so they could be left running. Negative PIDs are POSIX-only, which is fine for the Ubuntu runner.

Comment thread __tests__/bundlers/browser.mjs Outdated
Comment on lines +62 to +74
process.env.CHROME || 'google-chrome',
[
'--headless=new',
'--no-first-run',
'--no-default-browser-check',
// The page only loads our own bundle, on an ephemeral CI runner, so we do not depend on the runner allowing the sandbox.
'--no-sandbox',
'--remote-debugging-port=0',
`--user-data-dir=${profileDir}`,
'about:blank'
],
// In its own process group, so the cleanup below also kills its renderer and GPU processes.
{ detached: true, stdio: ['ignore', 'ignore', 'pipe'] }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's brainstorm methods for starting chrome without touching potentially unstable chrome CLI API.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Options, roughly from least to most of Chrome's own interface we'd own:

  1. Selenium service container + WebDriver (no new dependencies): run selenium/standalone-chrome pinned to the html2 grid's version (141.0) as a job services: container, and drive it with selenium-webdriver, which the repo already uses. We pass no Chrome flags, the browser version is pinned and matches html2, and the protocol is W3C WebDriver. Costs an image pull per leg, and the app server must be reachable from the container (--add-host=host.docker.internal:host-gateway).
  2. Puppeteer (puppeteer.launch()): Chrome for Testing pinned by the Puppeteer version, and the Chrome team maintains the flags. Adds a fixture-only dependency plus a ~170 MB browser download per leg (cacheable).
  3. Playwright (chromium.launch()): same idea, heavier; could later add Firefox and WebKit.
  4. chrome-launcher (Lighthouse): small, but only wraps the same flags.

Either way, I'd move error capture into the fixture page: error/unhandledrejection listeners and a console.error wrapper installed before the bundle, read back with one script call. The driver then only navigates, waits and reads, so no CDP is needed.

I'd go with 1: no new dependencies, the same browser as our html2 tests, and nothing of Chrome's CLI in our code.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would like to go with 4 for this particular scenario.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4acc9c6: run.mjs starts Chrome with chrome-launcher (only --headless), and the page reports its result back over HTTP, so there is no DevTools protocol code.

Comment thread __tests__/bundlers/browser.mjs Outdated
@@ -0,0 +1,205 @@
// Serves dist/ and loads it in headless Chrome over the DevTools protocol: Web Chat must render the bot message

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite hard to maintain, let's brainstorm some other ideas on how to organize this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Three ways to organize it, each removing most of browser.mjs:

  1. Bundle in the matrix, render in html2. The bundler legs only install and bundle (20–55 s each) and upload dist/ as artifacts. One html2 test page per bundle loads it in the existing Selenium grid, and the harness's existing checks apply: uncaught errors fail the test (run.js), and so does any console.error (done.js). That deletes browser.mjs, adds no new runner code, and reviewers already know the pattern. Costs: the HTML test job needs the bundles (needs: plus an artifact download, about 1 minute later start), and local runs need the grid.
  2. Playwright Test in __tests__/bundlers only. A playwright.config whose webServer serves dist/, plus a spec of about 15 lines (pageerror/console listeners, expect(page.getByRole('feed')).toContainText(...)). The framework owns browser launch, the server, timeouts, retries and traces. Costs a fixture-only dependency and a cached Chromium download.
  3. Selenium service container with in-page checks. The fixture page records errors and sets data-result on <body>; a script of about 30 lines using selenium-webdriver (already used) loads it and waits for that attribute. No new dependencies, but a Docker pull per leg.

Separately, one folder per bundler with its own plain config (webpack.config.js, vite.config.js, ...) instead of the switch in build.mjs would make each setup readable on its own.

I'd pick 1 if the extra minute on the HTML test job is fine, and 2 otherwise.

@OEvgeny Eugene (OEvgeny) Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon private discussion with William it be beneficial to have configuration for apps visible as the specific bundler docs mention: different folder, each mimic a esbuild/webpack4/5/vite environment.

Build things under each folder (with our recently built tarballs), and then put them under a unified testing environment.

When we need to debug issues related to specific build, we would "go under the folder, run some commands to produce build, then use browser to browse that folder and we can see it in our eyes."

and React version is a script to install the specific version

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked as discussed in 4acc9c6 and 5277f3b: __tests__/bundlers/{esbuild,vite,webpack4,webpack5}/ are each a small app set up as in that bundler's getting-started docs (own package.json, config, index.html, src/), webpack 4's config is the HOSTING.md snippet. React is a script: npm run react16 / react18 / react19. To debug one: cd __tests__/bundlers/vite && npm install && npm run react16 -- ../../../packages/{api,bundle,component,core,directlinespeech,fluent-theme}/*.tgz && npm run build, then open dist/ (Vite: npm run preview). The same check for every folder is node ../run.mjs vite: it serves dist/, injects a small report.js before the app's scripts and starts Chrome with chrome-launcher (option 4, no Chrome flags of ours besides --headless, no CDP code). browser.mjs, build.mjs and test.sh are gone. The React scripts take the tarballs as arguments, so React and Web Chat come from one npm install --no-save (a second install would put back the versions from package.json).

Eugene Olonov added 2 commits October 28, 2026 21:38
…cked in Chrome by run.mjs with chrome-launcher

Each folder under __tests__/bundlers/ (esbuild, vite, webpack4, webpack5) is a self-contained app with its own
package.json, config and src/ (the same in every folder, CI diffs it). npm scripts react16/18/19 install a React version;
the configs alias react-dom/client to a shim when React 16 is installed. run.mjs serves dist/ with report.js injected
and opens it with chrome-launcher, replacing browser.mjs (CDP), build.mjs and test.sh.
…all, run.mjs answers an invalid report with 400

`npm run react16 -- <tarballs>` keeps package.json untouched: a separate install would put back package.json's React.
Also README: WSL needs CHROME_PATH; webpack4 config comment.

import { render } from 'react-dom';

export function createRoot(container) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we shouldn't ponyfill createRoot in this test case.

This test case is to emulate what was happening during React 16 days, i.e. no one know about createRoot.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, removed the shim: src/index.react16.js renders with ReactDOM.render like a React 16 app, and build.mjs picks it as the entry when React 16 is installed; the shared part is src/app.js. (9588d4c)

@@ -0,0 +1,7 @@
// React 16 has no "react-dom/client": the bundler config points it here when React 16 is installed.

import { render } from 'react-dom';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as comment on esbuild, it should back in the React 16 days.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: vite.config.js points index.html's script (%VITE_ENTRY%, Vite's HTML constant replacement) to src/index.react16.js on React 16, so there is still one index.html/dist/index.html. (9588d4c)

@@ -0,0 +1,7 @@
// React 16 has no "react-dom/client": the bundler config points it here when React 16 is installed.

import { render } from 'react-dom';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done the same way: entry is ./src/index.react16.js on React 16 (also in webpack 5), no alias. (9588d4c)

Comment thread __tests__/bundlers/esbuild/package.json
Comment thread __tests__/bundlers/esbuild/package.json Outdated
"react19": "npm install --no-save react@19.3.1 react-dom@19.3.1"
},
"dependencies": {
"botframework-webchat": "4.19.2",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should remove all entries here as we are going to install the tarball. We don't need these entries.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed; React and Web Chat now come only from npm run react<N> -- <tarballs>, so package.json has just the bundler (and Babel for webpack 4). (14e2b32)

Comment thread __tests__/bundlers/run.mjs
Eugene Olonov added 3 commits October 29, 2026 01:11
…f chrome-launcher

One image per app on selenium/standalone-chrome:141.0 (pinned by digest) serves the app's dist with report.js
injected; run.mjs talks bare-HTTP WebDriver to it and removes the container on every path.
@OEvgeny
Eugene (OEvgeny) merged commit 5842b25 into main Oct 29, 2026
45 checks passed
@OEvgeny
Eugene (OEvgeny) deleted the OEvgeny/fix-ci-webpack-consumers branch October 29, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants