npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fileport

v1.3.2

Published

File Upload by Dropzone FilePort — (build. ESM / IIFE)

Readme

filePort — Dropzone-based File Attachment Uploader

A drag & drop file attachment UI component. Dropzone.js v5.9.3 is bundled, so you only need to import fileport without installing it separately. Written in TypeScript, with type definition files (.d.ts) included.


File Structure

filePort/
├── src/                          # Source (TypeScript)
│   ├── filePort.ts               # Entry point — exports createUploader()
│   ├── fileManager.ts            # File state management (pure logic, no DOM)
│   ├── renderer.ts               # Table rendering + event delegation
│   ├── dzEvents.ts               # Dropzone initialization + event handlers
│   ├── types.ts                  # Shared type definitions
│   ├── filePort.css              # Uploader styles — table, status, buttons
│   ├── locales/                  # Built-in language packs
│   │   ├── index.ts              # Locale registry (LOCALES, DEFAULT_LOCALE)
│   │   ├── ko.ts                 # Korean pack
│   │   └── en.ts                 # English pack
│   └── dropzone/
│       ├── dropzone.min.js       # Dropzone.js library (included in bundle)
│       ├── dropzone.min.css      # Dropzone.js default styles (not required)
│       └── dropzone.min.d.ts     # Dropzone type declarations
├── dist/                         # Build output
│   ├── filePort.js               # ESM  (Dropzone included, minified)
│   ├── filePort.iife.js          # IIFE (for vanilla script tag usage, minified)
│   ├── filePort.dev.js           # ESM  (unminified, readable build)
│   ├── filePort.css              # Bundled styles
│   ├── filePort.d.ts             # Main type definitions
│   ├── types.d.ts                # Shared types (UploaderOptions, FileItem, etc.)
│   ├── fileManager.d.ts
│   ├── renderer.d.ts
│   └── dzEvents.d.ts
├── example/
│   ├── html/                     # Frontend-only example (no backend required)
│   ├── builder/                  # Drag-and-drop column layout builder
│   ├── express/                  # Express.js backend example
│   ├── react/                    # React example
│   ├── jsp/                      # JSP + Spring Boot example
│   └── backend-spring/           # Thymeleaf + Spring Boot example
├── tsconfig.json
├── vite.config.ts                # Build config (minified ESM/IIFE + .d.ts)
├── vite.dev.config.ts            # Build config (unminified readable build)
├── package.json
├── THIRD_PARTY_LICENSES.md
└── README.md

Build (ESM / IIFE)

Building src/filePort.ts with Vite library mode generates ESM, IIFE, and type definitions simultaneously.

npm install
npm run build   # → generates dist/
dist/
├── filePort.js        # ESM   (Dropzone included, minified)
├── filePort.iife.js   # IIFE  (Dropzone included, minified)
├── filePort.dev.js    # ESM   (unminified — readable for debugging)
└── *.d.ts             # Type definitions

npm run build produces both the minified bundles (for distribution) and an unminified filePort.dev.js (for reading/debugging). You can run them separately:

| Script | Output | | --- | --- | | npm run build | Minified bundles + filePort.dev.js | | npm run build:min | Minified bundles only | | npm run build:dev | Unminified filePort.dev.js only |

Dropzone JS is included in the bundle. Dropzone CSS is not required (Dropzone's default UI is disabled via previewsContainer: false).


Publish

npm login

# bump the "version" field in package.json first

npm publish

Installation

npm install fileport

Usage

TypeScript / ESM

import { createUploader, type UploaderOptions } from 'fileport'
import 'fileport/src/filePort.css'

const options: UploaderOptions = {
    uploadUrl:   '/upload',
    docKindList: [
        { id: 'CONTRACT', name: 'Contract' },
        { id: 'INVOICE',  name: 'Invoice' },
        { id: 'OTHER',    name: 'Other' },
    ],
    onSubmit: ({ files, extra, done }) => {
        fetch('/api/contents/insert', {
            method:  'POST',
            headers: { 'Content-Type': 'application/json' },
            body:    JSON.stringify({ ...extra, files }),
        }).then(() => done(true))
    },
}

const uploader = createUploader(options)
uploader.init()

document.getElementById('btnInsert')!.onclick = () => uploader.startSubmit()

JavaScript / ESM

import { createUploader } from 'fileport'
import 'fileport/src/filePort.css'

const uploader = createUploader({
    uploadUrl:   '/upload',
    onSubmit: ({ files, extra, done }) => {
        fetch('/api/contents/insert', {
            method:  'POST',
            headers: { 'Content-Type': 'application/json' },
            body:    JSON.stringify({ ...extra, files }),
        }).then(() => done(true))
    },
})

uploader.init()
document.getElementById('btnInsert').onclick = () => uploader.startSubmit()

Vanilla (script tag)

<link rel="stylesheet" href="filePort.css">
<script src="dist/filePort.iife.js"></script>
<script>
    const uploader = FilePort.createUploader({
        uploadUrl: '/upload',
        onSubmit: ({ files, extra, done }) => {
            done(true)
        },
    })
    uploader.init()

    document.getElementById('btnInsert').onclick = () => uploader.startSubmit()
</script>

React (TypeScript)

import { useEffect, useRef } from 'react'
import { createUploader, type UploaderOptions } from 'fileport'
import 'fileport/src/filePort.css'

export default function FileUploader() {
    const uploaderRef = useRef<ReturnType<typeof createUploader> | null>(null)

    useEffect(() => {
        const uploader = createUploader({
            uploadUrl: '/upload',
            onSubmit: ({ files, extra, done }) => {
                fetch('/api/contents/insert', {
                    method:  'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body:    JSON.stringify({ ...extra, files }),
                }).then(() => done(true))
            },
        })
        uploader.init()
        uploaderRef.current = uploader
        return () => { uploaderRef.current?.myDropzone?.destroy() }
    }, [])

    return (/* HTML structure */)
}

Thymeleaf + Spring Boot

<link rel="stylesheet" th:href="@{/filePort.css}">
<script th:src="@{/filePort.iife.js}"></script>

<script th:inline="javascript">
    const uploader = FilePort.createUploader({
        uploadUrl: /*[[@{/upload}]]*/ '/upload',
        onSubmit: ({ files, extra, done }) => {
            fetch(/*[[@{/api/contents/insert}]]*/ '/api/contents/insert', {
                method:  'POST',
                headers: { 'Content-Type': 'application/json' },
                body:    JSON.stringify({ ...extra, files }),
            }).then(() => done(true))
        },
    })
    uploader.init()
</script>

HTML Structure

Elements matching the IDs in elementConfig are required.

<link rel="stylesheet" href="/filePort.css">

<div id="tableWrapper" class="table-wrapper">
    <div class="table-scroll">
        <table id="fileTable">
            <thead>
                <tr>
                    <th><input type="checkbox" id="checkAll"></th>
                    <th>File name</th>
                    <th>Document type</th>
                    <th>Size</th>
                    <th>Status</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody id="fileList"></tbody>
        </table>
    </div>
    <div class="table-footer">
        <span id="footerSize"></span>
        <div class="footer-progress">
            <div class="progress-bar-wrap">
                <div class="progress-bar-fill" id="progressFill"></div>
            </div>
            <span id="footerPercent"></span>
        </div>
    </div>
</div>

<button id="btnFileAdd">Add file</button>
<button id="btnInsert">Submit</button>

<script src="/filePort.iife.js"></script>
<!-- No need to load Dropzone JS/CSS separately -->

createUploader Options

| Option | Type | Default | Description | | --- | --- | --- | --- | | elementConfig | Partial<ElementConfig> | Default ID map | DOM ID mapping (see table below) | | uploadUrl | string | '/upload' | File upload server URL | | maxFilesize | number | 10 | Maximum file size (MB) | | acceptedFiles | string \| null | null | Allowed extensions (e.g., '.pdf,.docx') | | showDocKind | boolean | true | Whether to show the document type column | | simple | boolean | false | Simple 4-column mode (no checkbox / document type columns) | | docKindList | DocKind[] | [] | Document type list — shows static text if empty | | submitBtnId | string | 'btnInsert' | Submit button id | | columns | ColumnDef[] | — | Configure column order / visibility / header (see Column layout). When set, the library renders the header too and simple is ignored | | columnSettings | boolean \| ColumnSettingsOptions | — | Built-in gear (⚙) panel for end-user column personalization. { storageKey, title }; with storageKey, show/order is saved to localStorage. Requires columns | | onColumnsChange | (columns: ColumnDef[]) => void | — | Called when the column config changes (⚙ edit or setColumns) — for external-module sync | | locale | 'ko' \| 'en' | 'ko' | Built-in language pack (see Internationalization) | | messages | Partial<Record<MessageKey, string>> | {} | Override specific messages on top of the locale pack | | getMessage | (key: string) => string | — | Delegate message resolution entirely (e.g. i18next). Highest priority | | getExtra | () => Record<string, unknown> | () => ({}) | Function returning additional content fields | | onSubmit | (payload: SubmitPayload) => void | () => {} | Callback after upload completion | | onError | (fileName: string, message: string) => void | alert(...) | Callback on upload error |

elementConfig Defaults

| Key | Default | Role | | --- | --- | --- | | wrapperId | tableWrapper | Dropzone drop area | | fileAddBtnId | btnFileAdd | File selection button (clickable) | | tableId | fileTable | File list <table> | | tbodyId | fileList | File list <tbody> | | checkAllId | checkAll | Select-all checkbox | | footerSizeId | footerSize | Displays file size and count | | progressFillId | progressFill | Progress bar fill | | footerPercentId | footerPercent | Upload percentage |

SubmitPayload Type

interface SubmitPayload {
    files: SubmitFileItem[]           // List of successfully uploaded files
    extra: Record<string, unknown>    // Value returned by getExtra()
    done:  (success?: boolean) => void // Must be called after submission completes
}

Internationalization (i18n)

All user-facing text is managed through message keys, so the UI language can be switched without touching the markup. Built-in language packs are bundled (ko, en).

Resolution priority

When resolving a message key, the following sources are tried in order (highest first):

  1. getMessage — external resolver (e.g. i18next); used when it returns a value different from the key
  2. messages — your partial overrides
  3. locale pack — the selected built-in pack (default 'ko')
  4. the key itself — fallback when nothing matches

Usage

// 1) Built-in English pack
createUploader({ locale: 'en' })

// 2) English pack + override a few strings
createUploader({
    locale: 'en',
    messages: {
        'web.file.action.delete': 'Remove',
        'web.file.status.success': 'Complete',
    },
})

// 3) Delegate entirely to an external i18n library
createUploader({
    getMessage: (key) => i18next.t(key),
})

Message keys

| Key | ko | en | | --- | --- | --- | | web.confirm.file.fileUploadPlz | 파일을 이곳에 드래그하거나 버튼을 클릭하세요 | Drag files here or click the button | | web.file.countUnit | 건 | file(s) | | web.file.status.pending | 대기 | Pending | | web.file.status.uploading | 업로드중 | Uploading | | web.file.status.success | 완료 | Done | | web.file.status.error | 오류 | Error | | web.file.status.registered | 업로드완료 | Uploaded | | web.file.docKind.placeholder | 선택 | Select | | web.file.action.delete | 삭제 | Delete | | web.js.error.upload | 업로드 중 오류가 발생했습니다. 다시 첨부해 주세요. | An error occurred during upload. Please attach the file again. | | web.file.column.check | 선택 | Select | | web.file.column.name | 파일명 | Name | | web.file.column.dockind | 문서종류 | Type | | web.file.column.size | 크기 | Size | | web.file.column.status | 상태 | Status | | web.file.column.action | 작업 | Action | | web.file.columnSettings | 컬럼 설정 | Column settings |

Adding a language

  1. Copy src/locales/ko.ts to a new file (e.g. ja.ts) and translate the values
  2. Add the code to LocaleCode in src/types.ts
  3. Register it in LOCALES in src/locales/index.ts

The Messages type forces every pack to define all keys, so a missing translation fails the build.


Column layout

By default the table renders a fixed set of columns (checkbox · name · document type · size · status · action), with the header written as static HTML by you. Pass columns to control the order, visibility, and header label declaratively — when set, the library renders the .fp-thead header too, so header and body always stay in sync (and the simple option is ignored).

interface ColumnDef {
    key:      'check' | 'name' | 'dockind' | 'size' | 'status' | 'action'
    header?:  string   // header label; omit to use the locale default (web.file.column.*). Ignored for 'check'
    visible?: boolean  // default true; false removes the column from both header and body
}
createUploader({
    columns: [
        { key: 'status' },                       // reordered to the front
        { key: 'name', header: 'File name' },    // custom header label
        { key: 'size' },
        { key: 'action' },
        // 'check' and 'dockind' omitted → not rendered
    ],
})

When columns is set, your table only needs an empty header container; the library fills it in:

<div id="fileTable" class="fp-table">
    <div class="fp-thead"></div>      <!-- rendered by the library from columns -->
    <div id="fileList" class="fp-tbody"></div>
</div>

Visual builder

example/builder/index.html is a no-backend drag-and-drop builder: reorder columns by dragging, toggle visibility, edit header labels, and see a live preview. It outputs both the columns JSON and a ready-to-paste createUploader snippet. Open it directly in a browser (after npm run build, since it loads dist/filePort.iife.js).

Runtime updates (external modules)

columns and docKindList can be replaced at runtime, so an external module (menu switch, permission change, etc.) can push a new config:

menu.on('change', (cfg) => {
    uploader.setColumns(cfg.columns)       // replace columns + re-render header/body
    uploader.setDocKindList(cfg.docKinds)  // refresh the document-type <select>
})

User personalization (⚙)

Set columnSettings to embed a gear button (top-right of the wrapper) that lets the end user reorder columns and toggle visibility. With a storageKey, their choices are saved to localStorage and restored on the next visit:

createUploader({
    columns: baseColumns,
    columnSettings: { storageKey: 'my-table-cols' },
    onColumnsChange: (cols) => { /* optional: sync to a server */ },
})

Personalization is merged on top of the base columns: when an external module calls setColumns(newBase), the saved order/visibility is re-applied to the new base, so user preferences survive config changes. (The gear panel adjusts order and visibility only — header labels stay as defined in columns.)


Public API

const uploader = createUploader({ ... })

uploader.init()                          // Call after DOM is ready — initializes Dropzone, events, and rendering
uploader.startSubmit()                   // Submit button handler — starts upload or calls onSubmit directly
uploader.addFileInfo(data, type)         // Directly add scanned or existing files ('scan' | 'modify')

uploader.setColumns(columns)             // Replace column config at runtime (re-applies saved personalization)
uploader.getColumns()                    // Current applied column config (ColumnDef[] | null)
uploader.setDocKindList(list)            // Replace the document-type list at runtime

uploader.fileManager                     // FileManager instance (files[], updateStatus, etc.)
uploader.myDropzone                      // Dropzone instance (Dropzone.Dropzone | null)
uploader.isProcessing                    // Whether processing is in progress (boolean)

Upload Flow by Case

| Case | Description | Flow | | --- | --- | --- | | 1 | New files only | startSubmitprocessQueuequeuecompleteonSubmit | | 2 | Scanned files only | addFileInfo(data, 'scan')startSubmitonSubmit directly | | 3 | Existing files only (edit mode) | addFileInfo(data, 'modify')startSubmitonSubmit directly | | 4 | Scanned + new files mixed | startSubmitprocessQueuequeuecompleteonSubmit together | | 5 | Partial failure | queuecomplete → no successful files → re-enable button, prompt retry |


Examples

| Example | Description | Run | | --- | --- | --- | | example/html/ | Run directly in the browser without a backend | Open index.html in a browser | | example/builder/ | Drag-and-drop column layout builder | Open index.html in a browser (after npm run build) | | example/express/ | Express.js backend | npm install && npm starthttp://localhost:3000 | | example/react/ | React + Vite | npm install && npm run dev | | example/jsp/ | JSP + Spring Boot | Build with Gradle and run | | example/backend-spring/ | Thymeleaf + Spring Boot | http://localhost:8090 |