Skip to content

Commit bef708f

Browse files
authoredJun 11, 2020
refactor: code
BREAKING CHANGE: minimum supported Node.js version is `10.13`
1 parent 754baa8 commit bef708f

19 files changed

+2067
-130
lines changed
 

‎README.md

+409-57
Large diffs are not rendered by default.

‎src/index.js

+45-48
Original file line numberDiff line numberDiff line change
@@ -3,74 +3,71 @@
33
Author Tobias Koppers @sokra
44
*/
55

6+
import { SourceNode, SourceMapConsumer } from 'source-map';
67
import { getOptions, getCurrentRequest } from 'loader-utils';
7-
// import validateOptions from 'schema-utils';
8-
//
9-
// import schema from './options.json';
8+
import validateOptions from 'schema-utils';
109

11-
const { SourceNode } = require('source-map');
12-
const { SourceMapConsumer } = require('source-map');
10+
import schema from './options.json';
1311

14-
const HEADER = '/*** IMPORTS FROM imports-loader ***/\n';
12+
import { getImports, renderImports } from './utils';
1513

1614
export default function loader(content, sourceMap) {
1715
const options = getOptions(this) || {};
1816

19-
// validateOptions(schema, options, 'Loader');
17+
validateOptions(schema, options, {
18+
name: 'Imports loader',
19+
baseDataPath: 'options',
20+
});
2021

22+
const type = options.type || 'module';
2123
const callback = this.async();
2224

23-
if (this.cacheable) this.cacheable();
24-
const query = options;
25-
const imports = [];
26-
const postfixes = [];
27-
Object.keys(query).forEach((name) => {
28-
let value;
29-
if (typeof query[name] === 'string' && query[name].substr(0, 1) === '>') {
30-
value = query[name].substr(1);
31-
} else {
32-
let mod = name;
33-
if (typeof query[name] === 'string') {
34-
mod = query[name];
35-
}
36-
value = `require(${JSON.stringify(mod)})`;
37-
}
38-
if (name === 'this') {
39-
imports.push('(function() {');
40-
postfixes.unshift(`}.call(${value}));`);
41-
} else if (name.indexOf('.') !== -1) {
42-
name.split('.').reduce((previous, current, index, names) => {
43-
const expr = previous + current;
44-
45-
if (previous.length === 0) {
46-
imports.push(`var ${expr} = (${current} || {});`);
47-
} else if (index < names.length - 1) {
48-
imports.push(`${expr} = ${expr} || {};`);
49-
} else {
50-
imports.push(`${expr} = ${value};`);
51-
}
52-
53-
return `${previous}${current}.`;
54-
}, '');
55-
} else {
56-
imports.push(`var ${name} = ${value};`);
25+
let importsCode = `/*** IMPORTS FROM imports-loader ***/\n`;
26+
27+
let imports;
28+
29+
if (options.imports) {
30+
try {
31+
imports = getImports(type, options.imports);
32+
} catch (error) {
33+
callback(error);
34+
35+
return;
5736
}
58-
});
59-
const prefix = `${HEADER}${imports.join('\n')}\n\n`;
60-
const postfix = `\n${postfixes.join('\n')}`;
61-
if (sourceMap) {
37+
38+
importsCode += Object.entries(imports).reduce((acc, item) => {
39+
return `${acc}${renderImports(this, type, item[1])}\n`;
40+
}, '');
41+
}
42+
43+
if (options.additionalCode) {
44+
importsCode += `\n${options.additionalCode}`;
45+
}
46+
47+
let codeAfterModule = '';
48+
49+
if (options.wrapper) {
50+
importsCode += '\n(function() {';
51+
codeAfterModule += `\n}.call(${options.wrapper.toString()}));`;
52+
}
53+
54+
if (this.sourceMap && sourceMap) {
6255
const node = SourceNode.fromStringWithSourceMap(
6356
content,
6457
new SourceMapConsumer(sourceMap)
6558
);
66-
node.prepend(prefix);
67-
node.add(postfix);
59+
60+
node.prepend(`${importsCode}\n`);
61+
node.add(codeAfterModule);
62+
6863
const result = node.toStringWithSourceMap({
6964
file: getCurrentRequest(this),
7065
});
66+
7167
callback(null, result.code, result.map.toJSON());
68+
7269
return;
7370
}
7471

75-
callback(null, `${prefix}${content}${postfix}`, sourceMap);
72+
callback(null, `${importsCode}\n${content}${codeAfterModule}`, sourceMap);
7673
}

‎src/options.json

+81-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,85 @@
11
{
2+
"definitions": {
3+
"ObjectPattern": {
4+
"type": "object",
5+
"additionalProperties": false,
6+
"properties": {
7+
"syntax": {
8+
"enum": ["default", "named", "namespace", "side-effect"]
9+
},
10+
"moduleName": {
11+
"type": "string",
12+
"minLength": 1
13+
},
14+
"name": {
15+
"type": "string",
16+
"minLength": 1
17+
},
18+
"alias": {
19+
"type": "string",
20+
"minLength": 1
21+
}
22+
}
23+
},
24+
"ImportsStringPattern": {
25+
"type": "string",
26+
"minLength": 1
27+
}
28+
},
229
"type": "object",
3-
"properties": {},
30+
"properties": {
31+
"type": {
32+
"enum": ["module", "commonjs"]
33+
},
34+
"imports": {
35+
"anyOf": [
36+
{
37+
"$ref": "#/definitions/ImportsStringPattern"
38+
},
39+
{
40+
"$ref": "#/definitions/ObjectPattern"
41+
},
42+
{
43+
"type": "array",
44+
"minItems": 1,
45+
"items": {
46+
"anyOf": [
47+
{
48+
"$ref": "#/definitions/ImportsStringPattern"
49+
},
50+
{
51+
"$ref": "#/definitions/ObjectPattern"
52+
}
53+
]
54+
}
55+
}
56+
]
57+
},
58+
"wrapper": {
59+
"anyOf": [
60+
{
61+
"type": "string",
62+
"minLength": 1
63+
},
64+
{
65+
"type": "array",
66+
"minItems": 1,
67+
"items": {
68+
"type": "string",
69+
"minLength": 1
70+
}
71+
}
72+
]
73+
},
74+
"additionalCode": {
75+
"type": "string",
76+
"minLength": 1
77+
}
78+
},
79+
"anyOf": [
80+
{ "required": ["imports"] },
81+
{ "required": ["wrapper"] },
82+
{ "required": ["additionalCode"] }
83+
],
484
"additionalProperties": false
585
}

‎src/utils.js

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { stringifyRequest } from 'loader-utils';
2+
3+
function resolveImports(type, item) {
4+
let result;
5+
6+
if (typeof item === 'string') {
7+
const splittedItem = item.split(' ');
8+
9+
if (splittedItem.length > 4) {
10+
throw new Error(`Invalid "${item}" for import`);
11+
}
12+
13+
if (splittedItem.length === 1) {
14+
result = {
15+
type,
16+
syntax: 'default',
17+
moduleName: splittedItem[0],
18+
name: splittedItem[0],
19+
// eslint-disable-next-line no-undefined
20+
alias: undefined,
21+
};
22+
} else {
23+
result = {
24+
syntax: splittedItem[0],
25+
moduleName: splittedItem[1],
26+
name: splittedItem[2],
27+
// eslint-disable-next-line no-undefined
28+
alias: splittedItem[3] ? splittedItem[3] : undefined,
29+
};
30+
}
31+
} else {
32+
result = { syntax: 'default', ...item };
33+
34+
if (result.syntax === 'default' && !result.name) {
35+
result.name = result.moduleName;
36+
}
37+
}
38+
39+
if (!result.moduleName) {
40+
throw new Error(
41+
`The import should have "moduleName" option in "${item}" value`
42+
);
43+
}
44+
45+
if (
46+
['default', 'side-effect'].includes(result.syntax) &&
47+
typeof result.alias !== 'undefined'
48+
) {
49+
throw new Error(
50+
`The "${result.syntax}" syntax can't have "${result.alias}" alias in "${item}" value`
51+
);
52+
}
53+
54+
if (
55+
['side-effect'].includes(result.syntax) &&
56+
typeof result.name !== 'undefined'
57+
) {
58+
throw new Error(
59+
`The "${result.syntax}" syntax can't have "${result.name}" name in "${item}" value`
60+
);
61+
}
62+
63+
if (['namespace'].includes(result.syntax) && type === 'commonjs') {
64+
throw new Error(
65+
`The "commonjs" type not support "namespace" syntax import in "${item}" value`
66+
);
67+
}
68+
69+
if (
70+
['namespace', 'named'].includes(result.syntax) &&
71+
typeof result.name === 'undefined'
72+
) {
73+
throw new Error(
74+
`The "${result.syntax}" syntax should have "name" option in "${item}" value`
75+
);
76+
}
77+
78+
return result;
79+
}
80+
81+
function getImports(type, imports) {
82+
let result = [];
83+
84+
if (typeof imports === 'string') {
85+
result.push(resolveImports(type, imports));
86+
} else {
87+
result = [].concat(imports).map((item) => resolveImports(type, item));
88+
}
89+
90+
const sortedResults = {};
91+
92+
for (const item of result) {
93+
if (!sortedResults[item.moduleName]) {
94+
sortedResults[item.moduleName] = [];
95+
}
96+
97+
sortedResults[item.moduleName].push(item);
98+
}
99+
100+
for (const item of Object.entries(sortedResults)) {
101+
const defaultImports = item[1].filter(
102+
(entry) => entry.syntax === 'default'
103+
);
104+
const namespaceImports = item[1].filter(
105+
(entry) => entry.syntax === 'namespace'
106+
);
107+
const sideEffectImports = item[1].filter(
108+
(entry) => entry.syntax === 'side-effect'
109+
);
110+
111+
[defaultImports, namespaceImports, sideEffectImports].forEach(
112+
(importsSyntax) => {
113+
if (importsSyntax.length > 1) {
114+
const [{ syntax }] = importsSyntax;
115+
116+
throw new Error(
117+
`The "${syntax}" syntax format can't have multiple import in "${item}" value`
118+
);
119+
}
120+
}
121+
);
122+
}
123+
124+
return sortedResults;
125+
}
126+
127+
function renderImports(loaderContext, type, imports) {
128+
const [{ moduleName }] = imports;
129+
const defaultImports = imports.filter((item) => item.syntax === 'default');
130+
const namedImports = imports.filter((item) => item.syntax === 'named');
131+
const namespaceImports = imports.filter(
132+
(item) => item.syntax === 'namespace'
133+
);
134+
const sideEffectImports = imports.filter(
135+
(item) => item.syntax === 'side-effect'
136+
);
137+
const isModule = type === 'module';
138+
139+
// 1. Import-side-effect
140+
if (sideEffectImports.length > 0) {
141+
return isModule
142+
? `import ${stringifyRequest(loaderContext, moduleName)};`
143+
: `require(${stringifyRequest(loaderContext, moduleName)});`;
144+
}
145+
146+
let code = isModule ? 'import' : '';
147+
148+
// 2. Default import
149+
if (defaultImports.length > 0) {
150+
const [{ name }] = defaultImports;
151+
152+
code += isModule
153+
? ` ${name}`
154+
: `var ${name} = require(${stringifyRequest(
155+
loaderContext,
156+
moduleName
157+
)});`;
158+
}
159+
160+
// 3. Namespace import
161+
if (namespaceImports.length > 0) {
162+
if (defaultImports.length > 0) {
163+
code += `,`;
164+
}
165+
166+
const [{ name }] = namespaceImports;
167+
168+
code += ` * as ${name}`;
169+
}
170+
171+
// 4. Named import
172+
if (namedImports.length > 0) {
173+
if (defaultImports.length > 0) {
174+
code += isModule ? ', { ' : '\nvar { ';
175+
} else {
176+
code += isModule ? ' { ' : 'var { ';
177+
}
178+
179+
namedImports.forEach((namedImport, i) => {
180+
const comma = i > 0 ? ', ' : '';
181+
const { name, alias } = namedImport;
182+
const sep = isModule ? ' as ' : ': ';
183+
184+
code += alias ? `${comma}${name}${sep}${alias}` : `${comma}${name}`;
185+
});
186+
187+
code += isModule
188+
? ' }'
189+
: ` } = require(${stringifyRequest(loaderContext, moduleName)});`;
190+
}
191+
192+
if (!isModule) {
193+
return code;
194+
}
195+
196+
code += ` from ${stringifyRequest(loaderContext, moduleName)};`;
197+
198+
return code;
199+
}
200+
201+
export { getImports, renderImports };

‎test/__snapshots__/loader.test.js.snap

+414-7
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`validate options should throw an error on the "additionalCode" option with "/test/" value 1`] = `
4+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
5+
- options.additionalCode should be a non-empty string."
6+
`;
7+
8+
exports[`validate options should throw an error on the "additionalCode" option with "[""]" value 1`] = `
9+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
10+
- options.additionalCode should be a non-empty string."
11+
`;
12+
13+
exports[`validate options should throw an error on the "additionalCode" option with "[]" value 1`] = `
14+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
15+
- options.additionalCode should be a non-empty string."
16+
`;
17+
18+
exports[`validate options should throw an error on the "additionalCode" option with "{}" value 1`] = `
19+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
20+
- options.additionalCode should be a non-empty string."
21+
`;
22+
23+
exports[`validate options should throw an error on the "additionalCode" option with "false" value 1`] = `
24+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
25+
- options.additionalCode should be a non-empty string."
26+
`;
27+
28+
exports[`validate options should throw an error on the "additionalCode" option with "true" value 1`] = `
29+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
30+
- options.additionalCode should be a non-empty string."
31+
`;
32+
33+
exports[`validate options should throw an error on the "imports" option with "" value 1`] = `
34+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
35+
- options.imports should be an non-empty string."
36+
`;
37+
38+
exports[`validate options should throw an error on the "imports" option with "/test/" value 1`] = `"The import should have \\"moduleName\\" option in \\"/test/\\" value"`;
39+
40+
exports[`validate options should throw an error on the "imports" option with "[""]" value 1`] = `
41+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
42+
- options.imports[0] should be an non-empty string."
43+
`;
44+
45+
exports[`validate options should throw an error on the "imports" option with "[]" value 1`] = `
46+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
47+
- options.imports should be an non-empty array."
48+
`;
49+
50+
exports[`validate options should throw an error on the "imports" option with "{"syntax":"default","moduleName":"jQuery","name":"lib","alias":"lib_alias"}" value 1`] = `"The \\"default\\" syntax can't have \\"lib_alias\\" alias in \\"[object Object]\\" value"`;
51+
52+
exports[`validate options should throw an error on the "imports" option with "{"type":"string","moduleName":"jQuery","list":false}" value 1`] = `
53+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
54+
- options.imports should be one of these:
55+
non-empty string | object { syntax?, moduleName?, name?, alias? } | [non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item)
56+
Details:
57+
* options.imports has an unknown property 'type'. These properties are valid:
58+
object { syntax?, moduleName?, name?, alias? }
59+
* options.imports has an unknown property 'list'. These properties are valid:
60+
object { syntax?, moduleName?, name?, alias? }"
61+
`;
62+
63+
exports[`validate options should throw an error on the "imports" option with "{}" value 1`] = `"The import should have \\"moduleName\\" option in \\"[object Object]\\" value"`;
64+
65+
exports[`validate options should throw an error on the "imports" option with "false" value 1`] = `
66+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
67+
- options.imports should be one of these:
68+
non-empty string | object { syntax?, moduleName?, name?, alias? } | [non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item)
69+
Details:
70+
* options.imports should be a non-empty string.
71+
* options.imports should be an object:
72+
object { syntax?, moduleName?, name?, alias? }
73+
* options.imports should be an array:
74+
[non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item)"
75+
`;
76+
77+
exports[`validate options should throw an error on the "imports" option with "true" value 1`] = `
78+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
79+
- options.imports should be one of these:
80+
non-empty string | object { syntax?, moduleName?, name?, alias? } | [non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item)
81+
Details:
82+
* options.imports should be a non-empty string.
83+
* options.imports should be an object:
84+
object { syntax?, moduleName?, name?, alias? }
85+
* options.imports should be an array:
86+
[non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item)"
87+
`;
88+
89+
exports[`validate options should throw an error on the "type" option with "" value 1`] = `
90+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
91+
- options.type should be one of these:
92+
\\"module\\" | \\"commonjs\\""
93+
`;
94+
95+
exports[`validate options should throw an error on the "type" option with "[]" value 1`] = `
96+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
97+
- options.type should be one of these:
98+
\\"module\\" | \\"commonjs\\""
99+
`;
100+
101+
exports[`validate options should throw an error on the "type" option with "{}" value 1`] = `
102+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
103+
- options.type should be one of these:
104+
\\"module\\" | \\"commonjs\\""
105+
`;
106+
107+
exports[`validate options should throw an error on the "type" option with "string" value 1`] = `
108+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
109+
- options.type should be one of these:
110+
\\"module\\" | \\"commonjs\\""
111+
`;
112+
113+
exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
114+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
115+
- options should be one of these:
116+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
117+
Details:
118+
* options has an unknown property 'unknown'. These properties are valid:
119+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
120+
* options misses the property 'imports' | should be any non-object.
121+
* options misses the property 'wrapper' | should be any non-object.
122+
* options misses the property 'additionalCode' | should be any non-object."
123+
`;
124+
125+
exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
126+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
127+
- options should be one of these:
128+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
129+
Details:
130+
* options has an unknown property 'unknown'. These properties are valid:
131+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
132+
* options misses the property 'imports' | should be any non-object.
133+
* options misses the property 'wrapper' | should be any non-object.
134+
* options misses the property 'additionalCode' | should be any non-object."
135+
`;
136+
137+
exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
138+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
139+
- options should be one of these:
140+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
141+
Details:
142+
* options has an unknown property 'unknown'. These properties are valid:
143+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
144+
* options misses the property 'imports' | should be any non-object.
145+
* options misses the property 'wrapper' | should be any non-object.
146+
* options misses the property 'additionalCode' | should be any non-object."
147+
`;
148+
149+
exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
150+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
151+
- options should be one of these:
152+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
153+
Details:
154+
* options has an unknown property 'unknown'. These properties are valid:
155+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
156+
* options misses the property 'imports' | should be any non-object.
157+
* options misses the property 'wrapper' | should be any non-object.
158+
* options misses the property 'additionalCode' | should be any non-object."
159+
`;
160+
161+
exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
162+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
163+
- options should be one of these:
164+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
165+
Details:
166+
* options has an unknown property 'unknown'. These properties are valid:
167+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
168+
* options misses the property 'imports' | should be any non-object.
169+
* options misses the property 'wrapper' | should be any non-object.
170+
* options misses the property 'additionalCode' | should be any non-object."
171+
`;
172+
173+
exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
174+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
175+
- options should be one of these:
176+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
177+
Details:
178+
* options has an unknown property 'unknown'. These properties are valid:
179+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
180+
* options misses the property 'imports' | should be any non-object.
181+
* options misses the property 'wrapper' | should be any non-object.
182+
* options misses the property 'additionalCode' | should be any non-object."
183+
`;
184+
185+
exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
186+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
187+
- options should be one of these:
188+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
189+
Details:
190+
* options has an unknown property 'unknown'. These properties are valid:
191+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
192+
* options misses the property 'imports' | should be any non-object.
193+
* options misses the property 'wrapper' | should be any non-object.
194+
* options misses the property 'additionalCode' | should be any non-object."
195+
`;
196+
197+
exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
198+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
199+
- options should be one of these:
200+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
201+
Details:
202+
* options has an unknown property 'unknown'. These properties are valid:
203+
object { imports, … } | object { wrapper, … } | object { additionalCode, … }
204+
* options misses the property 'imports' | should be any non-object.
205+
* options misses the property 'wrapper' | should be any non-object.
206+
* options misses the property 'additionalCode' | should be any non-object."
207+
`;
208+
209+
exports[`validate options should throw an error on the "wrapper" option with "[""]" value 1`] = `
210+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
211+
- options.wrapper[0] should be an non-empty string."
212+
`;
213+
214+
exports[`validate options should throw an error on the "wrapper" option with "[]" value 1`] = `
215+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
216+
- options.wrapper should be an non-empty array."
217+
`;
218+
219+
exports[`validate options should throw an error on the "wrapper" option with "false" value 1`] = `
220+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
221+
- options.wrapper should be one of these:
222+
non-empty string | [non-empty string, ...] (should not have fewer than 1 item)
223+
Details:
224+
* options.wrapper should be a non-empty string.
225+
* options.wrapper should be an array:
226+
[non-empty string, ...] (should not have fewer than 1 item)"
227+
`;
228+
229+
exports[`validate options should throw an error on the "wrapper" option with "true" value 1`] = `
230+
"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.
231+
- options.wrapper should be one of these:
232+
non-empty string | [non-empty string, ...] (should not have fewer than 1 item)
233+
Details:
234+
* options.wrapper should be a non-empty string.
235+
* options.wrapper should be an array:
236+
[non-empty string, ...] (should not have fewer than 1 item)"
237+
`;

‎test/cjs.test.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import src from '../src';
2+
import cjs from '../src/cjs';
3+
4+
describe('cjs', () => {
5+
it('should exported', () => {
6+
expect(cjs).toEqual(src);
7+
});
8+
});

‎test/fixtures/inline-broken.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('../../src/cjs.js?imports=named%20lib_2%20name%20alias%20something!./some-library.js');

‎test/fixtures/inline.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('../../src/cjs.js?imports[]=lib_1&imports[]=lib_2!./some-library.js');

‎test/fixtures/inline2.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('../../src/cjs.js?type=commonjs&imports[]=default%20lib_2%20lib_2_all&imports[]=named%20lib_2%20lib_2_method%20lib_2_method_alias!./some-library.js');

‎test/fixtures/inline3.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require('../../src/cjs.js?wrapper=window&imports=default%20lib_2%20$!./some-library.js');
2+

‎test/fixtures/lib_1.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function lib() {}

‎test/fixtures/lib_2.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function lib() {}

‎test/fixtures/lib_3.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function lib() {}

‎test/fixtures/lib_4.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function lib() {}

‎test/helpers/getCompiler.js

+29-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,26 @@ import path from 'path';
33
import webpack from 'webpack';
44
import { createFsFromVolume, Volume } from 'memfs';
55

6-
export default (fixture, loaderOptions = {}, config = {}) => {
6+
export default (
7+
fixture,
8+
loaderOptions = {},
9+
config = {},
10+
disableLoader = false
11+
) => {
12+
const loaders = [];
13+
14+
if (!disableLoader) {
15+
loaders.push({
16+
test: path.resolve(__dirname, '../fixtures', fixture),
17+
use: [
18+
{
19+
loader: path.resolve(__dirname, '../../src'),
20+
options: loaderOptions || {},
21+
},
22+
],
23+
});
24+
}
25+
726
const fullConfig = {
827
mode: 'development',
928
devtool: config.devtool || false,
@@ -17,19 +36,17 @@ export default (fixture, loaderOptions = {}, config = {}) => {
1736
// libraryTarget: 'var',
1837
},
1938
module: {
20-
rules: [
21-
{
22-
test: /.js/i,
23-
rules: [
24-
{
25-
loader: path.resolve(__dirname, '../../src'),
26-
options: loaderOptions || {},
27-
},
28-
],
29-
},
30-
],
39+
rules: loaders,
3140
},
3241
plugins: [],
42+
resolve: {
43+
alias: {
44+
lib_1: path.resolve(__dirname, '../', 'fixtures', 'lib_1'),
45+
lib_2: path.resolve(__dirname, '../', 'fixtures', 'lib_2'),
46+
lib_3: path.resolve(__dirname, '../', 'fixtures', 'lib_3'),
47+
lib_4: path.resolve(__dirname, '../', 'fixtures', 'lib_4'),
48+
},
49+
},
3350
...config,
3451
};
3552

‎test/helpers/getModuleSource.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export default (name, stats) => {
22
const { modules } = stats.toJson({ source: true });
3-
const module = modules.find((m) => m.name === name);
3+
4+
const module = modules.find((m) => m.name.indexOf(name) !== -1);
45

56
return module.source;
67
};

‎test/loader.test.js

+503-4
Large diffs are not rendered by default.

‎test/validate-options.test.js

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { getCompiler, compile } from './helpers';
2+
3+
describe('validate options', () => {
4+
const tests = {
5+
type: {
6+
success: ['module', 'commonjs'],
7+
failure: ['string', '', {}, []],
8+
},
9+
imports: {
10+
success: [
11+
'lib_1',
12+
'globalObject1.foo',
13+
['globalObject1'],
14+
['globalObject1.foo'],
15+
{
16+
moduleName: 'jQuery',
17+
name: '$',
18+
},
19+
{
20+
syntax: 'named',
21+
moduleName: 'jQuery',
22+
name: 'lib',
23+
alias: 'lib_alias',
24+
},
25+
{
26+
syntax: 'default',
27+
moduleName: 'jQuery',
28+
name: 'lib',
29+
},
30+
],
31+
failure: [
32+
false,
33+
true,
34+
/test/,
35+
'',
36+
[],
37+
[''],
38+
{},
39+
{
40+
type: 'string',
41+
moduleName: 'jQuery',
42+
list: false,
43+
},
44+
{
45+
syntax: 'default',
46+
moduleName: 'jQuery',
47+
name: 'lib',
48+
alias: 'lib_alias',
49+
},
50+
],
51+
},
52+
wrapper: {
53+
success: ['window', ['window', 'document']],
54+
failure: [false, true, [], ['']],
55+
},
56+
additionalCode: {
57+
success: ['var x = 2;'],
58+
failure: [false, true, /test/, [], [''], {}],
59+
},
60+
unknown: {
61+
success: [],
62+
failure: [1, true, false, 'test', /test/, [], {}, { foo: 'bar' }],
63+
},
64+
};
65+
66+
function stringifyValue(value) {
67+
if (
68+
Array.isArray(value) ||
69+
(value && typeof value === 'object' && value.constructor === Object)
70+
) {
71+
return JSON.stringify(value);
72+
}
73+
74+
return value;
75+
}
76+
77+
async function createTestCase(key, value, type) {
78+
it(`should ${
79+
type === 'success' ? 'successfully validate' : 'throw an error on'
80+
} the "${key}" option with "${stringifyValue(value)}" value`, async () => {
81+
let compiler;
82+
83+
if (key === 'type') {
84+
compiler = getCompiler('some-library.js', {
85+
[key]: value,
86+
wrapper: 'window',
87+
});
88+
} else {
89+
compiler = getCompiler('some-library.js', {
90+
[key]: value,
91+
});
92+
}
93+
94+
let stats;
95+
96+
try {
97+
stats = await compile(compiler);
98+
} finally {
99+
if (type === 'success') {
100+
const validationErrors = [];
101+
102+
stats.compilation.errors.forEach((error) => {
103+
if (error.message.indexOf('ValidationError') !== -1) {
104+
validationErrors.push(error);
105+
}
106+
});
107+
expect(validationErrors.length).toBe(0);
108+
} else if (type === 'failure') {
109+
const {
110+
compilation: { errors },
111+
} = stats;
112+
113+
expect(errors).toHaveLength(1);
114+
expect(() => {
115+
throw new Error(errors[0].error.message);
116+
}).toThrowErrorMatchingSnapshot();
117+
}
118+
}
119+
});
120+
}
121+
122+
for (const [key, values] of Object.entries(tests)) {
123+
for (const type of Object.keys(values)) {
124+
for (const value of values[type]) {
125+
createTestCase(key, value, type);
126+
}
127+
}
128+
}
129+
});

0 commit comments

Comments
 (0)
Please sign in to comment.