Skip to content

Broken with Compiled Windows Exe (Node Single Executable Application SEA) #1104

@SammyLee40

Description

@SammyLee40

Hi,

I was recently trying out UWS and loved the performance gains, but wanted to make sure it worked with .exe.
(https://nodejs.org/api/single-executable-applications.html#single-executable-applications)

First, I tried using it with npx webpack. Works great!

Then, I tried using it by injecting the blob into my executable. This works for other .node files perfectly, but with
the same uws_win32_x64_120.node we used for our bundle.js that worked- we see it crash our exe with no error.

I've managed to strip away everything and get it down to the one function causing the error, so other than require()
or createRequire(), the real problem is dlopen(). For whatever reason, even with the same system, seconds after running
the working dlopen on a bundled JS file, the same method fails when run inside an executable. I've debugged to making
sure the node file exists and is found properly, but can't go much further without C code, as NodeJS exe refuses to catch
the error, crashing instead, making me think some kind of segfault or otherwise is happening on UWS's side.

I've created a working minimal example below.

To run it, just do:

node server.js

which should work and show:

node server.js

Starting server... before UWS
__dirname: C:\Users\slee\Downloads\minimal
UWS path: C:\Users\slee\Downloads\minimal\uws_win32_x64_120.node
Attempting to load UWS native addon...
UWS loaded successfully
module.exports keys: [
'App',
'SSLApp',
'H3App',
...
'DEDICATED_DECOMPRESSOR_1KB',
'DEDICATED_DECOMPRESSOR_512B',
'LIBUS_LISTEN_EXCLUSIVE_PORT'
]
Starting server... after UWS
App instance created successfully
Server started on port 3000

Then you can do npx webpack and node bundle.js and see the same output, indicating packing works fine.
However, if we compiled the executable by doing:

node -e "require('fs').copyFileSync(process.execPath, 'test.exe')" > Windows command
cp $(command -v node) test > Linux command

node --experimental-sea-config sea-config.json

npx postject test.exe NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 > Windows command
npx postject test NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 > Linux command
then running test.exe shows:

C:\Users\slee\Downloads\minimal>test.exe
Starting server... before UWS
__dirname: C:\Users\slee\Downloads\minimal
UWS path: C:\Users\slee\Downloads\minimal\uws_win32_x64_120.node
Attempting to load UWS native addon...

C:\Users\slee\Downloads\minimal>

Just crashing, despite our try/catch, indicating the dlopen or C code is faulting.

Here's a sample server.js:

const { dlopen } = require('node:process');
const { constants } = require('node:os');
const { join } = require('node:path');
const fs = require('fs');

console.log('Starting server... before UWS');

let uws;

try {
    console.log('__dirname:', __dirname);
    const uwsPath = join(__dirname, 'uws_linux_x64_120.node');
    console.log('UWS path:', uwsPath);

    if (!fs.existsSync(uwsPath)) {
        throw new Error(`UWS native addon not found at ${uwsPath}`);
    }

    console.log('Attempting to load UWS native addon...');
    
    const module = { exports: {} };
    dlopen(module, uwsPath, constants.dlopen.RTLD_NOW | constants.dlopen.RTLD_GLOBAL);
    
    uws = module.exports;
    console.log('UWS loaded successfully');
    console.log('module.exports keys:', Object.keys(uws));

    if (!uws.App) {
        throw new Error('App is not defined in the loaded module');
    }

    console.log('Starting server... after UWS');

    const app = new uws.App();
    console.log('App instance created successfully');

    app.get('/*', (res, req) => {
        res.end('Hello World!');
    });
    app.listen(3000, (token) => {
        if (token) {
            console.log('Server started on port 3000');
        }
    });

} catch (error) {
    console.error('Error occurred:', error);
    fs.writeFileSync('error.log', `${error.stack || error.toString()}\n\nModule exports: ${JSON.stringify(uws, null, 2)}`);
    process.exit(1);
}

module.exports = { uws };

And here is a webpack.config.js and sea-config.json to put in the same directory,

webpack.config.js:

const path = require('path');

module.exports = {
    target: 'node',
    mode: 'production',
    entry: './server.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname),
    },
    module: {
      rules: [
        {
          test: /\.node$/,
          use: 'node-loader',
        },
      ],
    },
    resolve: {
      extensions: ['.js', '.json', '.node'],
    },
    externals: {
      'uWebSockets.js': 'commonjs uWebSockets.js',  // Treat uWebSockets as an external dependency
    },
  };

sea-config.json:

{
"main": "bundle.js",
"output": "sea-prep.blob",
"assets": {}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions