excaliburjs / Excalibur

🎮 Your friendly TypeScript 2D game engine for the web 🗡️
https://excaliburjs.com
BSD 2-Clause "Simplified" License
1.8k stars 190 forks source link

docs: Add guidance for usage with Deno bundler #2062

Closed kamranayub closed 2 years ago

kamranayub commented 3 years ago

With Deno there are a couple extra steps to consume Excalibur to bundle a game for the browser.

esm.sh should generate a bundle that works, tested locally.

deno info https://esm.sh/excalibur

type: JavaScript
dependencies: 180 unique (total 816KB)

https://esm.sh/excalibur (192B)
├── https://cdn.esm.sh/v54/excalibur@0.25.0/deno/excalibur.js (355.24KB)

Outputs a bundle:

deno bundle https://esm.sh/excalibur excalibur.bundle.js

Using native in browser

Using as a native module works in Chrome without any extras:

<script type="module">
  import ex from "./excalibur.bundle.js";

  const game = new ex.Engine();
  game.start();
</script>

Using with bundler

More likely users will be writing a game and will be importing Excalibur. Deno uses URLs so we'll want to showcase pinning.

A custom tsconfig.json has to be used with strict turned off and there are a few DOM libs to add:

tsconfig.json

{
  "compilerOptions": {
    "strict": false,
    "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
  }
}

Then Excalibur can be imported from esh.sh:

index.ts

import { Engine } from "https://esm.sh/excalibur";

const game = new ex.Engine();
game.start();

And Deno will successfully bundle:

deno bundle index.ts game.bundle.js --config tsconfig.json
Check file:///E:/Development/Junk/deno-excalibur/index.ts
Bundle file:///E:/Development/Junk/deno-excalibur/index.ts
Emit "game.bundle.js" (678.91KB)

Thanks to @tarsupin for bringing this up.

Originally posted by @kamranayub in https://github.com/excaliburjs/Excalibur/discussions/2060#discussioncomment-1480720

tsxoxo commented 3 years ago

Would love to do a write-up of this!

kamranayub commented 3 years ago

@Return180bpm awesome!

This would probably be two changes. One for the main README here in this repo that shows importing Excalibur for Deno, and then a link to the docs page on bundling.

Our docs live in the excaliburjs.github.io repo so it would probably go there 👍 You don't necessarily need to run the docs locally to contribute since the pull request should create a preview URL to see your changes. There is some guidance in that repo on how to edit docs 😊

We have a document right now for installation:

https://excaliburjs.com/docs/installation#getting-excalibur

And also bundling:

https://excaliburjs.com/docs/bundlers

So all three places total probably need info added, with most of the information in this example under Bundlers (the rest can show the import and then link to a more detailed section in Bundlers)

tsxoxo commented 3 years ago

@kamranayub Thank You for the thorough explanation. That helps me a lot.

Little problem: Right now I want to set up a basic deno + excaliburjs so I can understand how it works. I'm following your instructions in the section "Using with a bundler."

When I run deno bundle with the custom tsconfig.json, I get

TS2686 [ERROR]: 'ex' refers to a UMD global, but the current file is a module. Consider adding an import instead.
const game = new ex.Engine();
                 ~~
    at file:///Users/me/Projects/Coding/deno-excaliburjs/src/index.ts:3:18

This might be trivial - I'm still getting the hang of how the module/library system works.

I have tried several things:

  1. Add allowUmdGlobalAccess to tsconfig.json - this gets ignored by the compiler for some reason
  2. Change the import statement: from import { Engine } from "https://esm.sh/excalibur" toimport * as ex from "https://esm.sh/excalibur", as suggested here. That allows me to build. But then I get into another domain of errors...

Before I go down that road I wanted to ask first. I might be missing something.

Any pointers, links or suggestions would be appreciated!

kamranayub commented 3 years ago

