vite icon indicating copy to clipboard operation
vite copied to clipboard

Cannot bundle .node files

Open forgetso opened this issue 2 years ago • 18 comments

Describe the bug

I am trying to create a server side bundle that includes packages containing .node files. In this specific instance, the problematic package is nodejs-polars.

in/nodejs-polars.linux-arm-gnueabihf.node does not exist
✓ 13 modules transformed.
✓ built in 1.39s
[commonjs] Unexpected character '' (Note that you need plugins to import files that are not JavaScript)
file: /home/projects/vitejs-vite-xp1ggj/node_modules/nodejs-polars/bin/series/list.js:1:0
1: ELF>�rd@X�@8
   ^
2: @@@0�bd�bd...
3: 

I have tried various plugins to resolve this but none of them seem to work.

My config is as follows:

import native from 'vite-plugin-native';
export default function () {
  return {
    ssr: {
      noExternal: ['nodejs-polars'],
    },
    optimizeDeps: {
      include: ['linked-dep', 'node_modules'],
      exclude: ['nodejs-polars'],
    },
    esbuild: {
      platform: 'node',
      target: 'node16',
    },
    build: {
      lib: {
        entry: './main.js',
        name: 'demo',
      },
      ssr: true,
      target: 'node16',
    },
    plugins: [native({ target: 'esm' })],
  };
}

Reproduction

https://stackblitz.com/edit/vitejs-vite-xp1ggj?file=vite.config.ts,package.json&terminal=dev

Steps to reproduce

Run vite build --config vite.config.ts

System Info

System:
    OS: Linux 5.4 Ubuntu 20.04.6 LTS (Focal Fossa)
    CPU: (8) x64 AMD FX(tm)-8350 Eight-Core Processor
    Memory: 9.66 GB / 31.31 GB
    Container: Yes
    Shell: 5.8 - /usr/bin/zsh
  Binaries:
    Node: 18.14.2 - ~/.nvm/versions/node/v18.14.2/bin/node
    Yarn: 1.22.19 - ~/.nvm/versions/node/v18.14.2/bin/yarn
    npm: 8.9.0 - ~/.nvm/versions/node/v18.14.2/bin/npm
  Browsers:
    Brave Browser: 115.1.56.14
    Chrome: 116.0.5845.96



### Used Package Manager

npm

### Logs

_No response_

### Validations

