-
Notifications
You must be signed in to change notification settings - Fork 317
New issue
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
Vue 项目架构设计与工程化实践 #14
Comments
厉害,学习了!支持95哥! |
@tongshouyu1019 你github账号怎么这么丑。哈哈哈哈哈哈哈哈 |
总结的nice啊 |
@LoLDragon 😝😝😝😝 |
cdn发布上线的配置不是可以直接设置assetsPublicPath吗 |
@suanmei 怎么设置 assetsPublicPath?是直接设置cdn的线上地址?文件打包在本地的静态文件也可以自动上到cdn上?上传完cdn 的静态文件地址能和生成的html文件里引入的静态文件地址也是一样的? |
哦哦,我没细看你具体的实现。我们只需要生成dist,通过rsync发布。assetsPublicPath直接设置cdn线上地址,打包的js和css在html中就直接替换成cdn地址的了,静态资源也是 |
@suanmei 噢噢噢,你们的静态文件地址是固定的?比如: |
修改html文件中的路径应该可以直接在构建的时候就可以了吧 ,根本不需要写个插件,另外上传cdn的事应该是交给deploy部署的时候部署到cdn的吧 |
@zhanfenghai html 是用HtmlWebpackPlugin生成的,我不太清楚你说的修改html中的路径是怎么修改,,能否举个例子??我们这上线是先把静态文件上cdn,然后在把文件上线到服务器,上html和静态文件是分开的,静态文件上传完cdn之后域名是随机的,文件名是hash的,每次修改代码上完cdn的cdn地址都完全不一样,我不太清楚你说的可能是场景不一样吧~~ |
事件机制其实是js非常强大的一个能力,就文中提到的问题而言,是事件机制的内存管理没有理解清楚,也很容易避免。事件机制的真正问题我认为是很容易滥用和难以维护,这一点是团队协作中比较头疼的,如果要用的话,需要有相关注释和文档辅助才好。 另外项目工程化相关的内容,包括但不限于开发环境的配置、mock服务、发布等功能,我们团队开源的dawn可以很方便的实现这些功能,可以更专注于业务。可以了解下https://github.com/alibaba/dawn |
@xdlrt 有道理👍👍👍 我们这因为项目有vuex,绝大部分需求其实是用不到全局事件机制的,所以我推荐我的小伙伴尽量不使用全局事件,这个确实很容易滥用~ 你发的链接我了解下 😄😄😄 |
静态资源上传到cdn有更加简便而且合理的做法,就是配置webpack的 |
@funkyLover 嗯嗯嗯,我看看~ 我们的cdn地址有好多个域名每次上传之后都是随机的,然后文件名也是根据内容md5的,不知道这种情况能不能使用这个 publicPath,我研究研究~ |
@berwin 文件名不会有影响,因为文件名的生成也是在webpack内部生成的,至于cdn地址随机的话,如果cdn地址只有上传的那一刻才能确定的话那估计就没法了 |
@berwin 利用hera在docker中先渲染html,再把html发送到服务器这个怎么实现呢?这样的意义是不是和服务端渲染一样,为了SEO和减少首屏渲染时间? |
@ishowman 不不不,你完全理解错了,Hera 不是渲染html,而是 Hera会启动一个docker环境,然后在这个环境跑 webpack 的编译逻辑,然后会编译出一个 纯html,然后在把这个html发送到服务器的某个目录下~ |
少了nginx配置环节 才算完整吧 |
@jiangtao 哈哈哈,nginx 已经超出这个标题的范畴啦~ |
厉害, 还跟不上老师的节奏! |
厉害了 支持95哥 |
很nice |
老铁没毛病,双击666 |
@funkyLover 我感觉你说的是对的,我确实弄麻烦了而且不是特别合理。但是按照你的推荐方式现在遇到一个问题我想不太明白怎么解决。
我想请教一下大佬,这个问题我应该怎么解决比较优雅一点呐~ 😁😁 |
文中说
访问store并不需要vue实例,直接把store 实例 import 进来就可以使用了,vue实例上的$store就是这个store实例。这里有尤大的回答: |
@berwin |
|
还有代码发布到编译机也不够专业,这是CI/CD的范畴,基于git进行代码拉取编译,才能保证可靠以及便于管理追踪 |
@enml 咦?难道你还不明白 “公司的技术环境,基础设施和开发体系”和 “开源社区” 并不完全一样?开源社区的解决方案并不是直接抄过来就行。换句话说,解决方案是选择合适的,而不是看似完美的。
首先需要达成一个共识是:什么是纯洁的?这个东西很主观,a->b是纯洁的,但cdn也是把 a->b,为什么就不纯洁了?就因为中间有异步请求就认为是不纯洁? 还有抛开纯洁不谈,在部署上线时是不care loader上传cdn这个时间的,所以在这里将本地地址a转换成cdn地址b我认为很合适。
这个方案的前提是,你的 文件路径 是可预测的,如果无法预测呢?
最后关于代码发布问题,这个又要谈到环境了,所谓的CI/CD只是解决方案之一而已,与专业与否没有一毛钱关系~ |
@berwin 抱歉啊,居然到现在才看到at我的信息,我只是个前端切图仔不是大佬哈😅
同理如果上传到cdn时文件名会发生变化的话,也是没有办法简单通过配置实现的 所以之前的评论并不知道你的场景是如此 了解到实际场景之后,我觉得像你之前实现也并没有什么问题 后面我自己思考了一下,也没有想到更好方法 思考得出的可能解也只是把上传获取最终文件名的步骤放到plugin中去实现而已 不知道从那时到现在有没有更进一步优化呢? |
@funkyLover 哈哈哈哈,后来我的个人项目用七牛云这种上传的CDN文件就是使用你的说的方案,设置下 publicPath 就行。公司的还在使用插件和loader~ 自己的用publicPath更方便,因为像七牛这种比如本地的文件是 但是公司的是 |
哦,随意,我只是技术讨论,对争吵没兴趣。我指出的文章中不专业的地方,明明有更优雅的实现,非要用一句“这是最适合我环境的做法”来反驳那就没意思。 |
@enml 我相信没有任何一个程序员在有更优雅的方案时还会选择更low的方案,所有的方案都是有权衡和取舍的~
这句话的前提是,“更优雅的实现真的更优雅”?你觉得是你太强大了还是我太菜不知道你说的这些方案?如果都不是,那么是不是应该在评论前先思考下,为什么有一个看似更优雅的方案但我却选择了一个不那么被理解的方案? 我也不喜欢吵架,我欢迎提建议和问题,但是,请注意基本的社交礼仪,我的原则是我会以相同的态度回复对方~ |
@enml 技术讨论有个很重要的点就是“场景”,不然就是纸上谈兵,无法落地的实现再优雅也解决不了问题 |
看到此文,有种相见恨晚的感觉。感谢95大佬的分享 |
很好奇楼主的反向校验后端api是怎么实现的,楼主能把这个api-proxy开源下吗? |
另外,我有个疑问,"......,因为在router中拿不到vue实例,无法直接操作vuex的方法,这个时候如果没有 event-bus 就很难操作。",为什么在router要拿vue实例呢,即便拿不到Vue实例也可以直接操作store吧?因为我最的项目涉及到了单点登录(就像楼主的登录需要去登录中心登录一样),我的做法是在全局的路由钩子中检查sid,如果有就拿着sid请求后端接口(token等认证信息)并存储到store中,这里store就不是通过vue实例获取的,是直接操作的。我觉得可能我们的架构稍微有些不同吧,不知道楼主现在是怎么解决这个问题的? |
@yangtoude 当时我以为只能在this里操作store,哈哈哈哈哈,还是太naive了~ 😁😁后来才发现原来store就是new Vue(),这一点其实和event-bus没什么不同,当时还是太naive了。 |
hello 请问打包到git上的源码可以看么 |
主要是你用的比较早,全靠自己摸索,我觉得你能想出这套架构设计经验来已经挺不容易了。 |
其实我也是想说这个,并不需要Vue的实例,直接引入就行。 |
webpack hook emit事件时,上传文件到cdn 也可以的。目的都是为了把复杂工作简单化,人工变自动化。复杂事情简单化 |
您好,有该架构的模块代码地址吗? 我看到您的文章里面提到proxy mocker这点,【不开启Mock的时候使用Mock规则来校验接口返回是否符合预期】从代码来看,我没有看到关闭mock时,如何用mock规则校验后端接口返回的数据格式,所以想看下架构源码,谢谢 |
如果使用 eventBus,有一个自动销毁事件的插件 vue-happy-bus 可以参考下,思路还是挺好的 |
Vue 项目架构设计与工程化实践
文中会讲述我从0~1搭建一个前后端分离的vue项目详细过程
Feature:
前段时间我们导航在开发一款新的产品,名叫 快言,是一个主题词社区,具体这个产品是干什么的就不展开讲了,有兴趣的小伙伴可以点进去玩一玩~
这个项目的1.0乞丐版上线后,需要一个管理系统来管理这个产品,这个时候我手里快言项目的功能已经上线,暂时没有其他需要开发的功能,所以我跑去找我老大把后台这个项目给拿下了。
技术选型
接到这个任务后,我首先考虑这个项目日后会变得非常复杂,功能会非常多。所以需要精心设计项目架构和开发流程,保证项目后期复杂度越来越高的时候,代码可维护性依然保持最初的状态
后台项目需要频繁的发送请求,操作dom,以及维护各种状态,所以我需要先为项目选择一款合适的mvvm框架,综合考虑最后项目框架选择使用 Vue,原因是:
所以最终选择了Vue
选择vue周边依赖(全家桶)
框架定了Vue 后,接下来我需要挑选一些vue套餐来帮助开发,我挑选的套餐有:
架构设计
在开发这个项目前,我去参加了北京的首届 vueconf 大会,其中有一个主题是阴明讲的《掘金 Vue.js 2.0 后端渲染及重构实践》,讲了掘金重构后的架构设计,我觉得他们的架构设计的挺不错,所以参考掘金的架构,设计了一个更适合我们自己业务场景的架构
整体架构图
目录结构
从目录结构上,可以发现我们的项目中没有后端代码,因为我们是纯前端工程,整个git仓库都是前端代码,包括后期发布上线都是前端项目独立上线,不依赖后端~
代码发布上线的时候会先进行编译,编译的结果是一个无任何依赖的html文件
index.html
,然后把这个index.html
发布到服务器上,在编译阶段所有的依赖,包括css,js,图片,字体等都会自动上传到cdn上,最后生成一个无任何依赖的纯html,大概是下面的样子:表现层
业务层
API 层
util 层
基础设施层
全局事件机制
关于这一层我想详细说一下,这一层最开始我觉得没什么用,并且这个东西很危险,新手操作不当很容易出bug,所以就没加,后来有一个需求正好用到了我才知道event-bus是用来干什么的
event-bus 我不推荐在业务中使用,在业务中使用这种全局的事件机制非常容易出bug,而且大部分需求通过vuex维护状态就能解决,那 event-bus 是用来干什么的呢?
用来处理特殊需求的,,,,那什么是特殊需求呢,我说一下我们在什么地方用到了event-bus
场景:
我们的项目是纯前端项目,又是个管理系统,所以登陆功能就比较神奇
上面是登陆的整体流程图,关于登陆前端需要做几个事情:
经过上面一系列的登陆流程,最后的结果是登陆之后会拿到一个用户信息,这个获取用户信息的操作是在router里发起的执行,那么问题就来了,router中拿到了用户信息我希望把这个用户信息放到store里,因为在router中拿不到vue实例,无法直接操作vuex的方法,这个时候如果没有 event-bus 就很难操作。
所以通常 event-bus 我们都会用在表现层下面的其他层级(没有vue实例)之间通信,而且必须要很清楚自己在做什么
为什么 event-bus 很容易出问题?好像它就是一个普通的事件机制而已,为什么那么危险?
这是个好问题,我说一下我曾经遇到的一个问题。先描述一个很简单的业务场景:“进入一个页面然后加载列表,然后点击了翻页,重新拉取一下列表”
用event-bus来写的话是这样的:
watch 路由,点击翻页后触发事件重新拉取一下列表,
功能写完后测试了发现功能都好使,没什么问题就上线了
然后过了几天偶然一次发现怎么 network 里这么多重复的请求?点了一次翻页怎么发了这么多个 fetchList 的请求???什么情况????
这里有一个新手很容易忽略的问题,即便是经验非常丰富的人也会在不注意的情况犯错,那就是生命周期不同步的问题,event-bus 的声明周期是全局的,只有在页面刷新的时候 event-bus 才会重置内部状态,而组件的声明周期相对来说就短了很多,所以上面的代码当我进入这个组件然后又销毁了这个组件然后又进入这个组件反复几次之后就会在 event-bus 中监听了很多个
word:refreshList
事件,每次触发事件实际都会有好多个函数在执行,所以才会在 network 中发现N多个相同的请求。所以发现这个bug之后赶紧加了几行代码把这个问题修复了:
自从出了这个问题之后,我就像与我一同开发后台的小伙伴说了这个事,建议所有业务需求最好不要在使用event-bus了,除非很清楚的知道自己正在干什么。
发布上线
项目架构搭建好了之后已经可以开始写业务了,所以我每天的白天是在开发业务功能,晚上和周末的时间用来开发编译上线的功能
编译源码
前面说了我们的项目是纯前端工程,所以期望是编译出一个无任何依赖的纯html文件
在使用 vue-cli 初始化项目的时候,官方的 webpack 模板会把webpack的配置都设置好,项目生成好了之后直接运行 npm run build 就可以编译源码,但是编译出来的html中依赖的js、css是本地的,所以我现在要做的事情就是想办法把这些编译后的静态文件上传cdn,然后把html中的本地地址替换成上传cdn之后的地址
项目是通过webpack插件
HtmlWebpackPlugin
来生成html的,所以我想这个插件应该会有接口来辅助我完成任务,所以我查看了这个插件的文档,发现这个插件会触发一些事件,我感觉这些事件应该可以帮助我完成任务,所以我写了demo来尝试一下各个事件都是干什么用的以及有什么区别,经过尝试发现了一个事件名叫html-webpack-plugin-alter-asset-tags
的事件可以帮助我完成任务,所以我写了下面这样的代码:其实原理并不复杂,
compilation.assets
里保存了文件内容,htmlPluginData
里保存了如何输出html, 所以从compilation.assets
中读取到文件内容然后上传CDN,然后用上传后的CDN地址把htmlPluginData
中的本地地址替换掉就行了。然后将这个插件添加到
build/webpack.prod.conf.js
配置文件中。这里有个关键点是,html中的依赖和静态文件中的依赖是不同的处理方式。
什么意思呢,举个例子:
源码编译后生成了几个静态文件,把这些静态文件上传到cdn,然后用cdn地址替换掉html里的本地地址(就是上面
CdnPlugin
刚刚做的事情)你以为完事了? No!No!No!
CdnPlugin
只是把在html中引入的编译后的js,css上传了cdn,但是js,css中引入的图片或者字体等文件并没上传cdn如果代码中引入了本地的某个图片或字体,编译后这些地址还是本地的,此时的html是有依赖的,是不纯的,如果只把html上线了,代码中依赖的这些图片和字体在服务器上找不到文件就会有问题
所以需要先把源码中依赖的静态文件(图片,字体等)上传到cdn,然后在把编译后的静态文件(js,css)上传cdn。
代码中依赖的静态文件例如图片,怎么上传cdn呢?
答案是用
loader
来实现,webpack 中的loader
以我的理解它是一个filter,或者是中间件,总之就是import
一个文件的时候,这个文件先通过loader
过滤一遍,把过滤后的结果返回,过滤的过程可以是babel
这种编译代码,当然也可以是上传cdn,所以我写了下面这样的代码:其实就是把
content
上传CDN,然后把CDN地址抛出去有了这个
loader
之后,在import
图片的时候,拿到的就是一个cdn的地址~但是我不想在开发环境也上传cdn,我希望只有在生成环境才用这个loader,所以我设置了一个
disable
的选项,如果disable
为true
,我使用url-loader
来处理这个文件内容。最后把loader也添加到配置文件中:
写好了
cdn-loader
和cdn-plugin
之后,已经可以编译出一个无任何依赖的纯html,下一步就是把这个html文件发布上线发布上线
我们部门有自己的发布上线的工具叫
hera
可以把代码发布到docker机上进行编译,然后把编译后的纯html文件发布到事先配置好的服务器的指定目录中编译的流程是先把代码发布到编译机上 -> 编译机启动
docker
(docker可以保证编译环境相同) -> 在docker
中执行npm install
安装依赖 -> 执行npm run build
编译 -> 把编译后的 html 发送到服务器因为每次编译都需要安装依赖,速度非常慢,所以我们有一个
diffinstall
的逻辑,每次安装依赖都会进行一次 diff,把有缓存的直接用缓存copy到node_modules,没缓存的使用qnpm安装,之后会把这次新安装的依赖缓存一份。依赖缓存了之后每次安装依赖速度明显快了很多。现在项目已经可以正常开发和上线啦~
api-proxy
虽然项目可以正常开发了,但我觉得还不够,我希望项目可以有 mock 数据的功能并且可以检查服务端返回的数据是否正确,可以避免因为接口返回数据不正确的问题debug好久。
所以我开发了一个简单的模块
api-proxy
,就是封装了一个http client,可以配置请求信息和Mock 规则,开启Mock的时候使用Mock规则生成Mock数据返回,不开启Mock的时候使用Mock规则来校验接口返回是否符合预期。那么 api-proxy 怎样使用呢?
举个例子:
使用:
考虑到特殊情况,也并不是强制必须这样使用,我还是抛出了一个 api方法来供开发者正常使用,例如:
这个 api 就是
axios
,并没做什么特殊处理。初始化配置文件
项目开发中会用到一些配置文件,比如开发环境需要配置一个server地址用来设置api请求的server。开发环境的配置文件每个人都不一样,所以我在
.gitignore
中把这个dev.conf 屏蔽掉,并没有入到版本库中,所以就带来了一个问题,每次有新人进入到这个项目,在第一次搭建项目的时候,总是要手动创建一个 dev.conf 文件,我希望能自动创建配置文件正巧之前我写了一个类似于 vue-cli 的工具 speike-cli,也是通过模板生成项目的一个工具,所以这一次正好派上用场,我把配置文件定义了一个模板,然后使用 speike 来生成了一个配置文件
初始化项目
这次该有的都有了,可以愉快的写码了,为了以后有类似的管理系统创建项目方便,我把这次精心设计的架构,编译逻辑等定制成了模板,日后可以直接使用
speike
选择这个模板来生成项目。整理与总结
经过上面一系列做的事,最后整理一下项目工程化的生命周期
了解更多可以看我写过的 PPT
The text was updated successfully, but these errors were encountered: