angular/angular-cli

Sqlite3 causing "Could not locate the bindings file" when added to SSR server

coltossoff opened this issue · 3 comments

Is this a bug report or a feature request?

  • Bug Report
  • Feature Request

Please provide the steps to reproduce the issue [Bug Report only]

I added ssr to my existing project and verified to it was working, I also added an extra endpoint to check that I could.
Then I added sqlite3 and the associated types.
Things started to go wrong when I would try opening a DB using new Database('./sqlite.db'), the project still builds but gives me the following error at runtime:

./node_modules/bindings/bindings.js:135
  throw err;
  ^

Error: Could not locate the bindings file. Tried:
 → C:\repos\AngularSignalsDemo\build\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\build\Debug\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\build\Release\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\out\Debug\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\Debug\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\out\Release\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\Release\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\build\default\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\compiled\22.0.0\win32\x64\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\addon-build\release\install-root\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\addon-build\debug\install-root\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\addon-build\default\install-root\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\lib\binding\node-v127-win32-x64\node_sqlite3.node
    at bindings (./node_modules/bindings/bindings.js:126:9)
    at Object.48047 (./node_modules/sqlite3/lib/sqlite3-binding.js:1:37)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at Object.98815 (./node_modules/sqlite3/lib/sqlite3.js:2:17)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at 69787 (C:\repos\AngularSignalsDemo\dist\testPrep\server\main.js:32:65)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at <anonymous> (./webpack/bootstrap:36:1)
    at Function.__webpack_require__.O (./webpack/runtime/chunk%20loaded:23:1)
    at __webpack_require__.x (./webpack/bootstrap:37:1) {
  tries: [
    'C:\\repos\\AngularSignalsDemo\\build\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\build\\Debug\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\build\\Release\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\out\\Debug\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\Debug\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\out\\Release\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\Release\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\build\\default\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\compiled\\22.0.0\\win32\\x64\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\addon-build\\release\\install-root\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\addon-build\\debug\\install-root\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\addon-build\\default\\install-root\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\lib\\binding\\node-v127-win32-x64\\node_sqlite3.node'
  ]
}

Node.js v22.0.0
A server error has occurred.
node exited with 1 code.

So I added bindings which seemed to be what it was asking for, but I keep getting the same result. At this point I decided to try creating a blank Express project to see if I have something screwed up on my computer, but it works. Then I try bringing options from tsconfig into to see if there's some issue there, but outside some easily fixed mistakes it still works.

As a result, I'm confused as to what is happening since adding sqlite to a server doesn't seem like a big ask. To be clear this is server-side not client-side.

Angular 17.3.6, sqlite3 5.1.7

server.ts

import 'zone.js/node';

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import * as express from 'express';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import AppServerModule from './src/main.server';
import { Database } from 'sqlite3';

