All files / src/utils static-import-lexer.ts

6.12% Statements 3/49
100% Branches 0/0
0% Functions 0/3
6.12% Lines 3/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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  1x 1x       1x                                                                                                                                                              
import type fs from 'node:fs';
import fsp from 'node:fs/promises';
import path from 'node:path';
import type { ImportMap, SpecifierMap } from '@esmx/import';
import type { ParsedModuleConfig } from '../module-config';
 
import * as esmLexer from 'es-module-lexer';
 
/**
 * 从 JS 代码中获取静态 import 的模块名列表。也许不能并发多个调用,没实验过。
 * @param code js 代码
 * @returns `Promise<string[]>` 静态 import 的模块名列表
 */
export async function getImportsFromJsCode(code: string): Promise<string[]> {
    await esmLexer.init;
    const [imports] = esmLexer.parse(code);
    // 静态导入 && 拥有模块名
    return imports
        .filter((item) => item.t === 1 && item.n)
        .map((item) => item.n as string);
}
 
/**
 * 从 JS 文件中获取静态 import 的模块名列表。
 * @param filepath js 文件路径
 * @returns `Promise<string[]>` 静态 import 的模块名列表
 */
export async function getImportsFromJsFile(
    filepath: fs.PathLike | fs.promises.FileHandle
) {
    const source = await fsp.readFile(filepath, 'utf-8');
    return getImportsFromJsCode(source);
}
 
export type ImportPreloadInfo = SpecifierMap;
/**
 * 获取导入的预加载信息。
 * @param specifier 模块名
 * @param importMap 导入映射对象
 * @param moduleConfig 模块配置
 * @returns
 *   - `Promise<{ [specifier: string]: ImportPreloadPathString }>` 模块名和文件路径的映射对象
 *   - `null` specifier 不存在
 */
export async function getImportPreloadInfo(
    specifier: string,
    importMap: ImportMap,
    moduleConfig: ParsedModuleConfig
) {
    const importInfo = importMap.imports;
    if (!importInfo || !(specifier in importInfo)) {
        return null;
    }
 
    const ans: ImportPreloadInfo = {
        // 入口文件也放入预加载列表
        [specifier]: importInfo[specifier]
    };
 
    // 词法分析是耗时操作,因此处理的文件越少越快,换句话说就是深度越浅越快,因此这里使用广度优先搜索
    const needHandles: string[] = [specifier];
    while (needHandles.length) {
        const specifier = needHandles.shift()!;
        let filepath = importInfo[specifier];
        const splitRes = filepath.split('/');
        if (splitRes[0] === '') splitRes.shift();
        // 这里默认路径的第一个目录是软包名称
        const name = splitRes.shift() + '';
        const link = moduleConfig.links[name];
        if (!link) {
            continue;
        }
        filepath = path.join(link.client, ...splitRes);
        const imports = await getImportsFromJsFile(filepath);
        for (const specifier of imports) {
            // 如果模块名在 importMap 中不存在,或已经处理过
            if (!(specifier in importInfo) || specifier in ans) continue;
            ans[specifier] = importInfo[specifier];
            needHandles.push(specifier);
        }
    }
 
    // 倒序,理论上倒序后浏览器解析可能会更快
    return Object.fromEntries(Object.entries(ans).reverse());
}