Skip to content

Commit

Permalink
fix: reduce import/require count
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi committed Mar 11, 2020
1 parent d0b0150 commit a17df49
Show file tree
Hide file tree
Showing 15 changed files with 1,145 additions and 2,421 deletions.
872 changes: 349 additions & 523 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -48,7 +48,7 @@
"htmlparser2": "^4.1.0",
"loader-utils": "^1.4.0",
"parse-srcset": "^1.0.2",
"schema-utils": "^2.6.4"
"schema-utils": "^2.6.5"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
Expand All @@ -60,7 +60,7 @@
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^25.1.0",
"commitlint-azure-pipelines-cli": "^1.0.3",
"cross-env": "^7.0.1",
"cross-env": "^7.0.2",
"del": "^5.1.0",
"del-cli": "^3.0.0",
"es-check": "^5.1.0",
Expand All @@ -72,7 +72,7 @@
"jest": "^25.1.0",
"jest-junit": "^10.0.0",
"lint-staged": "^10.0.8",
"memfs": "^3.0.4",
"memfs": "^3.1.1",
"npm-run-all": "^4.1.5",
"prettier": "^1.19.1",
"standard-version": "^7.1.0",
Expand Down
25 changes: 18 additions & 7 deletions src/index.js
@@ -1,13 +1,14 @@
import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';

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

import {
pluginRunner,
isProductionMode,
getImportCode,
getModuleCode,
getExportCode,
} from './utils';

Expand All @@ -27,7 +28,7 @@ export default function htmlLoader(content) {
typeof options.attributes === 'undefined' ? true : options.attributes;

if (attributes) {
plugins.push(attributePlugin(options));
plugins.push(sourcePlugin(options));
}

const minimize =
Expand Down Expand Up @@ -57,19 +58,29 @@ export default function htmlLoader(content) {
this.emitError(new Error(error));
}

const replacers = [];
const importedMessages = [];
const replaceableMessages = [];
const exportedMessages = [];

for (const message of messages) {
// eslint-disable-next-line default-case
switch (message.type) {
case 'import':
importedMessages.push(message.value);
break;
case 'replacer':
replacers.push(message.value);
replaceableMessages.push(message.value);
break;
case 'exports':
exportedMessages.push(message.value);
break;
}
}

const importCode = getImportCode(this, html, replacers, options);
const exportCode = getExportCode(html, replacers, options);
const codeOptions = { ...options, loaderContext: this };
const importCode = getImportCode(html, importedMessages, codeOptions);
const moduleCode = getModuleCode(html, replaceableMessages, codeOptions);
const exportCode = getExportCode(html, exportedMessages, codeOptions);

return `${importCode}${exportCode};`;
return `${importCode}${moduleCode}${exportCode}`;
}
4 changes: 2 additions & 2 deletions src/plugins/index.js
@@ -1,5 +1,5 @@
import attributePlugin from './attribute-plugin';
import sourcePlugin from './source-plugin';
import interpolatePlugin from './interpolate-plugin';
import minimizerPlugin from './minimizer-plugin';

export { attributePlugin, interpolatePlugin, minimizerPlugin };
export { sourcePlugin, interpolatePlugin, minimizerPlugin };
56 changes: 41 additions & 15 deletions src/plugins/attribute-plugin.js → src/plugins/source-plugin.js
@@ -1,7 +1,7 @@
import { parse } from 'url';

import { Parser } from 'htmlparser2';
import { isUrlRequest } from 'loader-utils';
import { isUrlRequest, urlToRequest } from 'loader-utils';

function isASCIIWhitespace(character) {
return (
Expand Down Expand Up @@ -511,8 +511,9 @@ export default (options) =>
parser.write(html);
parser.end();

const importsMap = new Map();
const replacersMap = new Map();
let offset = 0;
let index = 0;

for (const source of sources) {
const { value, startIndex, unquoted } = source;
Expand All @@ -524,26 +525,51 @@ export default (options) =>
source.value = uri.format();
}

const replacementName = `___HTML_LOADER_IDENT_${index}___`;
const importKey = urlToRequest(
decodeURIComponent(source.value),
options.root
);
let importName = importsMap.get(importKey);

if (!importName) {
importName = `___HTML_LOADER_IMPORT_${importsMap.size}___`;
importsMap.set(importKey, importName);

result.messages.push({
type: 'import',
value: {
type: 'source',
source: importKey,
importName,
},
});
}

result.messages.push({
type: 'replacer',
value: {
type: 'attribute',
replacementName,
source: decodeURIComponent(source.value),
unquoted,
},
});
const replacerKey = JSON.stringify({ importKey, unquoted });
let replacerName = replacersMap.get(replacerKey);

if (!replacerName) {
replacerName = `___HTML_LOADER_REPLACER_${replacersMap.size}___`;
replacersMap.set(replacerKey, replacerName);

result.messages.push({
type: 'replacer',
value: {
type: 'source',
importName,
replacerName,
unquoted,
},
});
}

// eslint-disable-next-line no-param-reassign
html =
html.substr(0, startIndex + offset) +
replacementName +
replacerName +
html.substr(startIndex + value.length + offset);

offset += replacementName.length - value.length;
index += 1;
offset += replacerName.length - value.length;
}

return html;
Expand Down
86 changes: 42 additions & 44 deletions src/utils.js
@@ -1,4 +1,6 @@
import { urlToRequest, stringifyRequest } from 'loader-utils';
import { stringifyRequest } from 'loader-utils';

const GET_SOURCE_FROM_IMPORT_NAME = '___HTML_LOADER_GET_SOURCE_FROM_IMPORT___';

export function pluginRunner(plugins) {
return {
Expand All @@ -21,70 +23,66 @@ export function isProductionMode(loaderContext) {
return loaderContext.mode === 'production' || !loaderContext.mode;
}

export function getImportCode(loaderContext, html, replacers, options) {
if (replacers.length === 0) {
export function getImportCode(html, importedMessages, codeOptions) {
if (importedMessages.length === 0) {
return '';
}

const importItems = [];

importItems.push(
options.esModule
? `import ___HTML_LOADER_GET_URL_IMPORT___ from ${stringifyRequest(
loaderContext,
require.resolve('./runtime/getUrl.js')
)}`
: `var ___HTML_LOADER_GET_URL_IMPORT___ = require(${stringifyRequest(
loaderContext,
require.resolve('./runtime/getUrl.js')
)});`
const { loaderContext, esModule } = codeOptions;
const stringifiedHelperRequest = stringifyRequest(
loaderContext,
require.resolve('./runtime/getUrl.js')
);

for (const replacer of replacers) {
const { replacementName, source } = replacer;
const request = urlToRequest(source, options.root);
const stringifiedRequest = stringifyRequest(loaderContext, request);
let code = esModule
? `import ${GET_SOURCE_FROM_IMPORT_NAME} from ${stringifiedHelperRequest};\n`
: `var ${GET_SOURCE_FROM_IMPORT_NAME} = require(${stringifiedHelperRequest});\n`;

if (options.esModule) {
importItems.push(`import ${replacementName} from ${stringifiedRequest};`);
} else {
importItems.push(
`var ${replacementName} = require(${stringifiedRequest});`
);
}
}
for (const item of importedMessages) {
const { importName, source } = item;
const stringifiedSourceRequest = stringifyRequest(loaderContext, source);

const importCode = importItems.join('\n');
code += esModule
? `import ${importName} from ${stringifiedSourceRequest};\n`
: `var ${importName} = require(${stringifiedSourceRequest});\n`;
}

return `// Imports\n${importCode}\n`;
return `// Imports\n${code}`;
}

export function getExportCode(html, replacers, options) {
let exportCode = html;
export function getModuleCode(html, replaceableMessages, codeOptions) {
let code = html;

if (!options.interpolate) {
exportCode = JSON.stringify(exportCode)
if (!codeOptions.interpolate) {
code = JSON.stringify(code)
// Invalid in JavaScript but valid HTML
.replace(/[\u2028\u2029]/g, (str) =>
str === '\u2029' ? '\\u2029' : '\\u2028'
);
}

for (const replacer of replacers) {
const { replacementName, unquoted } = replacer;
let replacersCode = '';

exportCode = exportCode.replace(
new RegExp(replacementName, 'g'),
() =>
`" + ___HTML_LOADER_GET_URL_IMPORT___(${replacementName}${
unquoted ? ', true' : ''
}) + "`
for (const item of replaceableMessages) {
const { importName, replacerName, unquoted } = item;

replacersCode += `var ${replacerName} = ${GET_SOURCE_FROM_IMPORT_NAME}(${importName}${
unquoted ? ', true' : ''
});\n`;

code = code.replace(
new RegExp(replacerName, 'g'),
() => `" + ${replacerName} + "`
);
}

if (options.esModule) {
return `// Exports\nexport default ${exportCode}`;
return `// Module\n${replacersCode}var code = ${code};\n`;
}

export function getExportCode(html, exportedMessages, codeOptions) {
if (codeOptions.esModule) {
return `// Exports\nexport default code;`;
}

return `// Exports\nmodule.exports = ${exportCode}`;
return `// Exports\nmodule.exports = code`;
}

0 comments on commit a17df49

Please sign in to comment.