- [X] Follow our [Code of Conduct](https://github.com/vitejs/vite/blob/main/CODE_OF_CONDUCT.md)
- [X] Read the [Contributing Guidelines](https://github.com/vitejs/vite/blob/main/CONTRIBUTING.md).
- [X] Read the [docs](https://vitejs.dev/guide).
- [X] Check that there isn't [already an issue](https://github.com/vitejs/vite/issues) that reports the same bug to avoid creating a duplicate.
- [X] Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to [vuejs/core](https://github.com/vuejs/core) instead.
- [X] Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/vitejs/vite/discussions) or join our [Discord Chat Server](https://chat.vitejs.dev/).
- [X] The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.

forgetso avatar Sep 04 '23 18:09 forgetso

This issue is preventing me from using many modules. No workaround or package has worked.

HappyGick avatar Sep 16 '23 17:09 HappyGick

I managed to bundle nodejs-polars using this config but it's incredibly hacky.

forgetso avatar Sep 16 '23 22:09 forgetso

Well, that's one hell of a hack. But I'll have to study and use it I guess. Thank you so much. Resolving .node files should be a feature in Vite.

HappyGick avatar Sep 17 '23 04:09 HappyGick

The same problem occurs with fsevent.

I have confirmed that it works by adding the following settings to astro.config.mjs.

  vite: {
    optimizeDeps: {
      exclude: ["fsevents"],
    },
  },

https://stackoverflow.com/questions/75640753/vite-esbuild-error-no-loader-is-configured-for-node-files-node-modules-fs

Error log
13:51:15 [vite] Forced re-optimization of dependencies

 astro  v4.4.9 ready in 341 ms

┃ Local    http://localhost:3001/
┃ Network  use --host to expose

13:51:15 watching for file changes...
✘ [ERROR] No loader is configured for ".node" files: node_modules/.pnpm/[email protected]/node_modules/fsevents/fsevents.node

    node_modules/.pnpm/[email protected]/node_modules/fsevents/fsevents.js:13:23:
      13 │ const Native = require("./fsevents.node");
         ╵                        ~~~~~~~~~~~~~~~~~

13:51:25 [200] / 40ms
13:51:25 [ERROR] [UnhandledRejection] Astro detected an unhandled rejection. Here's the stack trace:
Error: Build failed with 1 error:
node_modules/.pnpm/[email protected]/node_modules/fsevents/fsevents.js:13:23: ERROR: No loader is configured for ".node" files: node_modules/.pnpm/[email protected]/node_modules/fsevents/fsevents.node
    at failureErrorWithLog ({...}/node_modules/.pnpm/[email protected]/node_modules/esbuild/lib/main.js:1651:15)
    at {...}//node_modules/.pnpm/[email protected]/node_modules/esbuild/lib/main.js:1059:25
    at {...}//node_modules/.pnpm/[email protected]/node_modules/esbuild/lib/main.js:1527:9
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
  Hint:
    Make sure your promises all have an `await` or a `.catch()` handler.
  Error reference:
    https://docs.astro.build/en/reference/errors/unhandled-rejection/
  Stack trace:
    at {...}//node_modules/.pnpm/[email protected]/node_modules/fsevents/fsevents.js: ERROR: No loader is configured for ".node" files: node_modules/.pnpm/[email protected]/node_modules/fsevents/fsevents.node:13:23
    [...] See full stack trace in the browser, or rerun with --verbose.

totto2727 avatar Mar 04 '24 05:03 totto2727

I managed to bundle nodejs-polars using this config but it's incredibly hacky. image do you have any idea how to handle this problem of canvas library, i am using nuxt3 and vite but can't handle it, thank you

ghost avatar Mar 11 '24 03:03 ghost

vite.optimizeDeps.exclude solution doesn't work with pnpm workspaces. Example https://github.com/stereobooster/braindb

pnpm build
...
demo-astro-cli:build: 20:25:22 [vite] Re-optimizing dependencies because vite config has changed
demo-astro-cli:build: 20:25:23 [build] output: "static"
demo-astro-cli:build: 20:25:23 [build] directory: /mddb/packages/demo-astro-cli/dist/
demo-astro-cli:build: 20:25:23 [build] Collecting build info...
demo-astro-cli:build: 20:25:23 [build] ✓ Completed in 1.09s.
demo-astro-cli:build: 20:25:23 [build] Building static entrypoints...
demo-astro-cli:build: 20:25:25 [ERROR] [vite] x Build failed in 1.85s
demo-astro-cli:build: [commonjs--resolver] ../../node_modules/.pnpm/[email protected]/node_modules/fsevents/fsevents.node (1:0): Unexpected character '�' (Note that you need plugins to import files that are not JavaScript)
demo-astro-cli:build: file: /mddb/node_modules/.pnpm/[email protected]/node_modules/chokidar/index.js:1:0
demo-astro-cli:build: 1: ����@<�
                                ��*...
demo-astro-cli:build:    ^
4*mo-astro-cli:build: 2:
  h���/System/Library/Frameworks/CoreFoundation.framework/Versions...
demo-astro-cli:build: 3: ���H��1�H���L���L���H�����L�E�L��H��1�����uH�E�H�[A^A_]��}f.�UH��AWAVSPI��H��I��H�H...
demo-astro-cli:build:   Stack trace:
demo-astro-cli:build:     at getRollupError (file:///mddb/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/parseAst.js:379:41)
demo-astro-cli:build:     at convertNode (file:///mddb/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/node-entry.js:12914:10)
demo-astro-cli:build:     at Module.setSource (file:///mddb/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/node-entry.js:14073:24)
demo-astro-cli:build:   Caused by:
demo-astro-cli:build:   Unexpected character '�'
demo-astro-cli:build:     at getRollupError (file:///mddb/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/parseAst.js:379:41)
demo-astro-cli:build:     at convertNode (file:///mddb/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/node-entry.js:12914:10)
demo-astro-cli:build:     at Module.setSource (file:///mddb/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/es/shared/node-entry.js:14073:24)
demo-astro-cli:build:  ELIFECYCLE  Command failed with exit code 1.
demo-astro-cli:build: ERROR: command finished with error: command (/mddb/packages/demo-astro-cli) pnpm run build exited (1)
demo-astro-cli#build: command (/mddb/packages/demo-astro-cli) pnpm run build exited (1)

stereobooster avatar Apr 04 '24 18:04 stereobooster

We've managed to get .node files working, see our blog post here

goastler avatar Apr 10 '24 11:04 goastler

Are there any plans on this one? Even though @goastler shared a nice blog, it doesn't solve anything for the general public.

sagoez avatar Jun 13 '24 16:06 sagoez

The handling of .node files (and other binary-like files) which occur in nodejs packages needs to be addressed by either vite or a plugin. If vite will not support this (as it could be considered out of scope) then it will have to be a plugin. Our business interests have moved away from this at the moment, so making a plugin would be very low priority for @forgetso and myself at Prosopo.

Further, there's more complications with files other than .node files (iirc .glyph) which serve similar purpose, so we'd need to do a bunch of research into how to handle those files correctly.

tldr; probably better for someone who knows more about these files to implement a plugin / build support in vite

goastler avatar Jun 14 '24 08:06 goastler

related https://github.com/vitejs/vite/issues/5688

stereobooster avatar Oct 09 '24 19:10 stereobooster

For those searching for a solution, I (chatgpt to be honest) came up with a quick plugin:

function nativeFilesPlugin(): PluginOption {
  const files = new Map<string, { readonly fileName: string; readonly fileContent: Buffer }>();

  return {
    name: 'node-binaries-plugin',
    async load(id) {
      if (!id.endsWith('.node')) {
        return null;
      }

      const fileContent = await fs.readFile(id);
      const hash = createHash('sha256').update(fileContent).digest('hex').slice(0, 8);
      const fileName = `${path.basename(id, '.node')}.${hash}.node`;
      files.set(id, { fileName, fileContent });

      return `export default require('./${fileName}');`;
    },

    generateBundle(_, bundle) {
      for (const [id, { fileName, fileContent }] of files.entries()) {
        this.emitFile({ type: 'asset', fileName, source: fileContent });
        delete bundle[id];
      }
    },
  };
}

I'm building into cjs, so I also have these settings:

build: {
  rollupOptions: {
    output: {
      format: 'cjs',
    },
  },
  commonjsOptions: {
    requireReturnsDefault: 'auto',
  }
}

raveclassic avatar Oct 17 '24 17:10 raveclassic

It looks like an even simpler version also works:

{
      name: 'require-node-binaries-plugin',
      transform: (code, id) => {
        if (id.endsWith('.node')) {
          return code.replace(/export default "(.+)"/, 'export default require(".$1")');
        }

        return;
      },
    }

paired with build.ssrEmitAssets: true and assetsInclude: ['**/*.node']

raveclassic avatar Oct 18 '24 09:10 raveclassic

Small update here.

Native assets didn't work in my project because I'm using uWebSockets.js, which is an ESM-wrapper around CJS-module that does dynamic requires. No matter how hard I tried, I wasn't able to make them work with assets. Dynamic requires are typically handled by @rollup/plugin-commonjs which is a whole different story, as it does some code transformations that are hard to address, if vite treats .node binaries as assets.

So I had to revert to the original naive plugin, and, unfortunately, add a special hard-coded case for uWebSockets.js because of dynamic requires.

function nativeFilesPlugin(): PluginOption {
  interface NativeFile {
    readonly fileName: string;
    readonly fileContent: Buffer;
  }
  const nativeFiles = new Map<string, NativeFile>();
  const uws = require.resolve('uWebSockets.js');
  const readNativeFile = async (filePath: string): Promise<NativeFile> => {
    const fileContent = await readFile(filePath);
    const hash = createHash('sha256').update(fileContent).digest('hex').slice(0, 8);
    const fileName = `${path.basename(filePath, '.node')}.${hash}.node`;

    return { fileName, fileContent };
  };

  return {
    name: 'native-files-plugin',

    async load(id) {
      if (id === uws) {
        // Special handling for the ESM-wrapper around CJS-module with dynamic requires (uWebSockets.js).
        const nativeFile = await readNativeFile(
          // Yes, build the file name at build time, not runtime 🤷‍♂️(we can't do dynamic `import {} from ''`)
          path.resolve(path.dirname(uws), './uws_' + process.platform + '_' + process.arch + '_' + process.versions.modules + '.node'),
        );
        nativeFiles.set(id, nativeFile);

        return `export default require('./${nativeFile.fileName}')`;
      } else if (id.endsWith('.node')) {
        const nativeFile = await readNativeFile(id);
        nativeFiles.set(id, nativeFile);

        return `export default require('./${nativeFile.fileName}');`;
      }

      return;
    },

    generateBundle(_, bundle) {
      for (const [id, { fileName, fileContent }] of Array.from(nativeFiles.entries())) {
        this.emitFile({ type: 'asset', fileName, source: fileContent });
        delete bundle[id];
      }
    },
  };
}

I believe the issue should be handled on rollup's side, not on vite's, as I believe it's purely rollup's job to bundle everything into a module graph that vite would understand. There's also an on-going work on Rolldown (a Rust replacement for rollup), so maybe it should even go there, or maybe it's already supported there, I can't test it right now.

raveclassic avatar Oct 22 '24 07:10 raveclassic

In my case, everything was solved after deleting package-lock.json and node_modules and installing everything again.

cjoecker avatar Nov 19 '24 17:11 cjoecker

I have many native deps and its works fine, but sqlite3 / better-sqlite3 have some different code to load .node files.

In my case, fully solved by "rollup-plugin-natives": "^0.7.8"

Image

Viiprogrammer avatar Jan 18 '25 11:01 Viiprogrammer

I am trying to import a binary .node file on my project, but these solutions failed, any suggestions?

jixbo avatar Mar 28 '25 11:03 jixbo

After searching for a while, I wrote https://github.com/biw/vite-plugin-native-modules

biw avatar Nov 18 '25 00:11 biw