1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | exports.scanImportsFromFiles = exports.scanImports = exports.scanDepList = exports.matchDynamicImportValue = exports.getInstallTargets = void 0;
|
7 | const es_module_lexer_1 = require("es-module-lexer");
|
8 | const glob_1 = __importDefault(require("glob"));
|
9 | const path_1 = __importDefault(require("path"));
|
10 | const strip_comments_1 = __importDefault(require("strip-comments"));
|
11 | const url_1 = __importDefault(require("url"));
|
12 | const logger_1 = require("./logger");
|
13 | const util_1 = require("./util");
|
14 | const p_queue_1 = __importDefault(require("p-queue"));
|
15 | const CONCURRENT_FILE_READS = 1000;
|
16 |
|
17 |
|
18 | const BARE_SPECIFIER_REGEX = /^[@\w](?!.*(:\/\/))/;
|
19 | const ESM_IMPORT_REGEX = /import(?:["'\s]*([\w*${}\n\r\t, ]+)\s*from\s*)?\s*["'](.*?)["']/gm;
|
20 | const ESM_DYNAMIC_IMPORT_REGEX = /(?<!\.)\bimport\((?:['"].+['"]|`[^$]+`)\)/gm;
|
21 | const HAS_NAMED_IMPORTS_REGEX = /^[\w\s\,]*\{(.*)\}/s;
|
22 | const STRIP_AS = /\s+as\s+.*/;
|
23 | const DEFAULT_IMPORT_REGEX = /import\s+(\w)+(,\s\{[\w\s]*\})?\s+from/s;
|
24 | function createInstallTarget(specifier, all = true) {
|
25 | return {
|
26 | specifier,
|
27 | all,
|
28 | default: false,
|
29 | namespace: false,
|
30 | named: [],
|
31 | };
|
32 | }
|
33 | async function getInstallTargets(config, knownEntrypoints, scannedFiles) {
|
34 | let installTargets = [];
|
35 | if (knownEntrypoints.length > 0) {
|
36 | installTargets.push(...scanDepList(knownEntrypoints, config.root));
|
37 | }
|
38 |
|
39 | if (scannedFiles) {
|
40 | installTargets.push(...(await scanImportsFromFiles(scannedFiles, config)));
|
41 | }
|
42 | else {
|
43 | installTargets.push(...(await scanImports(process.env.NODE_ENV === 'test', config)));
|
44 | }
|
45 | return installTargets;
|
46 | }
|
47 | exports.getInstallTargets = getInstallTargets;
|
48 | function matchDynamicImportValue(importStatement) {
|
49 | const matched = strip_comments_1.default(importStatement).match(/^\s*('([^']+)'|"([^"]+)")\s*$/m);
|
50 | return (matched === null || matched === void 0 ? void 0 : matched[2]) || (matched === null || matched === void 0 ? void 0 : matched[3]) || null;
|
51 | }
|
52 | exports.matchDynamicImportValue = matchDynamicImportValue;
|
53 | function getWebModuleSpecifierFromCode(code, imp) {
|
54 |
|
55 | if (imp.d === -2) {
|
56 | return null;
|
57 | }
|
58 |
|
59 | if (imp.d === -1) {
|
60 | return code.substring(imp.s, imp.e);
|
61 | }
|
62 |
|
63 | const importStatement = code.substring(imp.s, imp.e);
|
64 | return matchDynamicImportValue(importStatement);
|
65 | }
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | function parseWebModuleSpecifier(specifier) {
|
71 | if (!specifier) {
|
72 | return null;
|
73 | }
|
74 |
|
75 | if (BARE_SPECIFIER_REGEX.test(specifier)) {
|
76 | return specifier;
|
77 | }
|
78 | return null;
|
79 | }
|
80 | function parseImportStatement(code, imp) {
|
81 | const webModuleSpecifier = parseWebModuleSpecifier(getWebModuleSpecifierFromCode(code, imp));
|
82 | if (!webModuleSpecifier) {
|
83 | return null;
|
84 | }
|
85 | const importStatement = strip_comments_1.default(code.substring(imp.ss, imp.se));
|
86 | if (/^import\s+type/.test(importStatement)) {
|
87 | return null;
|
88 | }
|
89 | const isDynamicImport = imp.d > -1;
|
90 | const hasDefaultImport = !isDynamicImport && DEFAULT_IMPORT_REGEX.test(importStatement);
|
91 | const hasNamespaceImport = !isDynamicImport && importStatement.includes('*');
|
92 | const namedImports = (importStatement.match(HAS_NAMED_IMPORTS_REGEX) || [, ''])[1]
|
93 | .split(',')
|
94 | .map((name) => name.replace(STRIP_AS, '').trim())
|
95 | .filter(util_1.isTruthy);
|
96 | return {
|
97 | specifier: webModuleSpecifier,
|
98 | all: isDynamicImport || (!hasDefaultImport && !hasNamespaceImport && namedImports.length === 0),
|
99 | default: hasDefaultImport,
|
100 | namespace: hasNamespaceImport,
|
101 | named: namedImports,
|
102 | };
|
103 | }
|
104 | function cleanCodeForParsing(code) {
|
105 | code = strip_comments_1.default(code);
|
106 | const allMatches = [];
|
107 | let match;
|
108 | const importRegex = new RegExp(ESM_IMPORT_REGEX);
|
109 | while ((match = importRegex.exec(code))) {
|
110 | allMatches.push(match);
|
111 | }
|
112 | const dynamicImportRegex = new RegExp(ESM_DYNAMIC_IMPORT_REGEX);
|
113 | while ((match = dynamicImportRegex.exec(code))) {
|
114 | allMatches.push(match);
|
115 | }
|
116 | return allMatches.map(([full]) => full).join('\n');
|
117 | }
|
118 | function parseJsForInstallTargets(contents) {
|
119 | let imports;
|
120 |
|
121 |
|
122 | try {
|
123 | [imports] = es_module_lexer_1.parse(contents) || [];
|
124 | }
|
125 | catch (err) {
|
126 |
|
127 |
|
128 |
|
129 | contents = cleanCodeForParsing(contents);
|
130 | [imports] = es_module_lexer_1.parse(contents) || [];
|
131 | }
|
132 | return (imports
|
133 | .map((imp) => parseImportStatement(contents, imp))
|
134 | .filter(util_1.isTruthy)
|
135 |
|
136 | .filter((target) => !/[./]macro(\.js)?$/.test(target.specifier)));
|
137 | }
|
138 | function parseCssForInstallTargets(code) {
|
139 | const installTargets = [];
|
140 | let match;
|
141 | const importRegex = new RegExp(util_1.CSS_REGEX);
|
142 | while ((match = importRegex.exec(code))) {
|
143 | const [, spec] = match;
|
144 | const webModuleSpecifier = parseWebModuleSpecifier(spec);
|
145 | if (webModuleSpecifier) {
|
146 | installTargets.push(createInstallTarget(webModuleSpecifier));
|
147 | }
|
148 | }
|
149 | return installTargets;
|
150 | }
|
151 | function parseFileForInstallTargets({ locOnDisk, baseExt, contents, root, }) {
|
152 | const relativeLoc = path_1.default.relative(root, locOnDisk);
|
153 | try {
|
154 | switch (baseExt) {
|
155 | case '.css':
|
156 | case '.less':
|
157 | case '.sass':
|
158 | case '.scss': {
|
159 | logger_1.logger.debug(`Scanning ${relativeLoc} for imports as CSS`);
|
160 | return parseCssForInstallTargets(contents);
|
161 | }
|
162 | case '.html':
|
163 | case '.svelte':
|
164 | case '.vue': {
|
165 | logger_1.logger.debug(`Scanning ${relativeLoc} for imports as HTML`);
|
166 | return [
|
167 | ...parseCssForInstallTargets(extractCssFromHtml(contents)),
|
168 | ...parseJsForInstallTargets(extractJsFromHtml({ contents, baseExt })),
|
169 | ];
|
170 | }
|
171 | case '.js':
|
172 | case '.jsx':
|
173 | case '.mjs':
|
174 | case '.ts':
|
175 | case '.tsx': {
|
176 | logger_1.logger.debug(`Scanning ${relativeLoc} for imports as JS`);
|
177 | return parseJsForInstallTargets(contents);
|
178 | }
|
179 | default: {
|
180 | logger_1.logger.debug(`Skip scanning ${relativeLoc} for imports (unknown file extension ${baseExt})`);
|
181 | return [];
|
182 | }
|
183 | }
|
184 | }
|
185 | catch (err) {
|
186 |
|
187 | logger_1.logger.error(`! ${locOnDisk}`);
|
188 | throw err;
|
189 | }
|
190 | }
|
191 |
|
192 | function extractJsFromHtml({ contents, baseExt }) {
|
193 |
|
194 |
|
195 | const allMatches = [];
|
196 | let match;
|
197 | let regex = new RegExp(util_1.HTML_JS_REGEX);
|
198 | if (baseExt === '.svelte' || baseExt === '.vue') {
|
199 | regex = new RegExp(util_1.SVELTE_VUE_REGEX);
|
200 | }
|
201 | while ((match = regex.exec(contents))) {
|
202 | allMatches.push(match);
|
203 | }
|
204 | return allMatches
|
205 | .map((match) => match[2])
|
206 | .filter((s) => s.trim())
|
207 | .join('\n');
|
208 | }
|
209 |
|
210 | function extractCssFromHtml(contents) {
|
211 |
|
212 |
|
213 | const allMatches = [];
|
214 | let match;
|
215 | let regex = new RegExp(util_1.HTML_STYLE_REGEX);
|
216 | while ((match = regex.exec(contents))) {
|
217 | allMatches.push(match);
|
218 | }
|
219 | return allMatches
|
220 | .map((match) => match[2])
|
221 | .filter((s) => s.trim())
|
222 | .join('\n');
|
223 | }
|
224 | function scanDepList(depList, cwd) {
|
225 | return depList
|
226 | .map((whitelistItem) => {
|
227 | if (!glob_1.default.hasMagic(whitelistItem)) {
|
228 | return [createInstallTarget(whitelistItem, true)];
|
229 | }
|
230 | else {
|
231 | const nodeModulesLoc = path_1.default.join(cwd, 'node_modules');
|
232 | return scanDepList(glob_1.default.sync(whitelistItem, { cwd: nodeModulesLoc, nodir: true }), cwd);
|
233 | }
|
234 | })
|
235 | .reduce((flat, item) => flat.concat(item), []);
|
236 | }
|
237 | exports.scanDepList = scanDepList;
|
238 | async function scanImports(includeTests, config) {
|
239 | await es_module_lexer_1.init;
|
240 | const ignore = includeTests ? config.exclude : [...config.exclude, ...config.testOptions.files];
|
241 | const includeFileSets = await Promise.all(Object.keys(config.mount).map((fromDisk) => {
|
242 | return glob_1.default.sync(`**/*`, {
|
243 | ignore,
|
244 | cwd: fromDisk,
|
245 | absolute: true,
|
246 | nodir: true,
|
247 | });
|
248 | }));
|
249 | const includeFiles = Array.from(new Set([].concat.apply([], includeFileSets)));
|
250 | if (includeFiles.length === 0) {
|
251 | return [];
|
252 | }
|
253 |
|
254 | const loadFileQueue = new p_queue_1.default({ concurrency: CONCURRENT_FILE_READS });
|
255 | const getLoadedFiles = async (filePath) => loadFileQueue.add(async () => {
|
256 | return {
|
257 | baseExt: util_1.getExtension(filePath),
|
258 | root: config.root,
|
259 | locOnDisk: filePath,
|
260 | contents: await util_1.readFile(url_1.default.pathToFileURL(filePath)),
|
261 | };
|
262 | });
|
263 | const loadedFiles = await Promise.all(includeFiles.map(getLoadedFiles));
|
264 | return scanImportsFromFiles(loadedFiles.filter(util_1.isTruthy), config);
|
265 | }
|
266 | exports.scanImports = scanImports;
|
267 | async function scanImportsFromFiles(loadedFiles, config) {
|
268 | await es_module_lexer_1.init;
|
269 | return loadedFiles
|
270 | .filter((sourceFile) => !Buffer.isBuffer(sourceFile.contents))
|
271 | .map((sourceFile) => parseFileForInstallTargets(sourceFile))
|
272 | .reduce((flat, item) => flat.concat(item), [])
|
273 | .filter((target) => {
|
274 | const aliasEntry = util_1.findMatchingAliasEntry(config, target.specifier);
|
275 | return !aliasEntry || aliasEntry.type === 'package';
|
276 | })
|
277 | .sort((impA, impB) => impA.specifier.localeCompare(impB.specifier));
|
278 | }
|
279 | exports.scanImportsFromFiles = scanImportsFromFiles;
|