Skip to content

Commit

Permalink
refactor: code
Browse files Browse the repository at this point in the history
alexander-akait authored Feb 18, 2021
1 parent e732324 commit 6654c9d
Showing 6 changed files with 1,540 additions and 993 deletions.
47 changes: 37 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -282,7 +282,9 @@ module.exports = {
};
```

Filter can also be used to extend the supported elements and attributes. For example, filter can help process meta tags that reference assets:
Filter can also be used to extend the supported elements and attributes.

For example, filter can help process meta tags that reference assets:

```js
module.exports = {
@@ -305,6 +307,7 @@ module.exports = {
) {
return true;
}

return false;
},
},
@@ -317,6 +320,38 @@ module.exports = {
};
```

**Note:** source with a `tag` option takes precedence over source without.

Filter can be used to disable default sources.

For example:

```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
sources: {
list: [
'...',
{
tag: 'img',
attribute: 'src',
type: 'src',
filter: () => false,
},
],
},
},
},
],
},
};
```

#### `urlFilter`

Type: `Function`
@@ -609,7 +644,7 @@ module.exports = {
],
},
output: {
publicPath: 'http://cdn.example.com/[hash]/',
publicPath: 'http://cdn.example.com/[fullhash]/',
},
};
```
@@ -640,14 +675,6 @@ require('html-loader?{"sources":{"list":[{"tag":"img","attribute":"src","type":"
// => '<img src="http://cdn.example.com/49eba9f/a992ca.jpg" data-src="data:image/png;base64,..." >'
```

```js
require('html-loader?-sources!./file.html');

// => '<img src="image.jpg" data-src="image2x.png" >'
```

> :warning: `-sources` sets `sources: false`.
### Process `script` and `link` tags

**script.file.js**
10 changes: 5 additions & 5 deletions src/HtmlSourceError.js
Original file line number Diff line number Diff line change
@@ -36,17 +36,17 @@ function offsetToPosition(source, offset) {
}

export default class HtmlSourceError extends Error {
constructor(error, startIndex, endIndex, source) {
constructor(error, startOffset, endOffset, source) {
super(error);

this.name = 'HtmlSourceError';
this.message = `${this.name}: ${this.message}`;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.startOffset = startOffset;
this.endOffset = endOffset;
this.source = source;

const startPosition = offsetToPosition(source, this.startIndex);
const endPosition = offsetToPosition(source, this.endIndex);
const startPosition = offsetToPosition(source, this.startOffset);
const endPosition = offsetToPosition(source, this.endOffset);

this.message += ` (From line ${startPosition.line}, column ${startPosition.column}; to line ${endPosition.line}, column ${endPosition.column})`;

155 changes: 65 additions & 90 deletions src/plugins/sources-plugin.js
Original file line number Diff line number Diff line change
@@ -4,129 +4,104 @@ import {
getFilter,
normalizeUrl,
requestify,
isUrlRequestable,
stringifyRequest,
typeSrc,
typeSrcset,
} from '../utils';

export default (options) =>
function process(html) {
const { list, urlFilter: maybeUrlFilter } = options.sources;
const sources = [];
const urlFilter = getFilter(maybeUrlFilter, (value) =>
isUrlRequestable(value)
);
const getAttribute = (tag, attribute, attributes, resourcePath) => {
const foundTag = list.get(tag.toLowerCase()) || list.get('*');

if (!foundTag) {
return false;
}

const foundAttribute = foundTag.get(attribute.toLowerCase());

if (!foundAttribute) {
return false;
}

const result = foundAttribute.filter
? foundAttribute.filter(tag, attribute, attributes, resourcePath)
: true;

return result ? foundAttribute : false;
};

const { resourcePath } = options;
const parser5 = new SAXParser({ sourceCodeLocationInfo: true });
const sources = [];

parser5.on('startTag', (node) => {
const { tagName, attrs, sourceCodeLocation } = node;
const { tagName, attrs: attributes, sourceCodeLocation } = node;

attrs.forEach((attribute) => {
const { prefix } = attribute;
attributes.forEach((attribute) => {
let { name } = attribute;

name = prefix ? `${prefix}:${name}` : name;
name = attribute.prefix ? `${attribute.prefix}:${name}` : name;

if (!sourceCodeLocation.attrs[name]) {
const handlers =
options.sources.list.get(tagName.toLowerCase()) ||
options.sources.list.get('*');

if (!handlers) {
return;
}

const foundAttribute = getAttribute(tagName, name, attrs, resourcePath);
const handler = handlers.get(name.toLowerCase());

if (!foundAttribute) {
if (!handler) {
return;
}

const { type } = foundAttribute;
if (
handler.filter &&
!handler.filter(tagName, name, attributes, options.resourcePath)
) {
return;
}

const target = html.slice(
const attributeAndValue = html.slice(
sourceCodeLocation.attrs[name].startOffset,
sourceCodeLocation.attrs[name].endOffset
);
const isValueQuoted =
attributeAndValue[attributeAndValue.length - 1] === '"' ||
attributeAndValue[attributeAndValue.length - 1] === "'";
const valueStartOffset =
sourceCodeLocation.attrs[name].startOffset +
attributeAndValue.indexOf(attribute.value);
const valueEndOffset =
sourceCodeLocation.attrs[name].endOffset - (isValueQuoted ? 1 : 0);
const optionsForTypeFn = {
tag: tagName,
isSelfClosing: node.selfClosing,
tagStartOffset: sourceCodeLocation.startOffset,
tagEndOffset: sourceCodeLocation.endOffset,
attributes,
attribute: name,
attributePrefix: attribute.prefix,
attributeNamespace: attribute.namespace,
attributeStartOffset: sourceCodeLocation.attrs[name].startOffset,
attributeEndOffset: sourceCodeLocation.attrs[name].endOffset,
value: attribute.value,
isValueQuoted,
valueEndOffset,
valueStartOffset,
html,
};

let result;

try {
result = handler.type(optionsForTypeFn);
} catch (error) {
options.errors.push(error);
}

const unquoted =
target[target.length - 1] !== '"' &&
target[target.length - 1] !== "'";

const result = [];

// eslint-disable-next-line default-case
switch (type) {
case 'src': {
typeSrc({ name, attribute, node, target, html, options }).forEach(
(i) => {
result.push(i);
}
);
break;
}

case 'srcset': {
typeSrcset({
name,
attribute,
node,
target,
html,
options,
}).forEach((i) => {
result.push(i);
});
break;
}
result = Array.isArray(result) ? result : [result];

default: {
type({ name, attribute, node, target, html, options }).forEach(
(i) => {
result.push(i);
}
);
for (const source of result) {
if (!source) {
// eslint-disable-next-line no-continue
continue;
}
}

for (const i of result) {
if (i) {
sources.push({
...i,
name,
unquoted,
});
}
sources.push({ ...source, name, isValueQuoted });
}
});
});

parser5.end(html);

const urlFilter = getFilter(options.sources.urlFilter);
const imports = new Map();
const replacements = new Map();

let offset = 0;

for (const source of sources) {
const { name, value, unquoted, startIndex, endIndex } = source;
const { name, value, isValueQuoted, startOffset, endOffset } = source;

let normalizedUrl = value;
let prefix = '';
@@ -140,7 +115,7 @@ export default (options) =>

normalizedUrl = normalizeUrl(normalizedUrl);

if (!urlFilter(name, value, resourcePath)) {
if (!urlFilter(name, value, options.resourcePath)) {
// eslint-disable-next-line no-continue
continue;
}
@@ -168,7 +143,7 @@ export default (options) =>
});
}

const replacementKey = JSON.stringify({ newUrl, unquoted, hash });
const replacementKey = JSON.stringify({ newUrl, isValueQuoted, hash });
let replacementName = replacements.get(replacementKey);

if (!replacementName) {
@@ -179,17 +154,17 @@ export default (options) =>
replacementName,
importName,
hash,
unquoted,
isValueQuoted,
});
}

// eslint-disable-next-line no-param-reassign
html =
html.slice(0, startIndex + offset) +
html.slice(0, startOffset + offset) +
replacementName +
html.slice(endIndex + offset);
html.slice(endOffset + offset);

offset += startIndex + replacementName.length - endIndex;
offset += startOffset + replacementName.length - endOffset;
}

return html;
616 changes: 318 additions & 298 deletions src/utils.js

Large diffs are not rendered by default.

1,664 changes: 1,082 additions & 582 deletions test/__snapshots__/sources-option.test.js.snap

Large diffs are not rendered by default.

41 changes: 33 additions & 8 deletions test/sources-option.test.js
Original file line number Diff line number Diff line change
@@ -35,6 +35,33 @@ describe("'sources' option", () => {
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should work prefer source with tag over without', async () => {
const compiler = getCompiler('simple.js', {
sources: {
list: [
{
tag: 'img',
attribute: 'src',
type: 'src',
filter: () => false,
},
{
attribute: 'src',
type: 'src',
},
],
},
});
const stats = await compile(compiler);

expect(getModuleSource('./simple.html', stats)).toMatchSnapshot('module');
expect(
execute(readAsset('main.bundle.js', compiler, stats))
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should work with "..." syntax', async () => {
const compiler = getCompiler('simple.js', {
sources: {
@@ -58,22 +85,20 @@ describe("'sources' option", () => {
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should work with "..." syntax and disable source', async () => {
it('should allow to add more attributes ti default values', async () => {
const compiler = getCompiler('simple.js', {
sources: {
list: [
'...',
{
tag: 'img',
attribute: 'src',
attribute: 'data-src',
type: 'src',
filter: (tag) => tag.toLowerCase() !== 'img',
},
{
tag: 'img',
attribute: 'srcset',
attribute: 'data-srcset',
type: 'srcset',
filter: (tag) => tag.toLowerCase() !== 'img',
},
],
},
@@ -88,16 +113,16 @@ describe("'sources' option", () => {
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should handle default src sources in all HTML tags except img tag (testing filter option)', async () => {
it('should work and override the "img" tag logic with "..."', async () => {
const compiler = getCompiler('simple.js', {
sources: {
list: [
'...',
{
tag: 'img',
attribute: 'src',
type: 'src',
// eslint-disable-next-line no-unused-vars
filter: (tag, attribute, sources) => tag.toLowerCase() !== 'img',
filter: () => false,
},
],
},

0 comments on commit 6654c9d

Please sign in to comment.