denoflare icon indicating copy to clipboard operation
denoflare copied to clipboard

Cannot use npm specifier

Open riderx opened this issue 1 year ago • 14 comments

If i change one import in my codebase to the new syntax for npm specifier in Deno. Like below: CleanShot 2024-02-02 at 03 39 50@2x I get this error on the deploy step: CleanShot 2024-02-02 at 03 39 56@2x it seems it's not supported

riderx avatar Feb 02 '24 03:02 riderx

Yep, Deno's npm: specifiers are not supported, as they are not supported by deno bundle - and we need to bundle into a single js for Cloudflare

johnspurlock-skymethod avatar Feb 04 '24 01:02 johnspurlock-skymethod

As deno bundle is now deprecated, are there plans to switch to an alternative bundler, eg deno_emit, or esbuild?

jollytoad avatar Mar 13 '24 10:03 jollytoad

Yes, deno_emit has never worked well, experimented in denoflare using --bundle backend=module

I imagine esbuild + esbuild_deno_loader will be the most full-featured solution.

I miss Deno.emit, and soon deno bundle : (

johnspurlock-skymethod avatar Mar 13 '24 13:03 johnspurlock-skymethod

I have personally changed completely my stack. Now in deno I use import map so my import look like npm one. I did install only npm and deno compatible packages. And then used hono to have a server who work in both envs the same.

now the only difference is the root file who is different for cloudflare and deno.

That it, no more build step to transform anything.

i believe in the future with winterjs this will become more and more easy.

my code is open source so you see it here: https://github.com/Cap-go/capgo/blob/main/cloudflare_workers/index.ts

it’s deployed in supabase edge (deno) Cloudflare workers and Netlify (nodejs)

riderx avatar Mar 13 '24 14:03 riderx

If it's any help I have a script that uses esbuild and esbuild-deno-loader (to build a service worker for the browser, but that's not so relevant), and it seems to work with both npm: and the new jsr: imports... https://github.com/jollytoad/home/blob/main/scripts/build.ts

(For context, I'm here because, I was curious to see if I could also deploy my site to Cloudflare too using denoflare, but after migrating to npm: (from esm.sh) and jsr:, it's seem not)

Do you think it'd be possible to have a bring-your-own-bundler feature in denoflare?

jollytoad avatar Mar 13 '24 14:03 jollytoad

Denoflare supports Cloudflare wasm/text/binary imports with their non-standard syntax using some post-bundling transformations - these would be tricky to do in general, but sure an escape hatch where you could DIY would be interesting and allow for fast experimentation if those aren't used.

It would need to work with the cli, so maybe a pointer to a module that supports a simple bundle interface?

johnspurlock-skymethod avatar Mar 13 '24 14:03 johnspurlock-skymethod

Is it possible to wrap some features with dynamic imports to build around this? Seems like the bundler sees node: or npm: anywhere in the code and panics prematurely. Now the code has to be patched to remove the references exclusively for this one use case unless I'm missing something?

Granitosaurus avatar Jul 25 '24 09:07 Granitosaurus

I may have some time today to look into an esbuild-based bundler option. @Granitosaurus do you have a sample worker I could use as a validation test?

johnspurlock-skymethod avatar Jul 25 '24 15:07 johnspurlock-skymethod

If it's any help I've just recently got my Deno Deploy site to build for Cloudflare Pages, I had to jump through a fair amount of hoops. It seems Cloudflare doesn't support import.meta.*, so I had to do some code transforms first and then bundle with esbuild. (esbuild doesn't have any option transpile import.meta.url/resolve either)

Transform stuff: https://github.com/jollytoad/home/blob/main/scripts/build_cloudflare.ts#L57 Bundle: https://github.com/jollytoad/home/blob/main/scripts/build_cloudflare.ts#L130

With this, I end up with a bundled _worker.js along with a folder of assets for deploying to Cloudflare Pages.

jollytoad avatar Jul 25 '24 15:07 jollytoad

hey @johnspurlock-skymethod thanks for the reply. Here's the minimal example that got me stumped:

// mod.ts
export class MyClass {
  constructor() {
    console.log("MyClass constructor");
  }
  
  async selector() {
    // dynamically import npm: package, in real use it would throw an error if called through unsupported environment like cf
    const cheerio = await import("npm:cheerio");
    return cheerio.load("<h2 class='title'>Hello world</h2>")("h2").text();
  }
}

and when serving through denoflare's index.ts it immediately throws error that npm specifiers have not yet been implemented:

// index.ts
import { MyClass } from "./mod.ts"

