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];