This release adds
asynchronous compilers
Show archive.org snapshot
and many other features requested by the community.
We also fixed a number of
performance regressions
Show archive.org snapshot
introduced by Unpoly
3.11
Show archive.org snapshot
.
Breaking changes are marked with a ⚠️ emoji and polyfilled by
unpoly-migrate.js
Show archive.org snapshot
.
Asynchronous compilers
Compiler functions can now be
async
Show archive.org snapshot
. This is useful when a compiler needs to fetch network resources, or when calling a library with an asynchronous API:
up.compiler('textarea.wysiwyg', async function(textarea) {
let editor = await import('wysiwyg-editor')
editor.init(textarea)
})
You can also use this to split up expensive tasks, giving the browser a chance to render and process user input:
up.compiler('.element', async function(element) {
doRenderBlockingWork(element)
await scheduler.yield()
doUserVisibleWork(element)
})
Cleaning up async work
Like synchronous compilers, async compiler functions can return a destructor function Show archive.org snapshot :
up.compiler('textarea.wysiwyg', async function(textarea) {
let editor = await import('wysiwyg-editor')
editor.init(textarea)
return () => editor.destroy(textarea)
})
Unpoly guarantees that the destructor is called, even if the element gets destroyed before the compiler function terminates.
Timing render-blocking mutations
Unpoly will run the first task Show archive.org snapshot of every compiler function before allowing the browser to render DOM changes. If an async compiler function runs for multiple tasks, the browser will render between tasks. If you have render-blocking mutations that should be hidden from the user, these must happen in the first task.
Async compilers will not delay the promise returned by rendering functions
up.render()
Show archive.org snapshot
or
up.layer.open()
Show archive.org snapshot
.
Async compilers will delay the promise returned by
up.render().finished
Show archive.org snapshot
and
up.hello()
Show archive.org snapshot
.
up.hello()
Show archive.org snapshot
is now async
⚠️ The
up.hello()
Show archive.org snapshot
function now returns a promise that fulfills when all synchronous and asynchronous compilers have terminated:
let textarea = up.element.createFromHTML('<textarea class="wysiwyg"></textarea>')
await up.hello(textarea)
// WYISWYG editor is now initialized
The fulfillment value is the same element that was passed as an argument:
let html = '<textarea class="wysiwyg"></textarea>'
let textarea = await up.hello(up.element.createFromHTML(html))
Performance fixes
Unpoly 3.11 introduced a number of performance regressions that would be very noticable on pages with many elements, or many forms. To address this, this release includes a number of performance fixes:
- Fix a performance regression where Unpoly would track the DOM for dynamically inserted
[up-validate]
Show archive.org snapshot fields for every form. Now fields are only tracked for forms that use[up-validate]
Show archive.org snapshot . - Features that need to track the insertion or removal of elements now only sync with the DOM once after a render pass.
- Watching a single field no longer tracks dynamically inserted fields.
- Improved the performance of internal form lookups.
HTML content-type required
⚠️ Unpoly now requires server responses with an HTML content-type, like text/html
or application/xhtml+xml
. Trying to render responses with a different type will throw an error, even if the response body contains HTML markup.
Restricting content types is a security precaution. It protects in a hypothetical scenario where an attacker can both upload a file and can use an existing XSS vulnerability to cause Unpoly to render that file. It doesn't affect applications that reliably escape user input.
You can configure which responses Unpoly will process by configuring a function in
up.fragment.config.renderableResponse
Show archive.org snapshot
. To render any response regardless of content-type, configure a function that always returns true`:
up.fragment.config.renderableResponse = (response) => true
New guides
The documentation Show archive.org snapshot has been extended with new guides:
- Enhancing elements with JavaScript Show archive.org snapshot teaches everything you need to know about compilers, destructors and preventing memory leaks.
- Submitting forms in-place Show archive.org snapshot shows how to have Unpoly handle forms from HTML or JavaScript.
- Notification flashes Show archive.org snapshot now contains guidance for suppressing flashes in cached responses Show archive.org snapshot .
Submit buttons can override form attributes
Submit buttons can now supplement or override most Unpoly attributes from the form:
<form method="post" action="/proposal/accept" up-submit>
<button type="submit" up-target="#success">Accept</button>
<button type="submit" up-target="#failure" up-confirm="Really reject?">Reject</button>
</form>
Individual submit buttons can now opt for a full page load, by setting an [up-submit="false"]
attribute:
<form method="post" action="/report/update" up-submit>
<button type="submit" name="command" value="save">Save report</button>
<button type="submit" name="command" value="download" up-submit="false">Download PDF</button>
</form>
See
[up-submit]
Show archive.org snapshot
for a list of overridable attributes
Sticky layout elements
When scrolling to reveal a target element, Unpoly will ensure that layout elements with
[up-fixed=top]
Show archive.org snapshot
are not covering the revealed content.
You can now use [up-fixed]
on elements with
position: sticky
Show archive.org snapshot
. Unpoly will measure sticky element like permanently fixed elements. The current scroll position is not taken into account.
Support partial tables responses
In the past Unpoly didn't allow a server to
optimize its response
Show archive.org snapshot
when the result was a single table row (or cell) without an enclosing <table>
:
Content-type: text/html
<tr>
<td>...</td>
</tr>
Unpoly can now parse responses that only contain a <tr>
, <td>
or <th>
element, without an enclosing <table>
(issue #91).
Expanding click areas
Unpoly lets you enlarge a link's click area using the
[up-expand]
Show archive.org snapshot
attribute. This version addresses inconsistent (or impractical) assignment of the
.up-active
Show archive.org snapshot
feedback class
Show archive.org snapshot
when an expanded link is clicked.
When either the
[up-expand]
Show archive.org snapshot
container or the first link is clicked, the
.up-active
Show archive.org snapshot
class
is now assigned to both elements:
<div up-expand class="up-active">
<a href="/foo" class="up-active">Foo</a>
<a href="/bar">Bar</a>
</div>
When a non-expanded link is clicked, now only that link becomes
.up-active
Show archive.org snapshot
:
<div up-expand>
<a href="/foo">Foo</a>
<a href="/bar" class="up-active">Bar</a>
</div>
Preserving fragments
Two changes were made to
preserving elements
Show archive.org snapshot
using the
[up-keep]
Show archive.org snapshot
attribute:
- Added an experimental event
up:fragment:kept
Show archive.org snapshot . This event is emitted after all keep conditions Show archive.org snapshot are evaluated and preservation can no longer be prevented. A listener can be sure that the element is going to be kept. - Fragments with both
[up-poll]
Show archive.org snapshot and[up-keep]
Show archive.org snapshot now continue polling when the element is kept (fixes #763)
Closing overlays
- When an overlay is closed Show archive.org snapshot , the overlay now remains in the layer stack Show archive.org snapshot until all destructors have run. This way destructor functions can still look up elements in their layer.
- ⚠️ The method
up.Layer#isOpen()
Show archive.org snapshot has been deprecated. Useup.Layer#isAlive() instead
. - ⚠️ The method
up.Layer#isClosed()
Show archive.org snapshot has been deprecated. Use!up.Layer#isAlive() instead
. - When
closing an overlay
Show archive.org snapshot
that is already closed, Unpoly now throws an
AbortError
instead of doing nothing. - Fix a crash when an
[up-switch]
Show archive.org snapshot input without a containing form is placed in an overlay, and that overlay is closed.
Manual booting
- ⚠️ To boot manually, the
[up-boot=manual]
Show archive.org snapshot must now be set on the<html>
element instead of on the<script>
loading Unpoly. - Unpoly now supports
manual booting
Show archive.org snapshot
when Unpoly is loaded as a
<script type="module">
.
Fragment API
- The
up.fragment.get()
Show archive.org snapshot function now has a{ destroying: true }
option. This allows to find destroyed elements that are still playing out their exit animation. Note that allup.fragment
Show archive.org snapshot functions normally ignore elements in an exit animation. - Added an experimental function
up.fragment.isAlive()
Show archive.org snapshot . It returns whether an element is both attached to the DOM and also not in an exit animation.
Smaller fixes and changes
- Reverted the implementation of
up.util.task()
Show archive.org snapshot to again queue macrotasks usingsetTimeout()
instead ofpostMessage()
. Unpoly 3.11 only recently switched topostMessage()
because of its tighter scheduling. Unfortunately message order is erratic withpostMessage()
in Safari, making it hard to reason about the sequence of asynchronous callbacks. - Fix a bug where Unpoly would no longer restore history entries after the page is reloaded (issue #773).
- Added an experimental method
up.Response#isHTML()
Show archive.org snapshot . It returns whether the response has a content-type Show archive.org snapshot oftext/html
orapplication/xml+html
. It doesn't test if the response body actual contains a valid HTML document. - Fix a crash when
up.render({ response })
Show archive.org snapshot is called while another request is in flight. - When rendering, and request with the same
{ failTarget }
Show archive.org snapshot was made while waiting for the network, and the first request responded with an error status and updates , the second request is now aborted.