fastify / fastify-swagger-ui

Serve Swagger-UI for Fastify
MIT License
132 stars 40 forks source link

Esbuild and fastify-swagger-ui build output can't access the swagger-ui-dist static files. #65

Closed drcampbell closed 8 months ago

drcampbell commented 1 year ago

Prerequisites

Fastify version

4.17.0

Plugin version

1.8.1

Node.js version

18.16.0

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

12.6.5

Description

Attempting to run code built with esbuild, fastify, fastify-swagger-ui results in the error:

node:internal/modules/cjs/loader:1075
  const err = new Error(message);
              ^

Error: Cannot find module './static/logo.svg'
Require stack:
- /Users/david/workspace/fastifySwaggerTest/dist/index.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15)
    at Function.resolve (node:internal/modules/cjs/helpers:116:19)
    at NE (file:///Users/david/workspace/fastifySwaggerTest/dist/index.js:533:3585)
    at lo.exec (file:///Users/david/workspace/fastifySwaggerTest/dist/index.js:17:1196)
    at Et.om (file:///Users/david/workspace/fastifySwaggerTest/dist/index.js:17:2907)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/Users/david/workspace/fastifySwaggerTest/dist/index.js' ]
}

Supplying a valid logo will run the server, but the assets served at static will 404.

In the provided esbuild.config.mjs, we are defining filename and dirname for integration with commonjs libraries.

Steps to Reproduce

Clone repo

run yarn start and observe that it doesn't error run yarn buildandrun and observe that it errors

Expected Behavior

No response

mcollina commented 1 year ago

This seems normal and expected. I do not even know how this could be fixed or handled on our part.

I would recommend to not bundle your servers.

drcampbell commented 1 year ago

Advantages to bundling your fastify apps: 1). Smaller image size which is less for Serverless compute to load and size in Storage 2). Smaller memory footprint.

If I run my application with TS-Node, it takes just over 600MB of memory.
If I build with esbuild and run the result, it takes just over 100MB of memory.

Deploying to Google Cloud Run with the unbuilt version requires a larger instance as I get a "heap error: out of memory" with the unbuilt version. I returned my app to being built and will try to find a different solution.

Uzlopak commented 1 year ago

You can bundle, but then handle the copying of the files to the expected places yourself.

arimgibson commented 1 year ago

Adding onto @Uzlopak's comment, there's a popular esbuild plugin, esbuild-plugin-copy, which can handle this task. I'm personally bundling through esbuild and then compiling to a Docker image, so I added a COPY directive in my Dockerfile.

Regardless of how you get the files into your final bundle, the location you'll want to copy from is node_modules/@fastify/swagger-ui/static.

mcollina commented 1 year ago

@arimgibson would you like to add a small guide in this repo on how to do this?

drcampbell commented 1 year ago

Thanks for the suggestions! I appreciate it.

arimgibson commented 1 year ago

@mcollina happily! Just straight into the README?

I'm at capacity right now but can submit that PR in ~1 week

Falven commented 10 months ago

@mcollina happily! Just straight into the README?

I'm at capacity right now but can submit that PR in ~1 week

Longest week of my life 😉

FYI I am getting 404s for other assets besides the index.html, swagger-initializer.js, hook.js when doing this. Not sure if this gives any hints:

[reload] {"level":50,"time":1699515508346,"pid":21380,"err":{"type":"TypeError","message":"The \"original\" argument must be of type function. Received an instance of Object","stack":"TypeError [ERR_INVALID_ARG_TYPE]: The \"original\" argument must be of type function. Received an instance of Object\n at promisify (node:internal/util:343:3)\n at Object. (node_modules\.pnpm\node_modules\@fastify\static\index.js:9:21)\n at Module._compile (node:internal/modules/cjs/loader:1256:14)\n at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)\n at Module.load (node:internal/modules/cjs/loader:1119:32)\n at Module._load (node:internal/modules/cjs/loader:960:12)\n at Module.require (node:internal/modules/cjs/loader:1143:19)\n at require (node:internal/modules/cjs/helpers:119:18)\n at Object. (node_modules\@fastify\swagger-ui\lib\routes.js:5:23)\n at Module._compile (node:internal/modules/cjs/loader:1256:14)","code":"ERR_INVALID_ARG_TYPE"},"msg":"The \"original\" argument must be of type function. Received an instance of Object"}

mcollina commented 9 months ago

no clue

davidjbng commented 9 months ago

FYI I am getting 404s for other assets besides the index.html, swagger-initializer.js, hook.js when doing this.

I was having the same issue and I could only get it to work by applying the following patch:

   // serve swagger-ui with the help of @fastify/static
   fastify.register(fastifyStatic, {
-    root: path.join(__dirname, '..', 'static'),
+    root: opts.baseDir || path.join(__dirname, '..', 'static'),
     prefix: staticPrefix,
     decorateReply: false
   })

Then defining the baseDir option

  await server.register(fastifySwaggerUi, {
    baseDir: process.env.ASSETS_ROOT ? path.resolve(process.env.ASSETS_ROOT) : undefined,
    // ...
  })

Copying the assets in my Dockerfile

COPY ./node_modules/@fastify/swagger-ui/static ./assets
ENV ASSETS_ROOT=/app/assets/
Falven commented 9 months ago

FYI I am getting 404s for other assets besides the index.html, swagger-initializer.js, hook.js when doing this.

I was having the same issue and I could only get it to work by applying the following patch:

   // serve swagger-ui with the help of @fastify/static
   fastify.register(fastifyStatic, {
-    root: path.join(__dirname, '..', 'static'),
+    root: opts.baseDir || path.join(__dirname, '..', 'static'),
     prefix: staticPrefix,
     decorateReply: false
   })

Then defining the baseDir option

  await server.register(fastifySwaggerUi, {
    baseDir: process.env.ASSETS_ROOT ? path.resolve(process.env.ASSETS_ROOT) : undefined,
  // ...
  })

Copying the assets in my Dockerfile

COPY ./node_modules/@fastify/swagger-ui/static ./assets
ENV ASSETS_ROOT=/app/assets/

Thank you! 😊

Uzlopak commented 9 months ago

@davidjbng

Can you provide a PR please?

davidjbng commented 9 months ago

Can you provide a PR please?

Yes I will. I noticed the csp.json is not available yet using my approach. Need to check that first. Edit: Does not seem to be related

wesbragagt commented 9 months ago

For those still looking for a solution, I manage to get it working with this setup in a esbuild.config.js file. You'd add a build script in the package.json that calls node esbuild.config.js

// esbuild.config.js
import esbuild from "esbuild";
import { promisify } from "util";
import path from "path";
import fs from "fs";

const copyDir = async (src, dest) => {
  const readdir = promisify(fs.readdir);
  const mkdir = promisify(fs.mkdir);
  const copyFile = promisify(fs.copyFile);
  const stat = promisify(fs.stat);

  // Create destination directory if not exists
  await mkdir(dest, { recursive: true });

  const files = await readdir(src);

  for (const file of files) {
    const srcPath = path.join(src, file);
    const destPath = path.join(dest, file);

    const stats = await stat(srcPath);
    if (stats.isDirectory()) {
      await copyDir(srcPath, destPath);
    } else {
      await copyFile(srcPath, destPath);
    }
  }
};

esbuild
  .build({
    entryPoints: ["src/index.ts"],
    logLevel: "info",
    outdir: "dist",
    bundle: true,
    platform: "node",
    format: "cjs",
  })
  .then(() => {
    copyDir("node_modules/@fastify/swagger-ui/static", "dist/static");
  });
DavidTanner commented 8 months ago

I have the assets copied to the dist/static directory, but am getting 404 errors for some of the resources. The .css, .js, and .png files are all returning 404, except index.html, and swagger-initializer.js are returning as expected.

DavidTanner commented 8 months ago

I was able to get the esbuild bundle to work by replacing '..' with '.' in the lib/routes.js file. The file expects the static folder to be in a parallel directory.

import { build } from 'esbuild';
import textReplace from 'esbuild-plugin-text-replace';
...
await build({
  ...
  plugins: [
    textReplace({
      include: /node_modules\/@fastify\/swagger-ui\/lib\/routes\.js$/,
      pattern: [
        [`'\.\.'`, `'.'`]
      ]
    })
]
somecodingwitch commented 6 months ago

You can bundle, but then handle the copying of the files to the expected places yourself.

Which files? That's the problem. Where are the default files?