We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文为博客迁移过来,原文链接: 利用AST解决项目webpack alias泛滥问题:2019-1-24
文章代码的源码仓库
AST(Abstract Syntax Tree)既抽象语法树,或称语法树,简单来说就是代码语法结构的一种抽象表示。比如 var answer = 6 * 7; 会被解析为这么一棵树
var answer = 6 * 7;
那么代码怎样才能解析成这一棵 AST, AST在前端领域一般又可以干嘛?
ast是由编译器解析生成的,简单的编译器可以由以下几部分组成:
tokens
我们前端构建中很常用的babel就是这种原理
babel 初始阶段并没有做任何事,基本上等于 const babel = code=> code; 先 tokenizer, parser 解析代码,再 transformer 的时候,完全不改动原来的 ast
对编译器原理有兴趣的,可以看我以前写的小demo,500行简单易懂 min-compiler,看完会有个整体概念。
而生成的AST我们可以用来做什么? AST你都拿到了,剩下的事情就是对这棵树做你想要的操作,比如代码转换(babel),代码压缩等。
这里我用他来处理webpack的alias泛滥问题。
webpack alias 在很多情况下可以提供便利,但是如果项目参加的人太多,又没有什么约束,大家贪图方便什么都加到alias....就会变成这样子
我们先来整理一下思路
我们这里的把alias改为其他值,指的是这种情况
目录结构: - src - components - btn alias: { btn: path.resolve(basepath, 'src/components/btn'), btn: path.resolve(basepath, 'src/components'), } 原来的引入 import Btn from 'btn'; 改为 import Btn from 'components/btn';
这里我们用 esprima 来做代码分析生成ast,用 estraverse 来转换代码,用 escodegen 生成代码。直接上代码
const aliasConfig = { /* webpack alias 配置*/} function translateAlias(filePath) { // 解析ast const codeStr = fs.readFileSync(filePath).toString(); const ast = esprima.parseModule(codeStr); // 转换ast estraverse.traverse(ast, { // 对于每个node节点都会进入这个函数 enter(node, parent) { // 判断是否是我们的目标文件 const isAliasDec = isRequireDeclaration(node, parent); if (isAliasDec) { // 替换掉alias => newAlias const newVal = getModulePath(node.value, filePath); node.value = newVal; } }, }); // 重新生成代码 const newCodeStr = escodegen.generate(ast); fs.writeFileSync(filePath, newCodeStr, {}); } // 工具函数: 判断是否是 require function isRequireDeclaration(node, parent) { const { type, value } = node; const { callee } = parent || {}; // 类型一致 && 该key在aliasKey中 && 是 require引入的 return ( type === 'Literal' && aliasKey.includes(value) && !allowAliasKey.includes(value) && isRequest(callee) ); } // 工具函数:获取路径 function getModulePath(aliasKey, filePath) { const firstDir = /\w*/.exec(aliasKey)[0]; const modulePath = aliasKey.replace(firstDir, aliasConfig[firstDir]); const aliasPath = aliasKey.replace(firstDir, aliasMap[firstDir]); if (!aliasConfig[firstDir] || !aliasMap[firstDir] || allowAliasKey.includes(firstDir)) return false; // 获取引入的模块与当前模块相对路径,判断是否太长,是就返回alias,否则就返回相对路径就完事了 const relativePath = path.relative(filePath, modulePath); const relativeTime = relativePath.split('../').length - 1; return (relativeTime < MAX_RELATIVE)? relativePath: aliasPath; } translateAlias(filePath);
试跑了一下,发现说虽然代码引用确实有被替换了,但是代码里面的所有空行和注释都丢了,而且一些规范格式也和原来不一样。 这显然是不行的,先不说格式的问题,一个文件连换行和注释都没有,那他就是没有灵魂的js~
看了下这是因为 esprima 在解析的时候,遇到空行和注释会直接跳过不解析生成AST,所以会导致后面生成的代码没有空行和注释。
esprima
我们平时项目上用的最多的转换代码的工具就是babel,那么我们也可以把 esTool 那一套换成 babel生态,用babel来帮我们做这些转换。
esTool
babel
原理和思路基本上是一样的,用 babylon 解析,babel-traverse 转换,再用babel-generator生成代码。 生成之后,先不写进去,而是用 prettier 格式化一遍再重写到本地,以保持和原来的风格一致。
babylon
babel-traverse
babel-generator
prettier
function translateAlias(filePath) { console.log(`开始处理第${i++}个: ${filePath}`) const code = fs.readFileSync(filePath).toString(); // 获取ast const ast = babylon.parse(code, { sourceType: 'module', plugins: ['jsx', 'objectRestSpread'] }); traverse(ast, { enter(path) { // 转换 CommonJs 的情况 translateRequireModulePath(path, filePath); // 转换 ESM 的情况 translateImportModulePath(path, filePath); } }); const newCode = generate(ast, {}); // 重新用项目的prettier配置格式化多一次再写入 const prettierCode = prettier.format(newCode.code, prettierConfig); fs.writeFileSync(filePath, prettierCode); console.log(`处理结束${filePath}`) }
到此减少webpack-alias的功能处理完成,最后总结一下
glob
require('xxx')
import xxx from 'xxx'
最后写的时候参考到的链接,大部分是类库的文档 迷你编译器 estools代码生成escodegen estools代码转换estraverse 代码解析esprima babel plugin book babel-generator babel-traverse babylon 在线ast生成 在线ast生成
The text was updated successfully, but these errors were encountered:
No branches or pull requests
文章代码的源码仓库
AST 简单介绍
AST(Abstract Syntax Tree)既抽象语法树,或称语法树,简单来说就是代码语法结构的一种抽象表示。比如
var answer = 6 * 7;
会被解析为这么一棵树那么代码怎样才能解析成这一棵 AST, AST在前端领域一般又可以干嘛?
编译器
ast是由编译器解析生成的,简单的编译器可以由以下几部分组成:
tokens
我们前端构建中很常用的babel就是这种原理
对编译器原理有兴趣的,可以看我以前写的小demo,500行简单易懂 min-compiler,看完会有个整体概念。
而生成的AST我们可以用来做什么?
AST你都拿到了,剩下的事情就是对这棵树做你想要的操作,比如代码转换(babel),代码压缩等。
这里我用他来处理webpack的alias泛滥问题。
webpack alias问题
webpack alias 在很多情况下可以提供便利,但是如果项目参加的人太多,又没有什么约束,大家贪图方便什么都加到alias....就会变成这样子
所以我决定把项目里的alias从23个缩减为7个。
利用 estools 解决webpack Alias
我们先来整理一下思路
我们这里的把alias改为其他值,指的是这种情况
这里我们用 esprima 来做代码分析生成ast,用 estraverse 来转换代码,用 escodegen 生成代码。直接上代码
试跑了一下,发现说虽然代码引用确实有被替换了,但是代码里面的所有空行和注释都丢了,而且一些规范格式也和原来不一样。
这显然是不行的,先不说格式的问题,一个文件连换行和注释都没有,那他就是没有灵魂的js~
看了下这是因为
esprima
在解析的时候,遇到空行和注释会直接跳过不解析生成AST,所以会导致后面生成的代码没有空行和注释。babel解决空行和注释等问题、prettier保持代码风格一致
我们平时项目上用的最多的转换代码的工具就是babel,那么我们也可以把
esTool
那一套换成babel
生态,用babel来帮我们做这些转换。原理和思路基本上是一样的,用
babylon
解析,babel-traverse
转换,再用babel-generator
生成代码。生成之后,先不写进去,而是用
prettier
格式化一遍再重写到本地,以保持和原来的风格一致。到此减少webpack-alias的功能处理完成,最后总结一下
glob
读取所有要转的js文件babylon
将js文件解析成ASTbabel-traverse
处理AST,判断如果是require('xxx')
或者import xxx from 'xxx'
替换掉这些路径babel-generator
将新生成的AST转化为代码prettier
格式化新生成的代码,保持与原项目风格一致告辞!
最后写的时候参考到的链接,大部分是类库的文档
迷你编译器
estools代码生成escodegen
estools代码转换estraverse
代码解析esprima
babel plugin book
babel-generator
babel-traverse
babylon
在线ast生成
在线ast生成
The text was updated successfully, but these errors were encountered: