Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
426e15d
Migrate to Vite 8, ESM-only TypeScript, template literals
yellowby Jul 10, 2026
4eab442
Add test suite: Vitest unit tests and Playwright browser tests
yellowby Jul 10, 2026
e8e5814
Support a real Iframely API key for demo pages via .env
yellowby Jul 10, 2026
f8f82ef
Migrate toolchain to TypeScript 7, oxlint and oxfmt
yellowby Jul 11, 2026
e307711
Make the ESM build manual-activation, SSR-safe; add iframely.configure
yellowby Jul 11, 2026
46d5711
Add iframely.unload() as the inverse of load()
yellowby Jul 11, 2026
ad0837e
Fail silently when options.js is used without embed.js
yellowby Jul 11, 2026
d01d62e
update package.json
yellowby Oct 19, 2026
878e8cc
update deps
yellowby Oct 19, 2026
1468951
Let configure() re-enable autorun after the ESM entries disable it
yellowby Oct 19, 2026
2134487
Add a default condition to every package export
yellowby Oct 19, 2026
3c46209
Declare the Node 24 requirement for development via devEngines
yellowby Oct 19, 2026
d580a0f
Feature-detect <iframe loading="lazy"> instead of assuming support
yellowby Oct 19, 2026
0e1f51e
Expose the public constants and legacy namespaces on the SSR stub
yellowby Oct 19, 2026
76099c0
add usage guide
yellowby Oct 19, 2026
7320147
Boot the options subsystem outside the shared core guard
yellowby Oct 19, 2026
0160918
Run every script of an imported widget
yellowby Oct 19, 2026
c0adb65
Cancel a pending import on unload and ignore its late response
yellowby Oct 19, 2026
f35a6e6
Tolerate a radio group with no selection in the options form
yellowby Oct 19, 2026
a42bad4
Ignore send-intersections from iframes without the wrapper structure
yellowby Oct 19, 2026
84b589e
Make unload() remove the configured parent wrapper like cancelWidget
yellowby Oct 19, 2026
1bf97f3
Escape option keys in the form builder's attribute selector
yellowby Oct 19, 2026
297c117
Only let a number into the range slider's style attribute
yellowby Oct 19, 2026
c4aeec4
Reject paths outside the repo root in the dev server
yellowby Oct 19, 2026
9c9d7f2
Share nonTextChildCount instead of keeping two copies
yellowby Oct 19, 2026
0330fa7
Use explicit .js extensions in relative imports
yellowby Oct 19, 2026
61328a5
Add a packaging check: pack, resolve every export, typecheck a consumer
yellowby Oct 19, 2026
692c857
Unit-test the dev server's path containment
yellowby Oct 19, 2026
1ffee5d
Clear the import cache when an import cycle ends
yellowby Oct 19, 2026
2415dab
Cover import request shaping, widget rendering and cache lifetime
yellowby Oct 19, 2026
477e3d7
Cover intersection observer options, callbacks and teardown
yellowby Oct 19, 2026
97ce746
Add lazy iframe tests
yellowby Oct 19, 2026
b0e2389
Refresh the usage guide for 2.0 and link it from the README
yellowby Oct 19, 2026
9bcdf22
Add the 2.0.0 changelog
yellowby Oct 19, 2026
6dcfbe2
Prepare the 2.0.0-next.1 pre-release
yellowby Oct 20, 2026
da86fe3
Silence the no-console warnings in the packaging check
yellowby Oct 20, 2026
e48786e
Keep the server stub out of the script-tag bundles
yellowby Oct 20, 2026
dd9103d
Modernize utils with DOM and array APIs the build already targets
yellowby Oct 20, 2026
0645416
Keep the dev server's .env and .git off the network
yellowby Oct 26, 2026
5d7db06
Release per-widget state when a widget is cancelled
yellowby Oct 26, 2026
5c97fc2
Name the shared ESM core chunk core.js
yellowby Oct 26, 2026
3047501
Document how the ESM entry's autorun default affects a script tag
yellowby Oct 26, 2026
94dc452
Escape a messaged src before using it in a selector
yellowby Oct 26, 2026
7f48cab
Use classList in the import wrapper cleanup and key in the form builder
yellowby Oct 26, 2026
41e0de6
Scope options form element ids to the form
yellowby Oct 26, 2026
87c871e
Make parseQueryString keep '=' in values and survive bad encoding
yellowby Oct 26, 2026
fda3aa4
Install optionsTranslator from registerLabels, not at import time
yellowby Oct 26, 2026
b6e88ee
Ship source and declaration maps for the npm build
yellowby Oct 26, 2026
8cbd5a5
Run the browser tests in Firefox and WebKit too
yellowby Oct 26, 2026
6ece211
Add the unreleased changelog entries since 2.0.0-next.1
yellowby Oct 26, 2026
3a6510b
Update dev dependencies and patch tinypool via an override
yellowby Oct 26, 2026
83b6a99
Add a GitHub Actions CI workflow
yellowby Oct 26, 2026
2a93b66
Add the audit workflow from publicly; drop pnpm audit from CI
yellowby Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copy to .env (git-ignored) to use a real Iframely API key with the demo
# pages served by `pnpm serve`. scripts/serve.mjs substitutes it into the
# demo HTML at request time; without it the pages use the TEST placeholder.
IFRAMELY_API_KEY=
51 changes: 0 additions & 51 deletions .eslintrc

This file was deleted.

47 changes: 47 additions & 0 deletions .github/workflows/audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Audit

# Compares pnpm audit against the committed audit.log snapshot, so a new
# advisory fails the run until someone reviews it and refreshes the
# snapshot. Every branch audits its own lockfile: on push, and on PRs
# against the merge result. The schedule matters too: advisories change
# over time without any code changes. GitHub only runs schedules on the
# default branch, so a stale snapshot there fails that run, not your
# branch's.
on:
workflow_dispatch:
push:
branches: ['**']
pull_request:
branches: [dev, main]
schedule:
- cron: '0 6 * * 1' # Mondays 06:00 UTC

permissions:
contents: read

jobs:
audit:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# Keep in sync with the local pnpm version (and ci.yml). Audit
# works off pnpm-lock.yaml, no install needed.
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 12.14.0

- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: .node-version

- name: Verify audit.log matches pnpm audit
run: |
pnpm audit > audit-current.log || true
if ! diff -u --strip-trailing-cr audit.log audit-current.log; then
echo "::error::audit.log is stale. The diff above shows what changed. Review the advisories, then refresh the snapshot with: pnpm audit > audit.log"
exit 1
fi
89 changes: 89 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: CI

on:
push:
branches: [main, dev]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

# Actions are pinned to commit SHAs; the comment names the release.

jobs:
check:
name: Typecheck, lint, unit tests, build
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# Keep in sync with the local pnpm version.
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 12.14.0

- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: .node-version
cache: pnpm

# Also enforces the lockfile's supply-chain policies.
- run: pnpm install --frozen-lockfile

# Vulnerabilities: see audit.yml.
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm format:check
- run: pnpm test

- run: pnpm build

# dist/ is committed: it must be exactly what the sources build to.
- name: Check that the committed dist/ is up to date
run: |
if [ -n "$(git status --porcelain -- dist)" ]; then
git status --short -- dist
echo "::error::dist/ is out of date. Run 'pnpm build' and commit the result."
exit 1
fi

- run: pnpm test:pkg

e2e:
name: Browser tests
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 12.14.0

- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: .node-version
cache: pnpm

- run: pnpm install --frozen-lockfile

- run: pnpm exec playwright install --with-deps chromium firefox webkit

- run: pnpm test:e2e

- if: failure()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: playwright-test-results
path: test-results/
retention-days: 7
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
node_modules/
package-lock.json
.npmrc
test-results/
playwright-report/
.env
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.18.0
84 changes: 84 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Changelog

## Unreleased

### Changed

- Options form element ids (and the labels' `for`) are prefixed with the form id passed to `buildOptionsForm`, e.g.
`w1-_hide` instead of `_hide`, so several forms can share a page. Input names are unchanged.
- Query strings (script `src`, `data-iframely-url`): a value keeps everything after the first `=`, a key without a
value reads as `''` instead of `"undefined"`, and a malformed percent-encoding is kept as written instead of
aborting the boot.
- `iframely.optionsTranslator` is installed when an i18n bundle registers its labels rather than at import time. On the
server it is an identity no-op on the stub.
- The ESM core chunk is `dist/esm/chunks/core.js` (was `server-stub.js`; not a public path). The npm build now ships
source maps and declaration maps.

### Fixed

- `cancelWidget` fires `unload-widget`, so a cancelled widget's intersection observer and timers are released, and no
longer throws for a widget that is already detached.
- A `postMessage` whose `src` contains quotes or backslashes no longer makes the message listener throw.
- The development server listens on loopback only and refuses dotfiles, so `.env` and `.git` are not served.

## 2.0.0-next.1 (2026-10-20)

First pre-release of 2.0, published under the `next` dist-tag (`npm install @iframely/embed.js@next`); `latest` stays
on 1.x until 2.0.0 ships. Changes since 1.11.0: a rewrite of the toolchain and sources with the same classic script
bundles, plus a real ES module build for npm consumers. See [USAGE.md](USAGE.md) for the recipes and the [README](README.md) for self-hosting and builds.

### Breaking changes

- **The npm package is ESM-only.** `package.json` has `"type": "module"` and an exports map with four entries:
`@iframely/embed.js`, `@iframely/embed.js/options`, `@iframely/embed.js/i18n/de` and `@iframely/embed.js/i18n/fr`.
Deep imports of files under `dist/` are not part of the public API. TypeScript declarations are included.
- **The ES module does not auto-run.** Importing `@iframely/embed.js` no longer scans the page on `DOMContentLoaded`.
Call `iframely.load()` when your markup is in the DOM, or call `iframely.configure({ autorun: true })` right after
the import to restore the script-tag behavior. The classic script tags (`dist/embed.js` and friends) are unchanged
and still auto-run.
- **`iframely.extendOptions` is renamed to `iframely.configure`.** The old name remains as a deprecated alias.
- **Legacy browser support is dropped.** ActiveX/Flash detection, `attachEvent`, vendor-prefixed animation frame
shims and the IE stylesheet fallbacks are gone, and the bundles target Vite's "baseline widely available" set.
- **Options form templates are TypeScript template literals** in `src/options/renderer.ts`; the EJS templates and
the webpack `ejs-loader` are gone, so custom `.ejs` templates are no longer picked up. Every scalar interpolation is
now HTML-escaped.
- **Development requires Node 24 and pnpm** (declared via `devEngines`; consumers of the npm package are not
affected). The build is Vite 8 with TypeScript 7, oxlint and oxfmt; webpack, ejs-loader and ESLint are removed.

### Added

- `iframely.configure(options)` to merge configuration from code. `autorun` can be switched on through it.
- `iframely.unload(container?)`, the inverse of `load()`: removes the widgets the library built from the document or a
container, releases their intersection observers and pending timers, cancels a pending import request, and fires
`unload-widget` per widget and a final `unload` event. It does not restore the original `<a data-iframely-url>`
anchors and does not cover import/shadow DOM widgets.
- Server-side rendering support: importing any entry without a `window` yields an inert stub whose methods are no-ops
and whose public constants (`VERSION`, `DOMAINS`, `CDN`, `SUPPORTED_THEMES`, ...) and legacy namespaces are readable.
- `iframely.SUPPORT_IFRAME_LOADING_ATTR` is feature-detected from the iframe prototype instead of being derived from a
Chrome version sniff; browsers without native lazy loading fall back to the server-side `lazy=1` mode.
- A test suite: Vitest unit tests over `src/`, Playwright browser tests against the built bundles with the Iframely API
stubbed, and `pnpm test:pkg`, which packs the npm tarball and checks every export via `import()` and `require()`, the
SSR stub and the type declarations under both `bundler` and `nodenext` resolution.
- Demo pages under `demo/`, served by `pnpm serve`, with an optional real API key from a git-ignored `.env`.
- `USAGE.md`, a short recipe guide for the script tag, bundlers, SSR frameworks and the options form.

### Fixed

- Options form: the group template compiled to a lodash `_.escape` call that was not bundled and threw at runtime.
- Options form: option keys containing quotes or brackets no longer break event binding, a radio group with no matching
value no longer throws on later changes, and the range slider only accepts a number for its `style` value.
- `options.js` loaded without `embed.js` now warns and returns instead of throwing.
- The options subsystem now boots when `embed.js` is loaded before `embed-options.js`, or the base npm entry is
imported before the options entry.
- Import widgets: every `<script>` in a widget runs (a script following a removed script was skipped), a late import
response after `unload()` is ignored, and the import cache is cleared between import cycles.
- The `send-intersections` message from an iframe without the library's wrapper structure no longer throws.
- `unload()` removes the host wrapper configured with `parent=`, like `cancelWidget` does.
- Package exports resolve for `require()` on Node 22.12+ and for resolvers using the `default` condition, and the
shipped declarations resolve under `moduleResolution: node16 / nodenext`.
- The development server rejects encoded-traversal requests that would resolve outside the repository.

### Removed

- The stale `dist/embed-autoplay*` bundles, `lazy-loading-native.js`, the webpack configuration, the EJS templates and
the ESLint setup.
Loading
Loading