The widget ships in two flavours and has three build outputs. This page covers what each produces, how cache busting works, and how to host the static bundle on your own CDN.
Build outputs
The repo defines three relevant scripts in package.json:
| Script | Output | What it's for |
|---|---|---|
yarn start | dev server on localhost:3000 | Local iteration |
yarn build | build/ (CRA static SPA + appmint-chat.js) | CDN-hosted standalone widget |
yarn build:lib | dist/bundle.js (UMD) | Experimental — no consumer ships from this output today |
yarn build is the canonical "ship to CDN" path. It runs react-scripts build, then node update-script-hashes.js to copy the hashed CRA output to a stable main.js filename so customers can pin a single URL.
yarn build:lib was intended for an @appmint/chat npm package. The package was never published — chat-client/package.json is private: true with no exports field — so the Rollup output isn't currently consumed anywhere. Treat the lib build as exploratory until the package surface is finalised.
Cache busting
Both bundles use content-hashed filenames. The wrinkle: customers paste the URL into a <script> tag and never update it, so the URL must stay stable while the contents change. update-script-hashes.js solves this by:
- Finding the largest non-chunk JS file in
build/static/js/(the main bundle). - Copying it to
build/static/js/main.js. - Doing the same for the main CSS file.
So even though CRA writes main.abc123.js, the script also produces main.js for stable embedding. The script tag URL https://web.appmint.space/chat-client/static/js/main.js always resolves to the latest build.
// update-script-hashes.js — find largest non-chunk JS, copy to main.js
const files = fs.readdirSync(jsDir);
const mainJs = files
.filter(f => f.endsWith('.js') && !f.endsWith('.chunk.js'))
.sort((a, b) => fs.statSync(path.join(jsDir, b)).size - fs.statSync(path.join(jsDir, a)).size)[0];
fs.copyFileSync(path.join(jsDir, mainJs), path.join(jsDir, 'main.js'));
The bundled chat-demo also ships at build/chat-demo.html — a complete script-tag example you can copy as a starting point.
Hosting on your own CDN
The default deploy target is a DigitalOcean Space (deploy.js in the repo). To self-host:
- 1
Build
yarn install yarn buildOutput lands in
build/. Confirmbuild/static/js/main.jsexists and matches the hashed file in size — if not,update-script-hashes.jsdidn't run; check thepostbuildchain inpackage.json. - 2
Sync to your bucket
The repo ships a deploy script that pushes
build/to a DigitalOcean Space:node deploy.jsIt reads credentials from
.env.deploy(gitignored) —DO_SPACES_BUCKET,DO_SPACES_REGION,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY, optionalDO_SPACES_PREFIXandDO_CDN_ENDPOINT_ID. For S3, Cloudflare R2, or any other S3-compatible target, the sameaws s3 syncinvocation works — just point--endpoint-urlat the right host.Make sure all uploaded objects are publicly readable (
--acl public-reador the equivalent bucket policy). - 3
Replace the script URLs
In your embed snippet, replace the AppMint CDN host with your own:
<link rel="stylesheet" href="https://your-cdn.example.com/chat-client/static/css/main.css"> <script src="https://your-cdn.example.com/chat-client/appmint-chat.js" onload="initializeChat()"></script>The bundle has no compiled-in CDN URL — only AppEngine endpoints. So self-hosting works without a code change.
- 4
Purge the CDN cache
If your CDN sits in front of the bucket (Cloudflare, DO CDN, CloudFront), invalidate the paths after each deploy:
# DigitalOcean doctl compute cdn flush <endpoint-id> --files "/*" # CloudFront aws cloudfront create-invalidation --distribution-id <id> --paths "/chat-client/*"deploy.jsdoes this automatically whenDO_CDN_ENDPOINT_IDis set.
Versioning the bundle
There is no published npm package. For the script-tag bundle there's no version pin — every page pulls the current build. To support customers who want to pin, publish versioned URLs alongside main.js:
/chat-client/v0.1.0/static/js/main.js
/chat-client/v0.2.0/static/js/main.js
/chat-client/static/js/main.js # always latest
The repo doesn't ship this layout out of the box. Add it to deploy.js if you need it.
Pre-deploy checklist
Before pushing a new build:
- Verify
build/index.htmlandbuild/chat-demo.htmlexist. - Open
chat-demo.htmllocally with the new bundle and confirm the bubble appears. - Smoke-test send/receive against a staging AppEngine to catch socket regressions.
- If you bumped a major React version or
socket.io-clientversion, run the unit tests insrc/locales/__tests__/and any consumer integration tests.
The CSS bundle (main.css) is required for the layout. Skipping it leaves the panel unstyled and badly positioned. Always sync both JS and CSS together.
Rollbacks
The simplest rollback is to keep the previous main.js in a versioned folder and swap the stable URL back:
aws s3 cp s3://<bucket>/chat-client/v0.1.0/static/js/main.js \
s3://<bucket>/chat-client/static/js/main.js \
--endpoint-url <url> --acl public-read
CDN invalidate, refresh customer pages, done. Because the widget loads its config at runtime, downgrading the bundle won't lose any customer data — only the rendered UI rolls back.