oven-sh / bun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one

Home Page:https://bun.sh

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add support for `FormData`

gornostay25 opened this issue · comments

Version

0.1.3

Platform

Linux 4b2918398c1b 5.15.0-1012-gcp #17~20.04.1-Ubuntu SMP Thu Jun 23 16:10:34 UTC 2022 x86_64 GNU/Linux

What steps will reproduce the bug?

...
fetch: (req)=>{
      console.log(req.formData) // => undefined
},
...

How often does it reproduce? Is there a required condition?

Always

What is the expected behavior?

https://developer.mozilla.org/en-US/docs/Web/API/Request/formData

What do you see instead?

...
fetch: (req)=>{
      console.log(req.formData) // => undefined
     ...
       req.formData() //throw error request.formData is not a function. (In 'request.formData()', 'request.formData' is undefined)
    ...
},
...

Additional information

No response

FormData hasn't been imported from WebKit yet. It had some dependencies on HTMLFileElement that made it trickier. It needs to be added though

FormData hasn't been imported from WebKit yet. It had some dependencies on HTMLFileElement that made it trickier. It needs to be added though

Are you planning on importing it from WebKit or building a custom implementation? The API seems pretty minimal (if we would not support the optional HTMLElement parameter in the constructor).

I found we can patch Request to add this through a polyfill, e.g.

import multipart from 'parse-multipart-data';
import { FormData } from 'formdata-polyfill/esm.min.js';

Request.prototype.formData = async function formData() {
  const boundary = multipart.getBoundary(this.headers.get('content-type'));
  const buffer = Buffer.from(await new Response(this.body).text());
  const parts = multipart.parse(buffer, boundary);
  const form = new FormData();

  for (let i = 0; i < parts.length; i++) {
    form.append(parts[i].name, parts[i].data);
  }
  return form;
};

Interestingly enough, calling this.text() will kill the process with a Segmentation fault: 11 -- that's why I ended up using Response for getting the request text.

@gornostay25 just add parse-multipart-data in your dependencies, then, update your script as below:

// node_modules/@sveltejs/kit/src/exports/node/polyfills.js"

import { FormData } from "formdata-polyfill/esm.min.js";
+import multipart from "parse-multipart-data";

/** @type {Record<string, any>} */
const globals = {
  FormData,
};

export default function installPolyfills() {
+  Request.prototype.formData = async function formData() {
+    const boundary = multipart.getBoundary(this.headers.get('content-type'));
+    const buffer = Buffer.from(await new Response(this.body).text());
+    const parts = multipart.parse(buffer, boundary);
+    const form = new FormData();
+
+    for (let i = 0; i < parts.length; i++) {
+      form.append(parts[i].name, parts[i].data);
+    }
+    return form;
+  };
  
  for (const name in globals) {
    Object.defineProperty(globalThis, name, {
      enumerable: true,
      configurable: true,
      writable: true,
      value: globals[name],
    });
  }
}

Also make sure you're including a polyfill for File as well, I just did this:

globalThis.File = class File {
  constructor(bytes, filename, options = {}) {
    Object.defineProperties(this, {
      name: { value: filename },
      type: { value: options.type },
      size: { value: bytes.length },
      lastModified: { value: options.lastModified || null },
      lastModifiedDate: { value: options.lastModified ? new Date(options.lastModified) : null },
      arrayBuffer: { value: () => new TextEncoder().encode(bytes) },
      text: { value: () => Promise.resolve(bytes.toString()) },
      [Symbol.toStringTag]: { value: 'File' },
    });
  }
};

Implemented in #2051

Serialize FormData but missing content-type "boundary"? @Jarred-Sumner

image

image

Can you advice how to use it ? #621 (comment) @Jarred-Sumner

Understood now. But 2 awaits cause a prob: 500 Internal Server Error

if (pathName === "/w/Xapix") { const bodyhere2 = await req.text() // const bodyhere2 = await req.json() const formData = await req.formData(); console.log(formData.get("foo")) return new Response(null, { status: 200 }); }

I've created a middleware for uploading multipart form data.

export const saveFiles = async (c: Context, next: Next) => {
  try {
    const formData = await c.req.formData();
    const files = formData.getAll('files');
    const saveFilePromises = files.map(async (targetFile, index) => {
      const file = targetFile as BunFile;
      const buffer = await file.arrayBuffer();

      const originalName = file.name;
      const filename = `${uuidV4()}.${index}_${originalName}`;
      const path = `static/${filename}`;
      Bun.write(path, buffer);
      return {
        oroginalName: originalName,
        name: filename,
        path: path,
        size: file.size,
        type: file.type,
      };
    });
    const result = await Promise.all(saveFilePromises);

    c.set('savedFiles', result);
  } catch (error: any) {
    c.res = new Response(error.message, { status: 500 });
  }
  await next();
};