Skip to content

Commit

Permalink
Update export structure, change bundler, move tests to ESM (#749)
Browse files Browse the repository at this point in the history
* Put Cordova- and React Native-specific code into separate folders

* First attempt at using new `exports` structure in `package.json`

* Unify exported interface between browser and node variant

* Disable unused imports

* Rename files to better match their exported classes

* Add exports for node-specific APIs

* Turn node demo into ESM

* Move tests to ESM and build using rollup

Browserify does not support `exports` in `package.json`, so we replace it with Rollup. In addition, the tests are moved to ESM.

* Let bundle be generated by rollup as well

* Bundle TypeScript directly to use in sourcemaps

* Explicitly change case of NoopUrlStorage file

* Avoid resolving ESM types to CJS files

* Add fallback methods for bundler that don't support `exports`

* Add `publint` and `arethetypeswrong` as linters
  • Loading branch information
Acconut authored Jan 17, 2025
1 parent 54f4a7c commit 14d583d
Show file tree
Hide file tree
Showing 43 changed files with 881 additions and 1,168 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ lib.esm
dist
.DS_Store
yarn-error.log
tus-js-client-*.tgz
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"correctness": {
"noNewSymbol": "error",
"noUndeclaredVariables": "error",
"noUnusedVariables": "error"
"noUnusedVariables": "error",
"noUnusedImports": "error"
}
}
},
Expand Down
10 changes: 5 additions & 5 deletions demos/nodejs/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const fs = require('fs')
const tus = require('../..')
import { createReadStream } from 'fs'
import { Upload } from 'tus-js-client'

const path = `${__dirname}/../../README.md`
const file = fs.createReadStream(path)
const path = `${import.meta.dirname}/../../README.md`
const file = createReadStream(path)

const options = {
endpoint: 'https://tusd.tusdemo.net/files/',
Expand All @@ -24,5 +24,5 @@ const options = {
},
}

const upload = new tus.Upload(file, options)
const upload = new Upload(file, options)
upload.start()
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isReactNativeFile, isReactNativePlatform } from './isReactNative.js'
import { uriToBlob } from './uriToBlob.js'
import { isReactNativeFile, isReactNativePlatform } from '../reactnative/isReactNative.js'
import { uriToBlob } from '../reactnative/uriToBlob.js'

import type { FileReader, FileSource, UploadInput } from '../options.js'
import { BlobFileSource } from './sources/BlobFileSource.js'
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion lib/browser/fileSignature.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactNativeFile, UploadInput, UploadOptions } from '../options.js'
import { isReactNativeFile, isReactNativePlatform } from './isReactNative.js'
import { isReactNativeFile, isReactNativePlatform } from '../reactnative/isReactNative.js'

