Skip to content

Commit

Permalink
Convert package to ESM
Browse files Browse the repository at this point in the history
Migration Guide:

This relases changes the package from a Common JS module to an EcmaScript module, and drops support for older versions of Node.

- The minimum version of Node.js supported is now: `12.20.0`, `14.13.1`, and `16.0.0`
- The package must now be imported using the native `import` syntax instead of with `require`
  • Loading branch information
LinusU committed Jul 26, 2021
1 parent ed4ce55 commit 08d0c24
Show file tree
Hide file tree
Showing 21 changed files with 164 additions and 174 deletions.
7 changes: 3 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
language: node_js
node_js:
- '13'
- '12'
- '10'
- '10.13.0'
- '16.0.0'
- '14.13.1'
- '12.20.0'
os:
- linux
- osx
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Multer adds a `body` object and a `file` or `files` object to the `request` obje
Basic usage example:

```javascript
const multer = require('multer')
const express = require('express')
import multer from 'multer'
import express from 'express'

const app = express()
const upload = multer()
Expand Down Expand Up @@ -49,8 +49,8 @@ app.post('/cool-profile', cpUpload, (req, res, next) => {
In case you need to handle a text-only multipart form, you can use the `.none()` method, example:

```javascript
const multer = require('multer')
const express = require('express')
import multer from 'multer'
import express from 'express'

const app = express()
const upload = multer()
Expand Down
10 changes: 4 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const createFileFilter = require('./lib/file-filter')
const createMiddleware = require('./lib/middleware')
import bytes from 'bytes'

const bytes = require('bytes')
import createFileFilter from './lib/file-filter.js'
import createMiddleware from './lib/middleware.js'

const kLimits = Symbol('limits')

Expand Down Expand Up @@ -60,7 +60,7 @@ class Multer {
}
}

