Skip to content

feat: vendor all runtime CDN dependencies#282

Closed
shsteimer wants to merge 5 commits intomainfrom
feat/vendor-cdn-deps
Closed

feat: vendor all runtime CDN dependencies#282
shsteimer wants to merge 5 commits intomainfrom
feat/vendor-cdn-deps

Conversation

@shsteimer
Copy link
Copy Markdown
Collaborator

Summary

  • Replace all runtime CDN imports (esm.sh, unpkg, cdnjs, jsdelivr) with locally vendored browser-ready ES modules served from vendor/
  • Add scripts/vendor.mjs postinstall hook that copies or esbuild-bundles each dependency from node_modules/ into vendor/, with lockfile-hash idempotency
  • Configure dependabot with weekly schedule and semver-aware cooldown (major: 30d, minor: 7d, patch: 3d)
  • Remove https://esm.sh from CSP script-src in all optel pages
  • Centralize duplicate vendored diff.js copies, remove redundant sub-directory package.json files
  • Bump stylelint to 17.6.0 to match aem-boilerplate

See docs/rfc-vendor-cdn-deps.md for the full decision document covering all options evaluated, trade-offs, CDN comparison, and rationale.

Preview

https://feat-vendor-cdn-deps--helix-tools-website--adobe.aem.page/tools/optel/oversight/cwvperf.html

Test plan

  • Verify optel oversight pages render charts correctly (cwvperf, explorer, share, list, single, flow)
  • Verify optel explorer charts render correctly (was on chart.js 4.4.2, now 4.4.8)
  • Verify error-analyzer echarts renders
  • Verify index-admin and sitemap-admin YAML parse/stringify works
  • Verify log-viewer and cache tool Prism syntax highlighting works
  • Verify admin-edit Prism highlighting works
  • Verify version-admin diffJson works
  • Verify pdp-scanner diffChars works
  • Run npm install on clean checkout — vendor/ files generated
  • Run npm install again — postinstall skips (idempotent)
  • Confirm no CDN URLs remain: grep -r 'unpkg\|esm\.sh\|cdnjs\|jsdelivr' --include='*.js' --include='*.html'

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

🤖 Generated with Claude Code

Replace all runtime CDN imports (esm.sh, unpkg, cdnjs, jsdelivr) with
locally vendored browser-ready ES modules served from vendor/.

A postinstall script (scripts/vendor.mjs) copies or esbuild-bundles
each dependency from node_modules into vendor/, with lockfile-hash
idempotency. Vendor files are committed to git as required by EDS.

- Add chart.js, chartjs plugins, rum-distiller, echarts, yaml, diff,
  prismjs as npm dependencies with esbuild as dev dependency
- Update all import maps in optel/error-analyzer HTML files to /vendor/
- Update dynamic imports (yaml) and loadScript calls (prismjs) in JS
- Remove https://esm.sh from CSP script-src in optel pages
- Centralize duplicate vendored diff.js into vendor/
- Remove redundant sub-directory package.json files in optel
- Configure dependabot with weekly schedule and semver-aware cooldown
- Bump stylelint to 17.6.0 to match aem-boilerplate
- Update AGENTS.md with vendor documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aem-code-sync
Copy link
Copy Markdown

aem-code-sync bot commented Apr 7, 2026

Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
In case there are problems, just click a checkbox below to rerun the respective action.

  • Re-run PSI checks
  • Re-sync branch
Commits

@aem-code-sync
Copy link
Copy Markdown

aem-code-sync bot commented Apr 7, 2026

Page Scores Audits Google
📱 /tools/optel/oversight/cwvperf.html PERFORMANCE A11Y SEO BEST PRACTICES SI FCP LCP TBT CLS PSI
🖥️ /tools/optel/oversight/cwvperf.html PERFORMANCE A11Y SEO BEST PRACTICES SI FCP LCP TBT CLS PSI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aem-code-sync aem-code-sync bot temporarily deployed to feat/vendor-cdn-deps April 7, 2026 15:43 Inactive
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aem-code-sync aem-code-sync bot temporarily deployed to feat/vendor-cdn-deps April 7, 2026 15:59 Inactive
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aem-code-sync aem-code-sync bot temporarily deployed to feat/vendor-cdn-deps April 7, 2026 16:04 Inactive
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread .github/dependabot.yml
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.

I'd use renovate.


## Options Considered

### Option A: Full Vendoring (esbuild + postinstall) — SELECTED
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.

Agree with this approach.

