question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Module resoultion in unpkg doesn't work. (Bug in unpkg)

See original GitHub issue

Goal

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:closed
  • Created 3 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

7reactions
developitcommented, May 29, 2020

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:

// preact and preact/hooks: 10.3.x, and htm/preact: 3.x.x:
import { html, render } from 'https://npm.reversehttp.com/preact@10.3,preact@10.3/hooks,htm@3/preact';

Unpkg: the ?module issue

Regarding 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 from preact-render-to-string:

import { options as e, Fragment as t, createElement as r } from "https://unpkg.com/preact@>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0?module";

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:

import 'https://unpkg.com/preact@10?module'
import 'https://unpkg.com/preact@latest?module'

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:

// you must use @latest when importing preact in order to work around unpkg resolution:
import { h, render } from 'https://unpkg.com/preact@latest?module'
import { useState } from 'https://unpkg.com/preact@latest/hooks/dist/hooks.module.js?module'
// nothing imports these modules so you can version them if you like:
import { html } from 'https://unpkg.com/htm@3.0.4/preact/index.module.js?module'
import { styled, setPragma } from 'https://unpkg.com/goober@1.8.0/dist/goober.module.js'
setPragma(h);
const Banner = styled('h1')`
  background-color: red;
`
render(html`<${Banner}>thank you @_developit<//>`, document.body)

(here’s the above on jsfiddle without bundling)

1reaction
Pyrolisticalcommented, May 29, 2020

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 versioned npm.reversehttp.com or cdn.pika.dev would all work for now. But its all to fix the issue of the non-standard import{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”

Read more comments on GitHub >

github_iconTop Results From Across the Web

ngx-extended-pdf-viewer/README.md - UNPKG
If the PDF file is very large, the PDF viewer is working fine - except for the "find" functionality. More precisely, "find" works,...
Read more >
Plugins - esbuild
The setup function is run once for each build API call. Here's a simple plugin ... This is the path of the module...
Read more >
Using ES Modules with babel-standalone - Stack Overflow
Not sure if this is a bug where data-type="module" doesn't work or if I'm missing something. These are my scripts tags in index.html...
Read more >
cjs-module | Yarn - Package Manager
Environment agnostic CJS (Node.js) modules resolver. It implements a strict version of Node.js modules resolution logic, differences are as follows:.
Read more >
Publishing and consuming ECMAScript modules via packages
How resolution works depends on the platform. We'll learn more soon. Filename extensions in module specifiers #. Absolute specifiers and ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found