1.vscode如何配置才能单步调试用typescript写的程序程序node.js程序的一种可行方法
2.cookieAPI真难用,你造过相关的源码源码轮子吗
3.ESModule规范详解
4.Vite 源码学习3. package.json分析
5.微信小程序使用Promise
6.一文带你快速上手Rollup
vscode如何配置才能单步调试用typescript写的node.js程序的一种可行方法
为在VSCode中单步调试用TypeScript编写的Node.js程序,提供了一种实用的什意思配置步骤。
步骤如下:
首先,程序程序确保项目中安装了TypeScript编译器tsc,源码源码并在`tsconfig.json`中配置好目标为`"esnext"`,什意思数游源码模块类型为`"commonjs"`,程序程序启用`sourceMap`,源码源码并将输出文件目录设为`"build"`。什意思
在VSCode中,程序程序利用任务功能创建一个监视源代码并自动编译的源码源码默认任务。通过`Ctrl+Alt+P`(Mac下为`Command+Shift+P`)打开任务管理器,什意思选择`tsc: watch`,程序程序这将在`.vscode`目录下生成`task.json`。源码源码
执行任务时,什意思再次打开任务管理器,选择`Tasks: Run Build Task`,确保项目编译成功。接下来,配置`launch.json`文件,关键部分包括设置入口文件路径为`"program": "${ workspaceFolder}/index.ts"`,根据`tsconfig.json`的`outDir`配置`"outFiles"`。
创建好`launch.json`后,选择`"ts-node Debugger"`配置,点击运行按钮(F5)或三角形图标开始调试。在需要的地方设置断点,确认断点生效和调试工具栏的显示。
通过以上步骤,你应该能成功在VSCode中配置并单步调试TypeScript Node.js项目。完成配置后的体验应该会让你的工作流程更加顺畅。
cookieAPI真难用,你造过相关的轮子吗
前言
歌德说过:读一本好书,就是在和高尚的人谈话。同理,读优秀的开源项目的源码,就是在和优秀的大佬交流,是站在巨人的肩膀上学习——今天我们将通过读js-cookie的源码,来学会造一个操作cookie的轮子~
1.准备简单介绍一下cookieCookie是直接存储在浏览器中的一小串数据。它们是HTTP协议的一部分,由RFC规范定义。最常见的用处之一就是身份验证我们可以使用document.cookie属性从浏览器访问cookie。
这个库,是干啥的?不用这个库时?cookie的原生API,非常“丑陋”:
修改我们可以写入document.cookie。但这不是一个数据属性,它是一个访问器(getter/setter)。对其的赋值操作会被特殊处理。对document.cookie的写入操作只会更新其中提到的cookie,而不会涉及其他cookie。例如,此调用设置了一个名称为user且值为John的cookie:
document.cookie?=?"user=John";?//?只会更新名称为?user?的?cookiedocument.cookie?=?"user=John;?path=/;?expires=Tue,??Jan??::?GMT"赋值时传入字符串,并且键值对以=相连,如果多项还要用分号;隔开...
删除将过期时间设置为过去,自然就是qq诱导源码删除了~
//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";但是很明显,这语义化也太差了..
js-cookieAPI我们先来了解一下API
//?setCookies.set('name',?'value',?{ ?expires:?7,?path:?''?})//?get?Cookies.get('name')?//?=>?'value'Cookies.get()?//?=>?{ ?name:?'value'?}//?removeCookies.remove('name')OK我们大概可以知道是这样子
set(key,?value)get(key)remove(key)简洁方便多了,并且一眼就知道这行代码是在干什么~
2.读源码三部曲?这段可能有点太细了,如果嫌啰嗦,只想看实现可以直接跳到下面的实现部分~
一?READMEwhy一个简单、轻量级的JavaScriptAPI,用于处理cookie适用于所有浏览器?接受任何字符大量的测试?不依赖支持ES模块支持AMD/CommonJSRFC兼容的有用的Wiki?启用自定义编码/解码<字节gzip!
优点多多呀
表示后文会详细提及~BasicUsage大概就是前面写过的API介绍
二package.json依赖确实是很少依赖,并且只有开发依赖,没有生产依赖,很nice~
scripts"scripts":?{ "test":?"grunt?test","format":?"grunt?exec:format","dist":?"rm?-rf?dist/*?&&?rollup?-c","release":?"release-it"?},exportsexports":?{ ".":?{ ?"import":?"./dist/js.cookie.mjs",?"require":?"./dist/js.cookie.js"},看来入口在/dist/js.cookie这点从index.js也能看出
module.exports?=?require('./dist/js.cookie')当然,目前是没有dist这个目录的。这需要打包~
.mjs另外我们刚才看到了.mjs这个后缀,这我还是第一次见,你呢
.mjs:表示当前文件用ESM的方式进行加载
.js:采用CJS的方式加载。
ESM和CJSESM是将javascript程序拆分成多个单独模块,并能按需导入的标准。和webpack,babel不同的是,esm是javascript的标准功能,在浏览器端和nodejs中都已得到实现。也就是熟悉的import、exportCJS也就是commonJS,也就是module.exports、require。
更多介绍以及差别不再赘述~
三src进入src,首当其冲的就是api.mjs,这一眼就是关键文件啊?emm..一个init方法,其中包含set和get方法,返回一个Objectremove方法藏在其中~乍一看,代码当然还是能看得懂每行都是在做啥的呀~但是总所周知?开源项目也是不断迭代出来的~也不是一蹴而就的——若川哥
okok,我们来一步步"抄"一下源码
3.实现?下面为了传参返回值更加清晰用了TS语法~
3.1最简易版本set设置一个键值对,要这样
document.cookie?=?`${ key}=${ value};?expires=${ expires};?path=${ path}`除了键值对还有后面的属性~可别把它忘记了我们用写一个接口限制一下传入的属性:
interface?Attributes?{ ?path:?string;?//可访问cookie的路径,默认为根目录?domain?:?string;?//可访问?cookie?的域?expires?:?string?|?number?|?Date?//?过期时间:UTC时间戳string?||?过期天数?[`max-age`]?:number?//ookie?的过期时间距离当前时间的秒数?//...}const?TWENTY_FOUR_HOURS?=?e5?//h的毫秒数//源码中是init的时候传入defaultAttributes,这里先暂做模拟const?defaultAttributes:?Attributes?=?{ path:?'/'}function?set(key:?string,?value:?string,?attributes:?Attributes):?string?|?null?{ ?attributes?=?{ ...defaultAttributes,?...attributes}?//?if?(attributes.expires)?{ //如果有过期时间//?如果是数字形式的,就将过期天数转为?UTC?stringif?(typeof?attributes.expires?===?'number')?{ ?attributes.expires?=?new?Date(Date.now()?+?attributes.expires?*?TWENTY_FOUR_HOURS)?attributes.expires?=?attributes.expires.toUTCString()}?}?//遍历属性键值对并转换为字符串形式?const?attrStr?=?Object.entries(attributes).reduce((prevStr,?attrPair)?=>?{ const?[attrKey,?attrValue]?=?attrPairif?(!attrValue)?return?prevStr//将key拼接进去prevStr?+=?`;?${ attrKey}`//?attrValue?有可能为?truthy,所以要排除?true?值的情况if?(attrValue?===?true)?return?prevStr//?排除?attrValue?存在?";"?号的情况prevStr?+=?`=${ attrValue.split(';?')[0]}`return?prevStr?},?'')?return?document.cookie?=?`${ key}=${ value}${ attrStr}`}get//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";0我们知道document.cookie长这个样子,那么就根据对应规则操作其字符串获得键值对将其转化为Object先
//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";1要注意的有意思的一个点是,可能value中就有'='这个字符,所以还要特殊处理一下~
比如他就是"颜文字==_="?(~~应该不会有人真往cookie里面放表情吧hh~~但是value中有'='还是真的有可能滴~?其实一开始我真没想过这个问题,是看了源码才知道的
Record接收两个参数——keys、values,使得对象中的key、value必须在keys、values里面。
removeremove就简单啦,用set把过期时间设置为过去就好了~
//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";.2接受任何字符从技术上讲,cookie的名称和值可以是任何字符。为了保持有效的格式,它们应该使用内建的encodeURIComponent函数对其进行转义~再使用ecodeURIComponent函数对其进行解码。还记得README中写的接收任何字符吗~这就需要我们自己来在里面进行编码、解码的封装~
set//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";3get//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";.3封装编码和解码两个操作源码中converter.mjs封装了这两个操作为write和read,并作为defaultConverter导出到api.mjs,最后作为converter传入init——降低了代码的耦合性,为后面的仿QQ收藏源码自定义配置做了铺垫~前面编码解码变成了这样:
//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";.4启用自定义编码/解码我们是具有内置的encodeURIComponent和decodeURIComponent,但是也并不是必须使用这两个来进行编码和解码,也可以用别的方法——也就是前面README中说的可以自定义编码/解码~除了这两个方法可自定义,其余的属性也可以自定义默认值,并且配置一次后,后续不用每次都传入配置——所以我们需要导出时有对应的两个方法
//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";6封装在其中,利用对象合并时有重复属性名的情况是后面的覆盖掉前面的这一特性完成该自定义配置属性以及转换方法的功能。现在的cookie大概是这样的一个对象
//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";.5防止全局污染现在的cookie直接在全局上下文下,很危险,谁都能更改,而且还不一定能找到,我们将其设置为局部的,封装到init函数中,调用init传入相应的自定义属性以及自定义转换方法得到一个初始化的cookie对象现在大概就是源码的架构形状了~
//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";.6确保一些属性不会给改变用Object.create来生成对象,并用Object.freeze把对象atributes和converter冻结。
//?删除?cookie(让它立即过期)document.cookie?=?"expires=Thu,??Jan??::?GMT";document.cookie?=?"user=John;?max-age=0";9Obecj.create的第二个参数
属性描述符
现在你就不能修改Cookie的attributes、converter属性了~
4.总结&收获?总结init及其中属性&返回而用init函数生成对象是为了解决全局污染问题,并且更新对象时也是用的init现在你再回头看源码是不是就更加清晰了~
扩展说到cookie这个在浏览器中存储数据的小东西,就不得不提一下localstorage、sessionStorage
cookie、localstorage、sessionStorage的区别Web存储对象localStorage和sessionStorage也允许我们在浏览器上保存键/值对。
那他们的区别呢
在页面刷新后(对于sessionStorage)甚至浏览器完全重启(对于localStorage)后,数据仍然保留在浏览器中。默认情况下cookie如果没有设置expires或max-age,在关闭浏览器后就会消失
与cookie不同,Web存储对象不会随每个请求被发送到服务器,存储在本地的数据可以直接获取。因此,我们可以保存更多数据,减少了客户端和服务器端的交互,节省了网络流量。大多数浏览器都允许保存至少2MB的数据(或更多),并且具有用于配置数据的设置。
还有一点和cookie不同,服务器无法通过HTTPheader操纵存储对象。一切都是在JavaScript中完成的。
以及..他们的原生API比cookie的"好看"太多~[doge]
CookiesessionStoragelocalstorage生命周期默认到浏览器关闭,可自定义浏览器关闭除非自行删除或清除缓存,否则一直存在与服务器通信/post/ESModule规范详解
在介绍ESModule规范之前,我们先了解下AMD和CMD两种规范。AMD规范
AMD规范采用非同步加载模块,允许指定回调函数
node模块通常位于本地,加载速度快,所以适用于同步加载
浏览器环境下,模块需要远程请求获取,所以适用于异步
require.js是AMD的一个具体实现库
CMD规范
CMD整合了Commonjs和AMD的优点,模块加载是异步的
CMD专门用于浏览器端,sea.js是CMD规范的实现
AMD和CMD最大的问题是没有通过语法升级来解决模块化的问题。它们去定义模块化还是调用js方法的方式去生成一个模块,如果当项目模块达到成百上千个,这种方式无法进行模块规模化的应用。要想模块规模化应用则需要一种标准的语法规范,这是机房管理源码AMD和CMD都没有实现的。
ESModule规范
ESModule设计理念是希望在编译时就确定模块依赖关系即输入输出
Commonjs和AMD必须在运行时才能确定依赖和输入、输出
ESModule通过import加载模块,通过export输出模块
下面我们来详细介绍ESModule。
ESModule使用export正常导出,import导入所有通过export导出的属性,在import中可以通过结构的方式,解构出来。
export导出
constname='dog'constauthor='xiaoming'export{ name,author}exportconstsay=function(){ console.log('hello,world')}import导入
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'默认导出exportdefaultconstname='dog'constauthor='xiaoming'constsay=function(){ console.log('hello,world')}exportdefault{ name,author,say}导入
importmesfrom'./a.js'console.log(mes)//{ name:'dog',...}exportdefaultanything?默认导出。?anything?可以是函数,属性方法,或者对象。
对于引入默认导出的模块,importanyNamefrom'module',anyName可以是自定义名称。
混合导入|导出exportconstname='dog'exportconstauthor='xiaoming'exportdefaultfunctionsay(){ console.log('hello,world')}导入有两种方式,第一种是:
importtheSay,{ name,authorasbookAuthor}from'./a.js'console.log(theSay,//?say(){ console.log('hello,world')}name,//'dog'bookAuthor//'xiaoming')第二种:
importtheSay,*asmesfrom'./a'console.log(theSay,//?say(){ console.log('hello,world')}mesmes对象如下,可以看到把导出的所有属性都收敛到了一个对象里面,其中exportdefault导出值的key为default。
{ name:'dog',author:'xiaoming',default:?say(){ console.log('hello,world')}}ESModule特点静态语法ESModule的设计理念是希望在编译时就确定模块依赖关系即输入输出,那如何在编译时就能确定依赖关系呢?
在传统编译语言的流程中,程序中的一段源代码在执行之前都需要经过"编译"。对于JavaScript这样的解释型语言来说,也是需要编译的,只不过编译过程发生在代码执行前的几微秒(甚至更短)的时间内。
要想在编译阶段就能确定依赖关系,那必须要把import进行类似于变量提升。我们来看一下JavaScript中的变量提升。
functiontest(){ console.log(a)console.log(foo())vara=1functionfoo(){ return2}}test()在编译阶段发生了变量提升,经过预编译,执行顺序就变成了这样:
functiontest(){ functionfoo(){ return2}varaconsole.log(a)console.log(foo())a=1}test()所以打印结果是:
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'0import提升其实JavaScript代码在编译阶段发现有import也会像var一样进行提升。为了验证这一点,看一下如下demo。
main.js
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'1a.js
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'2b.js
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'3执行顺序如下:
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'4当执行main.js,可以看到先答应"b模块加载",但是main.js第一行代码是console.log('main.js开始执行'),但是并没有执行。这是因为在编译阶段把import进行了提升,类似于var的变量提升,所以会首先执行import语句。
也就是在编译阶段去加载模块,然后在执行阶段就去执行文件,这跟var变量的执行顺序是一样的,即首先会把vara=undefined提升,然后在执行阶段去赋值。
因为这种静态语法,所以import?,?export?不能放在块级作用域或条件语句中。
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'5在编译过程中确定了导入和导出的关系,所以更方便去查找依赖,更方便去treeshaking(摇树),这也是ESModule支持tree-shaking操作的原因。同时,还可以使用各种lint工具对模块依赖进行检查,比如:eslint。cc支付源码2021
导出绑定:不能修改import导入的属性//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'6当执行main.js的时候会报错:UncaughtTypeError:Assignmenttoconstantvariable。通过import导入的值可以看出是一个const常量,不能修改。
引用传递Common.js是值的拷贝,ESModule是引用传递。
Common.js
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'7当第一次打印导入的变量a的值是1,然后执行plus方法,再次打印a发现值仍然是1。当我们通过get方法获取模块内的变量a的时候,发现值为2。所以,在Commonjs规范下,导入的变量只是值的拷贝而已,具体细节可以参考上一篇文章#CommonJS规范详解。
ESModule
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'8当第一次打印导入的变量a的值是1,然后执行plus方法,再次打印a发现值是2。也就是,使用import导入的变量是与原变量是引用关系,而不是拷贝。
import()动态引入import()?返回一个?Promise?对象,返回的?Promise?的then成功回调中,可以获取模块的加载成功信息。我们来简单看一下?import()?是如何使用的。
//name,author,say对应a.js中的name,author,sayimport{ name,author,say}from'./a.js'9打印结果如下:
CommonjsVSESModule通过上面的介绍,我们来总结下Commonjs和ESModule的区别:
Commonjs的输出是值的拷贝,ESModule的输出是值的引用。
Commonjs是运行时加载,只有在运行结束后才能确定输入和输出,ESModule在编译的时候就能明确的知道输入和输出,它是一个确定的结果。
像这段代码在仅仅被parse成AST(抽象语法树)时,很难分析出究竟依赖了哪些模块。
constname='dog'constauthor='xiaoming'constsay=function(){ console.log('hello,world')}exportdefault{ name,author,say}0同样,Commonjs在做模块导出时也无法静态识别:
constname='dog'constauthor='xiaoming'constsay=function(){ console.log('hello,world')}exportdefault{ name,author,say}1但是在ESModule中,import/exports一目了然,对于没有被import的部分,也很自然的容易区分出来,并进行tree-shaking。
总之,就是通过限定语法,让本来需要运行代码才能确定的依赖,可以在AST阶段就能确定下来。
Commonjs为同步加载,ESModule支持异步加载,可以通过import().then()来实现。
原文;/post/Vite 源码学习3. package.json分析
本文着重解析了Vite项目中dependencies和devDependencies依赖包的用途,以理解Vite如何利用第三方库进行开发。Vite的dependencies部分主要用于项目运行时,包含了JavaScript解析器如@babel/parser,支持CommonJS语法的@rollup/plugin-commonjs,以及用于处理TypeScript类型定义的@types/*等。例如,@vue/compiler-dom和@vue/compiler-sfc是Vue模板和SFC底层工具集,brotli-size则用于字符串和Buffer的压缩。devDependencies则主要为开发环境提供支持,如@babel/runtime为Babel运行时工具,jest用于测试,prettier则负责代码格式化。dependencies:关键库如@vue/compiler-dom负责Vue模板编译,@rollup/plugin-node-resolve用于模块定位,@types/*提供TypeScript类型支持。
devDependencies:如jest用于编写和运行测试,postcss和less是CSS处理工具,typescript支持项目使用TypeScript,vue-router和vuex则提供路由和状态管理功能。
这些依赖包的合理配置,使得Vite能够在高效开发的同时,确保项目的稳定性和可维护性。后续内容将继续探讨Vite的其他组件和配置。微信小程序使用Promise
在微信小程序开发过程中,我曾遇到使用Promise的问题。官方示例中的cb虽然曾经让我们熟悉,但现在转向Promise是必要的,否则我难以接受,这让我辗转反侧,一整夜未眠。清晨,我便迫不及待地寻找解决方案。
首先,尝试直接利用Promise,尽管在小程序环境中可行,但受浏览器支持范围的限制,不能保证所有环境都能顺利运行。为解决兼容性问题,我考虑引入第三方库,如bluebird或Q,这些库可以在不受浏览器依赖的情况下工作。
开始行动,我选择使用bluebird,添加到项目结构中。然而,在App.js中尝试时,发现Promise并未被定义,尝试失败。这时,我开始深入探究。
我怀疑是否因为小程序禁止加载第三方JS。但我不愿完全依赖自己的实现,于是回想起来,在logs.js中引入util.js的成功经验。小程序的加载机制并非CMD或AMD,而是有着独特的设计,类似Angular。每个js文件都有自己的包头和局部window对象,document可能并不总是可用。
再次尝试引入Q,我注意到它支持多种加载方式,包括CommonJS和RequireJS。尽管如此,我仍未能在调试中找到使用第三方包的途径,因为小程序有自己的加载规则,这使得阅读bluebird源码时感到困惑。
总结,要在微信小程序中使用Promise或者第三方库,需要理解和适应其独特的加载机制,这包括理解局部window对象和文档对象的限制,以及require函数和module对象的使用方式。这是一次对小程序内部机制的深入探索,也提醒我们在开发时要注意其特定的限制和优化策略。
一文带你快速上手Rollup
项目中一直用的都是webpack,前一段需要开发几个类库供其他平台使用,本来打算继续用webpack的,但感觉webpack用来开发js库,不仅繁琐而且打包后的文件体积也比较大。正好之前看vue源码,知道vue也是通过rollup打包的。这次又是开发类库的,于是就快速上手了rollup。
什么是rollup?
关于rollup的介绍,官方文档已经写的很清楚了:Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。
与Webpack偏向于应用打包的定位不同,rollup.js更专注于Javascript类库打包。
我们熟知的Vue、React等诸多知名框架或类库都是通过rollup.js进行打包的。
为什么是rollup?
webpack我相信做前端的同学大家都用过,那么为什么有些场景还要使用rollup呢?这里我简单对webpack和rollup做一个比较:
总体来说webpack和rollup在不同场景下,都能发挥自身优势作用。webpack对于代码分割和静态资源导入有着“先天优势”,并且支持热模块替换(HMR),而rollup并不支持。
所以当开发应用时可以优先选择webpack,但是rollup对于代码的Tree-shaking和ES6模块有着算法优势上的支持,若你项目只需要打包出一个简单的bundle包,并是基于ES6模块开发的,可以考虑使用rollup。
其实webpack从2.0开始就已经支持Tree-shaking,并在使用babel-loader的情况下还可以支持es6 module的打包。实际上,rollup已经在渐渐地失去了当初的优势了。但是它并没有被抛弃,反而因其简单的API、使用方式被许多库开发者青睐,如React、Vue等,都是使用rollup作为构建工具的。
快速上手
我们先花大概十分钟左右的时间来了解下rollup的基本使用以及完成一个hello world。
安装
首先全局安装rollup:
接着,我们初始化一个如下所示的项目目录
首先我们在src/index.js中写入如下代码:
然后在命令行执行以下命令:
执行命令,我们即可在dist目录下生成bundle.js文件:
这时,我们再在example/index.html中引入上面打包生成的bundle.js文件,打开浏览器:
如我们所预料的,控制台输出了柯森。
到这里,我们就用rollup打包了一个最最简单的demo。
可能很多同学看到这里对于上面命令行中的参数不是很明白,我依次说明下:
其实除了这两个,还有很多其他常用的命令(这里我暂且列举剩下两个也比较常用的,完整的 rollup 命令行参数):
使用配置文件(rollup.config.js)
使用命令行的方式,如果选项少没什么问题,但是如果添加更多的选项,这种命令行的方式就显得麻烦了。
为此,我们可以创建配置文件来囊括所需的选项
在项目中创建一个名为rollup.config.js的文件,增加如下代码:
然后命令行执行:
打开dist/bundle.js文件,我们会发现和上面采用命令行的方式打包出来的结果是一样的。
这里,我对配置文件的选项做下简单的说明:
到这里,相信你已经差不多上手rollup了。
进阶
但是,这对于真实的业务场景是远远不够的。
下面,我将介绍rollup中的几种常用的插件以及external属性、tree-shaking机制。
resolve插件
为什么要使用resolve插件
在上面的入门案例中,我们打包的对象是本地的js代码和库,但实际开发中,不太可能所有的库都位于本地,我们大多会通过npm下载远程的库。
与webpack和browserify这样的其他捆绑包不同,rollup不知道如何打破常规去处理这些依赖。因此我们需要添加一些配置。
resolve插件使用
首先在我们的项目中添加一个依赖the-answer,然后修改src/index.js文件:
执行npm run build。
这里为了方便,我将原本的rollup -c -w添加到了package.json的scripts中:"build": "rollup -c -w"
会得到以下报错:
打包后的bundle.js仍然会在Node.js中工作,但是the-answer不包含在包中。为了解决这个问题,将我们编写的源码与依赖的第三方库进行合并,rollup.js为我们提供了resolve插件。
首先,安装resolve插件:
修改配置文件rollup.config.js:
这时再次执行npm run build,可以发现报错已经没有了:
打开dist/bundle.js文件:
打包文件bundle.js中已经包含了引用的模块。
有些场景下,虽然我们使用了resolve插件,但可能我们仍然想要某些库保持外部引用状态,这时我们就需要使用external属性,来告诉rollup.js哪些是外部的类库。
external 属性
修改rollup.js的配置文件:
重新打包,打开dist/bundle.js文件:
这时我们看到the-answer已经是做为外部库被引入了。
commonjs插件
为什么需要commonjs插件
rollup.js编译源码中的模块引用默认只支持 ES6+的模块方式import/export。然而大量的npm模块是基于CommonJS模块方式,这就导致了大量 npm模块不能直接编译使用。
因此使得rollup.js编译支持npm模块和CommonJS模块方式的插件就应运而生:@rollup/plugin-commonjs。
commonjs插件使用
首先,安装该模块:
然后修改rollup.config.js文件:
babel插件
为什么需要babel插件?
我们在src目录下添加es6.js文件(⚠️ 这里我们使用了 es6 中的箭头函数):
然后修改rollup.config.js配置文件:
执行打包,可以看到dist/esBundle.js文件内容如下:
可以看到箭头函数被保留下来,这样的代码在不支持ES6的环境下将无法运行。我们期望在rollup.js打包的过程中就能使用babel完成代码转换,因此我们需要babel插件。
babel插件的使用
首先,安装:
同样修改配置文件rollup.config.js:
然后打包,发现会出现报错:
提示我们缺少@babel/core,因为@babel/core是babel的核心。我们来进行安装:
再次执行打包,发现这次没有报错了,但是我们尝试打开dist/esBundle.js:
可以发现箭头函数仍然存在,显然这是不正确的,说明我们的babel插件没有起到作用。这是为什么呢?
原因是由于我们缺少.babelrc文件,添加该文件:
我们看.babelrc配置了preset env,所以先安装这个插件:
这次再次执行打包,我们打开dist/esBundle.js文件:
可以看到箭头函数被转换为了function,说明babel插件正常工作。
json插件
为什么要使用json插件?
在src目录下创建json.js文件:
内容很简单,就是引入package.json,然后去打印author字段。
修改rollup.config.js配置文件:
执行打包,发现会发生如下报错:
提示我们缺少@rollup/plugin-json插件来支持json文件。
json插件的使用
来安装该插件:
同样修改下配置文件,将插件加入plugins数组即可。
然后再次打包,发现打包成功了,我们打开生成的dist/jsonBundle目录:
完美!!
tree-shaking机制
这里我们以最开始的src/index.js为例进行说明:
修改上述文件:
执行打包。打开dist/bundle.js文件:
再次修改src/index.js文件:
再次执行打包,打开打包文件:
发现了什么?
我们发现关于变量b的定义没有了,因为源码中并没有用到这个变量。这就是ES模块著名的tree-shaking机制,它动态地清除没有被使用过的代码,使得代码更加精简,从而可以使得我们的类库获得更快的加载速度。
总结
本文大致向大家介绍了什么是rollup以及如何快速上手rollup。文中提到的这些其实只是冰山一角,rollup能玩的东西还有很多,关于更多可以去 rollup 官网查询。
element-plus源码与二次开发:package.json解析
element-plus使用pnpm的workspace来搭建monorepo工程,允许在单一码仓库中集中管理大量互相依赖的包,同时确保发布时的独立性。pnpm-workspace.yaml文件在根目录声明内部可引用的包,执行pnpm i后,会在node_modules中创建软连接,无需手动link。
element-plus组件库将vue声明在peerDependencies中,避免在主项目安装组件库时额外安装vue。通过czg包定义规范,执行提交commit命令,确保遵循git规范。使用play子包进行简单的开发调试,引入本地组件库。通过gen命令快速创建新组件,使用模板生成组件基础模板。生成版本号文件命令用于在构建时提供rollup的banner参数,部署前更新版本号命令从环境变量获取TAG_VERSION和GIT_HEAD,写入到三个包的package.json中的version和gitHead字段。
清理dist目录命令使用pnpm run -r --parallel,以并行方式执行所有子包的命令,删除根目录下的dist目录,并执行所有子包的clean命令。构建文档和组件库的关键步骤包括使用rollup执行构建,通过@esbuild-kit/cjs-loader将esm和ts实时转换为CommonJS。生成类型声明文件和代码提示文件,复制源样式文件、编译为css、压缩,并输出到特定目录。启动组件库文档docs项目基于vitepress,构建组件文档,本地测试构建出的生产环境docs,生成多语言文件和CROWDIN_TOKEN。
执行各包的stub命令,使用unbuild打包工具,基于rollup,支持typescript,支持生成commonjs和esmodule和类型声明,无需额外配置。prepare Husky钩子脚本确保自动执行预定义命令,执行pnpm i后,自动执行pnpm stub,编译internal下的三个包入口。
通过上述详细解析,我们可以清晰了解到element-plus源码与二次开发中的核心功能与流程,从构建结构到构建流程,再到二次开发工具的运用,展示了其高效、灵活的特点。