shsteimer added a commit that referenced this pull request Apr 14, 2026
Replaces the runtime unpkg.com CDN import of `yaml` with a locally vendored
copy managed via npm. Adds Renovate config with minimumReleaseAge cooldowns
(3d patch / 7d minor / 30d major) and postUpgradeTasks to re-vendor on update.

Closes #282

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@shsteimer
Copy link
Copy Markdown
Collaborator Author

Superseded by #287, which implements the approach as a pilot on a single dependency (yaml) before rolling out more broadly. Switching to Renovate instead of Dependabot for equivalent cooldown support via minimumReleaseAge.

@shsteimer shsteimer closed this Apr 14, 2026
shsteimer added a commit that referenced this pull request Apr 14, 2026
…#287)

* feat(vendor): pilot yaml dependency vendoring with Renovate cooldowns

Replaces the runtime unpkg.com CDN import of `yaml` with a locally vendored
copy managed via npm. Adds Renovate config with minimumReleaseAge cooldowns
(3d patch / 7d minor / 30d major) and postUpgradeTasks to re-vendor on update.

Closes #282

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): move vendor script to project root, ignore *.mjs in hlx

vendor.mjs belongs alongside other root-level tooling (package.json,
.eslintrc.js) rather than scripts/, which is reserved for browser-served
AEM runtime code. Also adds *.mjs to .hlxignore so tooling scripts are
not served by the AEM CDN.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): move vendor.mjs into vendor/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): exclude vendor/vendor.mjs specifically in .hlxignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): pin yaml dep, centralize ensureYaml in index-admin

- Pin yaml dependency to exact version 2.8.3 (drop ^ range) so vendored
  output is always reproducible across installs
- Add ensureYaml() helper in index-admin to consolidate vendor import,
  replacing three inline YAML = YAML || await import(...) calls; also
  fixes stale unpkg CDN URL on the delete path
- Un-ignore vendor/vendor.mjs in .eslintignore so the vendor script is
  linted alongside the rest of the codebase

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): switch to esbuild bundling with postinstall + hash guard

- Add esbuild (0.28.0) as devDependency; use it to bundle yaml into a
  single vendor/yaml.js instead of copying ~80 individual files.
  This is the correct pattern for deps without a pre-built browser ESM
  and keeps the vendoring approach consistent for future additions.
- Add postinstall script so vendor files are regenerated automatically
  on npm install, not just via Renovate postUpgradeTasks.
- Add package-lock.json hash guard (vendor/.vendor-hash) so repeated
  installs skip the rebuild when nothing has changed.
- Update ensureYaml() import paths in index-admin and sitemap-admin to
  point to vendor/yaml.js.
- Remove legacy vendor/yaml/ directory tree (80 files → 1 file).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): explicitly hlxignore vendor/.vendor-hash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Apply suggestion from @claude[bot]

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* Apply suggestion from @shsteimer

* chore(vendor): use fs/promises, declarative DEPS list, write hash eagerly

- Switch all fs calls to fs/promises
- Replace one-off bundle/cleanup calls with a DEPS array; adding a new
  dep is now a single line
- Write hash file before bundling (optimistic) so it sits next to the
  hash compare rather than at the end of the script
- Bundle steps run in parallel via Promise.all
- Remove legacy vendor/yaml cleanup (migration complete)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(vendor): correct ensureYaml import paths in both tools

index-admin had a wrong path (vendor/yaml/yaml.js) introduced during
rebase; sitemap-admin was missing the eslint-disable comment.
Both now consistently import from vendor/yaml.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): auto-discover deps from package.json dependencies

Replace hardcoded DEPS array with automatic discovery from
package.json `dependencies`. esbuild resolves each package's browser
entry via its exports map, so no entry point overrides are needed.

Adding a new runtime dep now requires only a package.json change —
vendor.mjs needs no updates.

Scoped package names (@scope/name) are normalized to name.js for the
output filename.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): replace auto-discovery with explicit DEPS config array

Reading from package.json proved insufficient — chart.js plugins require
the external option to share a module instance, which can't be expressed
per-package via auto-discovery.

DEPS at the top of the script is the single place to update when adding
deps. The build loop is generic and never needs changing. Commented-out
entries document the chart.js/rum-distiller/echarts patterns for when
the full rollout happens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): scope output files to per-package subdirectories

vendor/yaml.js → vendor/yaml/yaml.js. Each vendored package now lives
in its own subdirectory, consistent with how the old cpSync approach
laid things out and easier to reason about when multiple deps are present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): enable esbuild minification

EDS serves committed files directly — no CDN minification step exists.
Minifying at vendor time halves the yaml bundle (209KB → 101KB) at
negligible build cost.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* minor updates

