From e246643bc74817a7d4c923e8a1766bf3c1954838 Mon Sep 17 00:00:00 2001 From: Lukasz Nojek <9399633+lukaszmn@users.noreply.github.com> Date: Tue, 7 Apr 2020 17:37:43 +0200 Subject: [PATCH] feat: added option onlyInternal excluding external links --- package-lock.json | 15 ++++------- package.json | 1 + readme.md | 23 ++++++++++------- src/index.js | 22 +++++++++++++--- test/test-plugin.js | 63 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a03276..793796d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5188,16 +5188,6 @@ "eslint-rule-composer": "^0.3.0" } }, - "eslint-plugin-dependencies": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-dependencies/-/eslint-plugin-dependencies-2.4.0.tgz", - "integrity": "sha512-IaW2phNpktrok2eDziZLYxmNaGysXjNj6NVji7LEv/qagHG2oshsmV+mUSxAGG5Jv9seuRBdX1YXEIaNlhkFJg==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "resolve": "^1.1.6" - } - }, "eslint-plugin-html": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.0.0.tgz", @@ -7851,6 +7841,11 @@ "integrity": "sha512-YqTdPLfwP7YFN0SsD3QUVCkm9ZG2VzOXv3DOrw5G5mkMbVwptTwVcFv7/C0vOpBmgTxAeTG19XpUs1E522LW9Q==", "dev": true }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==" + }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", diff --git a/package.json b/package.json index 219563e..710c751 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "style" ], "dependencies": { + "is-absolute-url": "^3.0.3", "is-url": "^1.2.4", "nanoid": "^3.0.2", "normalize-url": "5.0.0", diff --git a/readme.md b/readme.md index 9f0ca99..8f2cf97 100644 --- a/readme.md +++ b/readme.md @@ -66,16 +66,21 @@ output.html ## Options ### `tags` -Type: `Array` -Default: `['script', 'link']` -Description: *You can also expand the list by adding the tags you need...* +Type: `Array` +Default: `['script', 'link']` +Description: *You can also expand the list by adding the tags you need...* ### `attributes` -Type: `Array` -Default: `['src', 'href']` -Description: *You can also expand the list by adding the attributes you need...* +Type: `Array` +Default: `['src', 'href']` +Description: *You can also expand the list by adding the attributes you need...* ### `exclude` -Type: `Array` -Default: `[]` -Description: *You can also exclude the list by adding the tags you need...* +Type: `Array` +Default: `[]` +Description: *You can also exclude the list by adding the tags you need...* + +### `onlyInternal` +Type: `Array` +Default: `[]` +Description: *If you have external URL-s, some won't work if you add nanoid to them. If this list is empty, all external links are modified. Otherwise, only the URL-s starting with items in the array (case insensitive) will be modified. E.g. `['https://github.com/']` will add nanoid to all local links and only to `https://github.com/*`. It won't add nanoid to e.g. `http://github.com` or `https://fonts.google.com`. For simplicity, a link is considered external if it starts with a protocol (see [is-absolute-url](https://www.npmjs.com/package/is-absolute-url)) or with double slash `//`.* diff --git a/src/index.js b/src/index.js index 16adaa2..39fbd88 100644 --- a/src/index.js +++ b/src/index.js @@ -2,18 +2,30 @@ import {nanoid} from 'nanoid'; import isUrl from 'is-url'; import queryString from 'query-string'; import normalizeUrl from 'normalize-url'; +import isAbsoluteUrl from 'is-absolute-url'; -const setNanoid = url => { - const id = nanoid(); +const setNanoid = (onlyInternal, url) => { const fullUrl = /^[/?]/.test(url) ? `foo.bar${url}` : url; + if (onlyInternal.length > 0) { + const isAbsolute = isAbsoluteUrl(url) || url.startsWith('//'); + if (isAbsolute) { + const absoluteUrl = normalizeUrl(url, {normalizeProtocol: false}).toLowerCase(); + if (onlyInternal.every(start => !absoluteUrl.startsWith(start.toLowerCase()))) { + return url; + } + } + } + if (!isUrl(normalizeUrl(fullUrl))) { return url; } + const id = nanoid(); + let [uri, query] = url.split('?'); query = queryString.parse(query); - query.v = query.v ? query.v : id; + query.v = query.v || id; query = queryString.stringify(query); return `${uri}?${query}`; @@ -44,11 +56,13 @@ export default (options = {}) => { resolve(tree); } + const onlyInternal = options.onlyInternal && Array.isArray(options.onlyInternal) ? options.onlyInternal : []; + tree.walk(node => { if (node.tag && node.attrs) { node.attrs = Object.keys(node.attrs).reduce((attributeList, attr) => { if (tags.includes(node.tag) && attributes.includes(attr)) { - return Object.assign(attributeList, {[attr]: setNanoid(node.attrs[attr])}); + return Object.assign(attributeList, {[attr]: setNanoid(onlyInternal, node.attrs[attr])}); } return attributeList; diff --git a/test/test-plugin.js b/test/test-plugin.js index c8e9ecd..4e0b0d3 100644 --- a/test/test-plugin.js +++ b/test/test-plugin.js @@ -131,6 +131,69 @@ test('should add nano id for root path', async t => { t.is(id.length, 21); }); +test('should add nano id for external link', async t => { + const input = ''; + const html = (await processing(input)).html; + + const {url, id} = getParts(html, 'href'); + const rel = parser(html)[0].attrs.rel; + t.truthy(id); + t.truthy(rel); + t.is(rel, 'stylesheet'); + t.is(url, 'https://foo.com/style.css'); + t.is(id.length, 21); +}); + +test('should add nano id for allowed external link', async t => { + const input = ''; + const html = (await processing(input, {onlyInternal: ['https://foo.com/']})).html; + + const {url, id} = getParts(html, 'href'); + const rel = parser(html)[0].attrs.rel; + t.truthy(id); + t.truthy(rel); + t.is(rel, 'stylesheet'); + t.is(url, 'https://FOO.com/style.css'); + t.is(id.length, 21); +}); + +test('shouldn\'t add nano id for disallowed external link', async t => { + const input = ''; + const html = (await processing(input, {onlyInternal: ['http://foo.com/']})).html; + + const {url, id} = getParts(html, 'href'); + const rel = parser(html)[0].attrs.rel; + t.falsy(id); + t.truthy(rel); + t.is(rel, 'stylesheet'); + t.is(url, 'https://foo.com/style.css'); +}); + +test('should add nano id for disallowed protocol-less external link', async t => { + const input = ''; + const html = (await processing(input, {onlyInternal: ['//foo.com/']})).html; + + const {url, id} = getParts(html, 'href'); + const rel = parser(html)[0].attrs.rel; + t.truthy(id); + t.truthy(rel); + t.is(rel, 'stylesheet'); + t.is(url, '//FOO.com/style.css'); + t.is(id.length, 21); +}); + +test('shouldn\'t add nano id for disallowed protocol-less external link', async t => { + const input = ''; + const html = (await processing(input, {onlyInternal: ['//foo.org/']})).html; + + const {url, id} = getParts(html, 'href'); + const rel = parser(html)[0].attrs.rel; + t.falsy(id); + t.truthy(rel); + t.is(rel, 'stylesheet'); + t.is(url, '//foo.com/style.css'); +}); + function getParts(html, attributeName) { const parts = parser(html)[0].attrs[attributeName].split('?'); const url = parts[0];