export default {
    async fetch(request: Request, env: any) {
        try {
            // Nothing from the package is even used, just imported
            // const foo = new MyClass();
            return new Response("success")
        } catch (e) {
            return new Response(e.message)
        }
    },
}
Full Traceback
❯ : denoflare serve hello-local 
Compiling https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/cli-webworker/worker.ts into worker contents...
✅ Granted write access to <TMP>.
Bundled https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/cli-webworker/worker.ts (process) in 1082ms
runScript: index.ts
TS0: ⚠ Warning: `deno bundle` is deprecated and will be removed in Deno 2.0.
Use an alternative bundler like "deno_emit", "esbuild" or "rollup" instead.
Check file:///home/dex/projects/try-denoflare/index.ts
error: npm specifiers have not yet been implemented for this subcommand (https://github.com/denoland/deno/issues/15960). Found: npm:/[email protected]

  at undefined:
error: Uncaught (in promise) Error: bundle failed
        throw new Error('bundle failed');
              ^
    at bundle (https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/cli/bundle.ts:108:15)
    at eventLoopTick (ext:core/01_core.js:168:7)
    at async computeScriptContents (https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/cli/cli_serve.ts:122:45)
    at async runScript (https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/cli/cli_serve.ts:169:40)
    at async createLocalRequestServer (https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/cli/cli_serve.ts:177:13)
    at async Object.serve [as handler] (https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/cli/cli_serve.ts:194:32)
    at async CliCommand.routeSubcommand (https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/cli/cli_command.ts:104:13)
    at async https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/cli/cli.ts:41:5

I guess this is Typescript being overly safe here and prematurely breaking but maybe there's a way to tell it to shut up about it?

My goal is to get my package running on cf worker even if some methods that require npm: dependencies are not available and the only way rn it seems is to build out a different version of the package with these methods replaced 🤔

Granitosaurus avatar Jul 27 '24 07:07 Granitosaurus

@ Granitosaurus thanks - couple of notes on that sample.

  1. Deno has really good compile-time inspection even for dynamic imports these days, even static string templates! You can still fool it by doing string concat tho, so something like
await import("npm:cheerio" + "");

will prevent Deno from checking it.

This will still fail at runtime on cloudflare though (no npm:cheerio module)

  1. A library like cheerio works well today even without a new esbuild loader or dynamic imports. Change mod.ts to:
import cheerio from 'https://cdn.skypack.dev/[email protected]?dts';

export class MyClass {
    constructor() {
      console.log("MyClass constructor");
    }
    
    async selector() {
      // works in local denoflare serve and when pushed to cf with denoflare push
      return cheerio.load("<h2 class='title'>Hello world</h2>")("h2").text();
    }
  }

running on cloudflare here: https://cheerio.sw.workers.dev/

johnspurlock-skymethod avatar Jul 27 '24 16:07 johnspurlock-skymethod

Ok introduced a proof of concept esbuild backend in https://github.com/skymethod/denoflare/commit/827d5b1faebf233f4edd434de7fec7d9f1086483

Install denoflare as of this commit or greater to try it out.

Can use it with a --bundle backend=esbuild option to either denoflare serve or denoflare push.

Imports the esbuild stuff dynamically right now so doesn't bloat denoflare if never used.

Defaults to the native loader (which requires --allow-run), can use the portable (wasm) loader with a --bundle loader=portable option to either denoflare serve or denoflare push.

Since neither esbuild nor the esbuild deno loader does type checking, this backend doesn't do any type checking at all yet, I guess we'll need to shell out to deno check to bring this to the same usefulness level as deno bundle

johnspurlock-skymethod avatar Jul 28 '24 00:07 johnspurlock-skymethod

hey @johnspurlock-skymethod I've tested the new esbuild backend and it works great with my npm:cheerio dependency:

import * as cheerio from 'npm:[email protected]'

Thanks for clarifying everything!

Granitosaurus avatar Jul 28 '24 10:07 Granitosaurus

Great, glad to hear it

Now the esbuild backend is type-checking (via deno check under the hood) by default as of https://github.com/skymethod/denoflare/commit/ea757ffb39ed87a282f22f7cdd88f5b17bc97b1d

Can control the check level using the same option as the other backends: --bundle check=(all|local|none)


Also added two other optional bundle options for specifying the versions of the esbuild module and esbuild-deno-loader used under the hood

--bundle loaderModule=(jsr-spec|url) (defaults to ^0.10.3)

This is the version of the esbuild-deno-loader to use, you can specify a version spec for the canonical module, or provide a full url to a fork etc

--bundle esbuildModule=(version|url) (defaults to 0.23.0)

This is the version of the esbuild module to use, you can specify a version for the deno/x repo, or provide a full url to a fork etc

johnspurlock-skymethod avatar Jul 28 '24 17:07 johnspurlock-skymethod