You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
importReactfrom"react";import"./index.scss";constTool=()=>{return(<div><p>You should import style file like this:</p><p>import './index.scss'</p></div>);};
const{ program }=require("commander");program.version("0.0.1").option("-o, --out <path>","output root path");program.on("--help",()=>{console.log(` You can add the following commands to npm scripts: ------------------------------------------------------ "compile": "tsccss -o dist" ------------------------------------------------------`);});program.parse(process.argv);const{ out }=program.opts();console.log(out);if(!out){thrownewError("--out must be specified");}
接下来在项目根目录下,执行以下控制台命令:
node src/index.js -o dist
你会发现控制台打印了 dist ,是的,就是 -o dist 的作用,简单介绍下 version 和 option 。
契机
最近在搭建一个开源的项目环境时,我需要打一个 ES 模块的包,以便开发者可以直接通过
npm
就能安装并使用,但是这个项目注定了会有样式,而且我希望打出的包的文件目录和我开发目录是一致的,似乎Rollup
是一个不错的选择,但是我(自虐般地)选择了Typescript
自带的编译器tsc
,然后我就开始我的填坑之旅~tsc 遇到的坑
在使用
tsc
编译我的代码时,对我目前来说,有三个基本的坑,下面我会对它们进行简单的阐述,在此之前看下即将被编译的目录结构。简化引用路径问题
首先我是在
tsconfig.json
中写了简化引用路径配置的,比如针对以上目录,我是这样:那么无论我层级多深时,我要是想引用
util
或assets
里面的文件模块、资源就会特别方便,比如我在index.tsx
文件中这样引入:编译前:
编译后(预期 😢):
然而实际编译后的结果令我大失所望,
tsc
既然连这个都不支持转译!!它编译之后的代码还是老样子,于是我就去找官网查,发现也没有这个相关的配置项,于是跑到外网查了下发现有人是和我遇到了相同的问题的,它提供了一个解决方案就是,使用这个插件 tscpaths 并在编译后多加一段npm
命令即可:当执行到这个命令时:
这个插件会去遍历每一个我们已经由
tsc
编译之后的.js
文件,将我们简化的引用路径转为相对路径,大功告成~静态资源未打包问题
如上所示,如果我在
index.tsx
文件中引入一个放在assets
的图片资源:在经过
tsc
编译之后,而且在使用我们的命令行工具之后,我们的引用路径是对了,但是一看打包出来的目录中,是不会出现assets
这个资源文件夹的,其实这也正常,毕竟tsc
也仅仅是个 Typescript 的编译器,要实现其它的打包功能,要靠自己动手!解决问题的办法就是使用 copyfiles 命令行工具,它和上面我们介绍的插件一样,都是在
tsc
编译之后,做一些额外操作达到我们想要的目的。就像它的名字一样,它就是拿来复制文件的~我们在 npm scripts 下的 build 命令后面再加上这个:
copyfiles -f src/assets/* dist/assets
这样就能把资源文件夹复制到打包之后的文件目录下了。
引入样式文件后缀名问题
我们做一个项目时在所难免会用到
sass
或less
,本项目就选择了sass
,我在index.tsx
中引入样式文件方式如下:但是在
tsc
编译为.js
文件之后,打开index.js
发现引入的样式后缀还是.scss
。作为给别的开发者使用的包,一定是要引入.css
文件的格式的,你不可能确定别人用的都是sass
,所以我又去网上找解决方案,发现很少有人提这个问题,而且也没有找到可以用的插件什么的。就在一筹莫展之时,我突然想到,卧槽,这不就是类似于上面提到的
tscpaths
这个工具吗,也是在文件内做字符串替换,太像了!于是我赶紧下载了它的源码,看了下大概是使用 node 读取了tsconfig.json
中bathUrl
和paths
配置,以及用户自定义的入口、出口路径来找到.js
文件,分析成相对路径之后再正则匹配到对应的引用路径去替换掉!立马有了思路准备实践,突然想到全局正则匹配做替换的局限性,比如在开发者代码中也写了与引用一样的代码(这种情况基本不可能发生,但是仍要考虑),那不是把人家的逻辑代码都改了吗?比如以下代码:
怎么办,你做全局替换,是会替换掉别人逻辑源代码的。。当然,可以写更好的查找算法(或正则)来精确替换,但是无形中考虑的情况就非常多了;我们有没有更好的实现方式呢?这时候我想到了抽象语法树(AST)。
注意⚠️ :另外要说一下,
tsc
也不会编译.scss
文件的,它需要node-sass
来将每个.scss
文件编译到对应打包目录,在tsc
编译之后,再执行以下命令即可:AST 是什么?
如果你了解或者使用过
ESLint
、Babel
及Webpack
这类工具,那么恭喜你,你已经对 AST 的强大之处有了最直观的了解了,比如ESLint
是怎么修复你的代码的呢?看下面不太严谨的图:不严谨的语言描述就是,eslint 将当前的 js 代码解析成了一个抽象语法树,在这棵树上做了一些修整,比如剪掉一条树枝,就是去除代码中多出的空格
space
;比如修整了一条树枝,就是var
转换为const
等。修整完之后再转换为我们的 js 代码!这个树中的每条“枝”都代表了 js 代码中的某个字段的描述对象,比如以下简单的代码:
我们先自己定制一套简单的转换为 AST 语法规则,可以这样表示上面这行代码:
是的,这就是一颗简易的抽象语法树了,就这么简单,它只是一种特殊的对象结构来表示我们的 js 代码而已,如果我们有一个手段,能拿到表示
1
这个值的节点,并将init.value
改为2
,再将该语法树转换为 js 源码,那就能得到:那么上面说的“转换”规则是不用我们自己去写的,随着 JavaScript 语言的发展,由一些大佬创建的项目 ESTree 用于更新 AST 规则,目前已成为社区标准。然后社区中一些其它项目比如 ESlint 和 Babel 就会使用 ESTree 或在此基础上做一些修改,然后衍生出自己的一套规则,并制作相应的转换工具,暴露出一些 API 给开发者使用。
搭配工具
因为生成的 AST 结构上看起来是特别繁杂的,如果没有好用工具或文档,学习时或写代码时会很困扰,那么接下来就给大家介绍三个利器。
在线调试工具 AST Explorer
有了这个网站你就能实时地去查看解析之后的 AST 是什么样子的,以及它们的类型是什么,这在之后写代码去对 AST 做修改特别有用!因为你可以明确自己想要修改的地方是哪里。
比如上图中,我们想要修改
1
为2
,我们通过某个工具去找到这个 AST 中的type
为Literal
这个节点,将其value
设为2
,再转换为 js 代码就实现了这个需求。类似的工具是很多的,我们就选用 Facebook 官方的开源工具:jscodeshift
AST 转换工具 jscodeshift
jscodeshift 是基于 recast 封装的一个库,相比于 recast 不友好的 api 设计,jscodeshift 将其封装并暴露出对 js 开发者来说更为友好的 api,让我们在操作修改 AST 的时候更加方便。
我建议大家先知道这个工具就行,具体的 api 使用我下面会跟大家挑几个典型的说一说,有个具体的印象就行,说实话,这个库的文档写的并不好,也不适合初学者阅读,特别是英语还不好的人。当你使用过它的一些 api 后有了直观的感觉,再去阅读也不迟~
AST 类型大全 @babel/types
这是一本 AST 类型词典,如果我们想要生成一些新的代码,也就是要生成一些新的节点,按照语法规则,你必须将你要添加的节点类型按照规范传入,比如
const
的类型就为type: VariableDeclaration
,当然了,type
只是一个节点的一个属性而已,还有其他的,你都可以在这里面查阅到。下面是常用的节点类型含义对照表,更多的类型大家可以细看 @babel/types:
AST 节点的增删改查
上面说到了 jscodeshift 的 api 设计的是比较友好的,那么我们就以一个树的增删改查来简单地带大家了解一下,不过在这之前需要先搭建一个简单的开发环境。
开发环境
第一步:创建一个项目文件夹
mkdir ast-demo cd ast-demo
第二步:项目初始化
第三步:安装 jscodeshift
第四步:新建
4
个 js 文件,分别对应增删该查。第五步:在做以下事例时,请大家打开 AST Explorer ,把要转换的
value
都复制进来看看它的树结构,以便更好地理解。查找节点
find.js
:在控制台执行以下命令:
然后你就能看到控制台打印了
antd
。在此说明一下,上面代码中定义的
value
字符串就是我们要操作的文本内容,实际应用中我们一般都是读取文件,然后做处理。在上面的
.find
函数中,第一个参数为要查找的类型,第二个参数为查询条件,如果你将上面的value
复制到 AST Explorer 上看看,你就知道这个查询条件为什么是这种结构了。修改节点
update.js
:上面的代码目的是将从
antd
引入的Button
改为Input
,为了很精确地定位在这一行,我们先通过ImportDeclaration
和条件参数去找到,在向内找到Button
这个节点,简单的判断之后就可以做修改了。你能看到最后一行我们执行了
toSource()
,该方法就是将AST
转回为我们的源码,控制台打印如下:增加节点
create.js
:上面代码首先仍然是找到
antd
那行,然后在specifiers
这个数组的最后一位添加一个新的节点,表现在转换后的 js 代码上就是,新增了一个Select
的引入:删除节点
delete.js
:删除引入
antd
一整行,就是这么简单。更多 API
上面所实现的增删改查其实都是多种实现方式中的一种而已,只要你对 API 很熟练,或者脑洞够大,那可就谁也拦不住了~这里我只想说,去官方的 collection 及 extensions 看看你就知道有哪些 API 了,然后多尝试、多动手,总会实现你想要的效果的。
实战解析
明确需求
在对 jscodeshift 有了初步了解之后,我们接下来做一个命令行工具来解决我在上面提出的“引入样式文件后缀名问题”,接下来会简单使用到 commander ,它使 nodejs 命令行接口变得更简单~
我再次明确下我目前的需求:由
tsc
编译之后的目录,比如dist
,我要将里面生成的所有 js 文件中关于样式文件的引入,比如import './style.scss'
,全部转换成以.css
为后缀的方式。该命令行工具我给它命名为:tsccss。
搭建环境
就像上面一样,我们先初始化项目,因为演示为主,所以我们就不使用 Typescript 了,就写原生 nodejs 原生模块写法,如果对项目要求较高的,也可以加上
ESLint
、Prettier
等规范代码的工具,如果大家有兴趣,可以前往我在 github 上已经写好了的这个命令行工具 tsccss ,可以做个参考。好的,现在我们一气呵成,按下面步骤来:
现在目录如下:
接下来在
package.json
中找个位置加入以下代码:其中
bin
字段很重要,在其他开发者下载了你这个包之后,人家在tsccss xxxxxx
时就会以 node 执行后面配置的文件,即src/index.js
,当然,我们的index.js
还要在最顶部加上这行代码:这句代码解决了不同的用户 node 路径不同的问题,可以让系统动态的去查找 node 来执行你的脚本文件。
使用 commander
直接在
index.js
中加入以下代码:接下来在项目根目录下,执行以下控制台命令:
你会发现控制台打印了
dist
,是的,就是-o dist
的作用,简单介绍下version
和option
。作用:定义命令程序的版本号;
用法示例:.version('0.0.1', '-v, --version') ;
参数解析:
作用:用于定义命令选项;
用法示例:.option('-n, --name ', 'edit your name', 'vortesnail');
参数解析:
(标志后面可跟参数,可以用 <> 或者 [] 修饰,前者意为必须参数,后者意为可选参数)
所以大家还可以试试这两个命令:
读取 dist 下 js 文件
dist
目录是假定我们要去做样式文件后缀名替换的文件根目录,现在需要使用globby
工具自动读取该目录下的所有 js 文件路径,在顶部需要引入两个函数:然后在下面继续追加代码:
files
即dist
目录下所有 js 文件路径,我们故意在该目录下新建几个任意的 js 文件,再执行下node src/index.js -o dist
,看看控制台是不是正确打印出了这些文件的绝对路径。编写替换方法
因为有了前面的增删改查的铺垫,其实现在这一步已经很简单了,思路就是:
ImportDeclaration
的节点;source.value
是否以.scss
或.less
结尾;.css
。就这么简单,我们直接引入 jscodeshift :
然后追加以下代码:
可以看到,该方法直接返回了转换后的 js 代码,是可以直接写入源文件的内容。
读写文件
拿到文件路径
files
后,需要 node 原生模块fs
来帮助我们读写文件,这部分代码很简单,思路就是:读 js 文件,将文件内容转换为 AST 做节点值替换,再转为 js 代码,最后写回该文件,就 OK 了。现在你到
dist
目录下的index1.js
、index2.js
文件中,随便输入以下内容,以便查看效果:然后最后一次执行我们的命令:
再看刚才的
index1.js
或index2.js
,是不是全部正确替换了:舒服了~ 😊
上面的代码还是可以优化很多地方的,比如大家还可以写一些额外的代码来统计替换的位置、数量、文件修改数量等,这些都可以在控制台打印出来,在别人使用时也能得到较好的反馈~甚至替换的正则方法也可以再做改进,看大家的了!
最后想说的
虽然上面的实战是非常简单的一种 AST 用法,但是这篇文章的主要作用就是能带大家入门,利用这种思维去解决工作或学习中遇到的一些问题,在我看来,有了对某方法的事物认知之后,你的解决问题的方式就会无形之中多了一种。其实技术在某种程度来说并不是最重要的,重要的是对技术的认知。
毕竟,你不知道某个东西,利用它的想法都不会产生,但是你知道了,无论技术实现再难,也总是可以攻克的!
最后感谢大家能认真读到这里,文章中有错误的地方,欢迎探讨。
参考文章:
commander
像玩 jQuery 一样玩 AST
jscodeshift 简易教程
The text was updated successfully, but these errors were encountered: