html-loader
Version:
Html loader module for webpack
1,058 lines (882 loc) • 29.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parseSrcset = parseSrcset;
exports.parseSrc = parseSrc;
exports.normalizeUrl = normalizeUrl;
exports.requestify = requestify;
exports.isUrlRequestable = isUrlRequestable;
exports.stringifyRequest = stringifyRequest;
exports.typeSrc = typeSrc;
exports.typeSrcset = typeSrcset;
exports.normalizeOptions = normalizeOptions;
exports.pluginRunner = pluginRunner;
exports.getFilter = getFilter;
exports.getImportCode = getImportCode;
exports.getModuleCode = getModuleCode;
exports.getExportCode = getExportCode;
exports.c0ControlCodesExclude = c0ControlCodesExclude;
var _path = _interopRequireDefault(require("path"));
var _HtmlSourceError = _interopRequireDefault(require("./HtmlSourceError"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function isASCIIWhitespace(character) {
return (// Horizontal tab
character === '\u0009' || // New line
character === '\u000A' || // Form feed
character === '\u000C' || // Carriage return
character === '\u000D' || // Space
character === '\u0020'
);
} // (Don't use \s, to avoid matching non-breaking space)
// eslint-disable-next-line no-control-regex
const regexLeadingSpaces = /^[ \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex
const regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex
const regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/;
const regexTrailingCommas = /[,]+$/;
const regexNonNegativeInteger = /^\d+$/; // ( Positive or negative or unsigned integers or decimals, without or without exponents.
// Must include at least one digit.
// According to spec tests any decimal point must be followed by a digit.
// No leading plus sign is allowed.)
// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
const regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/;
function parseSrcset(input) {
// 1. Let input be the value passed to this algorithm.
const inputLength = input.length;
let url;
let descriptors;
let currentDescriptor;
let state;
let c; // 2. Let position be a pointer into input, initially pointing at the start
// of the string.
let position = 0;
let startUrlPosition; // eslint-disable-next-line consistent-return
function collectCharacters(regEx) {
let chars;
const match = regEx.exec(input.substring(position));
if (match) {
[chars] = match;
position += chars.length;
return chars;
}
} // 3. Let candidates be an initially empty source set.
const candidates = []; // 4. Splitting loop: Collect a sequence of characters that are space
// characters or U+002C COMMA characters. If any U+002C COMMA characters
// were collected, that is a parse error.
// eslint-disable-next-line no-constant-condition
while (true) {
collectCharacters(regexLeadingCommasOrSpaces); // 5. If position is past the end of input, return candidates and abort these steps.
if (position >= inputLength) {
if (candidates.length === 0) {
throw new Error('Must contain one or more image candidate strings');
} // (we're done, this is the sole return path)
return candidates;
} // 6. Collect a sequence of characters that are not space characters,
// and let that be url.
startUrlPosition = position;
url = collectCharacters(regexLeadingNotSpaces); // 7. Let descriptors be a new empty list.
descriptors = []; // 8. If url ends with a U+002C COMMA character (,), follow these substeps:
// (1). Remove all trailing U+002C COMMA characters from url. If this removed
// more than one character, that is a parse error.
if (url.slice(-1) === ',') {
url = url.replace(regexTrailingCommas, ''); // (Jump ahead to step 9 to skip tokenization and just push the candidate).
parseDescriptors();
} // Otherwise, follow these substeps:
else {
tokenize();
} // 16. Return to the step labeled splitting loop.
}
/**
* Tokenizes descriptor properties prior to parsing
* Returns undefined.
*/
function tokenize() {
// 8.1. Descriptor tokenizer: Skip whitespace
collectCharacters(regexLeadingSpaces); // 8.2. Let current descriptor be the empty string.
currentDescriptor = ''; // 8.3. Let state be in descriptor.
state = 'in descriptor'; // eslint-disable-next-line no-constant-condition
while (true) {
// 8.4. Let c be the character at position.
c = input.charAt(position); // Do the following depending on the value of state.
// For the purpose of this step, "EOF" is a special character representing
// that position is past the end of input.
// In descriptor
if (state === 'in descriptor') {
// Do the following, depending on the value of c:
// Space character
// If current descriptor is not empty, append current descriptor to
// descriptors and let current descriptor be the empty string.
// Set state to after descriptor.
if (isASCIIWhitespace(c)) {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
currentDescriptor = '';
state = 'after descriptor';
}
} // U+002C COMMA (,)
// Advance position to the next character in input. If current descriptor
// is not empty, append current descriptor to descriptors. Jump to the step
// labeled descriptor parser.
else if (c === ',') {
position += 1;
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return;
} // U+0028 LEFT PARENTHESIS (()
// Append c to current descriptor. Set state to in parens.
else if (c === '\u0028') {
currentDescriptor += c;
state = 'in parens';
} // EOF
// If current descriptor is not empty, append current descriptor to
// descriptors. Jump to the step labeled descriptor parser.
else if (c === '') {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return; // Anything else
// Append c to current descriptor.
} else {
currentDescriptor += c;
}
} // In parens
else if (state === 'in parens') {
// U+0029 RIGHT PARENTHESIS ())
// Append c to current descriptor. Set state to in descriptor.
if (c === ')') {
currentDescriptor += c;
state = 'in descriptor';
} // EOF
// Append current descriptor to descriptors. Jump to the step labeled
// descriptor parser.
else if (c === '') {
descriptors.push(currentDescriptor);
parseDescriptors();
return;
} // Anything else
// Append c to current descriptor.
else {
currentDescriptor += c;
}
} // After descriptor
else if (state === 'after descriptor') {
// Do the following, depending on the value of c:
if (isASCIIWhitespace(c)) {// Space character: Stay in this state.
} // EOF: Jump to the step labeled descriptor parser.
else if (c === '') {
parseDescriptors();
return;
} // Anything else
// Set state to in descriptor. Set position to the previous character in input.
else {
state = 'in descriptor';
position -= 1;
}
} // Advance position to the next character in input.
position += 1;
}
}
/**
* Adds descriptor properties to a candidate, pushes to the candidates array
* @return undefined
*/
// Declared outside of the while loop so that it's only created once.
function parseDescriptors() {
// 9. Descriptor parser: Let error be no.
let pError = false; // 10. Let width be absent.
// 11. Let density be absent.
// 12. Let future-compat-h be absent. (We're implementing it now as h)
let w;
let d;
let h;
let i;
const candidate = {};
let desc;
let lastChar;
let value;
let intVal;
let floatVal; // 13. For each descriptor in descriptors, run the appropriate set of steps
// from the following list:
for (i = 0; i < descriptors.length; i++) {
desc = descriptors[i];
lastChar = desc[desc.length - 1];
value = desc.substring(0, desc.length - 1);
intVal = parseInt(value, 10);
floatVal = parseFloat(value); // If the descriptor consists of a valid non-negative integer followed by
// a U+0077 LATIN SMALL LETTER W character
if (regexNonNegativeInteger.test(value) && lastChar === 'w') {
// If width and density are not both absent, then let error be yes.
if (w || d) {
pError = true;
} // Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes.
// Otherwise, let width be the result.
if (intVal === 0) {
pError = true;
} else {
w = intVal;
}
} // If the descriptor consists of a valid floating-point number followed by
// a U+0078 LATIN SMALL LETTER X character
else if (regexFloatingPoint.test(value) && lastChar === 'x') {
// If width, density and future-compat-h are not all absent, then let error
// be yes.
if (w || d || h) {
pError = true;
} // Apply the rules for parsing floating-point number values to the descriptor.
// If the result is less than zero, let error be yes. Otherwise, let density
// be the result.
if (floatVal < 0) {
pError = true;
} else {
d = floatVal;
}
} // If the descriptor consists of a valid non-negative integer followed by
// a U+0068 LATIN SMALL LETTER H character
else if (regexNonNegativeInteger.test(value) && lastChar === 'h') {
// If height and density are not both absent, then let error be yes.
if (h || d) {
pError = true;
} // Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes. Otherwise, let future-compat-h
// be the result.
if (intVal === 0) {
pError = true;
} else {
h = intVal;
} // Anything else, Let error be yes.
} else {
pError = true;
}
} // 15. If error is still no, then append a new image source to candidates whose
// URL is url, associated with a width width if not absent and a pixel
// density density if not absent. Otherwise, there is a parse error.
if (!pError) {
candidate.source = {
value: url,
startIndex: startUrlPosition
};
if (w) {
candidate.width = {
value: w
};
}
if (d) {
candidate.density = {
value: d
};
}
if (h) {
candidate.height = {
value: h
};
}
candidates.push(candidate);
} else {
throw new Error(`Invalid srcset descriptor found in '${input}' at '${desc}'`);
}
}
}
function parseSrc(input) {
if (!input) {
throw new Error('Must be non-empty');
}
let startIndex = 0;
let value = input;
while (isASCIIWhitespace(value.substring(0, 1))) {
startIndex += 1;
value = value.substring(1, value.length);
}
while (isASCIIWhitespace(value.substring(value.length - 1, value.length))) {
value = value.substring(0, value.length - 1);
}
if (!value) {
throw new Error('Must be non-empty');
}
return {
value,
startIndex
};
}
const moduleRequestRegex = /^[^?]*~/;
const matchNativeWin32Path = /^[A-Z]:[/\\]|^\\\\/i;
function normalizeUrl(url) {
return matchNativeWin32Path.test(url) ? decodeURI(url).replace(/[\t\n\r]/g, '') : decodeURI(url).replace(/[\t\n\r]/g, '').replace(/\\/g, '/');
}
function requestify(url) {
if (matchNativeWin32Path.test(url) || url[0] === '/') {
return url;
}
if (/^file:/i.test(url)) {
return url;
}
if (/^\.\.?\//.test(url)) {
return url;
} // A `~` makes the url an module
if (moduleRequestRegex.test(url)) {
return url.replace(moduleRequestRegex, '');
} // every other url is threaded like a relative url
return `./${url}`;
}
function isUrlRequestable(url) {
// Protocol-relative URLs
if (/^\/\//.test(url)) {
return false;
} // `file:` protocol
if (/^file:/i.test(url)) {
return true;
} // Absolute URLs
if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !matchNativeWin32Path.test(url)) {
return false;
} // It's some kind of url for a template
if (/^[{}[\]#*;,'§$%&(=?`´^°<>]/.test(url)) {
return false;
}
return true;
}
const matchRelativePath = /^\.\.?[/\\]/;
function isAbsolutePath(str) {
return matchNativeWin32Path.test(str) && _path.default.win32.isAbsolute(str);
}
function isRelativePath(str) {
return matchRelativePath.test(str);
}
function stringifyRequest(context, request) {
const splitted = request.split('!');
return JSON.stringify(splitted.map(part => {
// First, separate singlePath from query, because the query might contain paths again
const splittedPart = part.match(/^(.*?)(\?.*)/);
const query = splittedPart ? splittedPart[2] : '';
let singlePath = splittedPart ? splittedPart[1] : part;
if (isAbsolutePath(singlePath) && context) {
singlePath = _path.default.relative(context, singlePath);
if (isAbsolutePath(singlePath)) {
// If singlePath still matches an absolute path, singlePath was on a different drive than context.
// In this case, we leave the path platform-specific without replacing any separators.
// @see https://github.com/webpack/loader-utils/pull/14
return singlePath + query;
}
if (isRelativePath(singlePath) === false) {
// Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules).
singlePath = `./${singlePath}`;
}
}
return singlePath.replace(/\\/g, '/') + query;
}).join('!'));
}
function isProductionMode(loaderContext) {
return loaderContext.mode === 'production' || !loaderContext.mode;
}
const defaultMinimizerOptions = {
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, look at https://github.com/webpack-contrib/html-loader/issues/323
// `removeRedundantAttributes` is not safe, can affect certain style or script behavior, look at https://github.com/webpack-contrib/html-loader/issues/323
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true // `useShortDoctype` is not safe for XHTML
};
function getMinimizeOption(rawOptions, loaderContext) {
if (typeof rawOptions.minimize === 'undefined') {
return isProductionMode(loaderContext) ? defaultMinimizerOptions : false;
}
if (typeof rawOptions.minimize === 'boolean') {
return rawOptions.minimize === true ? defaultMinimizerOptions : false;
}
return rawOptions.minimize;
}
function getAttributeValue(attributes, name) {
const [result] = attributes.filter(i => i.name.toLowerCase() === name);
return typeof result === 'undefined' ? result : result.value;
}
function scriptSrcFilter(tag, attribute, attributes) {
let type = getAttributeValue(attributes, 'type');
if (!type) {
return true;
}
type = type.trim();
if (!type) {
return false;
}
if (type !== 'module' && type !== 'text/javascript' && type !== 'application/javascript') {
return false;
}
return true;
}
function linkHrefFilter(tag, attribute, attributes) {
let rel = getAttributeValue(attributes, 'rel');
if (!rel) {
return false;
}
rel = rel.trim();
if (!rel) {
return false;
}
rel = rel.toLowerCase();
const usedRels = rel.split(' ').filter(value => value);
const allowedRels = ['stylesheet', 'icon', 'mask-icon', 'apple-touch-icon', 'apple-touch-icon-precomposed', 'apple-touch-startup-image', 'manifest', 'prefetch', 'preload'];
return allowedRels.filter(value => usedRels.includes(value)).length > 0;
}
const META = new Map([['name', new Set([// msapplication-TileImage
'msapplication-tileimage', 'msapplication-square70x70logo', 'msapplication-square150x150logo', 'msapplication-wide310x150logo', 'msapplication-square310x310logo', 'msapplication-config', 'twitter:image'])], ['property', new Set(['og:image', 'og:image:url', 'og:image:secure_url', 'og:audio', 'og:audio:secure_url', 'og:video', 'og:video:secure_url', 'vk:image'])], ['itemprop', new Set(['image', 'logo', 'screenshot', 'thumbnailurl', 'contenturl', 'downloadurl', 'duringmedia', 'embedurl', 'installurl', 'layoutimage'])], ['name', new Set(['msapplication-task'])]]);
function linkItempropFilter(tag, attribute, attributes) {
let name = getAttributeValue(attributes, 'itemprop');
if (name) {
name = name.trim();
if (!name) {
return false;
}
name = name.toLowerCase();
return META.get('itemprop').has(name);
}
return false;
}
function linkUnionFilter(tag, attribute, attributes) {
return linkHrefFilter(tag, attribute, attributes) || linkItempropFilter(tag, attribute, attributes);
}
function metaContentFilter(tag, attribute, attributes) {
for (const item of META) {
const [key, allowedNames] = item;
let name = getAttributeValue(attributes, key);
if (name) {
name = name.trim();
if (!name) {
// eslint-disable-next-line no-continue
continue;
}
name = name.toLowerCase();
return allowedNames.has(name);
}
}
return false;
}
function typeSrc({
name,
attribute,
node,
target,
html,
options
}) {
const {
tagName,
sourceCodeLocation
} = node;
const {
value
} = attribute;
const result = [];
let source;
try {
source = parseSrc(value);
} catch (error) {
options.errors.push(new _HtmlSourceError.default(`Bad value for attribute "${attribute.name}" on element "${tagName}": ${error.message}`, sourceCodeLocation.attrs[name].startOffset, sourceCodeLocation.attrs[name].endOffset, html));
return result;
}
source = c0ControlCodesExclude(source);
if (!isUrlRequestable(source.value)) {
return result;
}
const startOffset = sourceCodeLocation.attrs[name].startOffset + target.indexOf(source.value, name.length);
result.push({
value: source.value,
startIndex: startOffset,
endIndex: startOffset + source.value.length
});
return result;
}
function typeSrcset({
name,
attribute,
node,
target,
html,
options
}) {
const {
tagName,
sourceCodeLocation
} = node;
const {
value
} = attribute;
const result = [];
let sourceSet;
try {
sourceSet = parseSrcset(value);
} catch (error) {
options.errors.push(new _HtmlSourceError.default(`Bad value for attribute "${attribute.name}" on element "${tagName}": ${error.message}`, sourceCodeLocation.attrs[name].startOffset, sourceCodeLocation.attrs[name].endOffset, html));
return result;
}
sourceSet = sourceSet.map(item => {
return {
source: c0ControlCodesExclude(item.source)
};
});
let searchFrom = name.length;
sourceSet.forEach(sourceItem => {
const {
source
} = sourceItem;
if (!isUrlRequestable(source.value)) {
return false;
}
const startOffset = sourceCodeLocation.attrs[name].startOffset + target.indexOf(source.value, searchFrom);
searchFrom = target.indexOf(source.value, searchFrom) + 1;
result.push({
value: source.value,
startIndex: startOffset,
endIndex: startOffset + source.value.length
});
return false;
});
return result;
}
function typeMsapplicationTask({
name,
attribute,
node,
target,
html,
options
}) {
const {
tagName,
sourceCodeLocation
} = node;
const [content] = typeSrc({
name,
attribute,
node,
target,
html,
options
});
const result = [];
if (!content) {
return result;
}
let startIndex = 0;
let endIndex = 0;
let foundIconUri;
let source;
content.value.split(';').forEach(i => {
if (foundIconUri) {
return;
}
if (!i.includes('icon-uri')) {
// +1 because of ";"
startIndex += i.length + 1;
return;
}
foundIconUri = true;
const [, aValue] = i.split('=');
try {
source = parseSrc(aValue);
} catch (error) {
options.errors.push(new _HtmlSourceError.default(`Bad value for attribute "icon-uri" on element "${tagName}": ${error.message}`, sourceCodeLocation.attrs[name].startOffset, sourceCodeLocation.attrs[name].endOffset, html));
return;
} // +1 because of "="
startIndex += i.indexOf('=') + source.startIndex + 1;
endIndex = startIndex + source.value.length;
});
if (!source) {
return result;
}
result.push({ ...content,
startIndex: content.startIndex + startIndex,
endIndex: content.startIndex + endIndex,
name: 'icon-uri',
value: source.value
});
return result;
}
function metaContentType({
name,
attribute,
node,
target,
html,
options
}) {
const isMsapplicationTask = node.attrs.filter(i => i.name.toLowerCase() === 'name' && i.value.toLowerCase() === 'msapplication-task');
return isMsapplicationTask.length === 0 ? typeSrc({
name,
attribute,
node,
target,
html,
options
}) : typeMsapplicationTask({
name,
attribute,
node,
target,
html,
options
});
}
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: linkUnionFilter
}, {
tag: 'link',
attribute: 'imagesrcset',
type: 'srcset',
filter: linkHrefFilter
}, {
tag: 'meta',
attribute: 'content',
type: metaContentType,
filter: metaContentFilter
}, {
tag: 'object',
attribute: 'data',
type: 'src'
}, {
tag: 'script',
attribute: 'src',
type: 'src',
filter: scriptSrcFilter
}, // Using href with <script> is described here: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script
{
tag: 'script',
attribute: 'href',
type: 'src',
filter: scriptSrcFilter
}, {
tag: 'script',
attribute: 'xlink:href',
type: 'src',
filter: scriptSrcFilter
}, {
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 rewriteSourcesList(sourcesList, attribute, source) {
for (const key of sourcesList.keys()) {
const item = sourcesList.get(key);
if (!item.has(attribute)) {
// eslint-disable-next-line no-continue
continue;
}
item.set(attribute, { ...item.get(attribute),
...source
});
sourcesList.set(key, item);
}
}
function createSourcesList(sources, accumulator = new Map()) {
for (const source of sources) {
if (source === '...') {
// eslint-disable-next-line no-continue
continue;
}
let {
tag = '*',
attribute = '*'
} = source;
tag = tag.toLowerCase();
attribute = attribute.toLowerCase();
if (tag === '*') {
rewriteSourcesList(accumulator, attribute, source);
}
if (!accumulator.has(tag)) {
accumulator.set(tag, new Map());
}
accumulator.get(tag).set(attribute, source);
}
return accumulator;
}
function smartMergeSources(array, factory) {
if (typeof array === 'undefined') {
return factory();
}
const result = array.some(i => i === '...') ? createSourcesList(array, factory()) : createSourcesList(array);
return result;
}
function getSourcesOption(rawOptions) {
if (typeof rawOptions.sources === 'undefined') {
return {
list: createSourcesList(defaultAttributes)
};
}
if (typeof rawOptions.sources === 'boolean') {
return rawOptions.sources === true ? {
list: createSourcesList(defaultAttributes)
} : false;
}
const sources = smartMergeSources(rawOptions.sources.list, () => createSourcesList(defaultAttributes));
return {
list: sources,
urlFilter: rawOptions.sources.urlFilter,
root: rawOptions.sources.root
};
}
function normalizeOptions(rawOptions, loaderContext) {
return {
preprocessor: rawOptions.preprocessor,
sources: getSourcesOption(rawOptions),
minimize: getMinimizeOption(rawOptions, loaderContext),
esModule: typeof rawOptions.esModule === 'undefined' ? true : rawOptions.esModule
};
}
function pluginRunner(plugins) {
return {
process: content => {
const result = {};
for (const plugin of plugins) {
// eslint-disable-next-line no-param-reassign
content = plugin(content, result);
}
result.html = content;
return result;
}
};
}
function getFilter(filter, defaultFilter = null) {
return (attribute, value, resourcePath) => {
if (defaultFilter && !defaultFilter(value)) {
return false;
}
if (typeof filter === 'function') {
return filter(attribute, value, resourcePath);
}
return true;
};
}
const GET_SOURCE_FROM_IMPORT_NAME = '___HTML_LOADER_GET_SOURCE_FROM_IMPORT___';
function getImportCode(html, loaderContext, imports, options) {
if (imports.length === 0) {
return '';
}
const stringifiedHelperRequest = `"${_path.default.relative(loaderContext.context, require.resolve('./runtime/getUrl.js')).replace(/\\/g, '/')}"`;
let code = options.esModule ? `import ${GET_SOURCE_FROM_IMPORT_NAME} from ${stringifiedHelperRequest};\n` : `var ${GET_SOURCE_FROM_IMPORT_NAME} = require(${stringifiedHelperRequest});\n`;
for (const item of imports) {
const {
importName,
source
} = item;
code += options.esModule ? `var ${importName} = new URL(${source}, import.meta.url);\n` : `var ${importName} = require(${source});\n`;
}
return `// Imports\n${code}`;
}
function getModuleCode(html, replacements) {
let code = JSON.stringify(html) // Invalid in JavaScript but valid HTML
.replace(/[\u2028\u2029]/g, str => str === '\u2029' ? '\\u2029' : '\\u2028');
let replacersCode = '';
for (const item of replacements) {
const {
importName,
replacementName,
unquoted,
hash
} = item;
const getUrlOptions = [].concat(hash ? [`hash: ${JSON.stringify(hash)}`] : []).concat(unquoted ? 'maybeNeedQuotes: true' : []);
const preparedOptions = getUrlOptions.length > 0 ? `, { ${getUrlOptions.join(', ')} }` : '';
replacersCode += `var ${replacementName} = ${GET_SOURCE_FROM_IMPORT_NAME}(${importName}${preparedOptions});\n`;
code = code.replace(new RegExp(replacementName, 'g'), () => `" + ${replacementName} + "`);
}
return `// Module\n${replacersCode}var code = ${code};\n`;
}
function getExportCode(html, options) {
if (options.esModule) {
return `// Exports\nexport default code;`;
}
return `// Exports\nmodule.exports = code;`;
}
function isASCIIC0group(character) {
// C0 and
// eslint-disable-next-line no-control-regex
return /^[\u0001-\u0019\u00a0]/.test(character);
}
function c0ControlCodesExclude(source) {
let {
value,
startIndex
} = source;
if (!value) {
throw new Error('Must be non-empty');
}
while (isASCIIC0group(value.substring(0, 1))) {
startIndex += 1;
value = value.substring(1, value.length);
}
while (isASCIIC0group(value.substring(value.length - 1, value.length))) {
value = value.substring(0, value.length - 1);
}
if (!value) {
throw new Error('Must be non-empty');
}
return {
value,
startIndex
};
}