* chore(vendor): clean stale artifacts and write hash only on success

Delete vendor subdirectories before rebuilding so removed DEPS entries
don't leave orphaned files. Move hash file write to after all builds
complete so a failed build forces a retry on next run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
usman-khalid pushed a commit that referenced this pull request Apr 15, 2026
…#287)

* feat(vendor): pilot yaml dependency vendoring with Renovate cooldowns

Replaces the runtime unpkg.com CDN import of `yaml` with a locally vendored
copy managed via npm. Adds Renovate config with minimumReleaseAge cooldowns
(3d patch / 7d minor / 30d major) and postUpgradeTasks to re-vendor on update.

Closes #282

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): move vendor script to project root, ignore *.mjs in hlx

vendor.mjs belongs alongside other root-level tooling (package.json,
.eslintrc.js) rather than scripts/, which is reserved for browser-served
AEM runtime code. Also adds *.mjs to .hlxignore so tooling scripts are
not served by the AEM CDN.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): move vendor.mjs into vendor/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): exclude vendor/vendor.mjs specifically in .hlxignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): pin yaml dep, centralize ensureYaml in index-admin

- Pin yaml dependency to exact version 2.8.3 (drop ^ range) so vendored
  output is always reproducible across installs
- Add ensureYaml() helper in index-admin to consolidate vendor import,
  replacing three inline YAML = YAML || await import(...) calls; also
  fixes stale unpkg CDN URL on the delete path
- Un-ignore vendor/vendor.mjs in .eslintignore so the vendor script is
  linted alongside the rest of the codebase

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): switch to esbuild bundling with postinstall + hash guard

- Add esbuild (0.28.0) as devDependency; use it to bundle yaml into a
  single vendor/yaml.js instead of copying ~80 individual files.
  This is the correct pattern for deps without a pre-built browser ESM
  and keeps the vendoring approach consistent for future additions.
- Add postinstall script so vendor files are regenerated automatically
  on npm install, not just via Renovate postUpgradeTasks.
- Add package-lock.json hash guard (vendor/.vendor-hash) so repeated
  installs skip the rebuild when nothing has changed.
- Update ensureYaml() import paths in index-admin and sitemap-admin to
  point to vendor/yaml.js.
- Remove legacy vendor/yaml/ directory tree (80 files → 1 file).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): explicitly hlxignore vendor/.vendor-hash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Apply suggestion from @claude[bot]

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* Apply suggestion from @shsteimer

* chore(vendor): use fs/promises, declarative DEPS list, write hash eagerly

- Switch all fs calls to fs/promises
- Replace one-off bundle/cleanup calls with a DEPS array; adding a new
  dep is now a single line
- Write hash file before bundling (optimistic) so it sits next to the
  hash compare rather than at the end of the script
- Bundle steps run in parallel via Promise.all
- Remove legacy vendor/yaml cleanup (migration complete)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(vendor): correct ensureYaml import paths in both tools

index-admin had a wrong path (vendor/yaml/yaml.js) introduced during
rebase; sitemap-admin was missing the eslint-disable comment.
Both now consistently import from vendor/yaml.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): auto-discover deps from package.json dependencies

Replace hardcoded DEPS array with automatic discovery from
package.json `dependencies`. esbuild resolves each package's browser
entry via its exports map, so no entry point overrides are needed.

Adding a new runtime dep now requires only a package.json change —
vendor.mjs needs no updates.

Scoped package names (@scope/name) are normalized to name.js for the
output filename.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): replace auto-discovery with explicit DEPS config array

Reading from package.json proved insufficient — chart.js plugins require
the external option to share a module instance, which can't be expressed
per-package via auto-discovery.

DEPS at the top of the script is the single place to update when adding
deps. The build loop is generic and never needs changing. Commented-out
entries document the chart.js/rum-distiller/echarts patterns for when
the full rollout happens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): scope output files to per-package subdirectories

vendor/yaml.js → vendor/yaml/yaml.js. Each vendored package now lives
in its own subdirectory, consistent with how the old cpSync approach
laid things out and easier to reason about when multiple deps are present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(vendor): enable esbuild minification

EDS serves committed files directly — no CDN minification step exists.
Minifying at vendor time halves the yaml bundle (209KB → 101KB) at
negligible build cost.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* minor updates

* chore(vendor): clean stale artifacts and write hash only on success

Delete vendor subdirectories before rebuilding so removed DEPS entries
don't leave orphaned files. Move hash file write to after all builds
complete so a failed build forces a retry on next run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
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.

2 participants