From 09310fd6e49e18f46e6a66ef71d8df48174aaa86 Mon Sep 17 00:00:00 2001 From: jinphic <78671853+jinphic@users.noreply.github.com> Date: Thu, 4 Jan 2024 12:19:30 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=97=E6=B3=95=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interview/webpack.md | 130 ++++++++++++++++++++++ "interview/\347\233\230\345\217\244.md" | 138 ++++++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 "interview/\347\233\230\345\217\244.md" diff --git a/interview/webpack.md b/interview/webpack.md index e69de29..9afffef 100644 --- a/interview/webpack.md +++ b/interview/webpack.md @@ -0,0 +1,130 @@ +工具链 + +webpack +webpack的热更新 +基础概念 +1.webpack compiler : 将js代码编译成bundle +2. bundle server: 提供文件在浏览器的访问 +3.HMR server:将热更新的文件输出到HMR runtime +4.HMR runtime:会被注入到bundle中,与HMR server 通过websocket链接,接收文件变化并且更新对应文件 +5.bundle.js: 构建输出的文件 +原理 +1.启动阶段 +a.webpack compiler 将对应文件打包成bundle.js 。发送给 bundler server +b.浏览器可以通过访问服务器的方式获得bundle.js +2.更新阶段 +a.webpack compiler 重新编译,发送结果给 HMR server +b.HMR server 计算得出哪些模块变化了,通过websocket发送给HMR runtime +c.HMR runtime 更新代码 +详细过程 +1.使用express启动本地服务,当浏览器访问资源时对此做响应。 +2.服务端和客户端使用websocket实现长连接 +3.webpack监听源文件的变化,即当开发者保存文件时触发webpack的重新编译。 +a.每次编译都会生成hash值、已改动模块的json文件、已改动模块代码的js文件 +b.编译完成后通过socket向客户端推送当前编译的hash戳 +4.客户端的websocket监听到有文件改动推送过来的hash戳,会和上一次对比。 +a.一致则走缓存 +b.不一致则通过ajax和jsonp向服务端获取最新资源 +5.使用内存文件系统去替换有修改的内容实现局部刷新 + +loader 和 plugin + loader是什么 +Webpack默认只认识JS,对于非JS的文件,比方说样式,图片,文件,json等等,就需要一些工具来帮忙翻译。而loader,就是那个翻译官,可以解析非原生JS的代码或文件。 +loader是文件加载器,能够加载资源文件,并对文件进行一些处理。处理一个文件可以使用到多个loader,执行的顺序是从下到上。 +第一个loader最后执行,第一个执行的loader接收源文件内容作为参数,其它loader接收前一个执行的loader的返回值作为参数,最后执行的loader会返回此模块的JavaScript源码。 + +plugin是什么 +Webpack插件是对Webpack功能的扩展和增强,可以帮助我们在打包过程中自动执行一些额外的操作,例如生成HTML文件、压缩代码、提取CSS等 +plugin让webpack的机制更加灵活,它在编译过程中留下的一系列生命周期的钩子,通过调用这些钩子来实现在不同编译结果时对源模块进行处理gin 的实现可以是一个类,使用时传入相关配置来创建一个实例,然后放到配置的 plugins 字段中,而 plugin 实例中最重要的方法是 apply,该方法在 webpack compiler 安装插件时会被调用一次,apply 接收 webpack compiler 对象实例的引用,你可以在 compiler 对象实例上注册各种事件钩子函数,来影响 webpack 的所有构建流程,以便完成更多其他的构建任务。 + + +Tree-shaking +通过静态分析消除js模块中未使用的代码,减小项目体积。 +Tree-Shaking依赖于es6的模块机制,因为es6模块是静态的,编译时就能确定模块的依赖关系,对于非es6模块的代码或者动态引入的代码,无法被消除掉。 +配置 +需要配置webpack.config 中的optimization.useExport 为true, 同时在babel配置中使用babel-preset-env, 开启module为true,避免在构建过程中,es模块被转化成cjs模块。 + +webpack打包流程 +1.解析配置文件,根据配置文件初始化webpack实例,构建打包流程 +2.解析模块依赖,webpack会从entry配置中指定入口文件开始,递归解析模块之间的依赖关系,并构建模块依赖图谱 +3.加载模块:webpack会根据模块依赖图谱,加载所有需要打包的模块,通过配置的loader将文件转换成webpack可以识别的模块。 +4.执行插件:webpack在打包流程中根据钩子触发事件,通知插件完成各种任务,比如生成html 和 压缩代码等 +5.输出打包结果:webpack将打包后的代码和资源输出到指定的输出目录 +6.监听变化:在开发模式下,webpack-dev-server 会执行HMR流程 + +webpack常见事件 +1.before-run: 在开始构建前触发,一般用于清理上一次构建的临时文件或者状态 +2.run: 开始构建时 +3.before-compile: 开始编译代码之前触发,用于添加一些额外的编译配置或者预处理 +4.compile:在webpack开始编译代码时触发,用于监听编译过程或者处理编译错误 +5.emit:在webpack生成输出文件之前触发,用于修改输出文件或生成一些附加文件 +6.after-emit: 在生成输出文件之后触发,用于清理中间文件或执行一些其他草走 +7.done:完成构建时触发,用于生成构建报告或者通知开发者构建结果。 + +webpack5 +1.更快的构建速度,特别是开发者模式下 +2.tree-shaking 优化: 改进了tree-shaking算法 +3.内置的持久化缓存:可以缓存每一个模块的编译结果,加速后续的构建 +4.原生支持webAssembly +5.构建umd产物默认是target:es6 +6.模块联邦 + +模块联邦 + +webpack开发环境优化 +1.使用cache-loader等实现缓存,将模块打包结果缓存避免重复构建 +2.使用dllplugin,将一些不常用的三方库,预先打包好,避免重复打包 +3.开发环境下权衡后是否需要sourcemap +4.多线程打包,thread-loader开启多线程需要额外的开销,并不一定会更快 +5.配置模块解析,webpack在模块解析时会搜索node_module目录,这个耗时大。可以使用alias指定特定路径,或者exclude不解析node_module +6.使用webpack5、vite 、rspack等 + +打包结果优化 +1.打包体积分析,webpack-analyzer +2.代码压缩, css、js压缩 +3.使用懒加载拆分bundle +4.开启gzip:使用compression-webpack-plugin插件,生成额外的gzip静态文件,然后部署时再开启Nginx的gzip即可。 +5.使用splitChunks提取公共代码 +6.分类第三方库,单独打包,提高缓存命中率 + + + +vite +为什么选择vite +webpack-dev-server启动缓慢,他会从入口开始,递归检索需要的模块,经过loader 和 plugin等处理。webpack的HMR随着模块数量变大而变慢。 +vite 是bundless,编写的模块是按文件加载,HMR是在原生ESM上执行的,同时利用HTTP的协商缓存源码模块,依赖模块则会使用强缓存。 +vite在启动时不需要打包,而是请求模块的时候实时编译,在HMR方面,也是让浏览器重新请求该模块即可,不像webpack那样需要把该模块关联的依赖全部编译一次,效率更高。 + +原理 +1.模块解析 +a.启动服务器 +b.解析依赖 +2.预构建 +a.判断本地缓存是否存在 +b.收集依赖的模块路径 +c.esbuild 将三方依赖构建缓存到.vite文件夹,而且将一些cjs的文件替换成esm + + +pnpm +在分析了CI过程后,发现CI耗时主要在构建镜像、依赖解析、依赖安装和构建产物这几部分上 +1.构建镜像:尽量使用相同的node版本,然后预先安装特定的工具,并且保持轻量,能减少导入镜像的耗时 +2.依赖分析:将依赖包的版本区间解析为某个具体的版本号 +a.npm是单线程 +b.yarn多线程解析依赖resolving,在解析完成后下载fetching,最后将下载好的依赖写入node_modules文件夹linking +c.pnpm也是多线程解析依赖,如果存在lockfile就不需要这一步了。 边解析边下载依赖,多个不同的依赖的解析和下载是并行的,可以节省依赖解析 + 依赖安装的整体耗时 +3.依赖安装: +a.部分项目每次CI都重复安装依赖,没有利用好缓存,导致每次CI都需要重新执行依赖安装 +4.构建产物: +a.项目有多条构建命令的时候,要注意指令是否可以并行执行。 + +不同项目pnpm提速波动原因 +1.安装依赖 +a.yarn 和 pnpm 在存在lockfile的情况下,不需要resolving,这一个很耗时的步骤被省略掉了 +b.在本地缓存存在的情况下,yarn 和 pnpm 都能避免从网络下载资源。但是pnpm是硬链接,会比yarn快。当依赖不复杂的情况下,io不够多,提升不明显 +c.在docker上没命中缓存时,pnpm的并行解析和下载,优势比较明显 +d.本地,pnpm会越来越快,因为会不断命中缓存,相同版本的依赖,reuse本地,硬链接很快。 + + + + + diff --git "a/interview/\347\233\230\345\217\244.md" "b/interview/\347\233\230\345\217\244.md" new file mode 100644 index 0000000..51d9589 --- /dev/null +++ "b/interview/\347\233\230\345\217\244.md" @@ -0,0 +1,138 @@ +盘古低代码复盘 +收益维度 +运营数据 +1.接入的团队(总量和新增) +2.创建的应用数量(总量和新增) +3.创建的页面数量 (总量和新增) +效率数据 +1.应用配置平均耗时 +2.页面配置平均耗时 +3.单应用接口开发数量 +4.单应用开发周期 7天 - 1天 +5.单应用人力成本 5人天 - 1人天 +6.联动、校验编写难度降低, AI辅助 +7.工单数量、客服号答疑 +8.发布速度 +9.微前端 + + +安全 +配置安全性 +1.配置事故率 +2.配置错误的可追溯性,日志、操作记录 +3.权限控制,应用级别的控制,页面级别的控制,同一页面内弹窗级别的控制 +4.crush优化,页面出现错误的情况避免配置数据丢失。重载页面后能进行配置数据回填 +逻辑安全性 +1.组件版本控制 +2.页面版本控制 +3.页面回滚 +4.外网接口在非正式环境的拦截 +5.发布环境控制 +6.发布速度 +7.按需加载 +8.接口请求避免出现 “413 payload too large" + +技术选型 +1.技术栈适配性: react、formily +2.是否开源 +3.代码的完成度 和 质量 +4.社区的生态,star 数量、discussion 、issue 回复速度 +5.团队积累 +6.接入难度 +7.扩展性 + +扩展点 +1.稳定性, clone一份维护,保持和向上兼容,外部资源替换 +2.跟进issue更新 +3.易用性改造,文档整理,素材的生成和消费路径缩减,指定准入规范 +4.新特性:插件式 +5.将这种可视化编排生成schema的编辑器的,抽象成可直接使用的 + +交互设计 +1.增强引导与提示 +2.简化/省略不必要的步骤,增加默认值 +3.保持配置流程连续性,避免来回跳 +4.其他交互细节优化 +5.文档同步更新 +6.接入ChatGPT,智能创建管理系统 + +HEART模型 +happiness:满意度 +engagement:参与度 +adoption:接受度 +retention:留存率 +Task success:任务完成率 + +素材的开发、构建和消费串联了整个平台 +素材开发 +1.本地开发、热更新 +2.本地预览 +3.二次开发创建素材库 +4.本地轻量版本的playground + +官方素材库设计 lerna+monorepo +1.分类: 基础组件,体积不大并且改动不频繁 +复杂组件,体积可大可小,改动频繁 或者是 业务类型组件需要独立的版本控制 +2.构建配置:公用配置 和 extend +3.版本控制 +4.正式版本发布控制在master +5.esm产物和umd产物 +6.脚手架创建 +7.本地轻量的playground 通过alias去引用对应包的东西,这样能实现修改组件里面实现热更新,同时打出cjs产物用于类型提醒 + +构建 +1.script module : 基于umi的项目,顶层await 改造量大 +2.npm包:参与构建,无环境区分、难按需加载、安装依赖耗时和版本控制难以解决, 重复安装依赖 +3.git submodule: 仓库耦合、无法二次开发、管理和更新过于复杂 +4.umd:传递性、依赖顺序、组件体积偏大 + +构建依赖分析 +umd具有传递性,一个包用umd产物,那么它本身external的那些依赖都必须是umd的 +某些重复使用的UI组件库和依赖库,需要单独构建到一个umd bundle中,避免package的构建重复打包 +依赖的顺序控制 + +步骤 +1.tsc 生成esm和cjs的产物用来发npm包在其他地方使用,也可以用作类型声明 +2.rollup 根据情况区分,是整个package一个umd产物,还是package中每个组件一个umd产物,这种情况先构建了无版本号的umd资源 +3.读取lerna配置的version,通过copy命令复制umd产物,文件名称带上版本号 +4.build maifest, 生成每个umd产物的资源索引文件,比如组件A它的js外链、css外链列表。 + + +二次开发 +1.初始化脚手架 +2.使用脚手架创建组件 +3.推送代码出发CI生成UMD资源文件 +4.二次开发的仓库绑定到低代码平台,低代码平台通过正则将git地址转成唯一标识前缀。 +5.拉二次开发资源的时候,可以通过拼接git标志和文件,拉取到二次开发的代码资源 + +素材的消费-配置平台 +1.通过脚本操作html模板,每次CI动态生成 +2.读取manifest文件,动态生成script外链,按预定顺序,插入html模板的插槽中 +3.使用的不带版本号的umd资源,确保每次都是最新的 + +素材的消费-产物平台 +1.产物的渲染完全依赖于fomily-render,通过接口拉取配置平台保存的schema, 使用Formily/SchemaField渲染。 +2.产物采用的是运行时渲染,而不是出码渲染。 SchemaField本身就是一个渲染器 +3.产物平台项目代码,只是一个空壳的layout,核心渲染部分完全是在配置平台配置的部分,这部分由SchemaFild 和 保存的schema渲染。 +需要做的按需引入用到的组件的umd。 +4.在配置平台保存的时候 a. 保存/发布 +b. 获取需要发布的机器的ip c. 通知其他机器一起执行发布 d. 判断是否需要重新构建 - 素材版本是否变了 - 产物平台的md5是否变了 e. 不需要重新构建,更新html版本号即可。 index_version.html 和 index.html +f. 需要重新构建,在空壳html 中,按需插入引用到的组件的最新的带版本号的外链 g. 更新html版本号,并且覆盖当前html +5.产物的环境区分: 资源外链都是通过node插入的,引入对应环境的资源链接 +6.资源复用,按需加载:资源外链都是通过node按需插入的 +7.发布速度达到3-5秒:只需要node动态插入组件外链资源,不需要重新构建项目 +8.项目隔离:每个项目一个html,发布和回滚,都可以通过保存的html版本号,找到对应的html文件。 +9.项目稳定性保证 + +监控 +1.页面错误告警 +2.素材体积看板 +3.素材-页面关联看板:每个页面用到了哪些组件,每个组件在哪些页面用到了 + + +Easyform +抽象成一个可直接调用的组件,提供给其他平台使用 +有一些平台需要 “配置一个底板, 在其他地方复用此底板生成表单,然后产品再在新的表单上填写数据”。 +生成底板和渲染表单的能力可以提供给其他项目使用 + +