Module resoultion in unpkg doesn't work. (Bug in unpkg)
See original GitHub issueGoal
I want to be able to use build websites with preact
with modern ES Modules as a single index.html page, ie. no bundler.
Problem
Right now the “browser” build of preact/hooks
can be seen here: https://unpkg.com/preact@10.4.4/hooks/dist/hooks.module.js
The problem is, its not browser compatible due to import{options as n}from"preact";
Current solutions
Use unpkg.com
with ?module
First of all its a bundler, second of all it load its own un-versioned preact which wouldn’t be the same preact I already loaded?
Use npm.reversehttp.com
Again, another bundler and it also always loads the latest version making it hard to build a stable app.
Desired solution: Make preact/hooks
pluggable into preact
I like how htm
and preact
are independent ES Modules that can be wired together, as seen here
https://twitter.com/pyrolistical/status/1266264165317935104
We can even continue to layer on a styled component library
<html>
<body>
<script type="module">
import htm from 'https://unpkg.com/htm@3.0.4/dist/htm.module.js'
import {h, render} from 'https://unpkg.com/preact@10.4.4/dist/preact.mjs'
import {styled, setPragma} from 'https://unpkg.com/goober@1.8.0/dist/goober.module.js'
const html = htm.bind(h)
setPragma(h)
const Banner = styled('h1')`
background-color: red;
`
render(html`<${Banner}>thank you @_developit<//>`, document.body)
</script>
</body>
</html>
If preact
exposed an plugin system of some sort we could do the following:
// fake code
import Preact, {h, render} from 'https://unpkg.com/preact@10.4.4/dist/preact.mjs'
import Hooks, {useState} from 'https://unpkg.com/preact@10.4.4/hooks/dist/hooks.module.js'
Preact.install(Hooks)
Why not just use a cdn bundler? I don’t think this scales well into larger projects. When everything is welded together with a bundler, its harder get in there and customize.
And its non-standard! We don’t need npm/commonjs anymore now that we have ES Modules.
Issue Analytics
- State:
- Created 3 years ago
- Comments:6 (3 by maintainers)
Preact’s usage is a standard import statement, and also not really at the core of this issue, as I’ll get to below.
One point of clarification regarding
npm.reversehttp.com
- it fully supports npm semver:Unpkg: the
?module
issueRegarding unpkg: this is a known bug in unpkg, which there are four open issues for - #198, #129, #123, #121. I’ve actually spoken to the maintainer about resolving this and tried to fix it myself, but it’s difficult. The
?module
parameter enables ES Module specifier transforms, but does not resolve imports according to their package.json versions. Instead, it uses the"dependencies"
value verbatim, resulting in broken specifiers like this one frompreact-render-to-string
:Here’s why that’s a bug rather than just a performance issue: that specifier URL produces a 302 redirect to the correct URL, which “works”. However, the ES Modules specification dictates that module instantiation must be tied to the source URL rather than the final redirected URL. Modules are normally supposed to be singletons, but using redirects like this silently breaks that important assumption. This breaks way more than just hooks - it disables most types of caching too. It means that the following code actually downloads and executes two completely separate copies of a module, despite them having the same final URL:
Now, I get that it’s super inconvenient that this is the case - heck, I built that whole CDN just to work around the issue for myself. But I also see that this is clearly an unpkg-specific implementation issue, and not something every library should seek to address. That is to say - since this is an infrastructure issue, we shouldn’t try to fix it like an architecture issue. Unpkg is Open Source, and while my previous attempt to fix this failed, it’s by no means an impossibility.
The real issue
I hope I’ve made the case for why changing Preact’s design isn’t appropriate here. However, even if we were to change that API to fix unpkg imports, doing so would not fix the hooks issue by default. This is because, while it looks like the problem here is related to imports, it’s actually because the whole concept of “hooks” inherently requires a singleton renderer module. This is true in all frameworks and libraries that implement the “callsite ordering” technique that hooks relies on, and it’s one of the more severe limitations of the idea. With imports patched up, the new issue would be that calling
Preact.install(hooks)
would work the first time, but break everything silently if it were ever to be called again.In the near term
For what it’s worth, it is actually possible to use hooks + htm + etc via unpkg today, without bundling or workarounds. All you have to do is make sure you’re using the already-resolved URLs for each module, so that no redirects are ever used:
(here’s the above on jsfiddle without bundling)
Yes, the real issue is “how does preact/hooks get the correct preact instance so it can mess with the options”
The solutions with
unpkg ?module
or versionednpm.reversehttp.com
orcdn.pika.dev
would all work for now. But its all to fix the issue of the non-standardimport{options as n}from"preact";
For ES Modules to work in the browser as is, the imports must an absolute url or its a relative one. Just
from 'preact'
is non-standard and requires cdn magic to convert it to an absolute url.Neither the
Preact.install(Hooks)
or relative import are nice and each have their problems. But I’m seeing is if we could find a solution that makes it so the dist files can just be hosted anywhere because they are truly ESM compatible as is. This way we can avoid the requirement of “we need a cdn that is smart enough to rewrite non-standard imports”