当前位置: 首页 > Web前端 > HTML

【Vite实践】Vite库模式能让你满意吗?也许你需要统一构建

时间:2023-03-28 01:07:57 HTML

2022年,我投入了Vite的怀抱,开始参与到Vite社区中,陆续开发了一些插件。Vite秉承开箱即用、简化配置的理念,确实显着提升了前端开发体验。但是,类库模型的构建存在一些不足。只能处理单输入和单输入输出的情况。主意。目前vite-plugin-build插件可以直接使用,也已经包含在Vite官方的awesome-vite中。我希望它可以满足一些人的需求。什么是统一构建?因为没有特别好听的名字,我暂且称之为统一搭建。我把统一构建总结为如下构建:Bundle构建是Vite(也是Rollup)库打包方式,单输入文件,单输出bundle文件,如果不设置Externaldependencies(外部),所有涉及到的依赖包都会被打包成一个bundle文件.优点:支持umd格式,可以作为浏览器中的外部依赖,不受业务代码bundle影响,可以利用浏览器缓存机制提高加载性能。缺点:不支持TreeShaking,不用的代码也会被加载。由于打包成bundle文件,本地源代码的可读性较差。文件夹构建(file-to-filetransformer)文件夹中所有符合格式(['ts','tsx','js','jsx','vue','svelte'])的文件将被转换为对应同名的.js文件,只支持commonjs和es两种格式。转换时,所有import依赖的包都不会被打包进去,按照需要的转换格式转换成commonjsrequire或者esimport语法。优点:es模式可以支持TreeShaking,本地源码可读性强。缺点:代码在Webpack、Vite等构建工具中会和业务代码一起打包到bundle文件中,难以发挥跨站缓存的优势。生成TypeScript声明文件支持生成原生TypeScript、VueTypeScript和SvelteTypeScript声明文件(如果有其他类型的框架也可以在这里扩展)。vite-plugin-build使用Vite与@vitejs/plugin-react、@vitejs/plugin-vue、@sveltejs/vite-plugin-svelte一起支持以上三种构建方式。为什么要开发一个Viteunitybuild插件?原因1、Vite构建场景单一,不支持以下场景:多输入多输出(multipleinputandmultiplebundle)转换文件夹(文件到文件的转换方式,不打包成bundle文件)生成TypeScript声明文件原因二,Vite社区缺乏直接替换工具。ViteGithub上的官方插件库使用的是unbuild,这是一个统一的构建工具。虽然很方便,但unbuild更倾向于处理纯JavaScript或TypeScript代码。对于React、Vue、Svelte等浏览器UI类型相关的Packing缺少关联的转换处理。第三个原因是Vite系统提供一站式服务。Vite统一构建插件可以让Vite和Vitest在某些场景下形成一个闭环系统,不需要使用其他构建和单元测试工具,一个Vite配置文件就可以打通世界。在Vite出现之前,公司群中的业务组件和一些个人Github项目最初都是使用Rollup构建的。Rollup不能开箱即用,需要各种插件配置,比较繁琐。后期使用Vite的库方式替换原来的Rollup可以减少很多插件配置,但是整个文件夹的所有文件都单独转为commonjs和es格式,仍然需要通过Vite提供的buildAPI来实现.下面是通过指定的文件夹,然后遍历运行build方法来实现(需要多一个Vite配置文件来配置):constfs=require('fs');constpath=require('path');constspawn=require('cross-spawn');constsrcDir=path.resolve(__dirname,'../src');//所有src文件夹包括子文件夹的js,ts,jsx,tsx文件路径数组constsrcFilePaths=getTargetDirFilePaths(srcDir);srcFilePaths.forEach((file)=>{constfileRelativePath=path.relative(srcDir,file);spawn('npm',['run','vite','--','build','--mode',fileRelativePath,'--outDir','es','--config','./vite.file.config.ts'],{stdio:'inherit',},);});同时还需要配置npmruntsc生成声明文件。pacakge.json脚本字段很麻烦:{"scripts":{"tsc:es":"tsc--declarationDires","tsc:lib":"tsc--declarationDirlib","tsc":"npmruntsc:lib&&npmruntsc:es","vite":"vite","build:lib":"vitebuild","build:file":"node./scripts/buildFiles.js","build":"npmruntsc&&npmrunbuild:file&&npmrunbuild:lib"}}新建工程,直接复制修改即可,虽然可以达到搭建的目的,但是不够方便,我比较懒,还是想有更简单的方法?可以通过使用如下脚本来解决吗?{"scripts":{"build":"vitebuild"}}实现思路首先无法通过Vite配置文件实现统一构建功能,即无法通过运行Vite构建来实现统一构建功能服务一次正常,或者需要多次运行Vite构建服务来实现这个功能(表面上用户是没有意识到的)。实现要点Bundle构建Vite库方式为bundle构建方式,但只能设置一个入口文件和一个bundle输出文件。实际场景可能需要多个入口文件和多个bundle输出文件。这个功能并不复杂,通过遍历多次vitebuild构建即可实现。代码大致如下:import{build}from'vite';constbuildPs=lastBuildOptions.map((buildOption)=>{returnbuild({...viteConfig,//透传vite.config.ts或vite.config.js的用户配置,插件需要自己过滤(vite-plugin-build)mode:'production',configFile:false,logLevel:'error',build:buildOption,});});等待Promise.all(buildPs);文件夹构建的实现思路并不复杂,如下:获取文件夹中所有匹配的文件路径遍历所有文件路径,运行vitebuild构建。由于Vue和Svelte的导入需要有后缀,所以需要额外去掉文件内容中的.vue和.svelte后缀。简单的代码实现大致如下:import{build}from'vite';从'fast-glob'导入fg;const{inputFolder='src',extensions=['ts','tsx','js','jsx','vue','svelte'],ignoreInputs,...restOptions,}=options;//获取所有js,ts,jsx,tsx,vue,sevele默认在项目根目录下的src文件夹包括子文件夹文件路径,三个扩展名的文件除外:.test.*,.spec.*and.d.ts//返回格式为['src/**/*.ts','src/**/*.tsx']constsrcFilePaths=fg.sync([`${inputFolder}/**/*.{${extensions.join(',')}}`],{ignore:ignoreInputs||[`**/*.spec.*`,'**/*.test.*','**/*.d.ts','**/__tests__/**'],});constbuildPromiseAll=srcFilePaths.map((fileRelativePath)=>build({...viteConfig,//透传vite.config.ts或vite.config.js的用户配置,插件需要自己过滤(vite-plugin-build)mode:'production',configFile:false,logLevel:'error',build:{lib:{entry:fileRelativePath,...},...restOptions},}));等待Promise.all(buildPromiseAll);等待removeSuffix();//去掉build文件内容中的`.vue`和`.svelte`后缀TypeScript声明文件生成各种前端UI框架,比如Vue和Svlete有自己的自定义语法,TypeScript语法需要特殊支持,声明文件的生成自然需要原生TypeScript的特殊处理。原生TypeScript官方直接提供了tsc工具,直接运行tscbin文件,通过相应的配置即可实现直接使用。简单的代码实现如下:importspawnfrom'cross-spawn';const{rootDir,outputDir}=选项;consttscPath=path.resolve(require.resolve('typescript').split('node_modules')[0],'node_modules/.bin/tsc');spawn.sync(tscPath,['--rootDir',rootDir,'--declaration','--emitDeclarationOnly','--declarationDir',outputDir],{stdio:'忽略',},);VueTypeScriptVue3出来后,对TypeScript的支持比较完善。可以用vue社区的vue-tsc代替tsc,用法和tsc一致。一个区别是vue-tsc生成的声明文件有.vue.d.ts后缀,所以需要重命名为.d.ts后缀。简单的代码实现如下:importspawnfrom'cross-spawn';const{rootDir,outputDir}=选项;constvueTscPath=path.resolve(require.resolve('vue-tsc/out/proxy').split('node_modules')[0],'node_modules/.bin/vue-tsc',);spawn.sync(vueTscPath,['--rootDir',rootDir,'--declaration','--emitDeclarationOnly','--declarationDir',outputDir],{stdio:'ignore',},);if(isVue){renameVueTdsFileName();//Rename.vue.d.tsto.d.ts}SvelteTypeScriptSvelte社区没有Vue强大,没有vue-tsc这样的工具,最后找到了svelte-type-generator来实现。参考svelte-type-generator的代码实现生成声明文件的功能(暂时不支持tsccli的功能)。const{compile}=require('svelte-tsc');const{rootDir,outputDir}=options;compile({rootDir,declaration:true,emitDeclarationOnly:true,declarationDir:outputDir,});如果(isVue){renameSvelteTdsFileName();//Rename.svelte.d.tsto.svelte.d.ts}打印构建信息由于会触发多次vite构建运行,如果直接输出默认的构建信息,会显得混乱,无法像vite一样运行build输出构建信息。所以需要拦截和隐藏原有的构建信息,自定义新构建信息的输出。拦截并隐藏原有构建信息,可以通过重写console.log和console.warn来达到目的,代码如下:exportconstrestoreConsole={...console};导出类InterceptConsole{公共日志:typeofconsole.log;公共警告:typeofconsole.warn;公开清除:typeofconsole.clear;constructor(){const{log,warn}=console;this.log=日志;this.warn=警告;}silent(){console.log=()=>{};console.warn=()=>{};}restore(){console.log=this.log;console.warn=this.warn;}}自定义输出新的构建信息函数参考Vite内置的reporter插件,通过对应的钩子函数实现构建信息的输出。最终效果Github实例vanillavanilla-tsreactreact-tsvuevue-tssveltesvelte-ts全部实例,运行如下命令$npminstall$npmrunbuildCodesandbox在线实例vanilla-tsreact-tsvue-tssvelte-ts有兴趣的也可以参与构建.目前svelte-tsc实现的用法和vue-tsc一样,有点挑战。生成的声明文件支持配置tsconfig配置文件路径svelte-tsc支持bin文件,功能参考vue-tsc,支持所有tsccli选项。