Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
2 tasks done
drcampbell opened this issue May 17, 2023 · 17 comments · Fixed by #108
Closed
2 tasks done
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers

Comments

@drcampbell
Copy link

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

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
Copy link
Member

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
Copy link
Author

drcampbell commented May 24, 2023

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
Copy link
Collaborator

Uzlopak commented May 25, 2023

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

@arimgibson
Copy link

arimgibson commented Jul 28, 2023

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
Copy link
Member

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

@drcampbell
Copy link
Author

Thanks for the suggestions! I appreciate it.

@arimgibson
Copy link

@mcollina happily! Just straight into the README?

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

@Uzlopak Uzlopak added documentation Improvements or additions to documentation good first issue Good for newcomers labels Aug 8, 2023
@Falven
Copy link

Falven commented Nov 9, 2023

@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
Copy link
Member

no clue

@davidjbng
Copy link
Contributor

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
Copy link

Falven commented Dec 5, 2023

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
Copy link
Collaborator

Uzlopak commented Dec 6, 2023

@davidjbng

Can you provide a PR please?

@davidjbng
Copy link
Contributor

davidjbng commented Dec 6, 2023

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
Copy link

wesbragagt commented Dec 11, 2023

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants