Skip to content

Commit a17df49

Browse files
fix: reduce import/require count
1 parent d0b0150 commit a17df49

15 files changed

+1145
-2421
lines changed

package-lock.json

Lines changed: 349 additions & 523 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"htmlparser2": "^4.1.0",
4949
"loader-utils": "^1.4.0",
5050
"parse-srcset": "^1.0.2",
51-
"schema-utils": "^2.6.4"
51+
"schema-utils": "^2.6.5"
5252
},
5353
"devDependencies": {
5454
"@babel/cli": "^7.8.4",
@@ -60,7 +60,7 @@
6060
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
6161
"babel-jest": "^25.1.0",
6262
"commitlint-azure-pipelines-cli": "^1.0.3",
63-
"cross-env": "^7.0.1",
63+
"cross-env": "^7.0.2",
6464
"del": "^5.1.0",
6565
"del-cli": "^3.0.0",
6666
"es-check": "^5.1.0",
@@ -72,7 +72,7 @@
7272
"jest": "^25.1.0",
7373
"jest-junit": "^10.0.0",
7474
"lint-staged": "^10.0.8",
75-
"memfs": "^3.0.4",
75+
"memfs": "^3.1.1",
7676
"npm-run-all": "^4.1.5",
7777
"prettier": "^1.19.1",
7878
"standard-version": "^7.1.0",

src/index.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { getOptions } from 'loader-utils';
22
import validateOptions from 'schema-utils';
33

4-
import { attributePlugin, interpolatePlugin, minimizerPlugin } from './plugins';
4+
import { sourcePlugin, interpolatePlugin, minimizerPlugin } from './plugins';
55
import Warning from './Warning';
66

77
import {
88
pluginRunner,
99
isProductionMode,
1010
getImportCode,
11+
getModuleCode,
1112
getExportCode,
1213
} from './utils';
1314

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

2930
if (attributes) {
30-
plugins.push(attributePlugin(options));
31+
plugins.push(sourcePlugin(options));
3132
}
3233

3334
const minimize =
@@ -57,19 +58,29 @@ export default function htmlLoader(content) {
5758
this.emitError(new Error(error));
5859
}
5960

60-
const replacers = [];
61+
const importedMessages = [];
62+
const replaceableMessages = [];
63+
const exportedMessages = [];
6164

6265
for (const message of messages) {
6366
// eslint-disable-next-line default-case
6467
switch (message.type) {
68+
case 'import':
69+
importedMessages.push(message.value);
70+
break;
6571
case 'replacer':
66-
replacers.push(message.value);
72+
replaceableMessages.push(message.value);
73+
break;
74+
case 'exports':
75+
exportedMessages.push(message.value);
6776
break;
6877
}
6978
}
7079

71-
const importCode = getImportCode(this, html, replacers, options);
72-
const exportCode = getExportCode(html, replacers, options);
80+
const codeOptions = { ...options, loaderContext: this };
81+
const importCode = getImportCode(html, importedMessages, codeOptions);
82+
const moduleCode = getModuleCode(html, replaceableMessages, codeOptions);
83+
const exportCode = getExportCode(html, exportedMessages, codeOptions);
7384

74-
return `${importCode}${exportCode};`;
85+
return `${importCode}${moduleCode}${exportCode}`;
7586
}

src/plugins/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import attributePlugin from './attribute-plugin';
1+
import sourcePlugin from './source-plugin';
22
import interpolatePlugin from './interpolate-plugin';
33
import minimizerPlugin from './minimizer-plugin';
44

5-
export { attributePlugin, interpolatePlugin, minimizerPlugin };
5+
export { sourcePlugin, interpolatePlugin, minimizerPlugin };

src/plugins/attribute-plugin.js renamed to src/plugins/source-plugin.js

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { parse } from 'url';
22

33
import { Parser } from 'htmlparser2';
4-
import { isUrlRequest } from 'loader-utils';
4+
import { isUrlRequest, urlToRequest } from 'loader-utils';
55

66
function isASCIIWhitespace(character) {
77
return (
@@ -511,8 +511,9 @@ export default (options) =>
511511
parser.write(html);
512512
parser.end();
513513

514+
const importsMap = new Map();
515+
const replacersMap = new Map();
514516
let offset = 0;
515-
let index = 0;
516517

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

527-
const replacementName = `___HTML_LOADER_IDENT_${index}___`;
528+
const importKey = urlToRequest(
529+
decodeURIComponent(source.value),
530+
options.root
531+
);
532+
let importName = importsMap.get(importKey);
533+
534+
if (!importName) {
535+
importName = `___HTML_LOADER_IMPORT_${importsMap.size}___`;
536+
importsMap.set(importKey, importName);
537+
538+
result.messages.push({
539+
type: 'import',
540+
value: {
541+
type: 'source',
542+
source: importKey,
543+
importName,
544+
},
545+
});
546+
}
528547

529-
result.messages.push({
530-
type: 'replacer',
531-
value: {
532-
type: 'attribute',
533-
replacementName,
534-
source: decodeURIComponent(source.value),
535-
unquoted,
536-
},
537-
});
548+
const replacerKey = JSON.stringify({ importKey, unquoted });
549+
let replacerName = replacersMap.get(replacerKey);
550+
551+
if (!replacerName) {
552+
replacerName = `___HTML_LOADER_REPLACER_${replacersMap.size}___`;
553+
replacersMap.set(replacerKey, replacerName);
554+
555+
result.messages.push({
556+
type: 'replacer',
557+
value: {
558+
type: 'source',
559+
importName,
560+
replacerName,
561+
unquoted,
562+
},
563+
});
564+
}
538565

539566
// eslint-disable-next-line no-param-reassign
540567
html =
541568
html.substr(0, startIndex + offset) +
542-
replacementName +
569+
replacerName +
543570
html.substr(startIndex + value.length + offset);
544571

545-
offset += replacementName.length - value.length;
546-
index += 1;
572+
offset += replacerName.length - value.length;
547573
}
548574

549575
return html;

src/utils.js

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { urlToRequest, stringifyRequest } from 'loader-utils';
1+
import { stringifyRequest } from 'loader-utils';
2+
3+
const GET_SOURCE_FROM_IMPORT_NAME = '___HTML_LOADER_GET_SOURCE_FROM_IMPORT___';
24

35
export function pluginRunner(plugins) {
46
return {
@@ -21,70 +23,66 @@ export function isProductionMode(loaderContext) {
2123
return loaderContext.mode === 'production' || !loaderContext.mode;
2224
}
2325

24-
export function getImportCode(loaderContext, html, replacers, options) {
25-
if (replacers.length === 0) {
26+
export function getImportCode(html, importedMessages, codeOptions) {
27+
if (importedMessages.length === 0) {
2628
return '';
2729
}
2830

29-
const importItems = [];
30-
31-
importItems.push(
32-
options.esModule
33-
? `import ___HTML_LOADER_GET_URL_IMPORT___ from ${stringifyRequest(
34-
loaderContext,
35-
require.resolve('./runtime/getUrl.js')
36-
)}`
37-
: `var ___HTML_LOADER_GET_URL_IMPORT___ = require(${stringifyRequest(
38-
loaderContext,
39-
require.resolve('./runtime/getUrl.js')
40-
)});`
31+
const { loaderContext, esModule } = codeOptions;
32+
const stringifiedHelperRequest = stringifyRequest(
33+
loaderContext,
34+
require.resolve('./runtime/getUrl.js')
4135
);
4236

43-
for (const replacer of replacers) {
44-
const { replacementName, source } = replacer;
45-
const request = urlToRequest(source, options.root);
46-
const stringifiedRequest = stringifyRequest(loaderContext, request);
37+
let code = esModule
38+
? `import ${GET_SOURCE_FROM_IMPORT_NAME} from ${stringifiedHelperRequest};\n`
39+
: `var ${GET_SOURCE_FROM_IMPORT_NAME} = require(${stringifiedHelperRequest});\n`;
4740

48-
if (options.esModule) {
49-
importItems.push(`import ${replacementName} from ${stringifiedRequest};`);
50-
} else {
51-
importItems.push(
52-
`var ${replacementName} = require(${stringifiedRequest});`
53-
);
54-
}
55-
}
41+
for (const item of importedMessages) {
42+
const { importName, source } = item;
43+
const stringifiedSourceRequest = stringifyRequest(loaderContext, source);
5644

57-
const importCode = importItems.join('\n');
45+
code += esModule
46+
? `import ${importName} from ${stringifiedSourceRequest};\n`
47+
: `var ${importName} = require(${stringifiedSourceRequest});\n`;
48+
}
5849

59-
return `// Imports\n${importCode}\n`;
50+
return `// Imports\n${code}`;
6051
}
6152

62-
export function getExportCode(html, replacers, options) {
63-
let exportCode = html;
53+
export function getModuleCode(html, replaceableMessages, codeOptions) {
54+
let code = html;
6455

65-
if (!options.interpolate) {
66-
exportCode = JSON.stringify(exportCode)
56+
if (!codeOptions.interpolate) {
57+
code = JSON.stringify(code)
6758
// Invalid in JavaScript but valid HTML
6859
.replace(/[\u2028\u2029]/g, (str) =>
6960
str === '\u2029' ? '\\u2029' : '\\u2028'
7061
);
7162
}
7263

73-
for (const replacer of replacers) {
74-
const { replacementName, unquoted } = replacer;
64+
let replacersCode = '';
7565

76-
exportCode = exportCode.replace(
77-
new RegExp(replacementName, 'g'),
78-
() =>
79-
`" + ___HTML_LOADER_GET_URL_IMPORT___(${replacementName}${
80-
unquoted ? ', true' : ''
81-
}) + "`
66+
for (const item of replaceableMessages) {
67+
const { importName, replacerName, unquoted } = item;
68+
69+
replacersCode += `var ${replacerName} = ${GET_SOURCE_FROM_IMPORT_NAME}(${importName}${
70+
unquoted ? ', true' : ''
71+
});\n`;
72+
73+
code = code.replace(
74+
new RegExp(replacerName, 'g'),
75+
() => `" + ${replacerName} + "`
8276
);
8377
}
8478

85-
if (options.esModule) {
86-
return `// Exports\nexport default ${exportCode}`;
79+
return `// Module\n${replacersCode}var code = ${code};\n`;
80+
}
81+
82+
export function getExportCode(html, exportedMessages, codeOptions) {
83+
if (codeOptions.esModule) {
84+
return `// Exports\nexport default code;`;
8785
}
8886

89-
return `// Exports\nmodule.exports = ${exportCode}`;
87+
return `// Exports\nmodule.exports = code`;
9088
}

0 commit comments

Comments
 (0)