diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..ca458cd --- /dev/null +++ b/index.d.ts @@ -0,0 +1,150 @@ +import { SendOptions } from 'send'; +import { RequestHandler } from 'express'; +import { Request } from 'express-serve-static-core'; + +export type StaticifyOptions = { + /** + * Include all files when scanning the public directory. + * By default, the directories from [ignore-by-default](https://github.com/novemberborn/ignore-by-default/blob/master/index.js) are ignored. + * + * @type {boolean} + * @default false + */ + includeAll?: boolean; + /** + * Generate a short (7-digit) MD5 hash instead of the full (32-digit) one. + * + * @type {boolean} + * @default true + */ + shortHash?: boolean; + /** + * If you are using the staticify convenience middleware through a specific route, it is necessary to indicate the route in this option. + * + * @type {string} + * @default '/' + */ + pathPrefix?: string; + /** + * `maxAge` for assets without a hash such as /image.png passed to [send](https://github.com/pillarjs/send). + * + * Can be defined as a number of milliseconds or string accepted by [ms](https://www.npmjs.org/package/ms#readme) module (eg. `'5d'`, `'1y'`, etc.) + * + * @type {(string|number)} + * @default 0 + */ + maxAgeNonHashed?: string | number; + /** + * You can pass any [send](https://github.com/pillarjs/send) options; used in `middleware` and `serve` functions. + * + * @type {SendOptions} + * @default { maxAge: '1y' } // hashed assets + * @default { maxAge: 0 } // non-hashed assets + */ + sendOptions?: SendOptions; +}; + +export type Statificy = { + _versions: object; + /** + Does the following transformation to the `path`, and returns immediately: + + ```js + staticify.getVersionedPath('/path/to/file.ext'); // --> /path/to/file..ext + ``` + + This method is meant to be used inside your templates. + + This method is really fast (simply an in-memory lookup) and returns immediately. + When you initialize this module, it crawls your public folder synchronously at startup, and pre-determines all the MD5 hashes for your static files. + This slows down application startup slightly, but it keeps the runtime performance at its peak. + * + * @param {string} path + * @returns {string} + * @memberof Statificy + */ + getVersionedPath(path: string): string; + /** + Takes the input string, and replaces any paths it can understand. For example: + + ```js + staticify.replacePaths('body { background: url("/index.js") }'); + ``` + + returns + + ```js + "body { background: url('/index.d766c4a983224a3696bc4913b9e47305.js') }" + ``` + + Perfect for use in your build script, to modify references to external paths within your CSS files. + + * + * @param {string} input + * @returns {string} + * @memberof Statificy + */ + replacePaths(input: string): string; + /** + Removes the MD5 identifier in a path. + + ```js + staticify.stripVersion('/path/to/file.ae2b1fca515949e5d54fb22b8ed95575.ext'); // --> /path/to/file.ext + ``` + + Note, this function doesn't verify that the hash is valid. It simply finds what looks like a hash and strips it from the path. + * + * @param {string} path + * @returns {string} + * @memberof Statificy + */ + stripVersion(path: string): string; + /** + Handles an incoming request for the file. + Internally calls `.stripVersion` to strip the version identifier, and serves the file with a `maxAge` of one year, using [send](https://github.com/pillarjs/send). + Returns a stream that can be `.pipe`d to a http response stream. + See [here](https://github.com/pillarjs/send#options) for the options you can pass. + + ```js + staticify.serve(req, { + sendOptions: { + maxAge: 3600 * 1000 // milliseconds + } + }).pipe(res); + ``` + * + * @param {Request} req + * @memberof Statificy + */ + serve(req: Request): void; + /** + Rebuilds the MD5 version cache described above. + Use this method sparingly. + This crawls your public folder synchronously (in a blocking fashion) to rebuild the cache. + This is typically only useful when you are doing some kind of live-reloading during development. + * + * @memberof Statificy + */ + refresh(): void; + /** + Convenience wrapper over `.serve` to handle static files in express.js. + + ```js + app.use(staticify.middleware); // `app` is your express instance + ``` + * + * @type {RequestHandler} + * @memberof Statificy + */ + middleware: RequestHandler; +}; + +/** + * Provides helpers to add a version identifier to your static asset's public URLs, and to remove the hash before serving the file from the file system. + * + * @export + * @param {string} root The root path to the static content. + * @param {StaticifyOptions} [options] Additional options. + * @returns {Statificy} + */ +export default function staticify(root: string, options?: StaticifyOptions): Statificy; diff --git a/package-lock.json b/package-lock.json index ee37415..e9ca2ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -161,12 +161,52 @@ "defer-to-connect": "^1.0.1" } }, + "@types/body-parser": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", + "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", "dev": true }, + "@types/express": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", + "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.0.tgz", + "integrity": "sha512-Xnub7w57uvcBqFdIGoRg1KhNOeEj0vB6ykUM7uFWyxvbdE89GFyqgmUcanAriMr4YOxNFZBAWkfcWIb4WBPt3g==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, "@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", @@ -178,6 +218,12 @@ "@types/node": "*" } }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -196,6 +242,32 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/send": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.14.5.tgz", + "integrity": "sha512-0mwoiK3DXXBu0GIfo+jBv4Wo5s1AcsxdpdwNUtflKm99VEMvmBPJ+/NBNRZy2R5JEYfWL/u4nAHuTUTA3wFecQ==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, "acorn": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", @@ -5471,6 +5543,12 @@ "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==", "dev": true }, + "typescript": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", + "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", + "dev": true + }, "uglify-js": { "version": "3.6.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.4.tgz", diff --git a/package.json b/package.json index bad4bb9..7170366 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "3.3.4", "description": "A better static asset handler for Node.js/Express.js", "main": "index.js", + "types": "index.d.ts", "author": "Rakesh Pai ", "license": "MIT", "repository": { @@ -12,8 +13,9 @@ "scripts": { "mocha": "mocha --exit", "xo": "xo", - "test": "npm run xo && npm run mocha", - "test:cov": "nyc npm run test" + "test": "npm run xo && npm run mocha && npm run tsc", + "test:cov": "nyc npm run test", + "tsc": "tsc --esModuleInterop --noEmit test/index.ts" }, "keywords": [ "static", @@ -24,7 +26,8 @@ "expressjs" ], "files": [ - "index.js" + "index.js", + "index.d.ts" ], "dependencies": { "ignore-by-default": "^1.0.1", @@ -32,9 +35,13 @@ "send": "^0.17.1" }, "devDependencies": { + "@types/express": "^4.17.2", + "@types/express-serve-static-core": "^4.17.0", + "@types/send": "^0.14.5", "mocha": "^6.2.2", "nyc": "^14.1.1", "should": "^13.2.3", + "typescript": "^3.7.2", "xo": "^0.25.3" }, "engines": { diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..9c91e89 --- /dev/null +++ b/test/index.ts @@ -0,0 +1,12 @@ +import staticify, { StaticifyOptions } from '../index'; + +const maxAgeNonHashed: number | string = 365 * 24 * 60 * 60 * 1000; +const staticifyOptions: StaticifyOptions = { + includeAll: true, + shortHash: false, + pathPrefix: '/static', + maxAgeNonHashed: maxAgeNonHashed, +}; +const staticified = staticify('/public', staticifyOptions); + +console.log(staticified);