From ca94201b01f55a36f41523b1217f805fe5c86a77 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Fri, 10 May 2019 15:26:01 -0700 Subject: [PATCH 01/52] First commit of working version for review. --- .gitignore | 3 + CHANGELOG.md | 16 + LICENSE | 28 + README.md | 58 +- custom_typings/parse5-traverse.d.ts | 9 + package-lock.json | 1574 ++++++++++++++++++++++++ package.json | 47 + src/koa-esm-specifier-transform.ts | 57 + src/koa-npm-resolution.ts | 32 + src/support/parse5-utils.ts | 178 +++ src/support/resolve-npm-specifier.ts | 31 + src/test/koa-npm-resolution.test.ts | 83 ++ src/test/resolve-npm-specifier.test.ts | 10 + src/test/test-utils.test.ts | 13 + src/test/test-utils.ts | 54 + src/transform-html.ts | 64 + src/transform-javascript-module.ts | 46 + tsconfig.json | 62 + 18 files changed, 2364 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 custom_typings/parse5-traverse.d.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/koa-esm-specifier-transform.ts create mode 100644 src/koa-npm-resolution.ts create mode 100644 src/support/parse5-utils.ts create mode 100644 src/support/resolve-npm-specifier.ts create mode 100644 src/test/koa-npm-resolution.test.ts create mode 100644 src/test/resolve-npm-specifier.test.ts create mode 100644 src/test/test-utils.test.ts create mode 100644 src/test/test-utils.ts create mode 100644 src/transform-html.ts create mode 100644 src/transform-javascript-module.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc4e13c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +lib +*.tgz diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1bda0d1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + + +## Unreleased + + * Rewrites resolvable NPM package specifiers in JavaScript module files. + * Rewrites resolvable NPM package specifiers in HTML files in ` + `, + }, + }, + async (server) => t.equal( + squeezeHTML((await request(server).get('/my-page.html')).text), + squeezeHTML(` + + `))); +}); + +test('ignores unresolvable specifier in inline module script', async (t) => { + t.plan(1); + createAndServe( + { + middleware: + [createMiddleware({packageRoot: resolvePath(__dirname, '../..')})], + routes: { + '/my-page.html': ` + + `, + }, + }, + async (server) => t.equal( + squeezeHTML((await request(server).get('/my-page.html')).text), + squeezeHTML(` + + `))); +}); diff --git a/src/test/resolve-npm-specifier.test.ts b/src/test/resolve-npm-specifier.test.ts new file mode 100644 index 0000000..766993e --- /dev/null +++ b/src/test/resolve-npm-specifier.test.ts @@ -0,0 +1,10 @@ +import {resolve as resolvePath} from 'path'; +import test from 'tape'; + +import resolve from '../support/resolve-npm-specifier'; + +test('resolve', (t) => { + t.plan(1); + const path = resolve(resolvePath(__dirname, '../..') + '/', 'resolve-from'); + t.equal(path, './node_modules/resolve-from/index.js'); +}); diff --git a/src/test/test-utils.test.ts b/src/test/test-utils.test.ts new file mode 100644 index 0000000..326c29e --- /dev/null +++ b/src/test/test-utils.test.ts @@ -0,0 +1,13 @@ +import test from 'tape'; + +import {squeezeHTML} from './test-utils'; + +test('squeezeHTML will not put inject newlines where no-spaces exist', (t) => { + t.plan(1); + t.equal(squeezeHTML('

Hello

'), '

Hello

'); +}); + +test('squeezeHTML will shrink multiple spaces to single spaces', (t) => { + t.plan(1); + t.equal(squeezeHTML('

Hello

'), '

\nHello\n