// The Express app is exported so that it can be used by serverless Functions.
export async function app(): Promise<express.Express> {
  const server = express.default();
  const distFolder = join(process.cwd(), 'dist/testPrep/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html'))
    ? join(distFolder, 'index.original.html')
    : join(distFolder, 'index.html');

  const commonEngine = new CommonEngine();

  server.set('view engine', 'html');
  server.set('views', distFolder);

  new Database('./sqlite.db')
  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  // Serve static files from /browser
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  // All regular routes use the Angular engine
  server.get('*', (req, res, next) => {
    const { protocol, originalUrl, baseUrl, headers } = req;

    commonEngine
      .render({
        bootstrap: AppServerModule,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: distFolder,
        providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });

  return server;
}

async function run(): Promise<void> {
  const port = process.env['PORT'] || 4000;

  // Start up the Node server
  const server = await app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export default AppServerModule;

package.json

{
  "name": "test-prep",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "dev:ssr": "ng run testPrep:serve-ssr",
    "serve:ssr": "node dist/testPrep/server/main.js",
    "build:ssr": "ng build && ng run testPrep:server",
    "prerender": "ng run testPrep:prerender"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^17.3.6",
    "@angular/common": "^17.3.6",
    "@angular/compiler": "^17.3.6",
    "@angular/core": "^17.3.6",
    "@angular/forms": "^17.3.6",
    "@angular/platform-browser": "^17.3.6",
    "@angular/platform-browser-dynamic": "^17.3.6",
    "@angular/platform-server": "^17.3.6",
    "@angular/router": "^17.3.6",
    "@angular/ssr": "^17.3.6",
    "bindings": "^1.5.0",
    "express": "^4.18.2",
    "rxjs": "~7.8.0",
    "sqlite3": "^5.1.7",
    "tslib": "^2.6.2",
    "zone.js": "~0.14.4"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^17.3.6",
    "@angular/cli": "^17.3.6",
    "@angular/compiler-cli": "^17.3.6",
    "@types/express": "^4.17.17",
    "@types/sqlite3": "^3.1.11",
    "@types/jasmine": "~4.3.0",
    "@types/node": "^18.19.31",
    "browser-sync": "^3.0.0",
    "jasmine-core": "~4.6.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "typescript": "~5.4.5"
  }
}

tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "ES2022",
    "module": "ES2022",
    "useDefineForClassFields": false,
    "esModuleInterop": true,
    "lib": [
      "ES2022",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

https://github.com/coltossoff/AngularSignalsDemo/tree/sqlite

Please provide the expected behavior vs the actual behavior you encountered [Bug Report only]

I expect it to be able to connect to the provided sqlite database.

Please provide a screenshot if possible [Bug Report only]

image

Please provide the exception or error you saw [Bug Report only]

$ npm run dev:ssr

> test-prep@0.0.0 dev:ssr
> ng run testPrep:serve-ssr

****************************************************************************************
This is a simple server for use in testing or debugging Angular applications locally.
It hasn't been reviewed for security issues.

DON'T USE IT FOR PRODUCTION!
****************************************************************************************
✔ Browser application bundle generation complete.
✔ Index html generation complete.

Initial chunk files | Names         |  Raw size
vendor.js           | vendor        |   2.65 MB | 
polyfills.js        | polyfills     | 107.59 kB | 
main.js             | main          |  20.40 kB | 
runtime.js          | runtime       |   5.90 kB | 
styles.css          | styles        |   1.73 kB | 

                    | Initial total |   2.78 MB

Build at: 2024-05-07T23:17:37.224Z - Hash: 10a6fb047964c894 - Time: 50467ms
✔ Server application bundle generation complete.

Initial chunk files | Names         | Raw size
vendor.js           | vendor        |  5.80 MB | 
main.js             | main          | 46.89 kB | 

                    | Initial total |  5.85 MB

Lazy chunk files    | Names         | Raw size
929.js              | xhr2          | 43.54 kB | 

Build at: 2024-05-07T23:17:42.646Z - Hash: e250577016f0c687 - Time: 65629ms

Compiled successfully.
./node_modules/bindings/bindings.js:135
  throw err;
  ^


Error: Could not locate the bindings file. Tried:
 → C:\repos\AngularSignalsDemo\build\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\build\Debug\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\build\Release\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\out\Debug\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\Debug\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\out\Release\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\Release\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\build\default\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\compiled\22.0.0\win32\x64\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\addon-build\release\install-root\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\addon-build\debug\install-root\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\addon-build\default\install-root\node_sqlite3.node
 → C:\repos\AngularSignalsDemo\lib\binding\node-v127-win32-x64\node_sqlite3.node
    at bindings (./node_modules/bindings/bindings.js:126:9)
    at Object.48047 (./node_modules/sqlite3/lib/sqlite3-binding.js:1:37)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at Object.98815 (./node_modules/sqlite3/lib/sqlite3.js:2:17)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at 69787 (C:\repos\AngularSignalsDemo\dist\testPrep\server\main.js:32:65)
    at __webpack_require__ (./webpack/bootstrap:19:1)
    at <anonymous> (./webpack/bootstrap:36:1)
    at Function.__webpack_require__.O (./webpack/runtime/chunk%20loaded:23:1)
    at __webpack_require__.x (./webpack/bootstrap:37:1) {
  tries: [
    'C:\\repos\\AngularSignalsDemo\\build\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\build\\Debug\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\build\\Release\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\out\\Debug\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\Debug\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\out\\Release\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\Release\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\build\\default\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\compiled\\22.0.0\\win32\\x64\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\addon-build\\release\\install-root\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\addon-build\\debug\\install-root\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\addon-build\\default\\install-root\\node_sqlite3.node',
    'C:\\repos\\AngularSignalsDemo\\lib\\binding\\node-v127-win32-x64\\node_sqlite3.node'
  ]
}

Node.js v22.0.0
A server error has occurred.
node exited with 1 code.

Is this a browser-specific issue? If so, please specify the device, browser, and version. [Bug Report only]

no, this is server related

Description [Feature Request only]

No response

Proposed solution [Feature Request only]

No response

Alternatives considered [Feature Request only]

No response

Since sqlite3 is a native binary, you need to exclude it from being bundled.

        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "externalDependencies": ["sqlite3"],
            "outputPath": "dist/testPrep/server",
            "main": "server.ts",
            "tsConfig": "tsconfig.server.json"
          },

Thanks, that worked. Is there documentation on externalDependencies, I needed something similar elsewhere but couldn't find anything?