-
-
Notifications
You must be signed in to change notification settings - Fork 561
/
dynamic-import-to-glob.js
127 lines (102 loc) · 3.56 KB
/
dynamic-import-to-glob.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import path from 'path';
import fastGlob from 'fast-glob';
export class VariableDynamicImportError extends Error {}
/* eslint-disable-next-line no-template-curly-in-string */
const example = 'For example: import(`./foo/${bar}.js`).';
function sanitizeString(str) {
if (str === '') return str;
if (str.includes('*')) {
throw new VariableDynamicImportError('A dynamic import cannot contain * characters.');
}
return fastGlob.escapePath(str);
}
function templateLiteralToGlob(node) {
let glob = '';
for (let i = 0; i < node.quasis.length; i += 1) {
glob += sanitizeString(node.quasis[i].value.raw);
if (node.expressions[i]) {
glob += expressionToGlob(node.expressions[i]);
}
}
return glob;
}
function callExpressionToGlob(node) {
const { callee } = node;
if (
callee.type === 'MemberExpression' &&
callee.property.type === 'Identifier' &&
callee.property.name === 'concat'
) {
return `${expressionToGlob(callee.object)}${node.arguments.map(expressionToGlob).join('')}`;
}
return '*';
}
function binaryExpressionToGlob(node) {
if (node.operator !== '+') {
throw new VariableDynamicImportError(`${node.operator} operator is not supported.`);
}
return `${expressionToGlob(node.left)}${expressionToGlob(node.right)}`;
}
function expressionToGlob(node) {
switch (node.type) {
case 'TemplateLiteral':
return templateLiteralToGlob(node);
case 'CallExpression':
return callExpressionToGlob(node);
case 'BinaryExpression':
return binaryExpressionToGlob(node);
case 'Literal': {
return sanitizeString(node.value);
}
default:
return '*';
}
}
const defaultProtocol = 'file:';
const ignoredProtocols = ['data:', 'http:', 'https:'];
function shouldIgnore(glob) {
const containsAsterisk = glob.includes('*');
const globURL = new URL(glob, defaultProtocol);
const containsIgnoredProtocol = ignoredProtocols.some(
(ignoredProtocol) => ignoredProtocol === globURL.protocol
);
return !containsAsterisk || containsIgnoredProtocol;
}
export function dynamicImportToGlob(node, sourceString) {
let glob = expressionToGlob(node);
if (shouldIgnore(glob)) {
return null;
}
glob = glob.replace(/\*\*/g, '*');
if (glob.startsWith('*')) {
throw new VariableDynamicImportError(
`invalid import "${sourceString}". It cannot be statically analyzed. Variable dynamic imports must start with ./ and be limited to a specific directory. ${example}`
);
}
if (glob.startsWith('/')) {
throw new VariableDynamicImportError(
`invalid import "${sourceString}". Variable absolute imports are not supported, imports must start with ./ in the static part of the import. ${example}`
);
}
if (!glob.startsWith('./') && !glob.startsWith('../')) {
throw new VariableDynamicImportError(
`invalid import "${sourceString}". Variable bare imports are not supported, imports must start with ./ in the static part of the import. ${example}`
);
}
// Disallow ./*.ext
const ownDirectoryStarExtension = /^\.\/\*\.[\w]+$/;
if (ownDirectoryStarExtension.test(glob)) {
throw new VariableDynamicImportError(
`${
`invalid import "${sourceString}". Variable imports cannot import their own directory, ` +
'place imports in a separate directory or make the import filename more specific. '
}${example}`
);
}
if (path.extname(glob) === '') {
throw new VariableDynamicImportError(
`invalid import "${sourceString}". A file extension must be included in the static part of the import. ${example}`
);
}
return glob;
}