function multer (options = {}) {
export default function multer (options = {}) {
if (options === null) throw new TypeError('Expected object for argument "options", got null')
if (typeof options !== 'object') throw new TypeError(`Expected object for argument "options", got ${typeof options}`)

Expand All @@ -70,5 +70,3 @@ function multer (options = {}) {

return new Multer(options)
}

module.exports = multer
4 changes: 1 addition & 3 deletions lib/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const errorMessages = new Map([
['LIMIT_UNEXPECTED_FILE', 'Unexpected file field']
])

class MulterError extends Error {
export default class MulterError extends Error {
constructor (code, optionalField) {
super(errorMessages.get(code))

Expand All @@ -19,5 +19,3 @@ class MulterError extends Error {
Error.captureStackTrace(this, this.constructor)
}
}

module.exports = MulterError
6 changes: 2 additions & 4 deletions lib/file-appender.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
function createFileAppender (strategy, req, fields) {
export default function createFileAppender (strategy, req, fields) {
switch (strategy) {
case 'NONE': break
case 'VALUE': req.file = null; break
case 'ARRAY': req.files = []; break
case 'OBJECT': req.files = Object.create(null); break
// istanbul ignore next
/* c8 ignore next */
default: throw new Error(`Unknown file strategy: ${strategy}`)
}

Expand All @@ -22,5 +22,3 @@ function createFileAppender (strategy, req, fields) {
}
}
}

module.exports = createFileAppender
6 changes: 2 additions & 4 deletions lib/file-filter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const MulterError = require('./error')
import MulterError from './error.js'

function createFileFilter (fields) {
export default function createFileFilter (fields) {
const filesLeft = new Map()

for (const field of fields) {
Expand All @@ -25,5 +25,3 @@ function createFileFilter (fields) {
filesLeft.set(file.fieldName, left - 1)
}
}

module.exports = createFileFilter
17 changes: 8 additions & 9 deletions lib/middleware.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const is = require('type-is')
const fs = require('fs')
const appendField = require('append-field')
import fs from 'node:fs'

const createFileAppender = require('./file-appender')
const readBody = require('./read-body')
import appendField from 'append-field'
import is from 'type-is'

import createFileAppender from './file-appender.js'
import readBody from './read-body.js'

async function handleRequest (setup, req) {
const options = setup()
Expand All @@ -25,11 +26,9 @@ async function handleRequest (setup, req) {
}
}

function createMiddleware (setup) {
return function multerMiddleware (req, res, next) {
export default function createMiddleware (setup) {
return function multerMiddleware (req, _, next) {
if (!is(req, ['multipart'])) return next()
handleRequest(setup, req).then(next, next)
}
}

module.exports = createMiddleware
35 changes: 18 additions & 17 deletions lib/read-body.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
const path = require('path')
const pify = require('pify')
const temp = require('fs-temp')
const Busboy = require('busboy')
const FileType = require('stream-file-type')
const hasOwnProperty = require('has-own-property')
import { extname } from 'node:path'
import { pipeline } from 'node:stream/promises'
import { promisify } from 'node:util'

const pump = pify(require('pump'))
const onFinished = pify(require('on-finished'))
import Busboy from 'busboy'
import { createWriteStream } from 'fs-temp'
import hasOwnProperty from 'has-own-property'
import _onFinished from 'on-finished'
import FileType from 'stream-file-type'

const MulterError = require('./error')
import MulterError from './error.js'

const onFinished = promisify(_onFinished)

function drainStream (stream) {
stream.on('readable', stream.read.bind(stream))
Expand All @@ -19,7 +21,8 @@ function collectFields (busboy, limits) {
const result = []

busboy.on('field', (fieldname, value, fieldnameTruncated, valueTruncated) => {
// istanbul ignore next: Currently not implemented (https://github.com/mscdex/busboy/issues/6)
// Currently not implemented (https://github.com/mscdex/busboy/issues/6)
/* c8 ignore next */
if (fieldnameTruncated) return reject(new MulterError('LIMIT_FIELD_KEY'))

if (valueTruncated) return reject(new MulterError('LIMIT_FIELD_VALUE', fieldname))
Expand Down Expand Up @@ -58,7 +61,7 @@ function collectFiles (busboy, limits, fileFilter) {
fieldName: fieldname,
originalName: filename,
clientReportedMimeType: mimetype,
clientReportedFileExtension: path.extname(filename || '')
clientReportedFileExtension: extname(filename || '')
}

try {
Expand All @@ -67,11 +70,11 @@ function collectFiles (busboy, limits, fileFilter) {
return reject(err)
}

const target = temp.createWriteStream()
const target = createWriteStream()
const detector = new FileType()
const fileClosed = new Promise((resolve) => target.on('close', resolve))

const promise = pump(fileStream, detector, target)
const promise = pipeline(fileStream, detector, target)
.then(async () => {
await fileClosed
file.path = target.path
Expand All @@ -92,7 +95,7 @@ function collectFiles (busboy, limits, fileFilter) {
})
}

async function readBody (req, limits, fileFilter) {
export default async function readBody (req, limits, fileFilter) {
const busboy = new Busboy({ headers: req.headers, limits: limits })

const fields = collectFields(busboy, limits)
Expand All @@ -119,10 +122,8 @@ async function readBody (req, limits, fileFilter) {
busboy.removeAllListeners()

// Wait for request to close, finish, or error
await onFinished(req).catch(/* istanbul ignore next: Already handled by req.on('error', _) */ () => {})
await onFinished(req).catch(/* c8 ignore next: Already handled by req.on('error', _) */ () => {})

throw err
}
}

module.exports = readBody
27 changes: 13 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
],
"license": "MIT",
"repository": "expressjs/multer",
"type": "module",
"exports": "./index.js",
"keywords": [
"form",
"post",
Expand All @@ -19,38 +21,35 @@
"middleware"
],
"dependencies": {
"append-field": "^1.0.0",
"append-field": "^2.0.0",
"busboy": "^0.3.1",
"bytes": "^3.1.0",
"fs-temp": "^1.1.1",
"has-own-property": "^1.0.0",
"fs-temp": "^2.0.0",

This comment has been minimized.

Copy link
@titanism

titanism Nov 22, 2022

We should backport this fs-temp fix to the older versions that are non-ESM to be honest.

"has-own-property": "^2.0.0",
"on-finished": "^2.3.0",
"pify": "^5.0.0",
"pump": "^3.0.0",
"stream-file-type": "^0.5.0",
"stream-file-type": "^0.6.1",
"type-is": "^1.6.18"
},
"devDependencies": {
"assert-rejects": "^1.0.0",
"c8": "^7.7.3",
"express": "^4.16.4",
"form-data": "^3.0.0",
"get-stream": "^5.1.0",
"form-data": "^4.0.0",
"get-stream": "^6.0.1",
"hasha": "^5.2.0",
"mocha": "^7.1.0",
"nyc": "^15.0.0",
"mocha": "^9.0.3",
"recursive-nullify": "^1.0.0",
"standard": "^14.3.3",
"standard": "^16.0.3",
"testdata-w3c-json-form": "^1.0.0"
},
"engines": {
"node": ">=10.13"
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"files": [
"LICENSE",
"index.js",
"lib/"
],
"scripts": {
"test": "standard && nyc --check-coverage --statements 100 mocha"
"test": "standard && c8 --check-coverage --statements 100 mocha"
}
}
33 changes: 17 additions & 16 deletions test/_util.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const fs = require('fs')
const path = require('path')
const pify = require('pify')
const hasha = require('hasha')
const assert = require('assert')
const stream = require('stream')
import assert from 'node:assert'
import fs from 'node:fs'
import stream from 'node:stream'
import { promisify } from 'node:util'

const onFinished = pify(require('on-finished'))
import hasha from 'hasha'
import _onFinished from 'on-finished'

const onFinished = promisify(_onFinished)

const files = new Map([
['empty', {
Expand Down Expand Up @@ -50,15 +51,15 @@ const files = new Map([
}]
])

exports.file = function file (name) {
return fs.createReadStream(path.join(__dirname, 'files', name + files.get(name).extension))
export function file (name) {
return fs.createReadStream(new URL(`files/${name}${files.get(name).extension}`, import.meta.url))
}

exports.knownFileLength = function knownFileLength (name) {
export function knownFileLength (name) {
return files.get(name).size
}

exports.assertFile = async (file, fieldName, fileName) => {
export async function assertFile (file, fieldName, fileName) {
if (!files.has(fileName)) {
throw new Error(`No file named "${fileName}"`)
}
Expand All @@ -80,15 +81,15 @@ exports.assertFile = async (file, fieldName, fileName) => {
assert.strictEqual(hash, expected.hash)
}

exports.assertFiles = (files) => {
return Promise.all(files.map((args) => exports.assertFile(args[0], args[1], args[2])))
export async function assertFiles (files) {
await Promise.all(files.map((args) => assertFile(args[0], args[1], args[2])))
}

function getLength (form) {
return pify(form.getLength).call(form)
return promisify(form.getLength).call(form)
}

exports.submitForm = async (multer, form) => {
export async function submitForm (multer, form) {
const length = await getLength(form)
const req = new stream.PassThrough()

Expand All @@ -101,7 +102,7 @@ exports.submitForm = async (multer, form) => {
'content-length': length
}

await pify(multer)(req, null)
await promisify(multer)(req, null)
await onFinished(req)

return req
Expand Down
23 changes: 12 additions & 11 deletions test/aborted-requests.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
/* eslint-env mocha */

const util = require('./_util')
const multer = require('../')
import assert from 'node:assert'
import { PassThrough } from 'node:stream'
import { promisify } from 'node:util'

const assertRejects = require('assert-rejects')
const FormData = require('form-data')
const PassThrough = require('stream').PassThrough
const pify = require('pify')
import FormData from 'form-data'

import * as util from './_util.js'
import multer from '../index.js'

function getLength (form) {
return pify(form.getLength).call(form)
return promisify(form.getLength).call(form)
}

function createAbortStream (maxBytes, aborter) {
Expand Down Expand Up @@ -50,9 +51,9 @@ describe('Aborted requests', () => {
'content-length': length
}

const result = pify(parser)(form.pipe(req), null)
const result = promisify(parser)(form.pipe(req), null)

return assertRejects(result, err => err.code === 'CLIENT_ABORTED')
return assert.rejects(result, err => err.code === 'CLIENT_ABORTED')
})

it('should handle clients erroring the request', async () => {
Expand All @@ -69,8 +70,8 @@ describe('Aborted requests', () => {
'content-length': length
}

const result = pify(parser)(form.pipe(req), null)
const result = promisify(parser)(form.pipe(req), null)

return assertRejects(result, err => err.message === 'TEST_ERROR')
return assert.rejects(result, err => err.message === 'TEST_ERROR')
})
})
Loading

0 comments on commit 08d0c24

Please sign in to comment.