Ah dang it, good catch, my code example is wrong 😅 Let me try again and see if I run into more errors. Usually what I found was I was missing libs to add, and the other error I saw was due to mismatches in the Deno default types that messed with DOM lib (I think it was crypto related).

tsxoxo commented 3 years ago

Things are getting interesting.

Results so far

  1. I can get it to work with skypack:
    
    import ex from "https://cdn.skypack.dev/excalibur";

//console.log(JSON.stringify(ex)); const game = new ex.Engine();

game.start();


2. But not esm.sh:

import * as ex from "https://esm.sh/excalibur";

//console.log(JSON.stringify(ex)); const game = new ex.Engine();

game.start();

I have tried several import variants. The only one that gets past the bundler is the above `import * as ex`, but then I get `Uncaught TypeError: mod.Engine is not a constructor`.

_Comparison of both `ex` objects from the console.logs:_ 
![image](https://user-images.githubusercontent.com/59713582/137912808-422a12c3-8567-4701-90cf-a36e8a3490c0.png)

---

### Aside questions
1. In the working skypack version, ex doesn't have an Engine() method, either, so how does that work?
2. How to get type annotations for excalibur in VSCode when working like this? Smth smth .d.ts?

---

### Some things that stuck out to me

1. [On skypack.dev](https://www.skypack.dev/view/excalibur) it says that excalibur doesn't have an ES Module Entrypoint?
![image](https://user-images.githubusercontent.com/59713582/137913996-ea76799e-e7f3-4cd4-aba1-224fe2569924.png)

2. [esm.sh/excalibur](https://esm.sh/excalibur) is:

export * from "https://cdn.esm.sh/v54/excalibur@0.25.0/es2021/excalibur.js"; export { default } from "https://cdn.esm.sh/v54/excalibur@0.25.0/es2021/excalibur.js";

So no export ex. In comparison, when using natively in the browser as you suggested, excalibur.bundle.js has

export { Yo as ex }; export { export_default as default };



Maybe this will be useful. Mind you, these observations might be moot or referring to things being as they should be.

---

### Conclusion 

Not really sure what's going on. Is something off with the esm.sh algorithm in this case? 

For now, I could do a write-up using skypack as an example. I could add a warning that for some reason, bundling with deno doesn't seem to work with esm at the moment. 

On the other hand, I would love to figure this out. This type of problem can be especially oblique and therefore frustrating for beginners and I would love to prevent that.

Any input would be appreciated!
kamranayub commented 3 years ago

We should check if we are missing module field in our package json... I thought we were shipping native ESM in npm 🤔 I haven't double checked that.

Things get a little weird with our entry point because we create an "ex" export that contains the public API for easier consumption in browser scripts (non modules). Then for webpack or other bundlers, we usually do named imports.

kamranayub commented 3 years ago

I had a typo in my code above, I was using

import * as ex from "https://esm.sh/excalibur";

const game = new ex.Engine();
game.start();

The problem looks like a bundling issue, because at the very bottom of the bundle I see this:

var qo = Hn(hr()), Ko = Hn(hr()), { ex: Yo  } = qo;
Ko.default;
const game = new Engine();
game.start();

And if I fix it to be qo.Engine everything works; it looks like the Deno bundler is stripping the imported alias/namespace instead of replacing ex with the minified qo object 🤷‍♂️

I'll look at the ESM bundling we have setup to see if that could be an issue too.

kamranayub commented 3 years ago

@Return180bpm I just pushed a quick commit that adds:

"module": "build/dist/index.js"

Which should be respected (by Skypack at least).

Update: New package seems like its closer but it can't load non-JS files.

game.bundle.js:1 [Package Error] "excalibur@v0.26.0-alpha.257" could not be built. 
[1/5] Verifying package is valid…
[2/5] Installing dependencies from npm…
[3/5] Building package using esinstall…
Running esinstall...
Failed to load node_modules/excalibur/build/dist/Loader.logo.png
  Unexpected character '�' (Note that you need plugins to import files that are not JavaScript)
Install failed.
Install failed.

We might need a Webpack config / step to output an ESM bundle or something...

tsxoxo commented 2 years ago

Update: New package seems like its closer but it can't load non-JS files.

game.bundle.js:1 [Package Error] "excalibur@v0.26.0-alpha.257" could not be built. 
[1/5] Verifying package is valid…
[2/5] Installing dependencies from npm…
[3/5] Building package using esinstall…
Running esinstall...
Failed to load node_modules/excalibur/build/dist/Loader.logo.png
  Unexpected character '�' (Note that you need plugins to import files that are not JavaScript)
Install failed.
Install failed.

Ok, I might be able to take a look at this. I'm a little new to this tho. Could You tell me what You did here so I can reproduce this?

EDIT - To specify what I'm asking

I see that You're running esinstall and I'm wondering how. - Do you have an extra script that does smth like the following?:

import {install} from 'esinstall';

await install(['excalibur'], {
  /*options*/
});

(as the esinstall seem to suggest)

If that's the case, then You also must have node_modules/ and a package.json - so do I just run smth like this?:

npm init && npm i excalibur

Or You could be doing something completely different. If so, let me know!

kamranayub commented 2 years ago

That is the bundle contents itself, it is generated from Skypack.

There's a PR that is ready that should add the module field and an ESM bundle to the output, we can try that once it's available. I think we just want to test it with Webpack and Roll-up templates we have.

https://github.com/excaliburjs/Excalibur/pull/2064

tsxoxo commented 2 years ago

That is the bundle contents itself, it is generated from Skypack.

Thank You for clarifying. Tried it out and getting the same response.

There's a PR that is ready that should add the module field and an ESM bundle to the output, we can try that once it's available. I think we just want to test it with Webpack and Roll-up templates we have.

Alright, just to make sure: as I understand, You're suggesting we wait for #2064 to be approved/merged before continuing with the guide for deno?

I thought deno was doing the bundling itself? I'm a bit out of my depth here :D.

kamranayub commented 2 years ago

Yeah, we have to confirm the code actually works through Deno bundler first before we can write up how to do it. I can continue looking at that.

kamranayub commented 2 years ago

@Return180bpm I think it's working with the new changes! This might be the way forward.

index.ts (recommended: named imports)

import { Engine } from "https://cdn.skypack.dev/excalibur@0.26.0-alpha.264?dts";

const game = new Engine();
game.start();

index.ts (alternative: import all)

import * as ex from "https://cdn.skypack.dev/excalibur@0.26.0-alpha.264?dts";

const game = new ex.Engine();
game.start();

Both approaches work 👍

tsconfig.json

{
  "compilerOptions": {
    "strict": false,
    "lib": ["dom", "dom.iterable", "deno.ns"]
  }
}

build

deno bundle --config tsconfig.json index.ts game.bundle.js

index.html

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Excalibur Deno Example</title>
  </head>
  <body>
    <script src="game.bundle.js"></script>
  </body>
</html>

Screenshot

image

tsxoxo commented 2 years ago

@kamranayub Thank You for laying it out so clearly. Really helps.

The @0.26.0-alpha.264 version works both on esm and skypack, with both imports! Looks like I have some writing to do.

One caveat: when I use skypack with the ?dts query string, I am getting 404s. I had to leave it out for this to work. image

Question: Can a dev have IntelliSense using excalibur this way? Is that what the ?dts gives you?

kamranayub commented 2 years ago

Yeah with Skypack at least the DTS option brings types. ESM might already be doing it by default, which is why you may not need ?dts

tsxoxo commented 2 years ago

Yep, exactly as You say. The issue here was that I needed the Deno extension for VSCode. Also, the 404s are gone. Not sure why 🤷 . But that means, everything is working, so I can't complain :).

I just wrote a draft (with help from You and my friend copy-paste :D) and want to ask: Why are named imports recommended over the others? Is it because of tree-shaking and a smaller final bundle size? I think that could be valuable for newcomers to know.

Thanks for all the help! Will look at where to integrate this with the existing docs tomorrow.

kamranayub commented 2 years ago

Why are named imports recommended over the others? Is it because of tree-shaking and a smaller final bundle size? I think that could be valuable for newcomers to know.

Good question. In theory it will help with tree-shaking but right now it's unlikely to tree shake much (if at all, although maybe that changes with this esm bundle).

Otherwise, it is more of how I see JS and ES module usage done across the projects I'm familiar with (in OSS or at work). Because of bundle size or tree-shaking potential, I think a lot of the community leans on named imports more than "import star."

As a newcomer, one might say that ex.* is a lot easier to work with... and I think I'd agree. It's probably worth giving the reasons why and letting people decide what they like better given any potential trade-offs.

tsxoxo commented 2 years ago

Hey @kamranayub, I'm on the excaliburjs.github.io repo now, where the docs live. Added my stuff to the .md file and wanted to run the site so I can see how it looks visually for the end-user - but I'm getting errors.

Following the instructions over there in the readme, I ran

npm i npm run develop

and I get:

Screenshot 2021-10-28 at 16 48 50

Tried to deploy it directly on GitHub pages but I'm getting a 404. (I'm guessing this has to do because it leaves out Gatsby.)

Is there any other way to run the site?

eonarheim commented 2 years ago

There is a hidden piece of config you need to pull the GH GQL in gatsby, it's possible it's not documented but you'll need a GH_TOKEN with a valid personal API github token

https://github.com/excaliburjs/excaliburjs.github.io/blob/site/gatsby-config.js#L4

On Thu, Oct 28, 2021 at 9:57 AM Tom Szwaja @.***> wrote:

Hey @kamranayub https://github.com/kamranayub, I'm on the excaliburjs.github.io repo now, where the docs live. Added my stuff to the .md file and wanted to run the site so I can see how it looks visually for the end-user - but I'm getting errors.

Following the instructions over there in the readme, I ran

npm i npm run develop

and I get:

[image: Screenshot 2021-10-28 at 16 48 50] https://user-images.githubusercontent.com/59713582/139281518-b72eb8fb-d9cd-4462-94e1-00624fc0f257.png

Tried to deploy it directly on GitHub pages but I'm getting a 404. (I'm guessing this has to do because it leaves out Gatsby.)

Is there any other way to run the site?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/excaliburjs/Excalibur/issues/2062#issuecomment-953928302, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEVNZ2ZT3ZNB2P6DWXIXI3UJFXEZANCNFSM5GEIDZMA .

kamranayub commented 2 years ago

@eonarheim thanks, yes @Return180bpm I just clarified this in the docs here: https://github.com/excaliburjs/excaliburjs.github.io/blob/site/README.md#gh_token-github-personal-access-token

tsxoxo commented 2 years ago

@eonarheim and @kamranayub: thank y'all for the quick response! I got it running with your help. PR coming :).


Edit:

I remembered, that in addition to the thing below, I had to do another thing to get the site running locally, so I think this merits an issue. I will open it later. You can disregard the rest of this post as I will be posting it again there.


Aside: I had to add a null check to get the /docs route running. (Other routes were running fine.)

/src/DocsPageTemplate.js

-!!headings.length && ( +{headings && !!headings.length && (

I'm not 100% sure this is a bulletproof fix, but it did get the thing working for now at least. Let me know if you think this thing is worth an issue or a PR.

For reference, below is the error in full. In the browser: Screenshot 2021-10-29 at 13 06 46

At the same time in the console: Screenshot 2021-10-29 at 13 05 24

kamranayub commented 2 years ago

Thanks, those will be fixed in an upcoming PR I am working on that will upgrade our dependencies

tsxoxo commented 2 years ago

@kamranayub, @eonarheim: This was my first PR. Thank You for all your help!