'); +}); diff --git a/src/test/test-utils.ts b/src/test/test-utils.ts new file mode 100644 index 0000000..40211bd --- /dev/null +++ b/src/test/test-utils.ts @@ -0,0 +1,54 @@ +import {Server} from 'http'; +import Koa from 'koa'; +import route from 'koa-route'; + +export type AppOptions = { + middleware?: Koa.Middleware[], + routes?: {[key: string]: string|Function}, +}; + +export function createApp(options: AppOptions): Koa { + const app = new Koa(); + const {middleware, routes} = options; + if (middleware) { + for (const m of middleware) { + app.use(m); + } + } + if (routes) { + for (const key of Object.keys(routes)) { + const value = routes[key]; + app.use(route.get(key, (ctx) => { + if (key.endsWith('.js')) { + ctx.type = 'js'; + } + if (key.endsWith('.html')) { + ctx.type = 'html'; + } + ctx.body = value; + })); + } + } + return app; +} + +export async function createAndServe( + options: AppOptions, callback: (server: Server) => void) { + serveApp(createApp(options), callback); +} + +export async function serveApp(app: Koa, callback: (server: Server) => void) { + const port = process.env.PORT || 3000; + const server = + app.listen(port).on('error', (e) => `ERROR: ${console.log(e)}`); + await callback(server); + server.close(); +} + +export function squeezeHTML(html: string): string { + return html.replace(/\s+/mg, ' ') + .replace(/>\s<') + .replace(/>\s/g, '>\n') + .replace(/\s { + const js = getTextContent(scriptTag); + const transformedJs = + transformJavaScriptModuleString(js, baseURL, transformSpecifier); + setTextContent(scriptTag, transformedJs); + }) + return; +} + +export function transformHTMLString( + html: string, url: string, transformSpecifier: TransformSpecifierFunction) { + const ast = parse(html); + removeFakeRootElements(ast); + transformHTMLAST(ast, url, transformSpecifier); + return serialize(ast); +} + +function getBaseURL(ast: Node, location: string): string { + const baseTag = getBaseTag(ast); + if (!baseTag) { + return location; + } + const baseHref = getAttr(baseTag, 'href'); + if (!baseHref) { + return location; + } + return resolveURL(location, baseHref); +} + +function getBaseTag(ast: Node): Node|undefined { + return getTags(ast, 'base').shift(); +} + +function getInlineModuleScripts(ast: Node): Node[] { + return getTags(ast, 'script') + .filter( + (node) => + getAttr(node, 'type') === 'module' && !getAttr(node, 'src')); +} + +function getTags(ast: Node, name: string): Node[] { + return query(ast, (node: Node) => (node).nodeName === name); +} + +function query(ast: Node, filter: (node: Node) => boolean): Node[] { + const nodes: Node[] = []; + traverse(ast, { + pre: (node: Node) => { + filter(node); + nodes.push(node); + } + }); + return nodes; +} diff --git a/src/transform-javascript-module.ts b/src/transform-javascript-module.ts new file mode 100644 index 0000000..de81765 --- /dev/null +++ b/src/transform-javascript-module.ts @@ -0,0 +1,46 @@ +import serialize from '@babel/generator'; +import {parse} from '@babel/parser'; +import traverse from '@babel/traverse'; +import {NodePath} from '@babel/traverse'; +import {CallExpression, ExportAllDeclaration, ExportNamedDeclaration, ImportDeclaration, isImport, isStringLiteral, Node, StringLiteral} from '@babel/types'; +import {TransformSpecifierFunction} from './koa-esm-specifier-transform'; + +export function transformJavaScriptModuleAST( + ast: Node, url: string, transformSpecifier: TransformSpecifierFunction) { + const importExportDeclaration = { + enter(path: NodePath) { + if (path.node && path.node.source && isStringLiteral(path.node.source)) { + const specifier = path.node.source.value; + path.node.source.value = transformSpecifier(url, specifier); + } + } + }; + traverse(ast, { + ImportDeclaration: importExportDeclaration, + ExportAllDeclaration: importExportDeclaration, + ExportNamedDeclaration: importExportDeclaration, + CallExpression: { + enter(path: NodePath) { + if (path.node && path.node.callee && isImport(path.node.callee) && + path.node.arguments.length === 1 && + isStringLiteral(path.node.arguments[0])) { + const argument = path.node.arguments[0]; + const specifier = argument.value; + const rewrittenSpecifier = transformSpecifier(url, specifier); + argument.value = rewrittenSpecifier; + } + } + } + }); +} + +export function transformJavaScriptModuleString( + js: string, url: string, transformSpecifier: TransformSpecifierFunction): + string { + const ast = parse(js, {sourceType: 'unambiguous'}); + const leadingSpace = js.match(/^\s*/)![0]; + const trailingSpace = js.match(/\s*$/)![0]; + transformJavaScriptModuleAST(ast, url, transformSpecifier); + return leadingSpace + serialize(ast).code + trailingSpace; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..083a152 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,62 @@ +{ + "include": [ + "./src/*.ts", + "./src/**/*.ts", + "./custom_typings/*.d.ts" + ], + "compilerOptions": { + /* Basic Options */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./lib/", /* Redirect output structure to the directory. */ + "rootDir": "./src/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "incremental": true, /* Enable incremental compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + } +} From f5e3cf99a8d1cada38745180b79c36ac11ac8138 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Fri, 10 May 2019 15:41:51 -0700 Subject: [PATCH 02/52] Added a .travis.yml --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..826c66f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +sudo: false +dist: trusty +cache: + directories: + - node_modules +script: +- npm run build +- npm test From 2fb57841d1d32341eea55f4e1419fce141597be7 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Fri, 10 May 2019 15:44:55 -0700 Subject: [PATCH 03/52] Added @babel/types to devDependencies. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 0c3aa5f..afe9b50 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "author": "", "license": "ISC", "devDependencies": { + "@babel/types": "^7.4.4", "@types/babel__generator": "^7.0.2", "@types/babel__traverse": "^7.0.6", "@types/babylon": "^6.16.5", From b5b2408e04dda97fc0f30ecfbdb2b494d59247e7 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Fri, 10 May 2019 15:55:09 -0700 Subject: [PATCH 04/52] Added @types/node to resolve tsc issue on travis/linux. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index afe9b50..215a31e 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@types/is-stream": "^2.0.0", "@types/koa": "^2.0.48", "@types/koa-route": "^3.2.4", + "@types/node": "^12.0.0", "@types/parse5": "^5.0.0", "@types/resolve-from": "^5.0.1", "@types/supertest": "^2.0.7", From 57397fa37bc4169f07380e461b5ac20c5caf6ee8 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Fri, 10 May 2019 16:03:37 -0700 Subject: [PATCH 05/52] Tell Travis-CI to use the latest version of node. --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 826c66f..95e5b04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: node_js +node_js: + - "node" sudo: false dist: trusty cache: directories: - - node_modules + - node_modules script: -- npm run build -- npm test + - npm run build + - npm test From 75fb7f76e1fa7429fa60ba27bb88e145c11080f7 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 15:35:04 -0700 Subject: [PATCH 06/52] Added linting and formatting and depcheck and fixed types and dropped parse5-traverse. --- .clang-format | 12 + custom_typings/parse5-traverse.d.ts | 9 - package-lock.json | 671 ++++++++++++++++++++++++---- package.json | 7 +- src/koa-esm-specifier-transform.ts | 6 +- src/support/parse5-utils.ts | 203 +++++---- src/transform-html.ts | 33 +- tsconfig.json | 5 - tslint.json | 62 +++ 9 files changed, 810 insertions(+), 198 deletions(-) create mode 100644 .clang-format delete mode 100644 custom_typings/parse5-traverse.d.ts create mode 100644 tslint.json diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..62cdf4c --- /dev/null +++ b/.clang-format @@ -0,0 +1,12 @@ +BasedOnStyle: Google +AlignAfterOpenBracket: AlwaysBreak +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +BinPackArguments: false +# This breaks async functions sometimes, see +# https://github.com/Polymer/polymer-analyzer/pull/393 +# BinPackParameters: false diff --git a/custom_typings/parse5-traverse.d.ts b/custom_typings/parse5-traverse.d.ts deleted file mode 100644 index 997b557..0000000 --- a/custom_typings/parse5-traverse.d.ts +++ /dev/null @@ -1,9 +0,0 @@ - -declare module 'parse5-traverse' { -import * as p5 from 'parse5'; - export type Node = p5.Node; - - export default function traverse( - root: Node, - options: {pre?: (node: Node) => void, post?: (node: Node) => void}): void; -} diff --git a/package-lock.json b/package-lock.json index 049773d..56fe687 100644 --- a/package-lock.json +++ b/package-lock.json @@ -449,6 +449,12 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "cache-content-type": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", @@ -459,6 +465,12 @@ "ylru": "^1.2.0" } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -483,6 +495,34 @@ "resolve": "^1.1.6" } }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -511,6 +551,12 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -560,6 +606,19 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -568,6 +627,12 @@ "ms": "2.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", @@ -601,12 +666,54 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, + "depcheck": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-0.8.0.tgz", + "integrity": "sha512-ZCwp4SeB6JYMyd2/lFC4RwtAZ8CqPY5c4Yq2sQQI0q3ycCs9M4GVqbKqvtMFFbZaVVjNw9gVEIPELCQxR3HDpw==", + "dev": true, + "requires": { + "@babel/parser": "^7.3.1", + "@babel/traverse": "^7.2.3", + "builtin-modules": "^3.0.0", + "deprecate": "^1.0.0", + "deps-regex": "^0.1.4", + "js-yaml": "^3.4.2", + "lodash": "^4.17.11", + "minimatch": "^3.0.2", + "node-sass-tilde-importer": "^1.0.2", + "please-upgrade-node": "^3.1.1", + "require-package-name": "^2.0.1", + "resolve": "^1.10.0", + "walkdir": "^0.3.2", + "yargs": "^13.2.2" + }, + "dependencies": { + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true + } + } + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, + "deprecate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/deprecate/-/deprecate-1.1.0.tgz", + "integrity": "sha512-b5dDNQYdy2vW9WXUD8+RQlfoxvqztLLhDE+T7Gd37I5E8My7nJkKu6FmhdDeRWJ8B+yjZKuwjCta8pgi8kgSqA==", + "dev": true + }, + "deps-regex": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deps-regex/-/deps-regex-0.1.4.tgz", + "integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ=", + "dev": true + }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -631,6 +738,12 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -698,6 +811,38 @@ "integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=", "dev": true }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -714,6 +859,21 @@ "object-assign": "^4.1.0" } }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -758,6 +918,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "get-stream": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", @@ -853,6 +1019,12 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", @@ -874,6 +1046,12 @@ "number-is-nan": "^1.0.0" } }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "is-generator-function": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", @@ -909,6 +1087,12 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1010,26 +1194,23 @@ "path-to-regexp": "^1.2.0" } }, - "koa-send": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.0.tgz", - "integrity": "sha512-90ZotV7t0p3uN9sRwW2D484rAaKIsD8tAVtypw/aBU+ryfV+fR2xrcAwhI8Wl6WRkojLUs/cB9SBSCuIb+IanQ==", + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "debug": "^3.1.0", - "http-errors": "^1.6.3", - "mz": "^2.7.0", - "resolve-path": "^1.4.0" + "invert-kv": "^2.0.0" } }, - "koa-static": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", - "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "debug": "^3.1.0", - "koa-send": "^5.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "lodash": { @@ -1037,12 +1218,32 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -1069,6 +1270,12 @@ "mime-db": "1.40.0" } }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1084,28 +1291,58 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", "dev": true }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-sass-tilde-importer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/node-sass-tilde-importer/-/node-sass-tilde-importer-1.0.2.tgz", + "integrity": "sha512-Swcmr38Y7uB78itQeBm3mThjxBy9/Ah/ykPIaURY/L6Nec9AyRoL/jJ7ECfMR+oZeCTVQNxVMu/aHU+TLRVbdg==", + "dev": true, + "requires": { + "find-parent-dir": "^0.3.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -1153,6 +1390,59 @@ "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", "dev": true }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "parse-ms": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", @@ -1164,23 +1454,30 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" }, - "parse5-traverse": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/parse5-traverse/-/parse5-traverse-1.0.3.tgz", - "integrity": "sha512-+gvNpmU91iJBjNrzvmhSSSf0B5bcWBYE1Eex8HrvnOrCMtzHPBKiy8MhFb2Li77AYwNErLiB4Mjfx97Me07+Pg==" - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -1202,6 +1499,15 @@ } } }, + "please-upgrade-node": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz", + "integrity": "sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "plur": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", @@ -1255,6 +1561,24 @@ "util-deprecate": "~1.0.1" } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "require-package-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz", + "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=", + "dev": true + }, "resolve": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", @@ -1269,36 +1593,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" }, - "resolve-path": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", - "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", - "dev": true, - "requires": { - "http-errors": "~1.6.2", - "path-is-absolute": "1.0.1" - }, - "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - } - } - }, "resumer": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", @@ -1323,12 +1617,51 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", "dev": true }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -1364,6 +1697,34 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "string.prototype.trim": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", @@ -1393,6 +1754,12 @@ "ansi-regex": "^2.0.0" } }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, "superagent": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", @@ -1475,24 +1842,6 @@ "through": "~2.3.8" } }, - "thenify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", - "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "dev": true, - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -1525,6 +1874,79 @@ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", + "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1553,6 +1975,64 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, + "walkdir": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.3.2.tgz", + "integrity": "sha512-0Twghia4Z5wDGDYWURlhZmI47GvERMCsXIu0QZWVVZyW9ZjpbbZvD9Zy9M6cWiQQRRbAcYajIyKNavaZZDt1Uw==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -1564,6 +2044,41 @@ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", "dev": true }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" + } + }, + "yargs-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "ylru": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", diff --git a/package.json b/package.json index 215a31e..fe1f9ce 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,9 @@ "scripts": { "build": "tsc", "clean": "rimraf lib", + "depcheck": "depcheck --ignore-dirs=lib --ignores=\"source-map-support,@types/*\"", + "format": "find src -name \"*.ts\" | xargs clang-format --style=file -i", + "lint": "tslint -p . --fix", "test": "tape -r source-map-support/register 'lib/test/*.test.js' 'lib/test/**/*.test.js' | tap-diff" }, "keywords": [], @@ -26,13 +29,14 @@ "@types/supertest": "^2.0.7", "@types/tape": "^4.2.33", "clang-format": "^1.2.4", + "depcheck": "^0.8.0", "koa": "^2.7.0", - "koa-static": "^5.0.0", "rimraf": "^2.6.3", "source-map-support": "^0.5.12", "supertest": "^4.0.2", "tap-diff": "^0.1.1", "tape": "^4.10.1", + "tslint": "^5.16.0", "typescript": "^3.4.5" }, "dependencies": { @@ -43,7 +47,6 @@ "is-stream": "^2.0.0", "koa-route": "^3.2.0", "parse5": "^5.1.0", - "parse5-traverse": "^1.0.3", "resolve-from": "^5.0.0" } } diff --git a/src/koa-esm-specifier-transform.ts b/src/koa-esm-specifier-transform.ts index 0ce29e0..8ea3585 100644 --- a/src/koa-esm-specifier-transform.ts +++ b/src/koa-esm-specifier-transform.ts @@ -11,7 +11,7 @@ export default middleware; export function middleware(transformSpecifier: TransformSpecifierFunction): Koa.Middleware { return async (ctx: Koa.Context, next: Function) => { - await next() + await next(); if (ctx.response.is('html')) { ctx.body = resolveSpecifiersInInlineScriptTags( @@ -22,7 +22,7 @@ export function middleware(transformSpecifier: TransformSpecifierFunction): ctx.body = resolveSpecifiersInJavaScriptModule( await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); } - } + }; } function resolveSpecifiersInInlineScriptTags( @@ -40,7 +40,7 @@ function resolveSpecifiersInJavaScriptModule( } // TODO(usergenic): This should probably be published as a separate npm package. -async function getBodyAsString(body: any): Promise { +async function getBodyAsString(body: Buffer|string): Promise { if (!body) { return ''; } diff --git a/src/support/parse5-utils.ts b/src/support/parse5-utils.ts index 9e2cac8..87608a0 100644 --- a/src/support/parse5-utils.ts +++ b/src/support/parse5-utils.ts @@ -5,11 +5,12 @@ * Once dom5 is updated, we can just use that package and not maintain these * here. */ -import {DefaultTreeElement, Node} from 'parse5'; +import {DefaultTreeCommentNode, DefaultTreeDocument, DefaultTreeDocumentFragment, DefaultTreeElement, DefaultTreeNode, DefaultTreeParentNode, DefaultTreeTextNode, Location} from 'parse5'; -const traverse = require('parse5-traverse'); - -export function filter(iter: any, predicate: any, matches: any[] = []) { +export function filter( + iter: IterableIterator, + predicate: (t: T) => boolean, + matches: T[] = []) { for (const value of iter) { if (predicate(value)) { matches.push(value); @@ -18,53 +19,64 @@ export function filter(iter: any, predicate: any, matches: any[] = []) { return matches; } -export function getAttr(ast: Node, name: string) { - if (ast.hasOwnProperty('attrs')) { - const attr = (<{name: string, value: any}[]>(ast).attrs) - .find(({name: attrName}) => attrName === name); +export function getAttr(element: DefaultTreeNode, name: string): string { + if (isElement(element)) { + const attr = element.attrs.find(({name: attrName}) => attrName === name); if (attr) { return attr.value; } } + return ''; } -export function getTextContent(node: any): string { +export function getTextContent(node: DefaultTreeNode): string { if (isCommentNode(node)) { - return node.data || ''; + return node.data; } if (isTextNode(node)) { - return node.value || ''; + return node.value; } const subtree = nodeWalkAll(node, isTextNode); return subtree.map(getTextContent).join(''); } -export function setAttr(ast: any, name: any, value: any) { - let attr = (<{name: string, value: any}[]>ast.attrs) - .find(({name: attrName}) => attrName === name); +export function setAttr(element: DefaultTreeNode, name: string, value: string) { + if (!isElement(element)) { + return; + } + const attr = element.attrs.find(({name: attrName}) => attrName === name); if (attr) { attr.value = value; } else { - ast.attrs.push({name, value}); + element.attrs.push({name, value}); } } -export function insertBefore(parent: any, oldNode: any, newNode: any) { +export function insertBefore( + parent: DefaultTreeNode, + oldNode: DefaultTreeNode, + newNode: DefaultTreeNode) { + if (!isParent(parent)) { + return; + } const index = parent.childNodes.indexOf(oldNode); insertNode(parent, index, newNode); } export function insertNode( - parent: any, index: any, newNode: any, replace: any = undefined) { - if (!parent.childNodes) { - parent.childNodes = []; + parent: DefaultTreeNode, + index: number, + newNode: DefaultTreeNode, + replace: DefaultTreeNode|undefined = undefined) { + if (!isParent(parent)) { + return; } - let newNodes = []; + let newNodes: DefaultTreeNode[] = []; let removedNode = replace ? parent.childNodes[index] : null; if (newNode) { if (isDocumentFragment(newNode)) { if (newNode.childNodes) { - newNodes = Array.from(newNode.childNodes); + newNodes = [...newNode.childNodes]; newNode.childNodes.length = 0; } } else { @@ -75,104 +87,135 @@ export function insertNode( if (replace) { removedNode = parent.childNodes[index]; } - Array.prototype.splice.apply( - parent.childNodes, - ([index, replace ? 1 : 0]).concat(newNodes)); - newNodes.forEach((n) => {n.parentNode = parent}); + parent.childNodes.splice(index, replace ? 1 : 0, ...newNodes); + newNodes.forEach((n) => { + if (isChild(n)) { + n.parentNode = parent; + } + }); - if (removedNode) { - removedNode.parentNode = undefined + if (removedNode && isChild(removedNode)) { + (<{parentNode: DefaultTreeParentNode | undefined}>removedNode).parentNode = + undefined; } } -export function isCommentNode(node: any) { - return node.nodeName === '#comment' +export function isChild(node: DefaultTreeNode): node is DefaultTreeTextNode| + DefaultTreeCommentNode|DefaultTreeElement { + return isCommentNode(node) || isElement(node) || isTextNode(node); } -export function isDocumentFragment(node: any) { - return node.nodeName === '#document-fragment' +export function isCommentNode(node: DefaultTreeNode): + node is DefaultTreeCommentNode { + return node.nodeName === '#comment'; } -export function isTextNode(node: any) { - return node.nodeName === '#text' +export function isDocument(node: DefaultTreeNode): node is DefaultTreeDocument { + return node.nodeName === '#document'; } -export const defaultChildNodes = (node: any) => node.childNodes +export function isDocumentFragment(node: DefaultTreeNode): + node is DefaultTreeDocumentFragment { + return node.nodeName === '#document-fragment'; +} -export function* depthFirst(node: any, getChildNodes: any = defaultChildNodes): - any { - yield node; - const childNodes = getChildNodes(node); - if (childNodes === undefined) { - return; - } - for (const child of childNodes) { - yield* depthFirst(child, getChildNodes); - } +export function isElement(node: DefaultTreeNode): node is DefaultTreeElement { + return !node.nodeName.startsWith('#'); +} + +export function isParent(node: DefaultTreeNode): node is DefaultTreeDocument| + DefaultTreeDocumentFragment|DefaultTreeElement { + return isElement(node) || isDocumentFragment(node) || isDocument(node); +} + +export function isTextNode(node: DefaultTreeNode): node is DefaultTreeTextNode { + return node.nodeName === '#text'; +} + +export const defaultChildNodes = (node: DefaultTreeParentNode) => + node.childNodes; + +export function* + depthFirst( + node: DefaultTreeNode, + getChildNodes: typeof defaultChildNodes = defaultChildNodes): + IterableIterator { + yield node; + if (isParent(node)) { + const childNodes = getChildNodes(node); + if (childNodes === undefined) { + return; } + for (const child of childNodes) { + yield* depthFirst(child, getChildNodes); + } + } +} export function nodeWalkAll( - node: any, - predicate: any, - matches: any = [], - getChildNodes: any = defaultChildNodes) { + node: DefaultTreeNode, + predicate: (node: DefaultTreeNode) => boolean, + matches: DefaultTreeNode[] = [], + getChildNodes: typeof defaultChildNodes = defaultChildNodes) { return filter(depthFirst(node, getChildNodes), predicate, matches); } -export function removeFakeRootElements(node: any) { - const fakeRootElements: any[] = []; - traverse(node, { - pre: (node: any) => { - if (node.nodeName && node.nodeName.match(/^(html|head|body)$/i) && - !node.sourceCodeLocation) { - fakeRootElements.unshift(node); - } +export function removeFakeRootElements(node: DefaultTreeNode) { + const fakeRootElements: DefaultTreeElement[] = []; + nodeWalkAll(node, (node) => { + if (node.nodeName && node.nodeName.match(/^(html|head|body)$/i) && + !(node) + .sourceCodeLocation) { + fakeRootElements.unshift(node as DefaultTreeElement); } - }) + return false; + }); fakeRootElements.forEach(removeNodeSaveChildren); } -export function removeNode(node: any) { - const parent = node.parentNode; - if (parent && parent.childNodes) { - const idx = parent.childNodes.indexOf(node); - parent.childNodes.splice(idx, 1); +export function removeNode(node: DefaultTreeNode) { + if (isChild(node)) { + const parent = node.parentNode; + if (parent && parent.childNodes) { + const idx = parent.childNodes.indexOf(node); + parent.childNodes.splice(idx, 1); + } } - node.parentNode = undefined; + (node as unknown as {parentNode: Object | undefined}).parentNode = undefined; } -export function removeNodeSaveChildren(node: any) { +export function removeNodeSaveChildren(node: DefaultTreeNode) { // We can't save the children if there's no parent node to provide // for them. + if (!isChild(node)) { + return; + } const fosterParent = node.parentNode; if (!fosterParent) { return; } - const children = (node.childNodes || []).slice(); - for (const child of children) { - insertBefore(node.parentNode, node, child); + if (isParent(node)) { + const children = (node.childNodes || []).slice(); + for (const child of children) { + insertBefore(fosterParent as unknown as DefaultTreeNode, node, child); + } } removeNode(node); } -export function setTextContent(node: any, value: any) { +export function setTextContent(node: DefaultTreeNode, value: string) { if (isCommentNode(node)) { node.data = value; } else if (isTextNode(node)) { node.value = value; - } else { - const tn = newTextNode(value); - tn.parentNode = node; - node.childNodes = [tn]; + } else if (isParent(node)) { + newTextNode(value, node); } } -export function newTextNode(value: any) { - return { - nodeName: '#text', - value: value, - parentNode: undefined, - attrs: [], - __location: undefined, - }; +export function newTextNode( + value: string, parentNode: DefaultTreeParentNode): DefaultTreeTextNode { + const textNode: DefaultTreeTextNode = {nodeName: '#text', value, parentNode}; + parentNode.childNodes = [textNode]; + return textNode; } diff --git a/src/transform-html.ts b/src/transform-html.ts index 2e66ed4..2395470 100644 --- a/src/transform-html.ts +++ b/src/transform-html.ts @@ -1,31 +1,33 @@ import {DefaultTreeNode, Node, parse, serialize} from 'parse5'; -import traverse from 'parse5-traverse'; import {resolve as resolveURL} from 'url'; + import {TransformSpecifierFunction} from './koa-esm-specifier-transform'; -import {getAttr, getTextContent, removeFakeRootElements, setTextContent} from './support/parse5-utils'; +import {getAttr, getTextContent, nodeWalkAll, removeFakeRootElements, setTextContent} from './support/parse5-utils'; import {transformJavaScriptModuleString} from './transform-javascript-module'; export function transformHTMLAST( - ast: Node, url: string, transformSpecifier: TransformSpecifierFunction) { + ast: DefaultTreeNode, + url: string, + transformSpecifier: TransformSpecifierFunction) { const baseURL = getBaseURL(ast, url); getInlineModuleScripts(ast).forEach((scriptTag) => { const js = getTextContent(scriptTag); const transformedJs = transformJavaScriptModuleString(js, baseURL, transformSpecifier); setTextContent(scriptTag, transformedJs); - }) + }); return; } export function transformHTMLString( html: string, url: string, transformSpecifier: TransformSpecifierFunction) { - const ast = parse(html); + const ast = parse(html); removeFakeRootElements(ast); transformHTMLAST(ast, url, transformSpecifier); return serialize(ast); } -function getBaseURL(ast: Node, location: string): string { +function getBaseURL(ast: DefaultTreeNode, location: string): string { const baseTag = getBaseTag(ast); if (!baseTag) { return location; @@ -37,28 +39,17 @@ function getBaseURL(ast: Node, location: string): string { return resolveURL(location, baseHref); } -function getBaseTag(ast: Node): Node|undefined { +function getBaseTag(ast: DefaultTreeNode): DefaultTreeNode|undefined { return getTags(ast, 'base').shift(); } -function getInlineModuleScripts(ast: Node): Node[] { +function getInlineModuleScripts(ast: DefaultTreeNode): DefaultTreeNode[] { return getTags(ast, 'script') .filter( (node) => getAttr(node, 'type') === 'module' && !getAttr(node, 'src')); } -function getTags(ast: Node, name: string): Node[] { - return query(ast, (node: Node) => (node).nodeName === name); -} - -function query(ast: Node, filter: (node: Node) => boolean): Node[] { - const nodes: Node[] = []; - traverse(ast, { - pre: (node: Node) => { - filter(node); - nodes.push(node); - } - }); - return nodes; +function getTags(ast: DefaultTreeNode, name: string): DefaultTreeNode[] { + return nodeWalkAll(ast, (node) => node.nodeName === name); } diff --git a/tsconfig.json b/tsconfig.json index 083a152..ce4c480 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,4 @@ { - "include": [ - "./src/*.ts", - "./src/**/*.ts", - "./custom_typings/*.d.ts" - ], "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..17719b7 --- /dev/null +++ b/tslint.json @@ -0,0 +1,62 @@ +{ + "rules": { + "arrow-parens": true, + "class-name": true, + "curly": true, + "indent": [ + true, + "spaces" + ], + "no-any": true, + "prefer-const": true, + "no-duplicate-variable": true, + "no-eval": true, + "no-internal-module": true, + "no-trailing-whitespace": true, + "no-var-keyword": true, + "one-line": [ + true, + "check-open-brace", + "check-whitespace" + ], + "quotemark": [ + true, + "single", + "avoid-escape" + ], + "semicolon": [ + true, + "always" + ], + "trailing-comma": [ + true, + "multiline" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": [ + true, + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +} From 494739098a0bfb8a4c3f7fcb1b34f21658dd4ff1 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 15:42:12 -0700 Subject: [PATCH 07/52] Typescript compile target ES5 -> ES2015 --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index ce4c480..eabc3ae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { /* Basic Options */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ From c984f934d1ecae382dac0a5301a627e6724615fe Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 15:43:41 -0700 Subject: [PATCH 08/52] Turn on Typescript declaration file generation. --- tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index eabc3ae..9187c75 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,8 @@ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./lib/", /* Redirect output structure to the directory. */ From 2ac77fa6931c1ff8ded4f56983ad73c4d6eb20dd Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 15:44:34 -0700 Subject: [PATCH 09/52] Remove downlevelIteration Typescript feature because newer target ES2015 --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 9187c75..6bf678d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,7 @@ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ From e29c9029e3ceefc3fdb3f66bc0654ebc427b6c8a Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 15:45:22 -0700 Subject: [PATCH 10/52] Turn on stricter Typescript checks. --- tsconfig.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 6bf678d..9b47ebc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,10 +31,10 @@ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ From f1fe49cb2411e14185d18f5fbe6d6c98f427a75e Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 15:48:32 -0700 Subject: [PATCH 11/52] Added copyright headers to typescript files. --- src/koa-esm-specifier-transform.ts | 13 +++++++++++++ src/koa-npm-resolution.ts | 13 +++++++++++++ src/support/parse5-utils.ts | 14 ++++++++++++++ src/support/resolve-npm-specifier.ts | 13 +++++++++++++ src/test/koa-npm-resolution.test.ts | 14 +++++++++++++- src/test/resolve-npm-specifier.test.ts | 13 +++++++++++++ src/test/test-utils.test.ts | 13 +++++++++++++ src/test/test-utils.ts | 13 +++++++++++++ src/transform-html.ts | 15 ++++++++++++++- src/transform-javascript-module.ts | 13 +++++++++++++ 10 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/koa-esm-specifier-transform.ts b/src/koa-esm-specifier-transform.ts index 8ea3585..0c2b099 100644 --- a/src/koa-esm-specifier-transform.ts +++ b/src/koa-esm-specifier-transform.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ import getStream from 'get-stream'; import isStream from 'is-stream'; import * as Koa from 'koa'; diff --git a/src/koa-npm-resolution.ts b/src/koa-npm-resolution.ts index 6928fe9..9e4be94 100644 --- a/src/koa-npm-resolution.ts +++ b/src/koa-npm-resolution.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ import * as Koa from 'koa'; import {resolve as resolvePath} from 'path'; import {parse as parseURL} from 'url'; diff --git a/src/support/parse5-utils.ts b/src/support/parse5-utils.ts index 87608a0..af5b34e 100644 --- a/src/support/parse5-utils.ts +++ b/src/support/parse5-utils.ts @@ -1,3 +1,17 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ + /** * TODO(usergenic): The following set of helper functions are more-or-less * copied from the npm package dom5 which could not be brought in at this diff --git a/src/support/resolve-npm-specifier.ts b/src/support/resolve-npm-specifier.ts index 4435034..622099b 100644 --- a/src/support/resolve-npm-specifier.ts +++ b/src/support/resolve-npm-specifier.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ import {relative} from 'path'; import resolveFrom from 'resolve-from'; diff --git a/src/test/koa-npm-resolution.test.ts b/src/test/koa-npm-resolution.test.ts index 3882d9d..7e47f68 100644 --- a/src/test/koa-npm-resolution.test.ts +++ b/src/test/koa-npm-resolution.test.ts @@ -1,9 +1,21 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ import {resolve as resolvePath} from 'path'; import request from 'supertest'; import test from 'tape'; import createMiddleware from '../koa-npm-resolution'; - import {createAndServe, squeezeHTML} from './test-utils'; test('transforms resolvable specifier in JavaScript module', async (t) => { diff --git a/src/test/resolve-npm-specifier.test.ts b/src/test/resolve-npm-specifier.test.ts index 766993e..1a52aaf 100644 --- a/src/test/resolve-npm-specifier.test.ts +++ b/src/test/resolve-npm-specifier.test.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ import {resolve as resolvePath} from 'path'; import test from 'tape'; diff --git a/src/test/test-utils.test.ts b/src/test/test-utils.test.ts index 326c29e..16ee47e 100644 --- a/src/test/test-utils.test.ts +++ b/src/test/test-utils.test.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ import test from 'tape'; import {squeezeHTML} from './test-utils'; diff --git a/src/test/test-utils.ts b/src/test/test-utils.ts index 40211bd..23bab5f 100644 --- a/src/test/test-utils.ts +++ b/src/test/test-utils.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ import {Server} from 'http'; import Koa from 'koa'; import route from 'koa-route'; diff --git a/src/transform-html.ts b/src/transform-html.ts index 2395470..128d02c 100644 --- a/src/transform-html.ts +++ b/src/transform-html.ts @@ -1,4 +1,17 @@ -import {DefaultTreeNode, Node, parse, serialize} from 'parse5'; +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +import {DefaultTreeNode, parse, serialize} from 'parse5'; import {resolve as resolveURL} from 'url'; import {TransformSpecifierFunction} from './koa-esm-specifier-transform'; diff --git a/src/transform-javascript-module.ts b/src/transform-javascript-module.ts index de81765..4c61a51 100644 --- a/src/transform-javascript-module.ts +++ b/src/transform-javascript-module.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ import serialize from '@babel/generator'; import {parse} from '@babel/parser'; import traverse from '@babel/traverse'; From 57f7e128d7f01fa12b70dfbf4fd446f8d3cc0e89 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 16:06:19 -0700 Subject: [PATCH 12/52] Converted function x() to const x = () --- src/koa-esm-specifier-transform.ts | 55 +++--- src/koa-npm-resolution.ts | 7 +- src/support/parse5-utils.ts | 269 +++++++++++++-------------- src/support/resolve-npm-specifier.ts | 21 +-- src/test/test-utils.ts | 28 ++- src/transform-html.ts | 67 ++++--- src/transform-javascript-module.ts | 77 ++++---- 7 files changed, 255 insertions(+), 269 deletions(-) diff --git a/src/koa-esm-specifier-transform.ts b/src/koa-esm-specifier-transform.ts index 0c2b099..b426861 100644 --- a/src/koa-esm-specifier-transform.ts +++ b/src/koa-esm-specifier-transform.ts @@ -20,40 +20,37 @@ import {transformJavaScriptModuleString} from './transform-javascript-module'; export type TransformSpecifierFunction = (baseURL: string, specifier: string) => string; -export default middleware; -export function middleware(transformSpecifier: TransformSpecifierFunction): - Koa.Middleware { - return async (ctx: Koa.Context, next: Function) => { - await next(); +export const middleware = (transformSpecifier: TransformSpecifierFunction): + Koa.Middleware => + async (ctx: Koa.Context, next: Function) => { + await next(); - if (ctx.response.is('html')) { - ctx.body = resolveSpecifiersInInlineScriptTags( - await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); - } + if (ctx.response.is('html')) { + ctx.body = resolveSpecifiersInInlineScriptTags( + await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); + } - if (ctx.response.is('js')) { - ctx.body = resolveSpecifiersInJavaScriptModule( - await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); - } - }; -} + if (ctx.response.is('js')) { + ctx.body = resolveSpecifiersInJavaScriptModule( + await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); + } +}; +export default middleware; -function resolveSpecifiersInInlineScriptTags( - body: string, - requestURL: string, - transformSpecifier: TransformSpecifierFunction): string { - return transformHTMLString(body, requestURL, transformSpecifier); -} +const resolveSpecifiersInInlineScriptTags = + (body: string, + requestURL: string, + transformSpecifier: TransformSpecifierFunction): string => + transformHTMLString(body, requestURL, transformSpecifier); -function resolveSpecifiersInJavaScriptModule( - body: string, - requestURL: string, - transformSpecifier: TransformSpecifierFunction): string { - return transformJavaScriptModuleString(body, requestURL, transformSpecifier); -} +const resolveSpecifiersInJavaScriptModule = + (body: string, + requestURL: string, + transformSpecifier: TransformSpecifierFunction): string => + transformJavaScriptModuleString(body, requestURL, transformSpecifier); // TODO(usergenic): This should probably be published as a separate npm package. -async function getBodyAsString(body: Buffer|string): Promise { +const getBodyAsString = async(body: Buffer|string): Promise => { if (!body) { return ''; } @@ -67,4 +64,4 @@ async function getBodyAsString(body: Buffer|string): Promise { return body; } return ''; -} +}; diff --git a/src/koa-npm-resolution.ts b/src/koa-npm-resolution.ts index 9e4be94..7227e7f 100644 --- a/src/koa-npm-resolution.ts +++ b/src/koa-npm-resolution.ts @@ -27,8 +27,7 @@ export type Options = { baseHref?: string, }; -export default middleware; -export function middleware(options: Options = {}): Koa.Middleware { +export const middleware = (options: Options = {}): Koa.Middleware => { const onDiskPackageRoot = resolvePath(options.packageRoot || '.'); const baseHref = (options.baseHref || '').replace(/^\//, '').replace(/[^\/]+$/, ''); @@ -42,4 +41,6 @@ export function middleware(options: Options = {}): Koa.Middleware { const resolvedPath = resolveNPMSpecifier(modulePath, specifier); return resolvedPath; }); -} +}; + +export default middleware; diff --git a/src/support/parse5-utils.ts b/src/support/parse5-utils.ts index af5b34e..adec3a1 100644 --- a/src/support/parse5-utils.ts +++ b/src/support/parse5-utils.ts @@ -21,19 +21,19 @@ */ import {DefaultTreeCommentNode, DefaultTreeDocument, DefaultTreeDocumentFragment, DefaultTreeElement, DefaultTreeNode, DefaultTreeParentNode, DefaultTreeTextNode, Location} from 'parse5'; -export function filter( - iter: IterableIterator, - predicate: (t: T) => boolean, - matches: T[] = []) { - for (const value of iter) { - if (predicate(value)) { - matches.push(value); - } - } - return matches; -} +export const filter = + (iter: IterableIterator, + predicate: (t: T) => boolean, + matches: T[] = []) => { + for (const value of iter) { + if (predicate(value)) { + matches.push(value); + } + } + return matches; + }; -export function getAttr(element: DefaultTreeNode, name: string): string { +export const getAttr = (element: DefaultTreeNode, name: string): string => { if (isElement(element)) { const attr = element.attrs.find(({name: attrName}) => attrName === name); if (attr) { @@ -41,9 +41,9 @@ export function getAttr(element: DefaultTreeNode, name: string): string { } } return ''; -} +}; -export function getTextContent(node: DefaultTreeNode): string { +export const getTextContent = (node: DefaultTreeNode): string => { if (isCommentNode(node)) { return node.data; } @@ -52,129 +52,123 @@ export function getTextContent(node: DefaultTreeNode): string { } const subtree = nodeWalkAll(node, isTextNode); return subtree.map(getTextContent).join(''); -} - -export function setAttr(element: DefaultTreeNode, name: string, value: string) { - if (!isElement(element)) { - return; - } - const attr = element.attrs.find(({name: attrName}) => attrName === name); - if (attr) { - attr.value = value; - } else { - element.attrs.push({name, value}); - } -} +}; -export function insertBefore( - parent: DefaultTreeNode, - oldNode: DefaultTreeNode, - newNode: DefaultTreeNode) { - if (!isParent(parent)) { - return; - } - const index = parent.childNodes.indexOf(oldNode); - insertNode(parent, index, newNode); -} - -export function insertNode( - parent: DefaultTreeNode, - index: number, - newNode: DefaultTreeNode, - replace: DefaultTreeNode|undefined = undefined) { - if (!isParent(parent)) { - return; - } - let newNodes: DefaultTreeNode[] = []; - let removedNode = replace ? parent.childNodes[index] : null; - if (newNode) { - if (isDocumentFragment(newNode)) { - if (newNode.childNodes) { - newNodes = [...newNode.childNodes]; - newNode.childNodes.length = 0; +export const setAttr = + (element: DefaultTreeNode, name: string, value: string) => { + if (!isElement(element)) { + return; } - } else { - newNodes = [newNode]; - removeNode(newNode); - } - } - if (replace) { - removedNode = parent.childNodes[index]; - } - parent.childNodes.splice(index, replace ? 1 : 0, ...newNodes); - newNodes.forEach((n) => { - if (isChild(n)) { - n.parentNode = parent; - } - }); - - if (removedNode && isChild(removedNode)) { - (<{parentNode: DefaultTreeParentNode | undefined}>removedNode).parentNode = - undefined; - } -} + const attr = element.attrs.find(({name: attrName}) => attrName === name); + if (attr) { + attr.value = value; + } else { + element.attrs.push({name, value}); + } + }; + +export const insertBefore = + (parent: DefaultTreeNode, + oldNode: DefaultTreeNode, + newNode: DefaultTreeNode) => { + if (!isParent(parent)) { + return; + } + const index = parent.childNodes.indexOf(oldNode); + insertNode(parent, index, newNode); + }; + +export const insertNode = + (parent: DefaultTreeNode, + index: number, + newNode: DefaultTreeNode, + replace: DefaultTreeNode|undefined = undefined) => { + if (!isParent(parent)) { + return; + } + let newNodes: DefaultTreeNode[] = []; + let removedNode = replace ? parent.childNodes[index] : null; + if (newNode) { + if (isDocumentFragment(newNode)) { + if (newNode.childNodes) { + newNodes = [...newNode.childNodes]; + newNode.childNodes.length = 0; + } + } else { + newNodes = [newNode]; + removeNode(newNode); + } + } + if (replace) { + removedNode = parent.childNodes[index]; + } + parent.childNodes.splice(index, replace ? 1 : 0, ...newNodes); + newNodes.forEach((n) => { + if (isChild(n)) { + n.parentNode = parent; + } + }); + + if (removedNode && isChild(removedNode)) { + (<{parentNode: DefaultTreeParentNode | undefined}>removedNode) + .parentNode = undefined; + } + }; -export function isChild(node: DefaultTreeNode): node is DefaultTreeTextNode| - DefaultTreeCommentNode|DefaultTreeElement { - return isCommentNode(node) || isElement(node) || isTextNode(node); -} +export const isChild = (node: DefaultTreeNode): node is DefaultTreeTextNode| + DefaultTreeCommentNode|DefaultTreeElement => + isCommentNode(node) || isElement(node) || isTextNode(node); -export function isCommentNode(node: DefaultTreeNode): - node is DefaultTreeCommentNode { - return node.nodeName === '#comment'; -} +export const isCommentNode = + (node: DefaultTreeNode): node is DefaultTreeCommentNode => + node.nodeName === '#comment'; -export function isDocument(node: DefaultTreeNode): node is DefaultTreeDocument { - return node.nodeName === '#document'; -} +export const isDocument = + (node: DefaultTreeNode): node is DefaultTreeDocument => + node.nodeName === '#document'; -export function isDocumentFragment(node: DefaultTreeNode): - node is DefaultTreeDocumentFragment { - return node.nodeName === '#document-fragment'; -} +export const isDocumentFragment = + (node: DefaultTreeNode): node is DefaultTreeDocumentFragment => + node.nodeName === '#document-fragment'; -export function isElement(node: DefaultTreeNode): node is DefaultTreeElement { - return !node.nodeName.startsWith('#'); -} +export const isElement = (node: DefaultTreeNode): node is DefaultTreeElement => + !node.nodeName.startsWith('#'); -export function isParent(node: DefaultTreeNode): node is DefaultTreeDocument| - DefaultTreeDocumentFragment|DefaultTreeElement { - return isElement(node) || isDocumentFragment(node) || isDocument(node); -} +export const isParent = (node: DefaultTreeNode): node is DefaultTreeDocument| + DefaultTreeDocumentFragment|DefaultTreeElement => + isElement(node) || isDocumentFragment(node) || isDocument(node); -export function isTextNode(node: DefaultTreeNode): node is DefaultTreeTextNode { - return node.nodeName === '#text'; -} +export const isTextNode = + (node: DefaultTreeNode): node is DefaultTreeTextNode => + node.nodeName === '#text'; export const defaultChildNodes = (node: DefaultTreeParentNode) => node.childNodes; -export function* - depthFirst( - node: DefaultTreeNode, - getChildNodes: typeof defaultChildNodes = defaultChildNodes): +export const depthFirst = function* + (node: DefaultTreeNode, + getChildNodes: typeof defaultChildNodes = defaultChildNodes): IterableIterator { - yield node; - if (isParent(node)) { - const childNodes = getChildNodes(node); - if (childNodes === undefined) { - return; - } - for (const child of childNodes) { - yield* depthFirst(child, getChildNodes); - } - } -} - -export function nodeWalkAll( - node: DefaultTreeNode, - predicate: (node: DefaultTreeNode) => boolean, - matches: DefaultTreeNode[] = [], - getChildNodes: typeof defaultChildNodes = defaultChildNodes) { - return filter(depthFirst(node, getChildNodes), predicate, matches); -} - -export function removeFakeRootElements(node: DefaultTreeNode) { + yield node; + if (isParent(node)) { + const childNodes = getChildNodes(node); + if (childNodes === undefined) { + return; + } + for (const child of childNodes) { + yield* depthFirst(child, getChildNodes); + } + } + }; + +export const nodeWalkAll = + (node: DefaultTreeNode, + predicate: (node: DefaultTreeNode) => boolean, + matches: DefaultTreeNode[] = [], + getChildNodes: typeof defaultChildNodes = defaultChildNodes) => + filter(depthFirst(node, getChildNodes), predicate, matches); + +export const removeFakeRootElements = (node: DefaultTreeNode) => { const fakeRootElements: DefaultTreeElement[] = []; nodeWalkAll(node, (node) => { if (node.nodeName && node.nodeName.match(/^(html|head|body)$/i) && @@ -185,9 +179,9 @@ export function removeFakeRootElements(node: DefaultTreeNode) { return false; }); fakeRootElements.forEach(removeNodeSaveChildren); -} +}; -export function removeNode(node: DefaultTreeNode) { +export const removeNode = (node: DefaultTreeNode) => { if (isChild(node)) { const parent = node.parentNode; if (parent && parent.childNodes) { @@ -196,9 +190,9 @@ export function removeNode(node: DefaultTreeNode) { } } (node as unknown as {parentNode: Object | undefined}).parentNode = undefined; -} +}; -export function removeNodeSaveChildren(node: DefaultTreeNode) { +export const removeNodeSaveChildren = (node: DefaultTreeNode) => { // We can't save the children if there's no parent node to provide // for them. if (!isChild(node)) { @@ -215,9 +209,9 @@ export function removeNodeSaveChildren(node: DefaultTreeNode) { } } removeNode(node); -} +}; -export function setTextContent(node: DefaultTreeNode, value: string) { +export const setTextContent = (node: DefaultTreeNode, value: string) => { if (isCommentNode(node)) { node.data = value; } else if (isTextNode(node)) { @@ -225,11 +219,12 @@ export function setTextContent(node: DefaultTreeNode, value: string) { } else if (isParent(node)) { newTextNode(value, node); } -} - -export function newTextNode( - value: string, parentNode: DefaultTreeParentNode): DefaultTreeTextNode { - const textNode: DefaultTreeTextNode = {nodeName: '#text', value, parentNode}; - parentNode.childNodes = [textNode]; - return textNode; -} +}; + +export const newTextNode = + (value: string, parentNode: DefaultTreeParentNode): DefaultTreeTextNode => { + const textNode: + DefaultTreeTextNode = {nodeName: '#text', value, parentNode}; + parentNode.childNodes = [textNode]; + return textNode; + }; diff --git a/src/support/resolve-npm-specifier.ts b/src/support/resolve-npm-specifier.ts index 622099b..773a074 100644 --- a/src/support/resolve-npm-specifier.ts +++ b/src/support/resolve-npm-specifier.ts @@ -14,31 +14,24 @@ import {relative} from 'path'; import resolveFrom from 'resolve-from'; -export default function resolveNPMSpecifier( - modulePath: string, specifier: string): string { +export default (modulePath: string, specifier: string): string => { const resolved = resolveFrom.silent(modulePath, specifier); if (resolved) { return relativePath(modulePath, resolved); } return specifier; -} +}; -export function relativePath(from: string, to: string): string { +export const relativePath = (from: string, to: string): string => { from = fslash(from); to = fslash(to); if (!from.endsWith('/')) { from = from.replace(/[^/]*$/, ''); } return ensureLeadingDot(relative(from, to)); -} +}; -function ensureLeadingDot(path: string): string { - if (path.startsWith('../') || path.startsWith('./')) { - return path; - } - return './' + path; -} +const ensureLeadingDot = (path: string): string => + (path.startsWith('../') || path.startsWith('./')) ? path : './' + path; -function fslash(path: string): string { - return path.replace(/\\/g, '/'); -} +const fslash = (path: string): string => path.replace(/\\/g, '/'); diff --git a/src/test/test-utils.ts b/src/test/test-utils.ts index 23bab5f..f63becd 100644 --- a/src/test/test-utils.ts +++ b/src/test/test-utils.ts @@ -20,7 +20,7 @@ export type AppOptions = { routes?: {[key: string]: string|Function}, }; -export function createApp(options: AppOptions): Koa { +export const createApp = (options: AppOptions): Koa => { const app = new Koa(); const {middleware, routes} = options; if (middleware) { @@ -43,25 +43,23 @@ export function createApp(options: AppOptions): Koa { } } return app; -} +}; -export async function createAndServe( - options: AppOptions, callback: (server: Server) => void) { - serveApp(createApp(options), callback); -} +export const createAndServe = + async (options: AppOptions, callback: (server: Server) => void) => + serveApp(createApp(options), callback); -export async function serveApp(app: Koa, callback: (server: Server) => void) { +export const serveApp = + async (app: Koa, callback: (server: Server) => void) => { const port = process.env.PORT || 3000; const server = app.listen(port).on('error', (e) => `ERROR: ${console.log(e)}`); await callback(server); server.close(); -} +}; -export function squeezeHTML(html: string): string { - return html.replace(/\s+/mg, ' ') - .replace(/>\s<') - .replace(/>\s/g, '>\n') - .replace(/\s html.replace(/\s+/mg, ' ') + .replace(/>\s<') + .replace(/>\s/g, '>\n') + .replace(/\s { - const js = getTextContent(scriptTag); - const transformedJs = - transformJavaScriptModuleString(js, baseURL, transformSpecifier); - setTextContent(scriptTag, transformedJs); - }); - return; -} +export const transformHTMLAST = + (ast: DefaultTreeNode, + url: string, + transformSpecifier: TransformSpecifierFunction) => { + const baseURL = getBaseURL(ast, url); + getInlineModuleScripts(ast).forEach((scriptTag) => { + const js = getTextContent(scriptTag); + const transformedJs = + transformJavaScriptModuleString(js, baseURL, transformSpecifier); + setTextContent(scriptTag, transformedJs); + }); + return; + }; -export function transformHTMLString( - html: string, url: string, transformSpecifier: TransformSpecifierFunction) { - const ast = parse(html); - removeFakeRootElements(ast); - transformHTMLAST(ast, url, transformSpecifier); - return serialize(ast); -} +export const transformHTMLString = + (html: string, + url: string, + transformSpecifier: TransformSpecifierFunction) => { + const ast = parse(html); + removeFakeRootElements(ast); + transformHTMLAST(ast, url, transformSpecifier); + return serialize(ast); + }; -function getBaseURL(ast: DefaultTreeNode, location: string): string { +const getBaseURL = (ast: DefaultTreeNode, location: string): string => { const baseTag = getBaseTag(ast); if (!baseTag) { return location; @@ -50,19 +52,16 @@ function getBaseURL(ast: DefaultTreeNode, location: string): string { return location; } return resolveURL(location, baseHref); -} +}; -function getBaseTag(ast: DefaultTreeNode): DefaultTreeNode|undefined { - return getTags(ast, 'base').shift(); -} +const getBaseTag = (ast: DefaultTreeNode): DefaultTreeNode|undefined => + getTags(ast, 'base').shift(); -function getInlineModuleScripts(ast: DefaultTreeNode): DefaultTreeNode[] { - return getTags(ast, 'script') - .filter( - (node) => - getAttr(node, 'type') === 'module' && !getAttr(node, 'src')); -} +const getInlineModuleScripts = (ast: DefaultTreeNode): DefaultTreeNode[] => + getTags(ast, 'script') + .filter( + (node) => + getAttr(node, 'type') === 'module' && !getAttr(node, 'src')); -function getTags(ast: DefaultTreeNode, name: string): DefaultTreeNode[] { - return nodeWalkAll(ast, (node) => node.nodeName === name); -} +const getTags = (ast: DefaultTreeNode, name: string): DefaultTreeNode[] => + nodeWalkAll(ast, (node) => node.nodeName === name); diff --git a/src/transform-javascript-module.ts b/src/transform-javascript-module.ts index 4c61a51..c4c1fce 100644 --- a/src/transform-javascript-module.ts +++ b/src/transform-javascript-module.ts @@ -18,42 +18,45 @@ import {NodePath} from '@babel/traverse'; import {CallExpression, ExportAllDeclaration, ExportNamedDeclaration, ImportDeclaration, isImport, isStringLiteral, Node, StringLiteral} from '@babel/types'; import {TransformSpecifierFunction} from './koa-esm-specifier-transform'; -export function transformJavaScriptModuleAST( - ast: Node, url: string, transformSpecifier: TransformSpecifierFunction) { - const importExportDeclaration = { - enter(path: NodePath) { - if (path.node && path.node.source && isStringLiteral(path.node.source)) { - const specifier = path.node.source.value; - path.node.source.value = transformSpecifier(url, specifier); - } - } - }; - traverse(ast, { - ImportDeclaration: importExportDeclaration, - ExportAllDeclaration: importExportDeclaration, - ExportNamedDeclaration: importExportDeclaration, - CallExpression: { - enter(path: NodePath) { - if (path.node && path.node.callee && isImport(path.node.callee) && - path.node.arguments.length === 1 && - isStringLiteral(path.node.arguments[0])) { - const argument = path.node.arguments[0]; - const specifier = argument.value; - const rewrittenSpecifier = transformSpecifier(url, specifier); - argument.value = rewrittenSpecifier; +export const transformJavaScriptModuleAST = + (ast: Node, + url: string, + transformSpecifier: TransformSpecifierFunction) => { + const importExportDeclaration = { + enter(path: NodePath) { + if (path.node && path.node.source && + isStringLiteral(path.node.source)) { + const specifier = path.node.source.value; + path.node.source.value = transformSpecifier(url, specifier); + } } - } - } - }); -} + }; + traverse(ast, { + ImportDeclaration: importExportDeclaration, + ExportAllDeclaration: importExportDeclaration, + ExportNamedDeclaration: importExportDeclaration, + CallExpression: { + enter(path: NodePath) { + if (path.node && path.node.callee && isImport(path.node.callee) && + path.node.arguments.length === 1 && + isStringLiteral(path.node.arguments[0])) { + const argument = path.node.arguments[0]; + const specifier = argument.value; + const rewrittenSpecifier = transformSpecifier(url, specifier); + argument.value = rewrittenSpecifier; + } + } + } + }); + }; -export function transformJavaScriptModuleString( - js: string, url: string, transformSpecifier: TransformSpecifierFunction): - string { - const ast = parse(js, {sourceType: 'unambiguous'}); - const leadingSpace = js.match(/^\s*/)![0]; - const trailingSpace = js.match(/\s*$/)![0]; - transformJavaScriptModuleAST(ast, url, transformSpecifier); - return leadingSpace + serialize(ast).code + trailingSpace; -} +export const transformJavaScriptModuleString = + (js: string, url: string, transformSpecifier: TransformSpecifierFunction): + string => { + const ast = parse(js, {sourceType: 'unambiguous'}); + const leadingSpace = js.match(/^\s*/)![0]; + const trailingSpace = js.match(/\s*$/)![0]; + transformJavaScriptModuleAST(ast, url, transformSpecifier); + return leadingSpace + serialize(ast).code + trailingSpace; + }; From c326c1c5260db0709710fc8bfeebe24c30772510 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 16:09:15 -0700 Subject: [PATCH 13/52] Removed unnecessary functions after great PR feedback. --- src/koa-esm-specifier-transform.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/koa-esm-specifier-transform.ts b/src/koa-esm-specifier-transform.ts index b426861..bcfa0bc 100644 --- a/src/koa-esm-specifier-transform.ts +++ b/src/koa-esm-specifier-transform.ts @@ -26,29 +26,17 @@ export const middleware = (transformSpecifier: TransformSpecifierFunction): await next(); if (ctx.response.is('html')) { - ctx.body = resolveSpecifiersInInlineScriptTags( + ctx.body = transformHTMLString( await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); } if (ctx.response.is('js')) { - ctx.body = resolveSpecifiersInJavaScriptModule( + ctx.body = transformJavaScriptModuleString( await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); } }; export default middleware; -const resolveSpecifiersInInlineScriptTags = - (body: string, - requestURL: string, - transformSpecifier: TransformSpecifierFunction): string => - transformHTMLString(body, requestURL, transformSpecifier); - -const resolveSpecifiersInJavaScriptModule = - (body: string, - requestURL: string, - transformSpecifier: TransformSpecifierFunction): string => - transformJavaScriptModuleString(body, requestURL, transformSpecifier); - // TODO(usergenic): This should probably be published as a separate npm package. const getBodyAsString = async(body: Buffer|string): Promise => { if (!body) { From 52962f12919f3248a8b8245756a48f8e0252d4bc Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 16:11:57 -0700 Subject: [PATCH 14/52] Removed unnecessary case from getBodyAsString function. --- src/koa-esm-specifier-transform.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/koa-esm-specifier-transform.ts b/src/koa-esm-specifier-transform.ts index bcfa0bc..bb6cee4 100644 --- a/src/koa-esm-specifier-transform.ts +++ b/src/koa-esm-specifier-transform.ts @@ -39,17 +39,14 @@ export default middleware; // TODO(usergenic): This should probably be published as a separate npm package. const getBodyAsString = async(body: Buffer|string): Promise => { - if (!body) { - return ''; - } if (Buffer.isBuffer(body)) { return body.toString(); } if (isStream(body)) { return await getStream(body); } - if (typeof body === 'string') { - return body; + if (typeof body !== 'string') { + return ''; } - return ''; + return body; }; From e8a71441e9ae1facf1f083f0423508470f842e92 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 16:13:32 -0700 Subject: [PATCH 15/52] Simplified the getBodyAsString function and added type for Stream. --- src/koa-esm-specifier-transform.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/koa-esm-specifier-transform.ts b/src/koa-esm-specifier-transform.ts index bb6cee4..d7624a2 100644 --- a/src/koa-esm-specifier-transform.ts +++ b/src/koa-esm-specifier-transform.ts @@ -14,6 +14,7 @@ import getStream from 'get-stream'; import isStream from 'is-stream'; import * as Koa from 'koa'; +import {Stream} from 'stream'; import {transformHTMLString} from './transform-html'; import {transformJavaScriptModuleString} from './transform-javascript-module'; @@ -38,7 +39,7 @@ export const middleware = (transformSpecifier: TransformSpecifierFunction): export default middleware; // TODO(usergenic): This should probably be published as a separate npm package. -const getBodyAsString = async(body: Buffer|string): Promise => { +const getBodyAsString = async(body: Buffer|Stream|string): Promise => { if (Buffer.isBuffer(body)) { return body.toString(); } From 2b7a3f333411989b9d9679b5229f59e6f780d33f Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 16:44:02 -0700 Subject: [PATCH 16/52] Cleaned up code around path/baseHref... --- package-lock.json | 6 ++++++ package.json | 1 + src/koa-npm-resolution.ts | 7 ++++--- src/support/url-utils.ts | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 src/support/url-utils.ts diff --git a/package-lock.json b/package-lock.json index 56fe687..cbef79e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -325,6 +325,12 @@ "integrity": "sha512-J5D3z703XTDIGQFYXsnU9uRCW9e9mMEFO0Kpe6kykyiboqziru/RlZ0hM2P+PKTG4NHG1SjLrqae/NrV2iJApQ==", "dev": true }, + "@types/path-is-inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/path-is-inside/-/path-is-inside-1.0.0.tgz", + "integrity": "sha512-hfnXRGugz+McgX2jxyy5qz9sB21LRzlGn24zlwN2KEgoPtEvjzNRrLtUkOOebPDPZl3Rq7ywKxYvylVcEZDnEw==", + "dev": true + }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", diff --git a/package.json b/package.json index fe1f9ce..140d1d8 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@types/koa-route": "^3.2.4", "@types/node": "^12.0.0", "@types/parse5": "^5.0.0", + "@types/path-is-inside": "^1.0.0", "@types/resolve-from": "^5.0.1", "@types/supertest": "^2.0.7", "@types/tape": "^4.2.33", diff --git a/src/koa-npm-resolution.ts b/src/koa-npm-resolution.ts index 7227e7f..4042622 100644 --- a/src/koa-npm-resolution.ts +++ b/src/koa-npm-resolution.ts @@ -14,8 +14,10 @@ import * as Koa from 'koa'; import {resolve as resolvePath} from 'path'; import {parse as parseURL} from 'url'; + import esmSpecifierTransform from './koa-esm-specifier-transform'; import resolveNPMSpecifier from './support/resolve-npm-specifier'; +import {getBasePath, noLeadingSlash} from './support/url-utils'; export type Options = { /* On-disk package root path used for NPM package resolution; defaults to @@ -29,10 +31,9 @@ export type Options = { export const middleware = (options: Options = {}): Koa.Middleware => { const onDiskPackageRoot = resolvePath(options.packageRoot || '.'); - const baseHref = - (options.baseHref || '').replace(/^\//, '').replace(/[^\/]+$/, ''); + const baseHref = noLeadingSlash(getBasePath(options.baseHref || '')); return esmSpecifierTransform((baseURL: string, specifier: string) => { - const path = (parseURL(baseURL).pathname || '/').replace(/^\//, ''); + const path = noLeadingSlash(parseURL(baseURL).pathname || '/'); if (!path.startsWith(baseHref)) { return specifier; } diff --git a/src/support/url-utils.ts b/src/support/url-utils.ts new file mode 100644 index 0000000..18f8aab --- /dev/null +++ b/src/support/url-utils.ts @@ -0,0 +1,16 @@ +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +export const noLeadingSlash = (href: string): string => href.replace(/^\//, ''); +export const getBasePath = (href: string): string => + href.replace(/[^\/]+$/, ''); From f6f78af1943ad2b62f3b9d6ede06fccb013dd8fe Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 16:51:20 -0700 Subject: [PATCH 17/52] Renamed variables for clarity. --- src/koa-npm-resolution.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/koa-npm-resolution.ts b/src/koa-npm-resolution.ts index 4042622..b1f8269 100644 --- a/src/koa-npm-resolution.ts +++ b/src/koa-npm-resolution.ts @@ -31,13 +31,15 @@ export type Options = { export const middleware = (options: Options = {}): Koa.Middleware => { const onDiskPackageRoot = resolvePath(options.packageRoot || '.'); - const baseHref = noLeadingSlash(getBasePath(options.baseHref || '')); + const basePath = + options.baseHref ? noLeadingSlash(getBasePath(options.baseHref)) : ''; return esmSpecifierTransform((baseURL: string, specifier: string) => { - const path = noLeadingSlash(parseURL(baseURL).pathname || '/'); - if (!path.startsWith(baseHref)) { + const pathname = parseURL(baseURL).pathname || '/'; + const path = noLeadingSlash(pathname); + if (!path.startsWith(basePath)) { return specifier; } - const debasedPath = path.slice(baseHref.length); + const debasedPath = path.slice(basePath.length); const modulePath = resolvePath(onDiskPackageRoot, debasedPath); const resolvedPath = resolveNPMSpecifier(modulePath, specifier); return resolvedPath; From 6adfd468162093edca6208ad9e777d40586a67bd Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 16:56:10 -0700 Subject: [PATCH 18/52] Renamed fslash -> forwardSlashesOnlyPlease --- src/support/resolve-npm-specifier.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/support/resolve-npm-specifier.ts b/src/support/resolve-npm-specifier.ts index 773a074..8b88149 100644 --- a/src/support/resolve-npm-specifier.ts +++ b/src/support/resolve-npm-specifier.ts @@ -23,8 +23,8 @@ export default (modulePath: string, specifier: string): string => { }; export const relativePath = (from: string, to: string): string => { - from = fslash(from); - to = fslash(to); + from = forwardSlashesOnlyPlease(from); + to = forwardSlashesOnlyPlease(to); if (!from.endsWith('/')) { from = from.replace(/[^/]*$/, ''); } @@ -34,4 +34,5 @@ export const relativePath = (from: string, to: string): string => { const ensureLeadingDot = (path: string): string => (path.startsWith('../') || path.startsWith('./')) ? path : './' + path; -const fslash = (path: string): string => path.replace(/\\/g, '/'); +const forwardSlashesOnlyPlease = (path: string): string => + path.replace(/\\/g, '/'); From 140fc95d363c10ad3bbd7df140024786849b6b5d Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 17:01:17 -0700 Subject: [PATCH 19/52] Moved path/url related stuff into a path-utils.ts --- src/koa-npm-resolution.ts | 2 +- src/support/{url-utils.ts => path-utils.ts} | 20 +++++++++++++++++++- src/support/resolve-npm-specifier.ts | 17 +---------------- 3 files changed, 21 insertions(+), 18 deletions(-) rename src/support/{url-utils.ts => path-utils.ts} (57%) diff --git a/src/koa-npm-resolution.ts b/src/koa-npm-resolution.ts index b1f8269..021bb3b 100644 --- a/src/koa-npm-resolution.ts +++ b/src/koa-npm-resolution.ts @@ -16,8 +16,8 @@ import {resolve as resolvePath} from 'path'; import {parse as parseURL} from 'url'; import esmSpecifierTransform from './koa-esm-specifier-transform'; +import {getBasePath, noLeadingSlash} from './support/path-utils'; import resolveNPMSpecifier from './support/resolve-npm-specifier'; -import {getBasePath, noLeadingSlash} from './support/url-utils'; export type Options = { /* On-disk package root path used for NPM package resolution; defaults to diff --git a/src/support/url-utils.ts b/src/support/path-utils.ts similarity index 57% rename from src/support/url-utils.ts rename to src/support/path-utils.ts index 18f8aab..4bc9ce2 100644 --- a/src/support/url-utils.ts +++ b/src/support/path-utils.ts @@ -11,6 +11,24 @@ * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ -export const noLeadingSlash = (href: string): string => href.replace(/^\//, ''); +import {relative} from 'path'; + +export const ensureLeadingDot = (path: string): string => + (path.startsWith('../') || path.startsWith('./')) ? path : './' + path; + +export const forwardSlashesOnlyPlease = (path: string): string => + path.replace(/\\/g, '/'); + export const getBasePath = (href: string): string => href.replace(/[^\/]+$/, ''); + +export const noLeadingSlash = (href: string): string => href.replace(/^\//, ''); + +export const relativePath = (from: string, to: string): string => { + from = forwardSlashesOnlyPlease(from); + to = forwardSlashesOnlyPlease(to); + if (!from.endsWith('/')) { + from = from.replace(/[^/]*$/, ''); + } + return ensureLeadingDot(relative(from, to)); +}; diff --git a/src/support/resolve-npm-specifier.ts b/src/support/resolve-npm-specifier.ts index 8b88149..9f641d6 100644 --- a/src/support/resolve-npm-specifier.ts +++ b/src/support/resolve-npm-specifier.ts @@ -11,8 +11,8 @@ * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ -import {relative} from 'path'; import resolveFrom from 'resolve-from'; +import {relativePath} from './path-utils'; export default (modulePath: string, specifier: string): string => { const resolved = resolveFrom.silent(modulePath, specifier); @@ -21,18 +21,3 @@ export default (modulePath: string, specifier: string): string => { } return specifier; }; - -export const relativePath = (from: string, to: string): string => { - from = forwardSlashesOnlyPlease(from); - to = forwardSlashesOnlyPlease(to); - if (!from.endsWith('/')) { - from = from.replace(/[^/]*$/, ''); - } - return ensureLeadingDot(relative(from, to)); -}; - -const ensureLeadingDot = (path: string): string => - (path.startsWith('../') || path.startsWith('./')) ? path : './' + path; - -const forwardSlashesOnlyPlease = (path: string): string => - path.replace(/\\/g, '/'); From e087422b47a9e1836366df4b6701d3154943d9b0 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 17:04:24 -0700 Subject: [PATCH 20/52] Use the getBasePath function instead of duplicate regexp. --- src/support/path-utils.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/support/path-utils.ts b/src/support/path-utils.ts index 4bc9ce2..8139c09 100644 --- a/src/support/path-utils.ts +++ b/src/support/path-utils.ts @@ -25,10 +25,7 @@ export const getBasePath = (href: string): string => export const noLeadingSlash = (href: string): string => href.replace(/^\//, ''); export const relativePath = (from: string, to: string): string => { - from = forwardSlashesOnlyPlease(from); + from = getBasePath(forwardSlashesOnlyPlease(from)); to = forwardSlashesOnlyPlease(to); - if (!from.endsWith('/')) { - from = from.replace(/[^/]*$/, ''); - } return ensureLeadingDot(relative(from, to)); }; From 3cd98852a3e988e357500b68a5455fff41e36429 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 17:16:18 -0700 Subject: [PATCH 21/52] Simplified another function. --- src/support/path-utils.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/support/path-utils.ts b/src/support/path-utils.ts index 8139c09..39f9826 100644 --- a/src/support/path-utils.ts +++ b/src/support/path-utils.ts @@ -24,8 +24,7 @@ export const getBasePath = (href: string): string => export const noLeadingSlash = (href: string): string => href.replace(/^\//, ''); -export const relativePath = (from: string, to: string): string => { - from = getBasePath(forwardSlashesOnlyPlease(from)); - to = forwardSlashesOnlyPlease(to); - return ensureLeadingDot(relative(from, to)); -}; +export const relativePath = (from: string, to: string): string => + ensureLeadingDot(relative( + getBasePath(forwardSlashesOnlyPlease(from)), + forwardSlashesOnlyPlease(to))); From 2abee46fb48185fc4cf63adb7c4667d26dd3fe50 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 17:26:33 -0700 Subject: [PATCH 22/52] Switched travis setting to use the xenial distro instead of trusty. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 95e5b04..c2d8ed3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: node_js node_js: - "node" sudo: false -dist: trusty +dist: xenial cache: directories: - node_modules From 3f92d27419250fb9feda7b26221ed26f081018bc Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 17:30:59 -0700 Subject: [PATCH 23/52] Renamed koa-npm-resolution to koa-node-resolve --- README.md | 28 +++++++++---------- package-lock.json | 2 +- package.json | 4 +-- ...-npm-resolution.ts => koa-node-resolve.ts} | 0 ...resolution.test.ts => koa-node-resolve.ts} | 2 +- 5 files changed, 17 insertions(+), 19 deletions(-) rename src/{koa-npm-resolution.ts => koa-node-resolve.ts} (100%) rename src/test/{koa-npm-resolution.test.ts => koa-node-resolve.ts} (98%) diff --git a/README.md b/README.md index f3fb668..c5ae014 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,29 @@ -# koa-npm-resolution +# koa-node-resolve -This is a middleware for Koa servers that resolves NPM package specifiers in ES modules to relative paths for use on the web. It is currently only intended for use as middleware in development servers to fascilitate build-free testing/iteration. +This is a middleware for Koa servers that resolves Node package specifiers in standard JS modules to relative paths for use on the web. It is currently only intended for use as middleware in development servers to fascilitate build-free testing/iteration. ## What? -So you want to write your front-end code using ES modules and you're using NPM packages and you don't want to run a build step to transform those various import statements from convenient forms like: `import stuff from 'stuff'` into `import stuff from './node_modules/stuff/index.js'` etc. This middleware simply transforms them when serving up the response from your server. +So you want to write your front-end code using standard JS modules and you're using NPM packages and you don't want to run a build step to transform those various import statements from convenient forms like: `import stuff from 'stuff'` into `import stuff from './node_modules/stuff/index.js'` etc. This middleware simply transforms them when serving up the response from your server. That means you can use the middleware in a simple server that delivers static files as well as in a proxy server that might be piping files from a test server such as the one `karma` starts up. (See [karma testing setup](#karma-testing-setup) below.) ## Installation ```sh -$ npm install --save koa-npm-resolution +$ npm install --save koa-node-resolve ``` ## Usage -Create your own mini-development server. This one depends on `koa` and `koa-static`, so you'll need to `npm install --save-dev koa koa-static` for your project to use it. +Create your own mini-development server in file `./dev-server.js`. This one depends on `koa` and `koa-static`, so you'll need to `npm install --save-dev koa koa-static` for your project to use it. ```js -/* dev-server.js */ -const Koa = require('koa') +const Koa = require('koa'); const server = new Koa() - .use(require('koa-npm-resolution').middleware()) + .use(require('koa-node-resolve').middleware()) .use(require('koa-static')('.')) - .listen(3000) + .listen(3000); ``` ```sh @@ -38,12 +37,11 @@ Now you can serve up your web assets and NPM package specifiers will be transfor In a `karma` setup, your `karma.conf.js` file could create the Koa server before exporting the config. The Koa server uses the `koa-proxy` package (therefore `npm install --save-dev koa-proxy`) in between the browser and the Karma server, transforming all the NPM package specifiers encountered in documents located under the `base/` URL namespace, which is a special Karma behavior for partitioning the package resources under test from Karma support resources. ```js -/* karma.conf.js */ -const Koa = require('koa') +const Koa = require('koa'); const server = new Koa() - .use(require('koa-npm-resolution').middleware({baseHref: 'base'})) + .use(require('koa-node-resolve').middleware({baseHref: 'base'})) .use(require('koa-proxy')({host: 'http://127.0.0.1:9876'})) - .listen(9877) + .listen(9877); module.exports = (config) => { config.set({ @@ -51,8 +49,8 @@ module.exports = (config) => { hostname: '127.0.0.1', port: 9877 } - }) + }); } ``` -In this setup, the Koa proxy server that runs the NPM resolution middleware will be on port 9877 and the Karma server will be on port 9876, so be sure to open up `http://127.0.0.1:9877` in your browser rather than `http://127.0.0.1:9876`. The `upstreamProxy` configuration block tells Karma, when it launches browsers, to points them to the Koa app instead of directly to the Karma server. +In this setup, the Koa proxy server that runs the Node resolution middleware will be on port 9877 and the Karma server will be on port 9876, so be sure to open up `http://127.0.0.1:9877` in your browser rather than `http://127.0.0.1:9876`. The `upstreamProxy` configuration block tells Karma, when it launches browsers, to points them to the Koa app instead of directly to the Karma server. diff --git a/package-lock.json b/package-lock.json index cbef79e..a267562 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "koa-npm-resolution", + "name": "koa-node-resolve", "version": "1.0.0", "lockfileVersion": 1, "requires": true, diff --git a/package.json b/package.json index 140d1d8..32a44f3 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "koa-npm-resolution", + "name": "koa-node-resolve", "version": "1.0.0", "description": "", - "main": "lib/koa-npm-resolution.js", + "main": "lib/koa-node-resolve.js", "scripts": { "build": "tsc", "clean": "rimraf lib", diff --git a/src/koa-npm-resolution.ts b/src/koa-node-resolve.ts similarity index 100% rename from src/koa-npm-resolution.ts rename to src/koa-node-resolve.ts diff --git a/src/test/koa-npm-resolution.test.ts b/src/test/koa-node-resolve.ts similarity index 98% rename from src/test/koa-npm-resolution.test.ts rename to src/test/koa-node-resolve.ts index 7e47f68..a84d586 100644 --- a/src/test/koa-npm-resolution.test.ts +++ b/src/test/koa-node-resolve.ts @@ -15,7 +15,7 @@ import {resolve as resolvePath} from 'path'; import request from 'supertest'; import test from 'tape'; -import createMiddleware from '../koa-npm-resolution'; +import createMiddleware from '../koa-node-resolve'; import {createAndServe, squeezeHTML} from './test-utils'; test('transforms resolvable specifier in JavaScript module', async (t) => { From 826e0313bf3f2f22d9887bef718f2a53ffe6e4bb Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 17:37:34 -0700 Subject: [PATCH 24/52] Added more tsconfig checks. --- tsconfig.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 9b47ebc..24882a0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,20 +23,20 @@ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ "noUnusedLocals": true, /* Report errors on unused locals. */ "noUnusedParameters": true, /* Report errors on unused parameters. */ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ From ae307e1827be6ef3cd14229372bcaa2d2477ee47 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 17:40:02 -0700 Subject: [PATCH 25/52] Remove comments from tsconfig.json --- tsconfig.json | 71 +++++++++++++-------------------------------------- 1 file changed, 18 insertions(+), 53 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 24882a0..fe2e4a8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,57 +1,22 @@ { "compilerOptions": { - /* Basic Options */ - "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true, /* Generates corresponding '.d.ts' file. */ - "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./lib/", /* Redirect output structure to the directory. */ - "rootDir": "./src/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "incremental": true, /* Enable incremental compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ - "noUnusedLocals": true, /* Report errors on unused locals. */ - "noUnusedParameters": true, /* Report errors on unused parameters. */ - "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "target": "es2015", + "module": "commonjs", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./lib/", + "rootDir": "./src/", + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "esModuleInterop": true } } From 9694920b1605104e26164eff70785d89922d2ad3 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 17:44:40 -0700 Subject: [PATCH 26/52] Renamed a test file so it actually gets tested! --- src/test/{koa-node-resolve.ts => koa-node-resolve.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/test/{koa-node-resolve.ts => koa-node-resolve.test.ts} (100%) diff --git a/src/test/koa-node-resolve.ts b/src/test/koa-node-resolve.test.ts similarity index 100% rename from src/test/koa-node-resolve.ts rename to src/test/koa-node-resolve.test.ts From ded0f144effcd212bc1d6d2f69466057fff20e99 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 18:13:08 -0700 Subject: [PATCH 27/52] koa-route is a devDependency not a dependency. --- package.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 32a44f3..ebffe65 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,14 @@ "lint": "tslint -p . --fix", "test": "tape -r source-map-support/register 'lib/test/*.test.js' 'lib/test/**/*.test.js' | tap-diff" }, + "repository": { + "type": "git", + "url": "https://github.com/PolymerLabs/koa-node-resolve.git" + }, + "bugs": { + "url": "https://github.com/PolymerLabs/koa-node-resolve/issues" + }, + "homepage": "https://github.com/PolymerLabs/koa-node-resolve", "keywords": [], "author": "", "license": "ISC", @@ -32,6 +40,7 @@ "clang-format": "^1.2.4", "depcheck": "^0.8.0", "koa": "^2.7.0", + "koa-route": "^3.2.0", "rimraf": "^2.6.3", "source-map-support": "^0.5.12", "supertest": "^4.0.2", @@ -46,7 +55,6 @@ "@babel/traverse": "^7.4.4", "get-stream": "^5.1.0", "is-stream": "^2.0.0", - "koa-route": "^3.2.0", "parse5": "^5.1.0", "resolve-from": "^5.0.0" } From 0af19fcb27e8b429fe2fae20c91789ada706c26f Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 18:15:52 -0700 Subject: [PATCH 28/52] Added 'files' to package.json --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index ebffe65..fbc1560 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,10 @@ "version": "1.0.0", "description": "", "main": "lib/koa-node-resolve.js", + "files": [ + "lib/*", + "!lib/test" + ], "scripts": { "build": "tsc", "clean": "rimraf lib", From 1ffd95a0e0038e3c2ab307dd7176d12564ea0974 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 18:29:56 -0700 Subject: [PATCH 29/52] Updated README per great suggestion by @aomarks --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c5ae014..a4c8fdc 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,19 @@ This is a middleware for Koa servers that resolves Node package specifiers in st ## What? -So you want to write your front-end code using standard JS modules and you're using NPM packages and you don't want to run a build step to transform those various import statements from convenient forms like: `import stuff from 'stuff'` into `import stuff from './node_modules/stuff/index.js'` etc. This middleware simply transforms them when serving up the response from your server. +This import uses a *bare module specifier*, which won't currently load natively in browsers (until [import maps](https://www.chromestatus.com/feature/5315286962012160) are available): -That means you can use the middleware in a simple server that delivers static files as well as in a proxy server that might be piping files from a test server such as the one `karma` starts up. (See [karma testing setup](#karma-testing-setup) below.) +```js +import {foo} from 'stuff'; +``` + +`koa-node-resolve` solves this problem by resolving `stuff` using the same rules as [Node `require()`](https://nodejs.org/api/modules.html#modules_all_together), and transforming the import specifier to a path that can be loaded natively by any browser that [supports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Browser_compatibility) standard JS modules: + +```js +import {foo} from './node_modules/stuff/index.js'; +``` + +Because this is middleware, you can use it in a simple static file server as well as a proxy server sitting in front of a test server such as the one `karma` starts up. (See [karma testing setup](#karma-testing-setup) below.) ## Installation From 776fd66f16dd54357bfd56d04f334aeef1000542 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 18:33:26 -0700 Subject: [PATCH 30/52] Move the bit about only-for-dev to separate paragraph in README. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a4c8fdc..951c60a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # koa-node-resolve -This is a middleware for Koa servers that resolves Node package specifiers in standard JS modules to relative paths for use on the web. It is currently only intended for use as middleware in development servers to fascilitate build-free testing/iteration. +This is a middleware for Koa servers that resolves Node package specifiers in standard JS modules to relative paths for use on the web. + +Because of the cost to parse HTML/JavaScript in the middleware, this is intended for use in development context to fascilitate build-free testing/iteration as opposed to in a high volume production webserver. ## What? From 6e0ab011e9b9001270e463e411ec7481fb5ac8b3 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 18:36:19 -0700 Subject: [PATCH 31/52] Renamed NPM package to Node package. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 951c60a..cddc276 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,11 @@ const server = new Koa() $ node dev-server.js ``` -Now you can serve up your web assets and NPM package specifiers will be transformed on request. +Now you can serve up your web assets and Node package specifiers will be transformed on request. ## Karma Testing Setup -In a `karma` setup, your `karma.conf.js` file could create the Koa server before exporting the config. The Koa server uses the `koa-proxy` package (therefore `npm install --save-dev koa-proxy`) in between the browser and the Karma server, transforming all the NPM package specifiers encountered in documents located under the `base/` URL namespace, which is a special Karma behavior for partitioning the package resources under test from Karma support resources. +In a `karma` setup, your `karma.conf.js` file could create the Koa server before exporting the config. The Koa server uses the `koa-proxy` package (therefore `npm install --save-dev koa-proxy`) in between the browser and the Karma server, transforming all the Node package specifiers encountered in documents located under the `base/` URL namespace, which is a special Karma behavior for partitioning the package resources under test from Karma support resources. ```js const Koa = require('koa'); From e9e495403d9804745140c43a34d75860fe9667c5 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 18:38:34 -0700 Subject: [PATCH 32/52] More README updates. --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cddc276..3727e5d 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,6 @@ This is a middleware for Koa servers that resolves Node package specifiers in standard JS modules to relative paths for use on the web. -Because of the cost to parse HTML/JavaScript in the middleware, this is intended for use in development context to fascilitate build-free testing/iteration as opposed to in a high volume production webserver. - -## What? - This import uses a *bare module specifier*, which won't currently load natively in browsers (until [import maps](https://www.chromestatus.com/feature/5315286962012160) are available): ```js @@ -20,6 +16,8 @@ import {foo} from './node_modules/stuff/index.js'; Because this is middleware, you can use it in a simple static file server as well as a proxy server sitting in front of a test server such as the one `karma` starts up. (See [karma testing setup](#karma-testing-setup) below.) +Note: HTML and JavaScript are parsed on every request for those content-types, it is intended for use in development context to fascilitate build-free testing/iteration as opposed to in a high volume production webserver. + ## Installation ```sh From b86b7ca834429118c9d3d6c9dbd060ae70bdd8b8 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Wed, 15 May 2019 20:36:15 -0700 Subject: [PATCH 33/52] Rename resolve-npm-specifier to resolve-node-specifier. --- package-lock.json | 12 +++++++++--- src/koa-node-resolve.ts | 4 ++-- ...ve-npm-specifier.ts => resolve-node-specifier.ts} | 0 ...cifier.test.ts => resolve-node-specifier.test.ts} | 5 +++-- 4 files changed, 14 insertions(+), 7 deletions(-) rename src/support/{resolve-npm-specifier.ts => resolve-node-specifier.ts} (100%) rename src/test/{resolve-npm-specifier.test.ts => resolve-node-specifier.test.ts} (83%) diff --git a/package-lock.json b/package-lock.json index a267562..7d00cc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -629,6 +629,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -1194,6 +1195,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/koa-route/-/koa-route-3.2.0.tgz", "integrity": "sha1-dimLmaa8+p44yrb+XHmocz51i84=", + "dev": true, "requires": { "debug": "*", "methods": "~1.1.0", @@ -1253,7 +1255,8 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true }, "mime": { "version": "1.6.0", @@ -1317,7 +1320,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "negotiator": { "version": "0.6.2", @@ -1494,6 +1498,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, "requires": { "isarray": "0.0.1" }, @@ -1501,7 +1506,8 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true } } }, diff --git a/src/koa-node-resolve.ts b/src/koa-node-resolve.ts index 021bb3b..f8c5f3a 100644 --- a/src/koa-node-resolve.ts +++ b/src/koa-node-resolve.ts @@ -17,7 +17,7 @@ import {parse as parseURL} from 'url'; import esmSpecifierTransform from './koa-esm-specifier-transform'; import {getBasePath, noLeadingSlash} from './support/path-utils'; -import resolveNPMSpecifier from './support/resolve-npm-specifier'; +import resolveSpecifier from './support/resolve-node-specifier'; export type Options = { /* On-disk package root path used for NPM package resolution; defaults to @@ -41,7 +41,7 @@ export const middleware = (options: Options = {}): Koa.Middleware => { } const debasedPath = path.slice(basePath.length); const modulePath = resolvePath(onDiskPackageRoot, debasedPath); - const resolvedPath = resolveNPMSpecifier(modulePath, specifier); + const resolvedPath = resolveSpecifier(modulePath, specifier); return resolvedPath; }); }; diff --git a/src/support/resolve-npm-specifier.ts b/src/support/resolve-node-specifier.ts similarity index 100% rename from src/support/resolve-npm-specifier.ts rename to src/support/resolve-node-specifier.ts diff --git a/src/test/resolve-npm-specifier.test.ts b/src/test/resolve-node-specifier.test.ts similarity index 83% rename from src/test/resolve-npm-specifier.test.ts rename to src/test/resolve-node-specifier.test.ts index 1a52aaf..ffb4c86 100644 --- a/src/test/resolve-npm-specifier.test.ts +++ b/src/test/resolve-node-specifier.test.ts @@ -14,10 +14,11 @@ import {resolve as resolvePath} from 'path'; import test from 'tape'; -import resolve from '../support/resolve-npm-specifier'; +import resolveSpecifier from '../support/resolve-node-specifier'; test('resolve', (t) => { t.plan(1); - const path = resolve(resolvePath(__dirname, '../..') + '/', 'resolve-from'); + const path = + resolveSpecifier(resolvePath(__dirname, '../..') + '/', 'resolve-from'); t.equal(path, './node_modules/resolve-from/index.js'); }); From 53bc342dba4619f6d78c3cb2d2440e8510846cc9 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Thu, 16 May 2019 12:59:58 -0700 Subject: [PATCH 34/52] Update README.md per @justinfagnani Co-Authored-By: Justin Fagnani --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3727e5d..9642701 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ import {foo} from './node_modules/stuff/index.js'; Because this is middleware, you can use it in a simple static file server as well as a proxy server sitting in front of a test server such as the one `karma` starts up. (See [karma testing setup](#karma-testing-setup) below.) -Note: HTML and JavaScript are parsed on every request for those content-types, it is intended for use in development context to fascilitate build-free testing/iteration as opposed to in a high volume production webserver. +Note: HTML and JavaScript are parsed on every request for those content-types, it is intended for use in development context to facilitate build-free testing/iteration as opposed to in a high volume production web server. ## Installation From f6355f0f8cb586e7976fd313a56aa924349d6822 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Thu, 16 May 2019 13:07:17 -0700 Subject: [PATCH 35/52] Removed is-stream and updated build script to do lint+format+depcheck+clean --- package-lock.json | 14 -------------- package.json | 8 ++++---- src/koa-esm-specifier-transform.ts | 5 ++++- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7d00cc8..e646eda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -259,15 +259,6 @@ "integrity": "sha512-TZDqvFW4nQwL9DVSNJIJu4lPLttKgzRF58COa7Vs42Ki/MrhIqUbeIw0MWn4kGLiZLXB7oCBibm7nkSjPkzfKQ==", "dev": true }, - "@types/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-ylk6pqia1IM23jBo4foOVnCE3zfYDZbpDF43sZrpmhnylWxS2I7FlJJWeySVP4w8RJhyrieP/CEACSFPkFQBLA==", - "dev": true, - "requires": { - "is-stream": "*" - } - }, "@types/keygrip": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.1.tgz", @@ -1074,11 +1065,6 @@ "has": "^1.0.1" } }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" - }, "is-symbol": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", diff --git a/package.json b/package.json index fbc1560..5ae57aa 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,14 @@ "!lib/test" ], "scripts": { - "build": "tsc", + "build": "npm run lint && npm run format && npm run depcheck && npm run clean && npm run compile", "clean": "rimraf lib", + "compile": "tsc", "depcheck": "depcheck --ignore-dirs=lib --ignores=\"source-map-support,@types/*\"", "format": "find src -name \"*.ts\" | xargs clang-format --style=file -i", "lint": "tslint -p . --fix", - "test": "tape -r source-map-support/register 'lib/test/*.test.js' 'lib/test/**/*.test.js' | tap-diff" + "test": "npm run test:raw --silent | tap-diff", + "test:raw": "tape -r source-map-support/register 'lib/test/*.test.js' 'lib/test/**/*.test.js'" }, "repository": { "type": "git", @@ -32,7 +34,6 @@ "@types/babel__traverse": "^7.0.6", "@types/babylon": "^6.16.5", "@types/get-stream": "^3.0.2", - "@types/is-stream": "^2.0.0", "@types/koa": "^2.0.48", "@types/koa-route": "^3.2.4", "@types/node": "^12.0.0", @@ -58,7 +59,6 @@ "@babel/parser": "^7.4.4", "@babel/traverse": "^7.4.4", "get-stream": "^5.1.0", - "is-stream": "^2.0.0", "parse5": "^5.1.0", "resolve-from": "^5.0.0" } diff --git a/src/koa-esm-specifier-transform.ts b/src/koa-esm-specifier-transform.ts index d7624a2..641bdb2 100644 --- a/src/koa-esm-specifier-transform.ts +++ b/src/koa-esm-specifier-transform.ts @@ -12,7 +12,6 @@ * http://polymer.github.io/PATENTS.txt */ import getStream from 'get-stream'; -import isStream from 'is-stream'; import * as Koa from 'koa'; import {Stream} from 'stream'; import {transformHTMLString} from './transform-html'; @@ -51,3 +50,7 @@ const getBodyAsString = async(body: Buffer|Stream|string): Promise => { } return body; }; + +const isStream = (value: Buffer|Stream|string): value is Stream => + value !== null && typeof value === 'object' && + typeof (<{pipe: Function | undefined}>value).pipe === 'function'; From fb3df3b6ec40339af55703ac2ef3cf190a438cd5 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Thu, 16 May 2019 13:08:04 -0700 Subject: [PATCH 36/52] Added else if to eliminate unnecessary iffing. --- src/koa-esm-specifier-transform.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/koa-esm-specifier-transform.ts b/src/koa-esm-specifier-transform.ts index 641bdb2..0ac43da 100644 --- a/src/koa-esm-specifier-transform.ts +++ b/src/koa-esm-specifier-transform.ts @@ -28,9 +28,7 @@ export const middleware = (transformSpecifier: TransformSpecifierFunction): if (ctx.response.is('html')) { ctx.body = transformHTMLString( await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); - } - - if (ctx.response.is('js')) { + } else if (ctx.response.is('js')) { ctx.body = transformJavaScriptModuleString( await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); } From f33653539d9fdcffc314612c7bec8a11f446a696 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Thu, 16 May 2019 13:27:50 -0700 Subject: [PATCH 37/52] Added rimraf to depcheck. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ae57aa..77c8ca6 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "npm run lint && npm run format && npm run depcheck && npm run clean && npm run compile", "clean": "rimraf lib", "compile": "tsc", - "depcheck": "depcheck --ignore-dirs=lib --ignores=\"source-map-support,@types/*\"", + "depcheck": "depcheck --ignore-dirs=lib --ignores=\"rimraf,source-map-support,@types/*\"", "format": "find src -name \"*.ts\" | xargs clang-format --style=file -i", "lint": "tslint -p . --fix", "test": "npm run test:raw --silent | tap-diff", From 462888fbd1b75788baac84ed5bf4ccd3c8310493 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Thu, 16 May 2019 13:39:24 -0700 Subject: [PATCH 38/52] Removed the sudo false from travis config. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c2d8ed3..29ad4d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: node_js node_js: - "node" -sudo: false dist: xenial cache: directories: From 595a39edc4edf9b7c02b77ba1397d80907401bc4 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Thu, 16 May 2019 13:41:35 -0700 Subject: [PATCH 39/52] Removed , from the LICENSE. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 529f69e..743ba35 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2019, The Polymer Authors. All rights reserved. +Copyright (c) 2019 The Polymer Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: From 39ac7c8d5cd77132c37d62ea7ec91a237a28f8ca Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Thu, 16 May 2019 13:42:21 -0700 Subject: [PATCH 40/52] CHANGELOG 'NPM' -> 'Node' --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bda0d1..97bca63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,5 +12,5 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased - * Rewrites resolvable NPM package specifiers in JavaScript module files. - * Rewrites resolvable NPM package specifiers in HTML files in ` `, }, @@ -63,7 +63,7 @@ test('transforms resolvable specifier in inline module script', async (t) => { squeezeHTML((await request(server).get('/my-page.html')).text), squeezeHTML(` `))); }); diff --git a/src/test/resolve-node-specifier.test.ts b/src/test/resolve-node-specifier.test.ts index ffb4c86..8f81932 100644 --- a/src/test/resolve-node-specifier.test.ts +++ b/src/test/resolve-node-specifier.test.ts @@ -19,6 +19,6 @@ import resolveSpecifier from '../support/resolve-node-specifier'; test('resolve', (t) => { t.plan(1); const path = - resolveSpecifier(resolvePath(__dirname, '../..') + '/', 'resolve-from'); - t.equal(path, './node_modules/resolve-from/index.js'); + resolveSpecifier(resolvePath(__dirname, '../..') + '/', 'resolve'); + t.equal(path, './node_modules/resolve/index.js'); }); From 21dd991534e22818785410a523e86b7ca6751f5a Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Mon, 20 May 2019 14:47:42 -0700 Subject: [PATCH 45/52] Renamed koa-esm-specifier-transform to koa-module-specifier-transform --- ...m.ts => koa-module-specifier-transform.ts} | 6 +-- src/koa-node-resolve.ts | 12 ++--- src/support/path-utils.ts | 8 +++ src/support/resolve-node-specifier.ts | 49 +++++++++---------- src/test/koa-node-resolve.test.ts | 10 ++-- src/test/resolve-node-specifier.test.ts | 4 +- src/transform-html.ts | 2 +- src/transform-javascript-module.ts | 2 +- 8 files changed, 49 insertions(+), 44 deletions(-) rename src/{koa-esm-specifier-transform.ts => koa-module-specifier-transform.ts} (89%) diff --git a/src/koa-esm-specifier-transform.ts b/src/koa-module-specifier-transform.ts similarity index 89% rename from src/koa-esm-specifier-transform.ts rename to src/koa-module-specifier-transform.ts index 0ac43da..e62faa4 100644 --- a/src/koa-esm-specifier-transform.ts +++ b/src/koa-module-specifier-transform.ts @@ -20,8 +20,9 @@ import {transformJavaScriptModuleString} from './transform-javascript-module'; export type TransformSpecifierFunction = (baseURL: string, specifier: string) => string; -export const middleware = (transformSpecifier: TransformSpecifierFunction): - Koa.Middleware => +export const koaModuleSpecifierTransform = (transformSpecifier: + TransformSpecifierFunction): + Koa.Middleware => async (ctx: Koa.Context, next: Function) => { await next(); @@ -33,7 +34,6 @@ export const middleware = (transformSpecifier: TransformSpecifierFunction): await getBodyAsString(ctx.body), ctx.request.url, transformSpecifier); } }; -export default middleware; // TODO(usergenic): This should probably be published as a separate npm package. const getBodyAsString = async(body: Buffer|Stream|string): Promise => { diff --git a/src/koa-node-resolve.ts b/src/koa-node-resolve.ts index da4e467..bc274ab 100644 --- a/src/koa-node-resolve.ts +++ b/src/koa-node-resolve.ts @@ -15,19 +15,17 @@ import * as Koa from 'koa'; import {resolve as resolvePath} from 'path'; import {parse as parseURL} from 'url'; -import esmSpecifierTransform from './koa-esm-specifier-transform'; +import {koaModuleSpecifierTransform} from './koa-module-specifier-transform'; import {noLeadingSlash} from './support/path-utils'; -import resolveSpecifier from './support/resolve-node-specifier'; +import {resolveNodeSpecifier} from './support/resolve-node-specifier'; -export const middleware = (packageRoot = '.'): Koa.Middleware => { +export const koaNodeResolve = (packageRoot = '.'): Koa.Middleware => { const onDiskPackageRoot = resolvePath(packageRoot); - return esmSpecifierTransform((baseURL: string, specifier: string) => { + return koaModuleSpecifierTransform((baseURL: string, specifier: string) => { const pathname = parseURL(baseURL).pathname || '/'; const path = noLeadingSlash(pathname); const modulePath = resolvePath(onDiskPackageRoot, path); - const resolvedPath = resolveSpecifier(modulePath, specifier); + const resolvedPath = resolveNodeSpecifier(modulePath, specifier); return resolvedPath; }); }; - -export default middleware; diff --git a/src/support/path-utils.ts b/src/support/path-utils.ts index 39f9826..76b09b9 100644 --- a/src/support/path-utils.ts +++ b/src/support/path-utils.ts @@ -13,6 +13,14 @@ */ import {relative} from 'path'; +/** + * Similar to `path.dirname()` except includes trailing slash and for a + * path `/like/this/` will return `/like/this/` instead of `/like` since the + * trailing slash indicates `this` is a folder name not a file name. + * (`path.dirname('/like/this/')` returns `/like`.) + */ +export const dirname = (path: string): string => path.replace(/[^\/]+$/, ''); + export const ensureLeadingDot = (path: string): string => (path.startsWith('../') || path.startsWith('./')) ? path : './' + path; diff --git a/src/support/resolve-node-specifier.ts b/src/support/resolve-node-specifier.ts index 8d84133..7f6d275 100644 --- a/src/support/resolve-node-specifier.ts +++ b/src/support/resolve-node-specifier.ts @@ -13,32 +13,31 @@ */ import nodeResolve from 'resolve'; -import {relativePath} from './path-utils'; +import {dirname, relativePath} from './path-utils'; -export default (modulePath: string, specifier: string): string => { - if (isURL(specifier)) { - return specifier; - } - try { - const dependencyPath = nodeResolve.sync(specifier, { - basedir: dirname(modulePath), - extensions: ['.js', '.json', '.node'], - packageFilter: (packageJson: { - main?: string, - module?: string, - 'jsnext:main'?: string, - }) => Object.assign(packageJson, { - main: - packageJson.module || packageJson['jsnext:main'] || packageJson.main - }) - }); - return relativePath(modulePath, dependencyPath); - } catch (error) { - return specifier; - } -}; - -const dirname = (path: string): string => path.replace(/[^\/]+$/, ''); +export const resolveNodeSpecifier = + (modulePath: string, specifier: string): string => { + if (isURL(specifier)) { + return specifier; + } + try { + const dependencyPath = nodeResolve.sync(specifier, { + basedir: dirname(modulePath), + extensions: ['.js', '.json', '.node'], + packageFilter: (packageJson: { + main?: string, + module?: string, + 'jsnext:main'?: string, + }) => Object.assign(packageJson, { + main: packageJson.module || packageJson['jsnext:main'] || + packageJson.main + }) + }); + return relativePath(modulePath, dependencyPath); + } catch (error) { + return specifier; + } + }; const isURL = (value: string) => { try { diff --git a/src/test/koa-node-resolve.test.ts b/src/test/koa-node-resolve.test.ts index bb21d16..82eaf50 100644 --- a/src/test/koa-node-resolve.test.ts +++ b/src/test/koa-node-resolve.test.ts @@ -15,14 +15,14 @@ import {resolve as resolvePath} from 'path'; import request from 'supertest'; import test from 'tape'; -import createMiddleware from '../koa-node-resolve'; +import {koaNodeResolve} from '../koa-node-resolve'; import {createAndServe, squeezeHTML} from './test-utils'; test('transforms resolvable specifier in JavaScript module', async (t) => { t.plan(1); createAndServe( { - middleware: [createMiddleware(resolvePath(__dirname, '../..'))], + middleware: [koaNodeResolve(resolvePath(__dirname, '../..'))], routes: { '/my-module.js': `import * as resolve from 'resolve';`, }, @@ -36,7 +36,7 @@ test('ignores unresolvable specifier in JavaScript module', async (t) => { t.plan(1); createAndServe( { - middleware: [createMiddleware(resolvePath(__dirname, '../..'))], + middleware: [koaNodeResolve(resolvePath(__dirname, '../..'))], routes: { '/my-module.js': `import * as wubbleFlurp from 'wubble-flurp';`, }, @@ -50,7 +50,7 @@ test('transforms resolvable specifier in inline module script', async (t) => { t.plan(1); createAndServe( { - middleware: [createMiddleware(resolvePath(__dirname, '../..'))], + middleware: [koaNodeResolve(resolvePath(__dirname, '../..'))], routes: { '/my-page.html': ` `, }, @@ -63,7 +65,7 @@ test('transforms resolvable specifier in inline module script', async (t) => { squeezeHTML((await request(server).get('/my-page.html')).text), squeezeHTML(` `))); }); @@ -72,11 +74,11 @@ test('ignores unresolvable specifier in inline module script', async (t) => { t.plan(1); createAndServe( { - middleware: [koaNodeResolve(resolvePath(__dirname, '../..'))], + middleware: [koaNodeResolve(fixturesPath)], routes: { '/my-page.html': ` `, }, @@ -85,7 +87,7 @@ test('ignores unresolvable specifier in inline module script', async (t) => { squeezeHTML((await request(server).get('/my-page.html')).text), squeezeHTML(` `))); }); diff --git a/src/test/resolve-node-specifier.test.ts b/src/test/resolve-node-specifier.test.ts index 3bcb311..030ec9c 100644 --- a/src/test/resolve-node-specifier.test.ts +++ b/src/test/resolve-node-specifier.test.ts @@ -14,11 +14,58 @@ import {resolve as resolvePath} from 'path'; import test from 'tape'; +import {ensureTrailingSlash} from '../support/path-utils'; import {resolveNodeSpecifier} from '../support/resolve-node-specifier'; -test('resolve', (t) => { - t.plan(1); - const path = - resolveNodeSpecifier(resolvePath(__dirname, '../..') + '/', 'resolve'); - t.equal(path, './node_modules/resolve/index.js'); +const fixturesPath = + ensureTrailingSlash(resolvePath(__dirname, '../../test/fixtures/')); +const resolve = (specifier: string): string => + resolveNodeSpecifier(fixturesPath, specifier); + +test('resolve package name', (t) => { + t.plan(3); + t.equal( + resolve('x'), + './node_modules/x/main.js', + 'should resolve to `package.json` "main"'); + t.equal( + resolve('y'), + './node_modules/y/jsnext.js', + 'should resolve to `package.json` "jsnext:main"'); + t.equal( + resolve('z'), + './node_modules/z/module.js', + 'should resolve to `package.json` "module"'); +}); + +test('resolve extension-less module subpath', (t) => { + t.plan(3); + t.equal( + resolve('z/jsnext'), + './node_modules/z/jsnext.js', + 'should resolve to `.js` extension'); + t.equal( + resolve('z/package'), + './node_modules/z/package.json', + 'should resolve to `.json` extension'); + t.equal( + resolve('z/binary-file'), + './node_modules/z/binary-file.node', + 'should resolve to `.node` extension'); +}); + +test('resolve extension-less relative path', (t) => { + t.plan(3); + t.equal( + resolve('./node_modules/z/jsnext'), + './node_modules/z/jsnext.js', + 'should resolve to `.js` extension'); + t.equal( + resolve('./node_modules/z/package'), + './node_modules/z/package.json', + 'should resolve to `.json` extension'); + t.equal( + resolve('./node_modules/z/binary-file'), + './node_modules/z/binary-file.node', + 'should resolve to `.node` extension'); }); From df8baca05538754910b4930c343476a0a8555ed6 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Tue, 21 May 2019 10:42:58 -0700 Subject: [PATCH 48/52] Updated .gitignore so that test/fixtures would be included. --- .gitignore | 6 +++--- test/fixtures/node_modules/x/main.js | 1 + test/fixtures/node_modules/x/package.json | 5 +++++ test/fixtures/node_modules/y/jsnext.js | 1 + test/fixtures/node_modules/y/main.js | 1 + test/fixtures/node_modules/y/package.json | 6 ++++++ test/fixtures/node_modules/z/binary-file.node | 1 + test/fixtures/node_modules/z/jsnext.js | 1 + test/fixtures/node_modules/z/main.js | 1 + test/fixtures/node_modules/z/module.js | 1 + test/fixtures/node_modules/z/package.json | 7 +++++++ 11 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/node_modules/x/main.js create mode 100644 test/fixtures/node_modules/x/package.json create mode 100644 test/fixtures/node_modules/y/jsnext.js create mode 100644 test/fixtures/node_modules/y/main.js create mode 100644 test/fixtures/node_modules/y/package.json create mode 100644 test/fixtures/node_modules/z/binary-file.node create mode 100644 test/fixtures/node_modules/z/jsnext.js create mode 100644 test/fixtures/node_modules/z/main.js create mode 100644 test/fixtures/node_modules/z/module.js create mode 100644 test/fixtures/node_modules/z/package.json diff --git a/.gitignore b/.gitignore index cc4e13c..de041f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -node_modules -lib -*.tgz +/node_modules/* +/lib/* +/*.tgz diff --git a/test/fixtures/node_modules/x/main.js b/test/fixtures/node_modules/x/main.js new file mode 100644 index 0000000..07a4876 --- /dev/null +++ b/test/fixtures/node_modules/x/main.js @@ -0,0 +1 @@ +module.exports = { x: 'x common' } diff --git a/test/fixtures/node_modules/x/package.json b/test/fixtures/node_modules/x/package.json new file mode 100644 index 0000000..37becdd --- /dev/null +++ b/test/fixtures/node_modules/x/package.json @@ -0,0 +1,5 @@ +{ + "name": "x", + "version": "1.0.0", + "main": "main.js" +} diff --git a/test/fixtures/node_modules/y/jsnext.js b/test/fixtures/node_modules/y/jsnext.js new file mode 100644 index 0000000..bf41362 --- /dev/null +++ b/test/fixtures/node_modules/y/jsnext.js @@ -0,0 +1 @@ +export const y = 'y jsnext:main' diff --git a/test/fixtures/node_modules/y/main.js b/test/fixtures/node_modules/y/main.js new file mode 100644 index 0000000..0de27cc --- /dev/null +++ b/test/fixtures/node_modules/y/main.js @@ -0,0 +1 @@ +module.exports = { y: 'y common' } diff --git a/test/fixtures/node_modules/y/package.json b/test/fixtures/node_modules/y/package.json new file mode 100644 index 0000000..e1e9044 --- /dev/null +++ b/test/fixtures/node_modules/y/package.json @@ -0,0 +1,6 @@ +{ + "name": "y", + "version": "1.0.0", + "jsnext:main": "jsnext.js", + "main": "main.js" +} diff --git a/test/fixtures/node_modules/z/binary-file.node b/test/fixtures/node_modules/z/binary-file.node new file mode 100644 index 0000000..cb27321 --- /dev/null +++ b/test/fixtures/node_modules/z/binary-file.node @@ -0,0 +1 @@ +*JUST PRETEND I AM A BINARY FILE* diff --git a/test/fixtures/node_modules/z/jsnext.js b/test/fixtures/node_modules/z/jsnext.js new file mode 100644 index 0000000..32adaca --- /dev/null +++ b/test/fixtures/node_modules/z/jsnext.js @@ -0,0 +1 @@ +export const z = 'z jsnext:main' diff --git a/test/fixtures/node_modules/z/main.js b/test/fixtures/node_modules/z/main.js new file mode 100644 index 0000000..568b3a3 --- /dev/null +++ b/test/fixtures/node_modules/z/main.js @@ -0,0 +1 @@ +module.exports = { z: 'z common' } diff --git a/test/fixtures/node_modules/z/module.js b/test/fixtures/node_modules/z/module.js new file mode 100644 index 0000000..717e940 --- /dev/null +++ b/test/fixtures/node_modules/z/module.js @@ -0,0 +1 @@ +export const z = 'z module' diff --git a/test/fixtures/node_modules/z/package.json b/test/fixtures/node_modules/z/package.json new file mode 100644 index 0000000..98f829a --- /dev/null +++ b/test/fixtures/node_modules/z/package.json @@ -0,0 +1,7 @@ +{ + "name": "z", + "version": "1.0.0", + "module": "module.js", + "jsnext:main": "jsnext.js", + "main": "main.js" +} From d06d5b7e252623196380db8f83f70000212b1619 Mon Sep 17 00:00:00 2001 From: Brendan Baldwin Date: Tue, 21 May 2019 12:22:17 -0700 Subject: [PATCH 49/52] Updated README code examples and changed export names from koaSomethingSomething to somethingSomething. --- README.md | 25 +++++++++++++++---------- src/koa-module-specifier-transform.ts | 6 +++--- src/koa-node-resolve.ts | 6 +++--- src/test/koa-node-resolve.test.ts | 10 +++++----- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 7b2ca0b..8070ad7 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,12 @@ $ npm install --save koa-node-resolve Create your own mini-development server in file `./dev-server.js`. This one depends on `koa` and `koa-static`, so you'll need to `npm install --save-dev koa koa-static` for your project to use it. ```js -const Koa = require("koa"); +const Koa = require('koa'); +const staticFiles = require('koa-static'); +const { nodeResolve } = require('koa-node-resolve'); const server = new Koa() - .use(require("koa-node-resolve").middleware()) - .use(require("koa-static")(".")) + .use(nodeResolve()) + .use(staticFiles('.')) .listen(3000); ``` @@ -47,22 +49,25 @@ Now you can serve up your web assets and Node package specifiers will be transfo In a `karma` setup, your `karma.conf.js` file could create the Koa server before exporting the config. The Koa server uses the `koa-proxy` package (therefore `npm install --save-dev koa-proxy`) in between the browser and the Karma server, transforming all the Node package specifiers encountered in documents located under the `base/` URL namespace, which is a special Karma behavior for partitioning the package resources under test from Karma support resources. ```js -const Koa = require("koa"); +const Koa = require('koa'); +const mount = require('koa-mount'); +const proxy = require('koa-proxy'); +const { nodeResolve } = require('koa-node-resolve'); const server = new Koa() - .use(require("koa-mount")("/base", require("koa-node-resolve").middleware())) - .use(require("koa-proxy")({ host: "http://127.0.0.1:9876" })) + .use(mount('/base', nodeResolve())) + .use(proxy({ host: 'http://127.0.0.1:9876' })) .listen(9877); module.exports = config => { config.set({ upstreamProxy: { - hostname: "127.0.0.1", + hostname: '127.0.0.1', port: 9877, }, files: [ - { pattern: "test/**/*.js", type: "module" }, - { pattern: "**/*.js", included: false }, - { pattern: "node_modules/**/*", included: false }, + { pattern: 'test/**/*.js', type: 'module' }, + { pattern: '**/*.js', included: false }, + { pattern: 'node_modules/**/*', included: false }, ], }); }; diff --git a/src/koa-module-specifier-transform.ts b/src/koa-module-specifier-transform.ts index e62faa4..c02b4c2 100644 --- a/src/koa-module-specifier-transform.ts +++ b/src/koa-module-specifier-transform.ts @@ -20,9 +20,9 @@ import {transformJavaScriptModuleString} from './transform-javascript-module'; export type TransformSpecifierFunction = (baseURL: string, specifier: string) => string; -export const koaModuleSpecifierTransform = (transformSpecifier: - TransformSpecifierFunction): - Koa.Middleware => +export const moduleSpecifierTransform = (transformSpecifier: + TransformSpecifierFunction): + Koa.Middleware => async (ctx: Koa.Context, next: Function) => { await next(); diff --git a/src/koa-node-resolve.ts b/src/koa-node-resolve.ts index 24bf5ea..31d14ea 100644 --- a/src/koa-node-resolve.ts +++ b/src/koa-node-resolve.ts @@ -15,12 +15,12 @@ import * as Koa from 'koa'; import {resolve as resolvePath} from 'path'; import {parse as parseURL} from 'url'; -import {koaModuleSpecifierTransform} from './koa-module-specifier-transform'; +import {moduleSpecifierTransform} from './koa-module-specifier-transform'; import {noLeadingSlash} from './support/path-utils'; import {resolveNodeSpecifier} from './support/resolve-node-specifier'; -export const koaNodeResolve = (root = '.'): Koa.Middleware => - koaModuleSpecifierTransform( +export const nodeResolve = (root = '.'): Koa.Middleware => + moduleSpecifierTransform( (baseURL: string, specifier: string) => resolveNodeSpecifier( resolvePath( resolvePath(root), diff --git a/src/test/koa-node-resolve.test.ts b/src/test/koa-node-resolve.test.ts index e6faf97..79c7a15 100644 --- a/src/test/koa-node-resolve.test.ts +++ b/src/test/koa-node-resolve.test.ts @@ -15,7 +15,7 @@ import {resolve as resolvePath} from 'path'; import request from 'supertest'; import test from 'tape'; -import {koaNodeResolve} from '../koa-node-resolve'; +import {nodeResolve} from '../koa-node-resolve'; import {createAndServe, squeezeHTML} from './test-utils'; const fixturesPath = resolvePath(__dirname, '../../test/fixtures/'); @@ -24,7 +24,7 @@ test('transforms resolvable specifier in JavaScript module', async (t) => { t.plan(1); createAndServe( { - middleware: [koaNodeResolve(fixturesPath)], + middleware: [nodeResolve(fixturesPath)], routes: { '/my-module.js': `import * as x from 'x';`, }, @@ -38,7 +38,7 @@ test('ignores unresolvable specifier in JavaScript module', async (t) => { t.plan(1); createAndServe( { - middleware: [koaNodeResolve(fixturesPath)], + middleware: [nodeResolve(fixturesPath)], routes: { '/my-module.js': `import * as wubbleFlurp from 'wubble-flurp';`, }, @@ -52,7 +52,7 @@ test('transforms resolvable specifier in inline module script', async (t) => { t.plan(1); createAndServe( { - middleware: [koaNodeResolve(fixturesPath)], + middleware: [nodeResolve(fixturesPath)], routes: { '/my-page.html': ` + `, + }, + }, + async (server) => { + t.equal( + squeeze((await request(server).get('/my-module.js')).text), + squeeze(` + import * as x from 'x'; + import * as y from "./node_modules/y/index.js"; + `), + 'should transform defined specifiers and leave others alone'); + t.equal( + squeeze((await request(server).get('/my-page.html')).text), + squeeze(` + + `), + 'should transform only defined specifiers in inline module script'); + }); +}); + +test('logs errors when parsing', async (t) => { + t.plan(3); + const errors: string[] = []; + const logger = {error: (...args: string[]) => errors.push(args.join(' '))}; + createAndServe( + { + middleware: [moduleSpecifierTransform( + (_baseURL, _specifier) => undefined, {logger})], + routes: { + '/my-module.js': ` + this is a syntax error; + `, + '/my-page.html': ` + + `, + }, + }, + async (server) => { + t.equal( + squeeze((await request(server).get('/my-module.js')).text), + squeeze(` + this is a syntax error; + `), + 'should leave a file with unparseable syntax error alone'); + t.equal( + squeeze((await request(server).get('/my-page.html')).text), + squeeze(` + + `), + 'should leave a file with unparseable inline module script alone'); + t.deepEqual(errors, [ + 'SyntaxError: Unexpected token, expected ";" (2:17)', + 'SyntaxError: Unexpected token, expected ";" (2:19)', + ]); + }); +}); + +test('logs specifier transform error when transforming', async (t) => { + t.plan(3); + const errors: string[] = []; + const logger = {error: (...args: string[]) => errors.push(args.join(' '))}; + createAndServe( + { + middleware: [moduleSpecifierTransform( + (_baseURL, _specifier) => { + throw new Error('whoopsie daisy'); + }, + {logger})], + routes: { + '/my-module.js': ` + import * as wubbleFlurp from 'wubble-flurp'; + `, + '/my-page.html': ` + + `, + }, + }, + async (server) => { + t.equal( + squeeze((await request(server).get('/my-module.js')).text), + squeeze(` + import * as wubbleFlurp from 'wubble-flurp'; + `)); + t.equal( + squeeze((await request(server).get('/my-page.html')).text), + squeeze(` + + `)); + t.deepEqual(errors, [ + 'Error: whoopsie daisy', + 'Error: whoopsie daisy', + ]); + }); +}); diff --git a/src/test/koa-node-resolve.test.ts b/src/test/koa-node-resolve.test.ts index 79c7a15..5b4c27b 100644 --- a/src/test/koa-node-resolve.test.ts +++ b/src/test/koa-node-resolve.test.ts @@ -16,44 +16,17 @@ import request from 'supertest'; import test from 'tape'; import {nodeResolve} from '../koa-node-resolve'; -import {createAndServe, squeezeHTML} from './test-utils'; +import {createAndServe, squeeze} from './test-utils'; const fixturesPath = resolvePath(__dirname, '../../test/fixtures/'); -test('transforms resolvable specifier in JavaScript module', async (t) => { - t.plan(1); +test('transforms resolvable specifiers', async (t) => { + t.plan(2); createAndServe( { middleware: [nodeResolve(fixturesPath)], routes: { '/my-module.js': `import * as x from 'x';`, - }, - }, - async (server) => t.equal( - (await request(server).get('/my-module.js')).text, - `import * as x from "./node_modules/x/main.js";`)); -}); - -test('ignores unresolvable specifier in JavaScript module', async (t) => { - t.plan(1); - createAndServe( - { - middleware: [nodeResolve(fixturesPath)], - routes: { - '/my-module.js': `import * as wubbleFlurp from 'wubble-flurp';`, - }, - }, - async (server) => t.equal( - (await request(server).get('/my-module.js')).text, - `import * as wubbleFlurp from 'wubble-flurp';`)); -}); - -test('transforms resolvable specifier in inline module script', async (t) => { - t.plan(1); - createAndServe( - { - middleware: [nodeResolve(fixturesPath)], - routes: { '/my-page.html': ` - `))); + async (server) => { + t.equal( + squeeze((await request(server).get('/my-module.js')).text), + squeeze(` + import * as x from "./node_modules/x/main.js"; + `), + 'should transform specifiers in JavaScript module'); + t.equal( + squeeze((await request(server).get('/my-page.html')).text), + squeeze(` + + `), + 'should transform specifiers in inline module script'); + }); }); -test('ignores unresolvable specifier in inline module script', async (t) => { - t.plan(1); +test('ignores unresolvable specifiers', async (t) => { + t.plan(2); createAndServe( { middleware: [nodeResolve(fixturesPath)], routes: { + '/my-module.js': ` + import * as wubbleFlurp from 'wubble-flurp'; + `, '/my-page.html': ` - `))); + async (server) => { + t.equal( + squeeze((await request(server).get('/my-module.js')).text), + squeeze(` + import * as wubbleFlurp from 'wubble-flurp'; + `)); + t.equal( + squeeze((await request(server).get('/my-page.html')).text), + squeeze(` + + `)); + }); }); diff --git a/src/test/test-utils.test.ts b/src/test/test-utils.test.ts index 16ee47e..d81c67d 100644 --- a/src/test/test-utils.test.ts +++ b/src/test/test-utils.test.ts @@ -13,14 +13,14 @@ */ import test from 'tape'; -import {squeezeHTML} from './test-utils'; +import {squeeze} from './test-utils'; -test('squeezeHTML will not put inject newlines where no-spaces exist', (t) => { +test('squeeze will not inject newlines where no-spaces exist', (t) => { t.plan(1); - t.equal(squeezeHTML('

Hello

'), '

Hello

'); + t.equal(squeeze('

Hello

'), '

Hello

'); }); -test('squeezeHTML will shrink multiple spaces to single spaces', (t) => { +test('squeeze will shrink multiple spaces to single spaces', (t) => { t.plan(1); - t.equal(squeezeHTML('

Hello

'), '

\nHello\n

'); + t.equal(squeeze('

Hello

'), '

\nHello\n

'); }); diff --git a/src/test/test-utils.ts b/src/test/test-utils.ts index f63becd..cbe415f 100644 --- a/src/test/test-utils.ts +++ b/src/test/test-utils.ts @@ -55,11 +55,11 @@ export const serveApp = const server = app.listen(port).on('error', (e) => `ERROR: ${console.log(e)}`); await callback(server); - server.close(); + await server.close(); }; -export const squeezeHTML = (html: string): string => html.replace(/\s+/mg, ' ') - .replace(/>\s<') - .replace(/>\s/g, '>\n') - .replace(/\s html.replace(/\s+/mg, ' ') + .replace(/>\s<') + .replace(/>\s/g, '>\n') + .replace(/\s