/**
* Generate a fingerprint for a file which will be used the store the endpoint
Expand Down
20 changes: 7 additions & 13 deletions lib/browser/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { DetailedError } from '../error.js'
import { DetailedError } from '../DetailedError.js'
import { NoopUrlStorage } from '../NoopUrlStorage.js'
import { enableDebugLog } from '../logger.js'
import { NoopUrlStorage } from '../noopUrlStorage.js'
import type { UploadInput, UploadOptions } from '../options.js'
import { BaseUpload, defaultOptions as baseDefaultOptions, terminate } from '../upload.js'

import { BrowserFileReader } from './fileReader.js'
import { BrowserFileReader } from './BrowserFileReader.js'
import { XHRHttpStack as DefaultHttpStack } from './XHRHttpStack.js'
import { fingerprint } from './fileSignature.js'
import { XHRHttpStack as DefaultHttpStack } from './httpStack.js'
import { WebStorageUrlStorage, canStoreURLs } from './urlStorage.js'

const defaultOptions = {
Expand Down Expand Up @@ -35,12 +35,6 @@ const isSupported =
typeof Blob === 'function' &&
typeof Blob.prototype.slice === 'function'

export {
Upload,
canStoreURLs,
defaultOptions,
isSupported,
enableDebugLog,
DefaultHttpStack,
DetailedError,
}
// Note: The exported interface must be the same as in lib/node/index.ts.
// Any changes should be reflected in both files.
export { Upload, defaultOptions, isSupported, canStoreURLs, enableDebugLog, DetailedError }
4 changes: 2 additions & 2 deletions lib/browser/sources/BlobFileSource.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isCordova } from '../../cordova/isCordova.js'
import { readAsByteArray } from '../../cordova/readAsByteArray.js'
import type { FileSource, SliceResult } from '../../options.js'
import { isCordova } from './isCordova.js'
import { readAsByteArray } from './readAsByteArray.js'

export class BlobFileSource implements FileSource {
_file: Blob
Expand Down
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
let isEnabled = false

// TODO: Replace this global state with an option for the Upload class
export function enableDebugLog(): void {
isEnabled = true
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
27 changes: 8 additions & 19 deletions lib/node/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import type { ReadStream } from 'node:fs'
import type { Readable } from 'node:stream'
import { DetailedError } from '../error.js'
import { DetailedError } from '../DetailedError.js'
import { NoopUrlStorage } from '../NoopUrlStorage.js'
import { enableDebugLog } from '../logger.js'
import { NoopUrlStorage } from '../noopUrlStorage.js'
import type { UploadInput, UploadOptions } from '../options.js'
import { BaseUpload, defaultOptions as baseDefaultOptions, terminate } from '../upload.js'

import { NodeFileReader } from './fileReader.js'
import { canStoreURLs } from './FileUrlStorage.js'
import { NodeFileReader } from './NodeFileReader.js'
import { NodeHttpStack as DefaultHttpStack } from './NodeHttpStack.js'
import { fingerprint } from './fileSignature.js'
import { NodeHttpStack as DefaultHttpStack } from './httpStack.js'
import { StreamFileSource } from './sources/StreamFileSource.js'
import { FileUrlStorage, canStoreURLs } from './urlStorage.js'

const defaultOptions = {
...baseDefaultOptions,
Expand Down Expand Up @@ -39,16 +38,6 @@ class Upload extends BaseUpload {
// tus-js-client not to function.
const isSupported = true

export {
Upload,
defaultOptions,
isSupported,
// Make FileUrlStorage module available as it will not be set by default.
FileUrlStorage,
canStoreURLs,
enableDebugLog,
DefaultHttpStack,
DetailedError,
// TODO: Remove `as`
StreamFileSource as StreamSource,
}
// Note: The exported interface must be the same as in lib/browser/index.ts.
// Any changes should be reflected in both files.
export { Upload, defaultOptions, isSupported, canStoreURLs, enableDebugLog, DetailedError }
2 changes: 1 addition & 1 deletion lib/options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReadStream } from 'node:fs'
import type { Readable } from 'node:stream'
import type { DetailedError } from './error.js'
import type { DetailedError } from './DetailedError.js'

export const PROTOCOL_TUS_V1 = 'tus-v1'
export const PROTOCOL_IETF_DRAFT_03 = 'ietf-draft-03'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactNativeFile } from '../options'
import type { ReactNativeFile } from '../options.js'

export function isReactNativePlatform() {
return (
Expand Down
File renamed without changes.
4 changes: 3 additions & 1 deletion lib/upload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Base64 } from 'js-base64'
// TODO: Package url-parse is CommonJS. Can we replace this with a ESM package that
// provides WHATWG URL? Then we can get rid of @rollup/plugin-commonjs.
import URL from 'url-parse'
import { DetailedError } from './error.js'
import { DetailedError } from './DetailedError.js'
import { log } from './logger.js'
import {
type FileSource,
Expand Down
5 changes: 5 additions & 0 deletions node/FileUrlStorage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"//": "Generated by scripts/setup-exports.js",
"main": "../../lib.cjs/node/FileUrlStorage.js",
"types": "../../lib.cjs/node/FileUrlStorage.d.ts"
}
5 changes: 5 additions & 0 deletions node/NodeHttpStack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"//": "Generated by scripts/setup-exports.js",
"main": "../../lib.cjs/node/NodeHttpStack.js",
"types": "../../lib.cjs/node/NodeHttpStack.d.ts"
}
5 changes: 5 additions & 0 deletions node/sources/StreamFileSource/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"//": "Generated by scripts/setup-exports.js",
"main": "../../../lib.cjs/node/sources/StreamFileSource.js",
"types": "../../../lib.cjs/node/sources/StreamFileSource.d.ts"
}
87 changes: 73 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,66 @@
"name": "tus-js-client",
"version": "4.3.0",
"description": "A pure JavaScript client for the tus resumable upload protocol",
"main": "lib.cjs/node/index.js",
"module": "lib.esm/node/index.js",
"files": ["lib.cjs/**/*", "lib.esm/**/*", "dist/**/*"],
"browser": {
"./lib.cjs/node/index.js": "./lib.cjs/browser/index.js",
"./lib.esm/node/index.js": "./lib.esm/browser/index.js"
},
"type": "module",
"main": "./lib.cjs/browser/index.js",
"types": "./lib.cjs/browser/index.d.ts",
"module": "./lib.esm/browser/index.js",
"exports": {
"./package.json": "./package.json",
".": {
"node": {
"import": {
"types": "./lib.esm/node/index.d.ts",
"default": "./lib.esm/node/index.js"
},
"require": {
"types": "./lib.cjs/node/index.d.ts",
"default": "./lib.cjs/node/index.js"
}
},
"default": {
"import": {
"types": "./lib.esm/browser/index.d.ts",
"default": "./lib.esm/browser/index.js"
},
"require": {
"types": "./lib.cjs/browser/index.d.ts",
"default": "./lib.cjs/browser/index.js"
}
}
},
"./node/sources/StreamFileSource": {
"import": {
"types": "./lib.esm/node/sources/StreamFileSource.d.ts",
"default": "./lib.esm/node/sources/StreamFileSource.js"
},
"require": {
"types": "./lib.cjs/node/sources/StreamFileSource.d.ts",
"default": "./lib.cjs/node/sources/StreamFileSource.js"
}
},
"./node/FileUrlStorage": {
"import": {
"types": "./lib.esm/node/FileUrlStorage.d.ts",
"default": "./lib.esm/node/FileUrlStorage.js"
},
"require": {
"types": "./lib.cjs/node/FileUrlStorage.d.ts",
"default": "./lib.cjs/node/FileUrlStorage.js"
}
},
"./node/NodeHttpStack": {
"import": {
"types": "./lib.esm/node/NodeHttpStack.d.ts",
"default": "./lib.esm/node/NodeHttpStack.js"
},
"require": {
"types": "./lib.cjs/node/NodeHttpStack.d.ts",
"default": "./lib.cjs/node/NodeHttpStack.js"
}
}
},
"files": ["lib.cjs/", "lib.esm/", "dist/", "node"],
"engines": {
"node": ">=18"
},
Expand All @@ -24,11 +76,12 @@
},
"homepage": "https://github.com/tus/tus-js-client",
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.3",
"@biomejs/biome": "^1.7.3",
"axios": "^0.29.0",
"browserify": "^17.0.0",
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-typescript": "^12.1.2",
"chokidar-cli": "^3.0.0",
"exorcist": "^2.0.0",
"into-stream": "^6.0.0",
"jasmine": "^5.1.0",
"jasmine-ajax": "^4.0.0",
Expand All @@ -38,7 +91,9 @@
"karma-chrome-launcher": "^3.1.1",
"karma-jasmine": "^5.1.0",
"npm-run-all": "^4.1.5",
"publint": "^0.3.2",
"puppeteer": "^22.3.0",
"rollup": "^4.30.1",
"temp": "^0.9.4",
"throttle": "^1.0.3",
"typescript": "^5.4.5",
Expand All @@ -56,8 +111,9 @@
},
"scripts": {
"clean": "rm -rf dist lib.cjs lib.esm",
"build-test-bundle": "mkdir -p dist && browserify test/spec/browser-index.cjs -d -o dist/browser-test-bundle.js",
"build-bundle": "mkdir -p dist && browserify lib.cjs/browser/index.js -s tus -d | exorcist ./dist/tus.js.map > dist/tus.js",
"build-exports": "node scripts/setup-exports.js",
"build-test-bundle": "mkdir -p dist && rollup test/spec/browser-index.js --format iife --sourcemap --plugin node-resolve --plugin commonjs --file dist/browser-test-bundle.js",
"build-bundle": "mkdir -p dist && rollup lib/browser/index.ts --format umd --name tus --plugin typescript --plugin node-resolve --plugin commonjs --file ./dist/tus.js --sourcemap --sourcemapFile ./dist/tus.js.map",
"build-minify": "uglifyjs ./dist/tus.js -o ./dist/tus.min.js --compress --mangle --source-map \"content='./dist/tus.js.map',url='tus.min.js.map'\"",
"build-transpile-esm": "tsc --project tsconfig-esm.json && echo '{\"type\":\"module\"}' > lib.esm/package.json",
"build-transpile-cjs": "tsc --project tsconfig-cjs.json && echo '{\"type\":\"commonjs\"}' > lib.cjs/package.json",
Expand All @@ -69,8 +125,11 @@
"watch": "npm-run-all --parallel watch-*",
"test-puppeteer": "karma start test/karma/puppeteer.conf.cjs",
"test-browserstack": "karma start test/karma/browserstack.conf.cjs",
"test-node": "jasmine test/spec/node-index.cjs",
"lint": "biome check .",
"test-node": "jasmine test/spec/node-index.js",
"lint-code": "biome check .",
"lint-package": "publint --pack npm",
"lint-type-exports": "attw --pack .",
"lint": "npm-run-all lint-*",
"fix": "biome check --write ."
}
}
62 changes: 62 additions & 0 deletions scripts/setup-exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import path, { relative } from 'path'
import { mkdir, readFile, writeFile } from 'fs/promises'

