Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 12 additions & 20 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';

import { sourcePlugin, minimizerPlugin } from './plugins';

import {
pluginRunner,
isProductionMode,
normalizeOptions,
getImportCode,
getModuleCode,
getExportCode,
Expand All @@ -14,13 +13,15 @@ import {
import schema from './options.json';

export default async function loader(content) {
const options = getOptions(this);
const rawOptions = getOptions(this);

validateOptions(schema, options, {
validateOptions(schema, rawOptions, {
name: 'HTML Loader',
baseDataPath: 'options',
});

const options = normalizeOptions(rawOptions, this);

if (options.preprocessor) {
// eslint-disable-next-line no-param-reassign
content = await options.preprocessor(content, this);
Expand All @@ -31,13 +32,10 @@ export default async function loader(content) {
const imports = [];
const replacements = [];

const attributes =
typeof options.attributes === 'undefined' ? true : options.attributes;

if (attributes) {
if (options.attributes) {
plugins.push(
sourcePlugin({
attributes,
attributes: options.attributes,
resourcePath: this.resourcePath,
imports,
errors,
Expand All @@ -46,13 +44,8 @@ export default async function loader(content) {
);
}

const minimize =
typeof options.minimize === 'undefined'
? isProductionMode(this)
: options.minimize;

if (minimize) {
plugins.push(minimizerPlugin({ minimize, errors }));
if (options.minimize) {
plugins.push(minimizerPlugin({ minimize: options.minimize, errors }));
}

const { html } = pluginRunner(plugins).process(content);
Expand All @@ -61,10 +54,9 @@ export default async function loader(content) {
this.emitError(error instanceof Error ? error : new Error(error));
}

const codeOptions = { ...options, loaderContext: this };
const importCode = getImportCode(html, imports, codeOptions);
const moduleCode = getModuleCode(html, replacements, codeOptions);
const exportCode = getExportCode(html, codeOptions);
const importCode = getImportCode(html, this, imports, options);
const moduleCode = getModuleCode(html, replacements, options);
const exportCode = getExportCode(html, options);

return `${importCode}${moduleCode}${exportCode}`;
}
25 changes: 1 addition & 24 deletions src/plugins/minimizer-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,9 @@ import { minify } from 'html-minifier-terser';

export default (options) =>
function process(html) {
const minimizeOptions =
typeof options.minimize === 'boolean' ||
typeof options.minimize === 'undefined'
? {
caseSensitive: true,
// `collapseBooleanAttributes` is not always safe, since this can break CSS attribute selectors and not safe for XHTML
collapseWhitespace: true,
conservativeCollapse: true,
keepClosingSlash: true,
// We need ability to use cssnano, or setup own function without extra dependencies
minifyCSS: true,
minifyJS: true,
// `minifyURLs` is unsafe, because we can't guarantee what the base URL is
// `removeAttributeQuotes` is not safe in some rare cases, also HTML spec recommends against doing this
removeComments: true,
// `removeEmptyAttributes` is not safe, can affect certain style or script behavior
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
// `useShortDoctype` is not safe for XHTML
}
: options.minimize;

try {
// eslint-disable-next-line no-param-reassign
html = minify(html, minimizeOptions);
html = minify(html, options.minimize);
} catch (error) {
options.errors.push(error);
}
Expand Down
148 changes: 2 additions & 146 deletions src/plugins/source-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,136 +6,6 @@ import { isUrlRequest, urlToRequest } from 'loader-utils';
import HtmlSourceError from '../HtmlSourceError';
import { getFilter, parseSrc, parseSrcset } from '../utils';

function getAttributeValue(attributes, name) {
const lowercasedAttributes = Object.keys(attributes).reduce((keys, k) => {
// eslint-disable-next-line no-param-reassign
keys[k.toLowerCase()] = k;

return keys;
}, {});

return attributes[lowercasedAttributes[name.toLowerCase()]];
}

const defaultAttributes = [
{
tag: 'audio',
attribute: 'src',
type: 'src',
},
{
tag: 'embed',
attribute: 'src',
type: 'src',
},
{
tag: 'img',
attribute: 'src',
type: 'src',
},
{
tag: 'img',
attribute: 'srcset',
type: 'srcset',
},
{
tag: 'input',
attribute: 'src',
type: 'src',
},
{
tag: 'link',
attribute: 'href',
type: 'src',
filter: (tag, attribute, attributes) => {
if (!/stylesheet/i.test(getAttributeValue(attributes, 'rel'))) {
return false;
}

if (
attributes.type &&
getAttributeValue(attributes, 'type').trim().toLowerCase() !==
'text/css'
) {
return false;
}

return true;
},
},
{
tag: 'object',
attribute: 'data',
type: 'src',
},
{
tag: 'script',
attribute: 'src',
type: 'src',
filter: (tag, attribute, attributes) => {
if (attributes.type) {
const type = getAttributeValue(attributes, 'type').trim().toLowerCase();

if (
type !== 'module' &&
type !== 'text/javascript' &&
type !== 'application/javascript'
) {
return false;
}
}

return true;
},
},
{
tag: 'source',
attribute: 'src',
type: 'src',
},
{
tag: 'source',
attribute: 'srcset',
type: 'srcset',
},
{
tag: 'track',
attribute: 'src',
type: 'src',
},
{
tag: 'video',
attribute: 'poster',
type: 'src',
},
{
tag: 'video',
attribute: 'src',
type: 'src',
},
// SVG
{
tag: 'image',
attribute: 'xlink:href',
type: 'src',
},
{
tag: 'image',
attribute: 'href',
type: 'src',
},
{
tag: 'use',
attribute: 'xlink:href',
type: 'src',
},
{
tag: 'use',
attribute: 'href',
type: 'src',
},
];

function parseSource(source) {
const URLObject = parse(source);
const { hash } = URLObject;
Expand All @@ -153,27 +23,13 @@ function parseSource(source) {

export default (options) =>
function process(html) {
let attributeList;
let maybeUrlFilter;
let root;

if (
typeof options.attributes === 'undefined' ||
options.attributes === true
) {
attributeList = defaultAttributes;
} else {
attributeList = options.attributes.list || defaultAttributes;
// eslint-disable-next-line no-undefined
({ urlFilter: maybeUrlFilter, root } = options.attributes);
}

const { list, urlFilter: maybeUrlFilter, root } = options.attributes;
const sources = [];
const urlFilter = getFilter(maybeUrlFilter, (value) =>
isUrlRequest(value, root)
);
const getAttribute = (tag, attribute, attributes, resourcePath) => {
return attributeList.find((element) => {
return list.find((element) => {
const foundTag =
typeof element.tag === 'undefined' ||
(typeof element.tag !== 'undefined' &&
Expand Down
Loading