fairy-stockfish / fairy-stockfish.wasm

WebAssembly port of the chess variant engine Fairy-Stockfish with NNUE support
https://fairy-stockfish-nnue-wasm.vercel.app/
GNU General Public License v3.0
22 stars 9 forks source link

"<script src="stockfish.js"></script>" vs. "import Stockfish from 'stockfish'" #12

Open sombor-shuffle opened 1 year ago

sombor-shuffle commented 1 year ago

Hello! I'm trying to use Fairy Stockfish inside of a Next.js project.

I installed the npm package fairy-stockfish-nnue.wasm.

Now, placing <script src="stockfish.js"></script> and then referencing Stockfish inside of another script element works just fine:

<script>

    console.log(Stockfish) // can be referenced here.

</script>

However, it remains inaccessible to the rest of my application. This lead me to try to import Stockfish from 'stockfish'. Sadly, using Stockfish now leads to an error:

console.log(Stockfish) gives Module not found: Can't resolve 'fs'.

Looking inside of stockfish.js, I can see that "fs" is indeed used, and from here on out I don't know how to proceed.

Any pointers to what I should do to be able to use Fairy Stockfish? :)

ianfab commented 1 year ago

Hi. Did you already have a look at the demo https://github.com/ianfab/fairy-stockfish-nnue-wasm-demo? I hope that should help to get started.

sombor-shuffle commented 1 year ago

Hi @ianfab, thank you for writing back to me!

I spent some time with the demo yesterday and today.

By reusing the code found there, it's possible for me to create a new html file (foo.html) inside of the public folder which...

  1. References Stockfish
  2. Prints all incoming messages from the engine
  3. Posts the initial "uci"
<html>
    <body>
        <script src="./lib/stockfish.js"></script>
        <script>

            (async () => {

                let stockfish = null;

                await Stockfish().then(_stockfish => {
                    stockfish = _stockfish;
                    stockfish.addMessageListener(console.log);
                });

                stockfish.postMessage("uci");

            })()

        </script>
    </body>
</html>

Opening localhost:5000/foo.html and looking at the console shows the engine's id and options, followed by "uciok".

This is really, really cool, and I'm trying to get it to work inside of a Next.js project (created with npx create-next-app).

The problem is that I get strange errors if I try to import it using ES6 syntax, i.e import Stockfish from '../../<path>'. I'm able to add a "raw" <script> as part of the JSX returned by a React component, but the Stockfish exposed there is not available elsewhere. I also get some linting issues.

Do you maybe have some advice on how I could proceed? Perhaps it's more appropriate to post this elsewhere? :)

...could I maybe use a worker that talks to Fairy Stockfish?

sombor-shuffle commented 1 year ago

An update on using a worker:

If foo.html becomes:

<html>
    <body>
        <script>

            const worker = new Worker('./worker.js');

        </script>
    </body>
</html>

and worker.js contains:

importScripts("./lib/stockfish.js");

let stockfish = null;

Stockfish().then(_stockfish => {
    stockfish = _stockfish;
    stockfish.addMessageListener(console.log);
})

Then the console will output the following error (formatting changed):

wasm streaming compile failed: TypeError: Failed to execute 'compile' on 'WebAssembly': HTTP status code is not ok (stockfish.js:11)

falling back to ArrayBuffer instantiation (stockfish.js:11)

failed to asynchronously prepare wasm: CompileError: WebAssembly.instantiate(): expected magic word 00 61 73 6d, found 3c 21 44 4f @+0 (stockfish.js:11) 

CompileError: WebAssembly.instantiate(): expected magic word 00 61 73 6d, found 3c 21 44 4f @+0 (stockfish.js:25)

Uncaught (in promise) RuntimeError: abort(CompileError: WebAssembly.instantiate(): expected magic word 00 61 73 6d, found 3c 21 44 4f @+0). Build with -s ASSERTIONS=1 for more info.
    at M (stockfish.js:25:112)
    at stockfish.js:138:293
sombor-shuffle commented 1 year ago

Working solution:


"use client"

import React, { useEffect } from 'react';

export default function Component() {

  useEffect(() => {
    // @ts-ignore
    console.log(Stockfish)
  });

  return (
      <React.Fragment>
        <script src="lib/stockfish.js"></script>
        { /* other things go here... */ }
      </React.Fragment>
  );
}

It is possible to include a <script> alongside the rest of the component and refer to Stockfish inside of an effect hook. It is necessary to also use @ts-ignore or else the linter complains "Cannot find name 'Stockfish'".

Is this a good solution? I'm really confused as to why it has to be done this way.

zeth commented 1 year ago

The pure Javascript code example two posts up by @sombor-shuffle should be the official documentation IMHO, the basic demo site and the fairyland demo site are both too mixed in with frameworks that the user probably won't be using.

sombor-shuffle commented 1 year ago

Hey @zeth, I realized later that the async... await pattern can be safely omitted. Also, if you happen to be using Next.js, there is the <Script /> component that could be used instead of the vanilla <script /> tag. From what I understand it lets Next.js bundle Stockfish together with all other assets, producing a single minified main script file. I found that you then have to set the "strategy" property to "beforeInteractive" (for this package).

I agree that the basic demo could be more accessible and I wouldn't mind re-writing it in plain js, if there is any enthusiasm for it.

It wouldn't be terrible at all if you could import Fairy Stockfish, although I lack whatever WebAssembly knowledge is required to make that possible.