honojs / website

Repository for hono.dev
https://hono.dev
68 stars 208 forks source link

Fix "Error [ERR_MODULE_NOT_FOUND]: Cannot find module" on getting started guide for NodeJS Dockerfile #410

Closed iceniveth closed 1 week ago

iceniveth commented 1 week ago

Description

The goal of this PR is to fix Error [ERR_MODULE_NOT_FOUND]: Cannot find module when building the app using docker compose build. This seems to occur when using ESM imports w/o file extension.

To reproduce the issue (reproduction repo)

  1. I've followed the documentation https://hono.dev/docs/getting-started/nodejs#dockerfile
  2. I'll create a file src/aboutRoute.ts with the content:

    import { Hono } from "hono";
    
    const aboutRoute = new Hono().basePath("/about");
    aboutRoute.get("/", (c) => {
      return c.text("This is about route!");
    });
    
    export default aboutRoute;
  3. import that file in the Hono app src/index.ts

    import { serve } from "@hono/node-server";
    import { Hono } from "hono";
    +import aboutRoute from "./aboutRoute";
    
    const app = new Hono();
    
    app.get("/", (c) => {
      return c.text("Hello Hono!");
    });
    
    +app.route("/", aboutRoute);
    
    const port = 3000;
    console.log(`Server is running on port ${port}`);
    
    serve({
      fetch: app.fetch,
      port,
    });
    
  4. Create a docker-compose.yml file:

    version: '3'
    
    services:
      app:
        build:
          context: .
        ports:
          - "3000:3000"
  5. Run this in the terminal
    docker compose up --build
  6. It will produce
    [+] Building 1.3s (16/16) FINISHED                                               docker:default
    => [app internal] load build definition from Dockerfile                                   0.0s
    => => transferring dockerfile: 655B                                                       0.0s
    => [app internal] load .dockerignore                                                      0.0s
    => => transferring context: 2B                                                            0.0s
    => [app internal] load metadata for docker.io/library/node:20-alpine                      1.1s
    => [app base 1/1] FROM docker.io/library/node:20-alpine@sha256:804aa6a6476a7e2a5df8db288  0.0s
    => [app internal] load build context                                                      0.0s
    => => transferring context: 16.80kB                                                       0.0s
    => CACHED [app runner 1/6] WORKDIR /app                                                   0.0s
    => CACHED [app runner 2/6] RUN addgroup --system --gid 1001 nodejs                        0.0s
    => CACHED [app runner 3/6] RUN adduser --system --uid 1001 hono                           0.0s
    => CACHED [app builder 1/4] RUN apk add --no-cache libc6-compat                           0.0s
    => CACHED [app builder 2/4] WORKDIR /app                                                  0.0s
    => CACHED [app builder 3/4] COPY package*json tsconfig.json src ./                        0.0s
    => CACHED [app builder 4/4] RUN npm ci &&     npm run build &&     npm prune --productio  0.0s
    => CACHED [app runner 4/6] COPY --from=builder --chown=hono:nodejs /app/node_modules /ap  0.0s
    => CACHED [app runner 5/6] COPY --from=builder --chown=hono:nodejs /app/dist /app/dist    0.0s
    => CACHED [app runner 6/6] COPY --from=builder --chown=hono:nodejs /app/package.json /ap  0.0s
    Attaching to hono-demo-esm-issue-app-1
    hono-demo-esm-issue-app-1  | node:internal/modules/esm/resolve:265
    hono-demo-esm-issue-app-1  |     throw new ERR_MODULE_NOT_FOUND(
    hono-demo-esm-issue-app-1  |           ^
    hono-demo-esm-issue-app-1  |
    hono-demo-esm-issue-app-1  | Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/dist/aboutRoute' imported from /app/dist/index.js
    hono-demo-esm-issue-app-1  |     at finalizeResolution (node:internal/modules/esm/resolve:265:11)
    hono-demo-esm-issue-app-1  |     at moduleResolve (node:internal/modules/esm/resolve:933:10)
    hono-demo-esm-issue-app-1  |     at defaultResolve (node:internal/modules/esm/resolve:1157:11)
    hono-demo-esm-issue-app-1  |     at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:383:12)
    hono-demo-esm-issue-app-1  |     at ModuleLoader.resolve (node:internal/modules/esm/loader:352:25)
    hono-demo-esm-issue-app-1  |     at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:227:38)
    hono-demo-esm-issue-app-1  |     at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:87:39)
    hono-demo-esm-issue-app-1  |     at link (node:internal/modules/esm/module_job:86:36) {
    hono-demo-esm-issue-app-1  |   code: 'ERR_MODULE_NOT_FOUND',
    hono-demo-esm-issue-app-1  |   url: 'file:///app/dist/aboutRoute'
    hono-demo-esm-issue-app-1  | }
    hono-demo-esm-issue-app-1  |
    hono-demo-esm-issue-app-1  | Node.js v20.14.0
    hono-demo-esm-issue-app-1 exited with code 1

Checking the dist/index.js via docker compose run --rm app cat dist/index.js shows that

import aboutRoute from "./aboutRoute"; // <-- doesn't have a file extension

which I think NodeJS Doesn't support extensionless import. Saw it on TS docs.

Extensionless relative paths are not supported in import paths in Node.js, and are not always supported in file paths specified in package.json files.

Propose Fix

To use tsc-alias, to resolve full paths. That way, the build output will replace incomplete import paths (those not ending in .js) with fully resolved paths (for ECMAScript Modules compatibility)

Here's a working example repo.

git clone -b fix-node-esm-issue https://github.com/iceniveth/hono-demo-esm-issue.git
docker compose up --build

Access http://localhost:3000/about w/o errors.

Update the docs for https://hono.dev/docs/getting-started/nodejs#dockerfile

Current Proposed Change
image image

Alternative Fix

If not tsc-alias, I see another approach is to use extensionless. I got it working as well repo

git clone -b extensionless https://github.com/iceniveth/hono-demo-esm-issue.git
docker compose up --build

Access http://localhost:3000/about w/o errors.

yusukebe commented 1 week ago

Hi @iceniveth

How about adding .js to the import statement?

import aboutRoute from "./aboutRoute.js";

I think it does not matter just for Node.js Docker.

iceniveth commented 1 week ago

Okay, will close this PR.

I kind-a used to this auto-import shortcut from VSCode which it doesn't put an extension:

auto

Not sure if there is a way to automatically put .js at the end (although my file is aboutRoute.ts). Or might just manually put it. 👻