mjackson / remix-the-web

Open source tools for Remix (or any framework!)
MIT License
723 stars 15 forks source link

form-data-parser only working on dev #26

Closed fcosrno closed 1 week ago

fcosrno commented 3 weeks ago

Hi there!

Thank you so much for form-data-parser and the other packages in remix-the-web. I'm using them to build a gallery route in an internal CMS. Good stuff. Unfortunately, I'm having issues with form-data-parser not working on build. Works fine in npm run dev, so the break came as a surprise.

Steps to recreate:

TLDR; Here's a repo.

Or

Create a fresh Remix install using node v20.18.0 (npm v10.8.2): npx create-remix@latest

Install remix-the-web packages: npm i @mjackson/file-storage @mjackson/form-data-parser

Create app/filestorage.server.ts:

import { LocalFileStorage } from '@mjackson/file-storage/local';

const fileStorage = new LocalFileStorage('public/');

export { fileStorage };

And app/routes/tinker.tsx:

import { type FileUpload, parseFormData } from '@mjackson/form-data-parser';
import type { ActionFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Form } from "@remix-run/react";
import { fileStorage } from "~/filestorage.server";

export async function loader() {
    return json({ ok: true });
}

export async function action({ request }: ActionFunctionArgs) {
    const uploadHandler = async (fileUpload: FileUpload) => {
        if (fileUpload.fieldName === 'my-file') {
            const storageKey = `user-avatar`;
            await fileStorage.set(storageKey, fileUpload);
            return fileStorage.get(storageKey);
        }
    }
    const formData = await parseFormData(request, uploadHandler)
    const file = formData.get('my-file')
    console.log({ file });

    // file has been processed
    return json({ ok: true })
}

export default function Tinker() {
    return <Form method='post' encType='multipart/form-data'>
        <input type='file' name='my-file' />
        <button>Submit</button>
    </Form>
}

Run npm run dev and navigate to http://localhost:5173/tinker. Upload a file. Server logs something like:


{
  file: File {
    size: 306001,
    type: 'image/jpeg',
    name: 'WhatsApp Image 2024-10-21 at 17.39.19.jpeg',
    lastModified: 1730160537649
  }
}

It works! Onwards. Now run npm run build followed by npm run start and navigate to http://localhost:3000/tinker. Upload a file. Server log shows something like:

{ file: null }

What's different between the two? Is Vite tree-shakin' some goodies out? Is there something else I should be looking at?

Thanks again! Remix is awesome.

HeathHopkins commented 2 weeks ago

I see this problem too. The uploadHandler function is executed in development, but not on a production build.

HeathHopkins commented 2 weeks ago

I see this problem too. The uploadHandler function is executed in development, but not on a production build.

The fix is to stop using remix-serve, because it calls installGlobals which is causing the issue.

I found an issue in the archived repo that described this problem and solution. Here's how to switch from remix-serve to express: https://remix.run/docs/en/main/start/quickstart

fcosrno commented 2 weeks ago

Ah, good find.

But is this still the case if Single Fetch is enabled? I assumed if you enable single fetch it should remove installGlobals. Per the Single Fetch documentation:

If you are using remix-serve, it will use undici automatically if Single Fetch is enabled.

Am I missing something else?

mjackson commented 1 week ago

Just make sure you're not using the installGlobals() polyfill and you should be good to go.

mfo-fluxer commented 1 week ago

@mjackson is the suggested solution to switch from remix-serve to e.g. express?

HeathHopkins commented 1 week ago

@fcosrno, using single fetch with remix-serve still calls installGlobals, but it uses native fetch. I had the same problem as you, but using express instead of remix-serve resolved it.