Fixed Cannot find module /app/node_modules/xlsx/dist/cpexcel.js

Today I want to share a problem I encountered when integrating SheetJS with Nuxt specifically in cases without authentication, accessing the path directly works fine. But when the path requires Server-Side Authentication, it throws the error below.

Cannot find module '/app/node_modules/xlsx/dist/cpexcel.js' from '/app/server/chunks/build/index-64cha0zE.mjs'

Why

  • SheetJS was designed to separate the codepage table into an optional file in order to reduce bundle size.SheetJS was designed to separate the codepage table into an optional file in order to reduce bundle size.
  • The legacy version (0.18.x) still relies on a CJS build that loads cpexcel via a dynamic require() wrapped in a try/catch block. As a result, when Nitro bundles the server output using Rollup and node-file-trace, Rollup treats the dynamic require() inside the try/catch as optional code and skips it. so cpexcel.js never gets copied into .output. At runtime, the path resolution is correct, but the file is simply not there because it was never included during the build.

Solution

Upgrading the SheetJS library from 0.18.5 → 0.20.3 has two approaches, but for versions after 18, SheetJS requires you to pull the package directly from https://cdn.sheetjs.com/ instead.

- First Solution (Public to Private Repo)
- Second Solution (Add tarball in Code Repository)
  • First, define the repository structure to determine where to place the tarball — in this case, SheetJS will be stored inside a vendor folder.
simple-redmine-client/
├── vendor/
│   └── xlsx-0.20.3.tgz
├── package.json
├── bun.lock
├── nuxt.config.ts
└── Dockerfile
  • Remove old xlsx package
bun remove xlsx
  • Install Package from Vendor Folder
# Download
curl -L -o vendor/xlsx-0.20.3.tgz https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz
# Install
bun add ./vendor/xlsx-0.20.3.tgz

Note: If you do not run bun remove first, it will cause a dependency exception when installing the package.

Next, update the code — if the existing code still uses require, refactor it to use a namespace import instead.

  • Old Code
import XLSX from 'xlsx'
const XLSX = require('xlsx')
const wb = XLSX.readFile('data.xlsx')

---
import XLSX from 'xlsx'          // <-- default import
const wb = XLSX.read(buf, { type: 'buffer' })
  • Improve Code
import * as XLSX from 'xlsx'   // 0.20.3 namespace import auto resolve ESM

If you have legacy codepages and need to read Windows-874 (TIS-620) files, you'll need to include cpexcel as well — just follow the steps in the code below.

import * as XLSX from 'xlsx'
import * as cptable from 'xlsx/dist/cpexcel.full.mjs'
XLSX.set_cptable(cptable)

// ไฟล์ CSV ที่ export จากระบบเก่า/Excel บน Windows ไทย → มักเป็น CP874 ไม่มี BOM
const wb = XLSX.read(buf, { type: 'buffer', codepage: 874 })
  • Test with bun run build and Then run manual test to export the Excel file and verify the output.
bun run build

For those using Container, you'll also need to COPY the tarball into the image during the build stage. You can spot the addition lines by looking at the highlighted sections.

# use the official Bun image
# see all versions at https://hub.docker.com/r/oven/bun/tags
FROM oven/bun:1.3 AS base
WORKDIR /app

# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
COPY package.json bun.lock ./
COPY vendor ./vendor
COPY . .
ENV npm_config_optional=true
ENV npm_config_platform=linux
ENV npm_config_arch=x64
RUN BUN_POSTINSTALL=enable bun install --frozen-lockfile

# Copy project files into the image

# [Optional] tests & build
# Test stage
FROM install AS test
ENV NODE_ENV=test
# Run tests and generate a report
RUN bun test --reporter=junit --reporter-outfile=test-results.xml

# Build stage
FROM install AS build
ENV NODE_ENV=production
RUN bun run build

# Copy production dependencies and source code into final image
FROM oven/bun:1.3-alpine AS runtime
WORKDIR /app

# Copy necessary files from the 'install' stage to the 'runtime' stage
# COPY --from=install /app/node_modules ./node_modules
COPY --from=build --chown=bun:bun /app/.output .

# Set user and expose port
USER bun
EXPOSE 3000/tcp

# Run the app
ENTRYPOINT [ "sh", "-c", "bun run /app/server/index.mjs" ]

Reference


Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts sent to your email.