如何在浏览器中简便提取JavaScript ES模块的所有导出名称?
取巧提取ESM导出名称的可行思路
以下两种方法无需实现完整词法分析或执行原代码业务逻辑,能覆盖你提到的解构赋值、多变量声明场景(注:无法100%覆盖所有极端语法,但满足大部分实用需求):
方法一:增强版正则表达式(轻量易实现)
针对目标场景优化正则,分模式匹配导出,步骤如下:
- 先移除代码中的单行/多行注释,避免干扰匹配
- 分别匹配基础导出、对象解构导出、数组解构导出、多变量声明四种模式
- 对每种模式的匹配结果做解析,提取最终导出名称
示例代码
function extractExportNames(esmCode) { // 移除所有注释 const cleanCode = esmCode .replace(/\/\/.*/g, '') .replace(/\/\*[\s\S]*?\*\//g, ''); const exportNames = new Set(); // 匹配基础导出:export const answer = 42 const basicExportRegex = /export\s+(?:const|let|var)\s+([a-zA-Z_$][\w$]*)\s*=/g; let match; while ((match = basicExportRegex.exec(cleanCode)) !== null) { exportNames.add(match[1]); } // 匹配对象解构导出:export const { name1, name2: bar } = o const objDestructRegex = /export\s+(?:const|let|var)\s+\{\s*([^}]+)\s*\}/g; while ((match = objDestructRegex.exec(cleanCode)) !== null) { match[1].split(',').forEach(item => { const [rawName, alias] = item.trim().split(':').map(s => s.trim()); exportNames.add(alias || rawName); }); } // 匹配数组解构导出:export const [ name1, name2 ] = array const arrDestructRegex = /export\s+(?:const|let|var)\s+\[\s*([^\]]+)\s*\]/g; while ((match = arrDestructRegex.exec(cleanCode)) !== null) { match[1].split(',').forEach(item => { // 处理数组解构中的默认值,比如 [name = 123] const name = item.trim().split('=')[0].trim(); exportNames.add(name); }); } // 匹配多变量声明:export const name1 = 1, name2 = 2 const multiVarRegex = /export\s+(?:const|let|var)\s+([^;]+)/g; while ((match = multiVarRegex.exec(cleanCode)) !== null) { match[1].split(',').forEach(item => { const name = item.trim().split('=')[0].trim(); if (/^[a-zA-Z_$][\w$]*$/.test(name)) { exportNames.add(name); } }); } return Array.from(exportNames); } // 测试用例 const testESM = ` export const answer = 42; // 解构赋值 export const { name1, name2: bar } = o; export const [ name1, name2 ] = array; // 同时声明与赋值 export const name1 = 1, name2 = 2/*, … */; `; console.log(extractExportNames(testESM)); // 输出: ["answer", "name1", "bar", "name2"]
局限:无法处理嵌套解构(如export const { obj: { nested } } = o)、带复杂表达式的极端声明场景。
方法二:借浏览器引擎解析(覆盖更多语法)
利用JavaScript引擎的语法解析能力,通过包裹代码避免执行原业务逻辑,直接提取导出名称:
- 将ESM代码中的
export关键字替换为普通变量声明 - 替换所有赋值表达式为
= undefined,彻底消除执行副作用 - 用
Function构造函数执行包裹后的代码,返回变量名称列表
示例代码
function extractViaEngineParse(esmCode) { try { // 替换export为普通声明,替换赋值为undefined避免副作用 const wrappedCode = esmCode .replace(/export\s+(const|let|var)\s+/g, '$1 ') .replace(/=\s*.+?(?=;|,|\n)/g, '= undefined'); // 构造函数返回所有声明的变量名 const getExports = new Function(` ${wrappedCode} const vars = {}; ${wrappedCode.split(';').filter(line => line.trim()).map(line => { const varName = line.trim().split(/\s|=/)[1]; return `vars.${varName} = true;`; }).join('\n')} return Object.keys(vars); `); return getExports(); } catch (err) { console.error('解析失败:', err); return []; } }
优势:能处理嵌套解构、复杂变量声明等更多场景,依赖浏览器原生解析能力,无需自己维护正则规则;
注意:若原代码存在语法错误,会抛出异常,需额外捕获处理。
内容的提问来源于stack exchange,提问作者vanilla
相关产品推荐
相关产品推荐