/**
* Each entry is a "subpackage" that users should be able to import as `tus-js-client/${entry}`.
*/
const EXPORTS = ['node/sources/StreamFileSource', 'node/FileUrlStorage', 'node/NodeHttpStack']

const root = path.join(import.meta.dirname, '..')

/**
* addExportsToPackageJson adds all entries from EXPORTS to the exports field of package.json,
* so loaders and bundlers supporting the exports field can resolve them properly to either ESM or CommonJS.
*/
async function addExportsToPackageJson() {
const packageJsonPath = path.join(root, 'package.json')
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'))

for (const destination of EXPORTS) {
packageJson.exports ??= {}
packageJson.exports[`./${destination}`] = {
import: {
types: `./lib.esm/${destination}.d.ts`,
default: `./lib.esm/${destination}.js`,
},
require: {
types: `./lib.cjs/${destination}.d.ts`,
default: `./lib.cjs/${destination}.js`,
},
}
}

await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
}

/**
* createExportsFallbacks setups fallbacks for loaders and bundlers not supporting the exports field.
* It creates subdirectories with a package.json pointing to the CommonJS file and its type definition,
* as explained in https://github.com/andrewbranch/example-subpath-exports-ts-compat/tree/main/examples/node_modules/package-json-redirects.
* Therefore, bundlers without `exports` support get at least the CommonJS file and types.
*/
async function createExportsFallbacks() {
for (const entry of EXPORTS) {
const fallbackPath = path.join(root, entry)
await mkdir(fallbackPath, { recursive: true })

const destinationMain = path.join(root, 'lib.cjs', `${entry}.js`)
const destinationTypes = path.join(root, 'lib.cjs', `${entry}.d.ts`)

const packageJson = {
'//': 'Generated by scripts/setup-exports.js',
main: relative(fallbackPath, destinationMain),
types: relative(fallbackPath, destinationTypes),
}

const packageJsonPath = path.join(fallbackPath, 'package.json')
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
}
}

await addExportsToPackageJson()
await createExportsFallbacks()
1 change: 1 addition & 0 deletions test/karma/base.conf.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Karma base configuration

// TODO: Can we use ESM here as well?
module.exports = (config) => {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
Expand Down
13 changes: 0 additions & 13 deletions test/spec/browser-index.cjs

This file was deleted.

Loading

0 comments on commit 14d583d

Please sign in to comment.