nmrugg / stockfish.js

The Stockfish chess engine in Javascript
GNU General Public License v3.0
906 stars 129 forks source link

Trouble using stockfish.js as npm package in node.js #91

Closed ps2-controller closed 4 months ago

ps2-controller commented 4 months ago

I've taken a look through all the files in the examples folder, as well as all the issues here on github, but they don't make it clear to me how to use stockfish as an npm package in node.js

I think I'm fundamentally approaching this in the wrong way somehow, but it's unclear how.

Specifically, I'm trying to get the best move within a lambda function. I ran npm install stockfish and have the following simple file getBestMove.ts

import stockfish from 'stockfish';

const fen = '3r1r1k/pp2Nqbp/3Rb2p/2P1pp2/7P/N1P3P1/PP2QP2/R3K3 w - - 2 30'

interface TBestMoveResponse {
  bestMove: string | null;
}

export const getBestMove = async () => {
  const engine = stockfish();

  const details = await new Promise<TBestMoveResponse>((resolve, reject) => {
    let bestMove = null;

    engine.onmessage = (line: string) => {
      if (line.startsWith('bestmove')) {
        const tokens = line.split(' ');
        bestMove = tokens[1];

        resolve({ bestMove });
      }
    };

    engine.postMessage(`position fen ${fen}`);
    engine.postMessage(`go depth 20`);
  });

  engine.postMessage('quit');

  return details;

}
// stockfish.d.ts

declare module 'stockfish' {
  const stockfish: any;
  export default stockfish;
}
// tsconfig.json

{
  "compilerOptions": {
    "target": "es2020",
    "module": "ES2020",
    "rootDir": "./",
    "sourceMap": true,
    "strict": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "outDir": "./dist",
    "typeRoots": ["./node_modules/@types", "./src/types/libraries"],
    "baseUrl": "./src",
    "types": ["node"],
  },
  "include": ["index.ts", "src/**/*"],
  "exclude": ["node_modules"]
}
I ran npm install stockfish and have the following simple file getBestMove.ts

import stockfish from 'stockfish';

const fen = '3r1r1k/pp2Nqbp/3Rb2p/2P1pp2/7P/N1P3P1/PP2QP2/R3K3 w - - 2 30'

interface TBestMoveResponse {
  bestMove: string | null;
}

export const getBestMove = async () => {
  const engine = stockfish();

  const details = await new Promise<TBestMoveResponse>((resolve, reject) => {
    let bestMove = null;

    engine.onmessage = (line: string) => {
      if (line.startsWith('bestmove')) {
        const tokens = line.split(' ');
        bestMove = tokens[1];

        resolve({ bestMove });
      }
    };

    engine.postMessage(`position fen ${fen}`);
    engine.postMessage(`go depth 20`);
  });

  engine.postMessage('quit');

  return details;

}
// stockfish.d.ts

declare module 'stockfish' {
  const stockfish: any;
  export default stockfish;
}
// tsconfig.json

{
  "compilerOptions": {
    "target": "es2020",
    "module": "ES2020",
    "rootDir": "./",
    "sourceMap": true,
    "strict": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "outDir": "./dist",
    "typeRoots": ["./node_modules/@types", "./src/types/libraries"],
    "baseUrl": "./src",
    "types": ["node"],
  },
  "include": ["index.ts", "src/**/*"],
  "exclude": ["node_modules"]
}
// webpack.config.cjs

const path = require('path');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const slsw = require('serverless-webpack');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  entry: slsw.lib.entries,
  experiments: {
    outputModule: true,
  },
  target: 'node',
  mode: slsw.isLocal ? 'development' : 'production',
  performance: {
    hints: false,
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: ['babel-loader', 'ts-loader'],
      },
    ]
  },
  node: false,
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin({
      terserOptions: {
        keep_classnames: true,
        keep_fnames: true,
      }
    }
    )],
  },
  resolve: {
    extensions: ['.cjs', '.mjs', '.js', '.ts'],
    plugins: [
      new TsconfigPathsPlugin({
        configFile: path.resolve(__dirname, 'tsconfig.json'),
      }),
    ]
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin(),
  ],
  output: {
    path: slsw.lib.webpack.isLocal ? path.join(__dirname, '.webpack') : path.join(__dirname, 'dist'),
    chunkFormat: 'module',
    library: {
      type: "module",
    },
  },
};

but when I build my project, I get Module not found: Error: Can't resolve 'stockfish'. I haven't had this issue importing any other modules in my entire project so far and am not treating the stockfish module any differently.

ps2-controller commented 4 months ago

Closing the issue - I got it working this way:

import { createRequire } from "module"
import path from 'path';
const require = createRequire(import.meta.url)

const stockfish_path = path.join('node_modules', 'stockfish', 'src', 'stockfish-nnue-16.js')
const load_engine = require('./load_engine.cjs')

from there, following the node_abstractions example in the examples folder