From b69c2f5324190daa7e5aaeffe05f02f926412b4b Mon Sep 17 00:00:00 2001 From: Cezar Andrew Villegas Santarin Date: Fri, 19 Jun 2020 01:58:20 +0800 Subject: [PATCH 1/2] Add type definition modules --- index.d.ts | 145 ++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 119 +++++++++++++++++++++++++++++++++---- package.json | 16 +++-- test/index.ts | 11 ++++ 4 files changed, 276 insertions(+), 15 deletions(-) create mode 100644 index.d.ts create mode 100644 test/index.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..c0860fc --- /dev/null +++ b/index.d.ts @@ -0,0 +1,145 @@ +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} + */ + 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} + */ + 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} + */ + 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 + */ + // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types + 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. + */ + 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 bfee858..6731ea4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -323,12 +323,31 @@ "defer-to-connect": "^1.0.1" } }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -341,6 +360,29 @@ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", "dev": true }, + "@types/express": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", + "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", + "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", @@ -358,6 +400,12 @@ "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", "dev": true }, + "@types/mime": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", + "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -382,6 +430,38 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==", + "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.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", + "integrity": "sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "2.34.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz", @@ -1176,8 +1256,7 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "camelcase-keys": { "version": "4.2.0", @@ -1618,8 +1697,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decamelize-keys": { "version": "1.1.0", @@ -4867,6 +4945,16 @@ "requires": { "ms": "^2.1.1" } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -6973,9 +7061,9 @@ } }, "typescript": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz", - "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==", + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", + "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", "dev": true }, "unc-path-regex": { @@ -7543,14 +7631,23 @@ "requires": { "ansi-regex": "^4.1.0" } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" diff --git a/package.json b/package.json index 5a53fe0..1e0f8b7 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "4.0.0", "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,17 +26,23 @@ "expressjs" ], "files": [ - "index.js" + "index.js", + "index.d.ts" ], "dependencies": { "ignore-by-default": "^1.0.1", "memoizee": "^0.4.14", - "send": "^0.17.1" + "send": "^0.17.1", + "yargs-parser": "^18.1.3" }, "devDependencies": { + "@types/express": "^4.17.6", + "@types/express-serve-static-core": "^4.17.7", + "@types/send": "^0.14.5", "mocha": "^7.2.0", "nyc": "^15.1.0", "should": "^13.2.3", + "typescript": "^3.9.5", "xo": "^0.30.0" }, "engines": { diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..792d4fa --- /dev/null +++ b/test/index.ts @@ -0,0 +1,11 @@ +import staticify, {StaticifyOptions} from '..'; + +const staticifyOptions: StaticifyOptions = { + includeAll: true, + shortHash: false, + pathPrefix: '/static', + maxAgeNonHashed: 365 * 24 * 60 * 60 * 1000 +}; +const staticified = staticify('/public', staticifyOptions); + +console.log(staticified); From e65e893d9e30e1777e144f3d29dfe8d7668065bc Mon Sep 17 00:00:00 2001 From: Cezar Andrew Villegas Santarin Date: Fri, 19 Jun 2020 02:04:08 +0800 Subject: [PATCH 2/2] Mention TypeScript support --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f928f02..dda9f31 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ And in your template: ``` +### TypeScript + +staticify ships with official type declarations for TypeScript out of the box. + ## Options Options are specified as the second parameter to `staticify`: