vue 登录页面单页面内存不断增加,最后就爆了

vue2单页面应用学习 - 简书
vue2单页面应用学习
最近开始学习vue2.0,但看完后完全不知道要如何开始一个项目,网上找了半天也没找到个如何一步步搭建项目出来的文章,于是在github上找到了一个开源项目clone下来用作学习参考。在此也写下记录来总结下自己的学习过程。
自己对es6,vue2.0的这种模块化项目构建方式十分十分不熟悉,除了js和页面布局的基础,基本属于新手起步(本人现在水平,仅供参考:es6以前学习过一遍,但一直也没使用过,现在基本忘光了,就记得个let、const了。vue只接触过1,而且没有做过移动端。目前本人工作中使用的是angular1和ionic来做移动端,现在学习这个也是希望能更新下自己的知识,主要的是想学习下这种项目构建方式,熟悉webpack等工具,慢慢过渡到使用es6和熟悉模块化、工程化的思想)。这篇文章也希望将来能帮到一些同样基础的小伙伴能在之后的学习过程中有一个借鉴,同时也希望看到这篇文章的大腿能指点一些学习中问题和不足,感谢!
这篇文章属于自己边学边写,可能更新过程中,会有不少疏漏的地方和错误的地方,在这个过程中我也会进行修改,请知悉!
-- jeneser
一.项目下载与运行
通过git clone拷贝下整个项目到本地,然后通过npm run dev看下效果
遇到的问题:
1.在此我遇到了第一个问题,运行npm run dev时狂报错,之后发现是8080端口被占用,找了半天也没找到谁在占用端口 - -
1.修改douban/build/dev-server.js中的port
var uri = 'http://localhost:' + '80'
devMiddleware.waitUntilValid(function () {
console.log('& Listening at ' + uri + '\n')
module.exports = app.listen(80, function (err) {
if (err) {
console.log(err)
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
这里我是将port写死为固定的80端口,80可以为任意未被占用的端口。至此,项目已经可以在本地正常运行。
已找到问题来源,在本地的eclipse中开启了服务,占用了8080端口。
后续又有新发现,这个port的参数其实是取自douban/config/index.js中配置的值,这里设置了webpack dev的一些参数,其中将dev的端口设置了8080,如果你的端口也有占用情况,可以在这里修改为一个没被占用的端口或者结束掉占用即可。
二、创建项目文件夹、搭建项目基础
1.安装nodist(也可以用nvmw或直接全局安装随意,搞个node环境出来就行)、全局安装webpack和vue-cli。创建项目文件夹,通过vue init webpack 项目名创建项目模版
2.通过npm i来下载各依赖项
3.运行npm run dev来测试模版是否下载成功
遇到的问题
1.在第二步下载依赖时一直报错,查看日志发现是下载node-sass报错,在网上查找解决方()如下:
1)在运行npm i前,在根目录创建.npmrc文件(直接在目录中创建不被允许,可以通过ide软件先加载项目目录后,再在根目录创建自定义文件即可)
2)创建完成后在.npmrc文件中复制如下代码:
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/
electron_mirror=https://npm.taobao.org/mirrors/electron/
registry=https://registry.npm.taobao.org
需注意在发布时需将最后一行注释。
三、查看项目目录及文件
查看学习项目的目录构成,打开各个文件浏览下源码。对比浏览器中打开的页面,理清整个应用的搭建框架,跳转逻辑。
遇到的问题
1.在查看跳转逻辑时,未找到SubjectView.vue应该如何跳转过去2.在查看router/index.js的源码是对router的配置生疏
1.手动在浏览器中输入url
居然蒙出来一个
在写文档的时候突然发现这个url中的book有点眼熟啊,不就是在主页时点击header的图书跳转进来的页面地址嘛。那后续的部分估计就是在图书也点一个书后的地址了。随便点击一个书目试了试果然如此,同理去点了电影也进来了这个详情页面,正好这个也对应上了SubjectView.vue页面源码的图书电影两个if判断,real chun
2.重新去vue-router的官网学习了一遍,详细了解了一下嵌套路由children、命名路由name、动态路由匹配(动态路径参数/:id)、命名视图(一个页面中有多个router-view标签,分别展示不同的视图view)、重定向redirect。同时参考源码的路由配置index.js,对整个页面跳转逻辑有了比较清晰的理解。但对于vue-router的学习仍然比较初步,后续文档并没有看完,在后续可以进行补充学习。
四、认真学习vuex大坑
在看源码的过程中,发现数据,业务方法自己都没有理清顺序,数据在什么地方,为什么这么写,怎么实现数据传递,反正乱七八糟地啥都没看明白,因此,文档先写到这里,要去补vuex的坑了。 - -
参照看了一遍文档,目前只看到了modules,还没看完,先写下今天的总结:
在看到actions之后,对于整个vuex的整体思路有了大致的了解,明白了它的运作过程(仅限使用)。在这里对整个自己的理解稍作整理。
首先,State对象里放置的是数据,这里的数据目前的粗浅理解是那种需要进行请求的。
然后是Getters,额...忘记了(明天再去补一下)。
重新看了下getters,作用其实挺简单的,估计是看过就忘了。getters的作用在于,当我们取得数据并存入state中后,在实际的页面中想要使用数据时,发现有些数据我们想要对它进行一些处理,比如说5个字的标题,我们想让他在3个字后添加个逗号,那我们就可以在getters中定义个方法来对数据进行下处理,然后在页面中取getters方法中返回的值,而不是直接拿state的值。
之后是Mutations和Actions,这两个对象中放的都是方法,区别在于mutations中的方法是用于修改state中的数据的,而且只有在mutations中的方法才可以直接对state进行修改,特别注意的一点是这个修改必须是同步的,也就是说,mutations中的方法一执行state中的数据就必须即可生效,so,不能在这里进行异步操作,例如请求数据再赋值之类的。再之后是actions,既然mutations中要求不能进行异步,那肯定就必须要解决这个问题,所以需要进行异步操作时的函数就要写在actions中了。同时推荐引入一个自己习惯使用的promise语法,本例中使用的是superagent,但之前看官网好像推荐的是axios,可以进行下学习。这里的重点是,actions中异步请求完得到数据是不可以直接对state中的数据进行变动的,需要在actions中进行commit也就是调用mutations中已定义好的你需要使用的方法来对state进行变动。
最后是Modules,首先我们要明确一点是,数据肯定是在state中的,那当项目大,视图多时,不同的视图的数据、mutations、actions啥的全放一个文件中显然就不太合适了,不仅看着冗长,而且不易于后续的维护。因此Modules就是解决这个问题的,我们可以像例子中那样将不同view的store分别定义一个js文件例如book.js、movie.js等然后将各view所需的state、mutations等分别放入各自的文件中,在通过Modules分别引入进来,不仅使文件结构清晰,后续代码维护也方便不少。modules没看完,感觉水有点深,后面的命名空间啥的没仔细研究,大致过了一下,看的迷糊糊的,就当先有个印象,之后有需要再去研究。
其实在现在这个点,我最大的疑惑是vue文件中的script标签中定义的data数据和store中state中的数据,什么数据应该写在哪里,为啥这样写不是很理解。
对于数据该放哪里,目前的理解是只单纯作用与本view的一些数据例如一个v-if='flag',来控制dom的显示隐藏,那这个flag就可以写在vue文件的data中,然后在methods中写个方法来改变它的值,其他的写到store中去。当然要是实在暂时搞不明白,全写store去得了。这个还需要后续认真研究下。
五、啥不会学啥
目前看来用vue2搭建一个单页面应用使用到的东西其实也不少,es6、vue2、vuex、vue-router、scss(可选)、一个好用的ui库、axios(数据请求)。
另外还要学习下webpack的基础知识,不求会写,但还是要看懂一些主要东西都是在设置啥,另外npm的package.json还是要学一下的。
六、主体搭建
其实到了这里,一个基础项目的搭建和框架的使用就有比较明确的认知了。通过vue-cli新建出项目主体,index.html、webapck的配置就都完成了,自己完善下main.js、写好router配置框架。就可以从app.vue开始写自己的.vue文件,开始页面制作了。路由可以在页面制作过程中慢慢去完善,当然如果你最初就有良好的构思,也可以直接写完,但一般在项目中都会不停改东西的吧。
这个例子的文件构建和写法思路其实就很符合我心中期望的前端模块化,估计再大型一些的项目文件的构建和js内容的书写上就又有区别了,但目前我也接触不到那么大的项目,还是先学好这个起步吧。
最后,这篇文章并没有啥技术性的东西,纯粹是一个上手思路,也是因为我自己在上手时搜了半天也没见到这种类似的文章,在开始强行入门时看着这么多文件,搞不清关系,看不懂逻辑,完全不知道我开始个项目要如何下手。因此记录下来自己的一个学习过程,给后续学习的小伙伴们一个借鉴,当然,文章中如果有疏漏,带偏了路概不负责呀。
在照着源码自己照搬时,写完hearderbar组件在运行时报错。对照报错信息发现是自己文件夹中依赖项不全,对照源码package.json和自己文件夹发现少了一些东西,补上后需重新 npm i,同时发现在webpack.config中缺少scss的rule,需手动添加大型单页面应用的进阶挑战 - WEB前端 - 伯乐在线
& 大型单页面应用的进阶挑战
阅读须知:这里的大型单页面应用(SPA Web App)是指页面和功能组件在一个某个量级以上,举个栗子,比如 30+个页面100+个组件,同时伴随着大量的数据交互操作和多个页面的数据同步操作。并且这里提到的页面,均属于 hash 页面,而多页面概念的页面,是一个独立的 html 文档。基于这个前提,我们再来讨论,否则我怕我们 Get 不到同一个 G 点上去。
挑战一:前端组件化
基于我们所说的前提,第一个面对的挑战是组件化。这里还是要强调的是组件化根本目的不是为了复用,很多人根本没想明白这点,总是觉得造的轮子别的业务可以用,说不定以后也可以用。
其实前端发展迭代这么快,交互变化也快,各种适配更新层出不穷。今天造的轮子,过阵子别人造了个高级轮子,大家都会选更高档的轮子,所以现在前端界有一个现象就是为了让别人用自己的轮子,自己使劲不停地造。
在前端工业化生产趋势下,如果要提高生产效率,就必须让组件规范化标准化,达到怎样的程度呢?一辆车除了底盘和车身框架需要自己设计打造之外,其他标准化零件都可以采购组装(专业学得差,有啥谬误请指正)。也就是说,除了 UI 和前端架构需要自己解决之外,其他的组件都是可以奉行拿来主义的,如果打算让车子跑得更稳更安全,可以对组件进行打磨优化完善。
说了这么说,倒不如看看徐飞的文章 里面写的内容都是经过一定实践得出的想法,所以大部分内容我是赞成而且深有体会的。
挑战二:路由去中心化
基于我们所说的前提,中心化的路由维护起来很坑爹(如果做一两个页面 DEMO 的就没必要出来现眼了)。MV* 架构就是存在这么个坑爹的问题,需要声明中心化 route(angular 和 react 等都需要先声明页面路由结构),针对不同的路由加载哪些组件模块。一旦页面多起来,甚至假如有人偷懒直接在某个路由写了一些业务耦合的逻辑,这个 route 的可维护性就变得有些糟糕了。而且用户访问的第一个页面,都需要加载 route,即使其他路由的代码跟当前页面无关。
我们再回过头来思考静态页面简单的加载方式。我们只要把 nginx 搭起来,把 html 页面放在对应的静态资源目录下,启动 nginx 服务后,在浏览器地址栏输入 127.0.0.1:8888/index.html 就可以访问到这个页面。再复杂一点,我们把目录整成下面的形式:
/post/.html
/post/.html
/post/.html
/category/js_base_knowledge.html
/category/css_junior_use.html
/category/life_is_beautiful.html
/post/.html/post/.html/post/.html/category/js_base_knowledge.html/category/css_junior_use.html/category/life_is_beautiful.html
这种目录结构很熟吧,对 SEO 很友好吧,当然这是后话了,跟我们今天说的不是一回事。这种目录结果,不用我们去给 Web Server 定义一堆路由规则,页面存在即返回,否则返回 404,完全不需要多余的声明逻辑。
基于这种目录结构,我们可以抽象成这样子:
/{page_type}/{page_name}.html
/{page_type}/{page_name}.html
其实还可以更简单:
/p/{name}.html
/p/{name}.html
从组件化的角度出发,还可以这样子:
/p/{name}/name.js
/p/{name}/name.tpl
/p/{name}/name.css
/p/{name}/name.js/p/{name}/name.tpl/p/{name}/name.css
所以,按照我们简化后的逻辑,我们只需要一个 page.js 这样一个路由加载器,按照我们约定的资源目录结构去加载相应的页面,我们就不需要去干声明路由并且中心化路由这种蠢事了。具体来看。咱也懒得去解析了,里面有注释。
挑战三:领域数据中心化
对于单向数据流循环和数据双向绑定谁优谁劣是永远也讨论没结果的问题,要看是什么业务场景什么业务逻辑,如果这个前提没统一好说啥都是白搭。当然,这个挑战的前提是非后台的单页面应用,后台的前端根本就不需要考虑前端内存缓存数据的处理,直接跟接口数据库交互就行了。明确了这个前提,我们接着讨论什么叫领域数据中心化。
前面讨论到两种数据绑定的方式,但是如果频繁跟接口交互:
内存数据销毁了,重新请求数据耗时浪费流量
如果两个接口字段部分不一样但是使用场景一样
多个页面直接有部分的数据相同,但是先来后到导致某些计数字段不一致
多个页面的数据相同,其中某些数据发生用户操作行为导致数据发生变动
因此,我们需要在业务视图逻辑层和数据接口层中间增加一个 store(领域模型),而这个 store 需要有一个统一的 内存缓存 cache,这个 cache 就是中心化的数据缓存。那这个 store 究竟是用来弄啥勒?
Store 具有多形态,每个 store 好比某一类物品的仓储(领域,换个词容易理解),如蔬果店 fruit-store, 服装店 clothes-store,蔬果店可以放苹果香蕉黑木耳,服装店可以放背心底裤人字拖。如果品种过于繁多,我们可以把蔬果店精细化运营变成香蕉专卖店,苹果专卖店(!== appstore),甚至是黑木耳专卖店…( _ _)ノ|,蔬果种类不一样,但是也都是称重按斤卖嘛。
var bannerStore = new fruitStore();
var appleStore = new fruitStore();
有了这些仓储之后,我们可以放心的把数据丢给视图逻辑层大胆去用。想修改数据?直接让 store 去改就行了,其他页面的 DOM 文本内容也得修改吧?那是其他页面的业务逻辑做的事,我们把事件抛出去就好了,他们处不处理那是他们的事,咱别瞎操心(业务隔离)。
那么 store 具体弄啥勒?
32 个赞位置可点赞或者取消,三个页面的赞数需要同步,按钮点赞与取消的状态也要同步。
条目是否已收藏,取消收藏后 Page B 需要删除数据,Page A+C 需要同步状态,如果在 Page C 又有收藏操作,Page B 需要相应增减数据,Page A 状态需要同步。
发评论,Page C 需要更新评论列表和评论数,Page A+B 需要更新评论数。如果 Page B 没有被加载过,这时候 Page B 拿到的数据应该是最新的,需要同步给 A+C 页面对应的数据进行更新。
所以,store 干的活就是数据状态读写和同步,如果把数据状态的操作放到各个页面自己去处理,页面一旦多了或者复杂起来,就会产生各个页面数据和状态可能不一致,页面之前双向引用(业务耦合严重)。store 还有另一个作用就是数据的输入输出格式化,简单举个栗子:
任何接口 API 返回的数据,都需要经过 input format 进行统一格式化,然后再写入 cache,因为读取的数据已按照我们约定的规范进行的处理,所以我们使用的时候也不需要理会接口是返回怎样的数据类型。
某些组件需要的数据字段格式可能不同,如果把数据处理放在模板进行处理,会导致模板无法更加简洁通用(业务耦合),所以需要 output format 进行处理。
所以,store 就是扮演着这样的角色——是数据状态读写和同步,以及数据输入输出的格式化处理。
挑战四:Hybrid App 化
现在 Hybrid App 架构应用很火啊 _ (:3」∠)_,不搞一下都不好意思说自己是做 H5的。这里所说的 Hybrid App 可不是那种内置打包的 html 源码那种,而是直接去服务端请求 html 文档那种,可能会使用离线缓存。有的人以为如果要使用 Hybrid 架构,就不能使用 SPA 的方式,其实 Hybrid 架构更应该使用 SPA。
遇到的几个问题,我简单列举一下:
客户端通过 url 传参
如果通过 http get 请求的 query 参数进行传参,会导致命中不到 html 文档缓存,所以通过 SPA 的 hash query 传参,可以规避这个问题。
与其他 html 页面进行跳转
这种场景下,进入新页面和返回旧页面导致 webview 会重新加载本地的 html 文档缓存,视觉体验很不爽,即使页面使用了离线缓存,而 SPA 可以规避这个问题。
使用了离线缓存的页面需要支持代码多版本化
由于采用了非覆盖性资源发布方式,所以需要仍然保留旧的代码一段时间,以防止用户使用旧的 html 文档访问某些按需加载功能或清除了本地缓存数据而拿不到旧版本代码。
js 和 css 资源 离线化
由于离线缓存的资源需要先在 manifest 文件声明,你也不可能总是手动去维护需要引用的 js 和 css 资源,并且那些按需加载的功能也会因此失去按需加载的意义。所以需要将 js 和 css 缓存到 localstorage,直接省去这一步维护操作。至于用户清除 localstorage,参考第三点解决方案。
图标资源离线化
将图标文件进行 base64 编码后存入 css 文件,方便离线使用。
挑战五:性能优化
已经说得很清楚了,直接传送门过去看吧,这里不罗嗦了。
可能感兴趣的话题
关于伯乐前端
伯乐前端分享Web前端开发,包括JavaScript,CSS和HTML5开发技术,前端相关的行业动态。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2017 伯乐在线【求助】请问各位大神,为什么电脑物理内存的突然就爆满了?【笔记本吧】_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:2,088,075贴子:
【求助】请问各位大神,为什么电脑物理内存的突然就爆满了?收藏
情况是这样的:电脑开始还好好的,突然360气泡那里就显示90+了,电脑就卡的不要不要的!后来就一直这样了!该怎么办呀,各位大神!
战神Z7-KP7G1采用第七代英特尔酷睿i7处理器/英特尔,让性能更超凡!/券后价6599元/发烧级甜点显卡/炫红背光键盘/8G/128G+1TB/金属A面
前排提示撸主等级。   --来自助手版贴吧客户端
前排提示撸主等级。
再开机是一样的啊!
尝试禁止windows update
360后台修复漏洞会爆到百分之八九十
遇到这种情况,删除360不就看不到了
CPU占用还是磁盘占用?修复漏洞下载肯定占带宽,下载肯定用磁盘吧闲了再修
笔记本,苏宁企业会员日,企业办公用品优惠采购,大牌镜头满千减百!苏宁易购,正品保障,急速物流,售后无忧,多种付款方式任你选,24小时一对一提供优质服务!
windows update win7下倒是有这个情况,关掉再说。但是 不清楚有没有补丁修复这些bug
开任务管理器看看是谁占的
来自永不屈服的lumia640
开任务管理器看看是谁占的
来自永不屈服的lumia640
360那个球不只是内存,如果CPU或者硬盘也满载了也会飙红
之前碰到过更新过显卡驱动之后爆内存,开网页看视频内存80+,退回老版本之后解决了。看看你的有没有驱动更新了。
不要太相信360,他是闲的和你找乐子呢
楼主真是勇士,还敢下载360
我8g内存只占用2g360加速球也显示90很红很红的
开网页视频瞬间爆过内存。。。。
楼主弄好了吗
开机是正常的 占用量只有10-20%
可是开机越久他占用量越大。 我这配置也不低了。一直觉的是电脑问题
可是找不出来。 像杀毒 清理内存 清理插件 清理软件都用过了。 都没用。
登录百度帐号推荐应用2016年, Vue框架在社区中逐渐活跃了起来, 同时公司也有更多的产品线启动, 这时很多团队需要做运营类管理后台, 而此时前端人力稀缺. 为了不重复建设和解放前端人力,
团队就准备基于webpack+vue框架做一个针对运营后台的前端打包和工程的解决方案, 然后进行公司内推广和培训, 让后端同学参与进来. 前端同学负责工程的建设和组件的开发, 后端同学负责具体业务开发.
到目前为止, 有5个以上团队采用这套前端解决方案进行运营后台或者工具项目开发, 极大的释放了前端人力, 效果不错. 在不断的演进中, 整个前端的客户的技术栈也逐渐由scrat+zepto 转型webpack+vue的开发方式.
因为是针对运营后台的设计初衷, 对性能和seo没有要求以及其他语言(java)的支持,采用是vue前端渲染方式进行设计的. 但随着新业务的发展, 加上vue支持服务端渲染的特性, 我们想在服务端渲染技术做一下研究和实践.
如下就针对webpack+vue服务端渲染进行相关研究和实践. 目前公司所有的前端新项目都是采用egg框架进行开发, 接下来主要讲的是基于egg项目如何实现webpack+vue工程化构建和服务端客户端渲染技术落地.
我们要解决什么问题
针对背景里面提到的一些问题, 基于webpack + egg项目的工程化, 当初想到和后面实践中遇到问题, 主要有如下问题需要解决:
Vue服务端渲染性能如何?
webpack 客户端(browser)运行模式打包支持
webpack 服务端(node)运行模式打包支持
如何实现服务端和客户端代码修改webpack热更新功能
webpack打包配置太复杂(客户端,服务端), 如何简化和多项目复用
开发, 测试, 正式等多环境支持, css/js/image的压缩和hash, cdn等功能如何配置, 页面依赖的css和js如何加载
如何快速扩展出基于vue, react前端框架服务端和客户端渲染的解决方案
webpack工程设计
我们知道webpack是一个前端打包构建工具, 功能强大, 意味的配置也会复杂. 我们可以通过针对vue, react等前端框架,采用不同的配置构建不同的解决方案.
虽然这样能实现, 但持续维护的成本大, 多项目使用时就只能采用拷贝的方式, 另外还有一些优化和打包技巧都需要各自处理.
基于以上的一些问题和想法, 我希望基于webpack的前端工程方案大概是这个样子:
webpack太复杂, 项目可重复性和维护性低, 是不是可以把基础的配置固化, 然后基于基础的配置扩展出具体的解决方案(vue/react等打包方案).
webpack配置支持多环境配置, 根据环境很方便的设置是否开启source-map, hash, 压缩等特性.
webpack配置的普通做法是写配置, 是不是可以采用面向对象的方式来编写配置.
能够基于基础配置很简单的扩展出基于vue, react 服务端渲染的解决方案
针对egg + webpack内存编译和热更新功能与框架无关, 可以抽离出来, 做成通用的插件
webpack基础配置固化
在使用webpack对不同的前端框架进行打包和处理时, 有些配置是公共的, 有些特性是共性的, 我们把这些抽离出来, 并提供接口进行设置和扩展.
option: entry读取, output, extensions 等基础配置
loader: babel-loader, json-loader, url-loader, style-loader, css-loader, sass-loader, less-loader, postcss-loader, autoprefixer 等
plugin: webpack.DefinePlugin(process.env.NODE_ENV), CommonsChunkPlugin等
js/css/image 是否hash
js/css/image 是否压缩
js/css commonChunk处理
开发辅助特性
编译进度条插件 ProgressBarPlugin
资源依赖表
ManifestPlugin
热更新处理
HotModuleReplacementPlugin
以上一些公共特性是初步梳理出来的, 不与具体的前端框架耦合. 针对这些特性可以单独写成一个npm组件, 并提供扩展接口进行覆盖, 删除和扩展功能.
在具体实现时, 可以根据环境变量 process.env.NODE_ENV 默认开启或者关闭一些特性. 比如本地开发时, 关闭 js/css/image 的hash和压缩,
开启热更新功能.
webpack配置面向对象实现
针对上面梳理的公共基础配置, 可以把webpack配置分离成三部分: option, loader, plugin
针对客户端和服务端打包的差异性, 设计成三个类 WebpackBaseBuilder, WebpackClientBuilder, WebpackServerBuilder
基于以上的想法, 大概代码实现:
WebpackBaseBuilder.js 公共配置
class WebpackBaseBuilder {
constructor(config) {
this.config = config;
this.initConfig();
this.initOption();
this.initConfigLoader();
this.initConfigPlugin();
initConfig() {
this.prod = process.env.NODE_ENV === 'production';
this.options = {};
this.loaders = [];
this.plugins = [];
this.setUglifyJs(this.prod);
this.setFileNameHash(this.prod);
this.setImageHash(this.prod);
this.setCssHash(this.prod);
this.setCssExtract(false);
initOption() {
this.options = {
entry: Utils.getEntry(this.config.build.entry),
resolve: {
extensions: ['.js']
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
initConfigLoader() {
// default custom loader config list, call createWebpackLoader to webpack loader
this.configLoaders = [
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: require.resolve('url-loader'),
limit: 1024
dynamic: () =& {
name: this.imageName
initConfigPlugin() {
// default custom plugin config list, call createWebpackPlugin to webpack plugin
this.configPlugins = [
enable: () =& {
return this.isUglifyJS;
clazz: webpack.optimize.UglifyJsPlugin,
args: () =& {
compress: {
warnings: false,
dead_code: true,
drop_console: true,
drop_debugger: true
setOption(option) {
this.options = merge(this.options, option);
setPublicPath(publicPath) {
this.options = merge(this.options, { output: { publicPath } });
setDevTool(devtool, force) {
if (!this.prod || force) {
this.options = merge(this.options, { devtool });
setConfigLoader(loader, isHead) {
setConfigPlugin(plugin, isHead) {
setLoader(loader, isHead) {
setPlugin(plugin, isHead) {
createWebpackLoader() {
return this.loaders;
createWebpackPlugin() {
return this.plugins;
create() {
this.createWebpackLoader();
this.createWebpackPlugin();
return this.getWebpackConfig();
getWebpackConfig() {
return merge({
rules: this.loaders
plugins: this.plugins
this.options);
setMiniCss(isMiniCss) {
this.isMiniCss = isMiniCss;
setUglifyJs(isUglifyJS) {
this.isUglifyJS = isUglifyJS
setFileNameHash(isHash, len = 7) {
if (isHash) {
this.filename = Utils.assetsPath(this.config, `js/[name].[hash:${len}].js`);
this.chunkFilename = Utils.assetsPath(this.config, `js/[id].[chunkhash:${len}].js`);
this.filename = Utils.assetsPath(this.config, 'js/[name].js');
this.chunkFilename = Utils.assetsPath(this.config, 'js/[id].js');
setImageHash(isHash, len = 7) {
if (isHash) {
this.imageName = Utils.assetsPath(this.config, `img/[name].[hash:${len}].[ext]`);
this.imageName = Utils.assetsPath(this.config, `img/[name].[ext]`);
setCssHash(isHash, len = 7) {
if (isHash) {
this.cssName = Utils.assetsPath(this.config, `css/[name].[contenthash:${len}].css`);
this.cssName = Utils.assetsPath(this.config, `img/[name].css`);
setCssExtract(isExtract) {
this.config.extractCss = isExtract;
module.exports = WebpackBaseBuilder;
WebpackServerBuilder.js 客户端打包公共配置
class WebpackClientBuilder extends WebpackBaseBuilder {
constructor(config) {
super(config);
this.initClientOption();
this.initClientConfigPlugin();
this.initHotEntry();
this.setCssExtract(this.prod);
this.setMiniCss(this.prod);
initHotEntry() {
initClientOption() {
initClientConfigPlugin() {
this.configPlugins.push({
enable: () =& {
return !this.prod;
clazz: webpack.HotModuleReplacementPlugin
module.exports = WebpackClientBuilder;
WebpackClientBuilder.js 服务端打包公共配置
class WebpackServerBuilder extends WebpackBaseBuilder {
constructor(config) {
super(config);
this.initServerOption();
this.setCssExtract(false);
initServerOption() {
this.setOption({
target: 'node',
libraryTarget: 'commonjs2',
path: path.join(this.config.baseDir, 'app/view')
externals: Utils.loadNodeModules()
module.exports = WebpackServerBuilder;
以上是正对webpack公共基础配置进行初步的设计, 还需不断完善, 目前已经基于此设想开发了
针对具体前端框架vue的打包方案实现
上面已经采用面向对象的方式实现了webpack的基础配置插件, 接下来我们在easywebpack的基础上扩展出
webpack+vue前端构建解决方案.
vue构建里面与vue相关主要有vue-style-loader和vue-html-loader, extensions,
process.env.VUE_ENV 环境变量, 我们在easywebpack上面扩展此特性即可.
Vue客户端和服务端打包公共配置config.js
const EasyWebpack = require('easywebpack');
const webpack = EasyWebpack.webpack;
const merge = EasyWebpack.merge;
exports.getLoader = config =& {
test: /\.vue$/,
loader: require.resolve('vue-loader'),
dynamic: () =& {
options: EasyWebpack.Loader.getStyleLoaderOption(config)
test: /\.html$/,
loader: require.resolve('vue-html-loader')
exports.initBase = options =& {
return merge({
resolve: {
extensions: ['.vue']
}, options);
exports.initClient = options =& {
return merge(exports.initBase(options), {
resolve: {
vue: 'vue/mon.js'
}, options);
exports.initServer = options =& {
return merge(exports.initBase(options), {
resolve: {
vue: 'vue/dist/mon.js'
plugins: [
// 配置 vue 的环境变量,告诉 vue 是服务端渲染,就不会做耗性能的 dom-diff 操作了
new webpack.DefinePlugin({
'process.env.VUE_ENV': '"server"'
}, options);
这些基础配置不复杂, 没有对所有的属性提供单独的方法进行设置, 直接通过setOption方法统一设置.
Vue客户端打包配置client.js
'use strict';
const EasyWebpack = require('easywebpack');
const baseConfig = require('./config');
class WebpackClientBuilder extends EasyWebpack.WebpackClientBuilder {
constructor(config) {
super(config);
this.setOption(baseConfig.initClient());
this.setConfigLoader(baseConfig.getLoader(this.config), true);
create() {
return super.create();
module.exports = WebpackClientBuilder;
Vue服务端打包配置server.js
'use strict';
const EasyWebpack = require('easywebpack');
const baseConfig = require('./config');
class WebpackServerBuilder extends EasyWebpack.WebpackServerBuilder {
constructor(config) {
super(config);
this.setOption(baseConfig.initServer());
this.setConfigLoader(baseConfig.getLoader(this.config), true);
module.exports = WebpackServerBuilder;
命令行运行编译 build.js
编译入口脚本
'use strict';
const EasyWebpack = require('easywebpack');
const clientConfig = require('./client')(config);
const serverConfig = require('./server')(config);
EasyWebpack.build([clientConfig, serverConfig]);
命令行运行
NODE_ENV=development && node build.js
NODE_ENV=production && node build.js
目前已经基于此实现开发了
egg + webpack内存编译和热更新
webpack基础配置和vue打包插件已完成, 那如何在本地开发实现修改代码自动编译功能呢?
webpack提供了很好的内存编译和热更新机制, 极大提高了编译效率和开发效率.
如果你仅仅是进行webpack编译客户端运行文件, 可以很方便的做到热更新机制: 修改代码, 不需要手动刷新界面, UI马上会自动更新.
这种更新不会刷新页面, 而是webpack通过监听文件修改, 通过socket建立客户端的连接, 把修改的内容通过socket发给浏览器,通过动态执行js达到页面更新的效果.
如果是修改Node.js服务端端代码想要项目自动重启和webpack编译内存继续存在而不是重新编译, 就需要借助egg框架的agent机制.
egg已经内置了worker和agent通信机制以及自动重启功能.
实现egg项目服务端代码修改项目自动重启的功能可以使用插件.
worker和agent通信机制: https://eggjs.org/zh-cn/core/cluster-and-ipc.html
当egg自动重启时, agent是不会销毁的, 这样就可以让webpack实例继续保持, 我们需要解决的是如何从webpack编译内存里面编译的文件内容.
内容包括webpack编译的服务端渲染文件和客户端用到js, css, image等静态资源. 这些内容我们都可以通过worker发现消息给agent, 然后读取内容,
再通过agent发送消息给worker. js, css, image等静态资源也这样处理的话就需要读取文件流,然后根据文件类型返回给对应的响应内容.
这里采用一种更简单的方式, 直接在agent里面启动一个koa的webpack编译服务, 这样就可以通过http的方式访问js, css, image等静态资源.
首先在agent中启动koa服务
针对webpack server 编译模式, 在egg agent里面单独启动koa编译服务, 同时开启跨域功能. agent.js 代码如下:
const webpack = require('webpack');
const koa = require('koa');
const cors = require('kcors');
const app = koa();
app.use(cors());
const Constant = require('./constant');
const Utils = require('./utils');
module.exports = agent =& {
const config = agent.config.webpack;
const webpackConfig = config.webpackConfig;
const compiler = webpack(webpackConfig);
const devMiddleware = require('koa-webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
watchOptions: {
ignored: /node_modules/,
app.use(devMiddleware);
app.listen(config.port + 1, err =& {
if (!err) {
agent.logger.info(`start webpack build service: http://127.0.0.1:${config.port}`);
koa服务启动以后, 就可以通过 http://127.0.0.1:port 方式访问js, css, image等静态资源.
agent中监听worker发送的消息
在agent.js 增加如下监听的代码:
通过agent.messenger.on 监听app worker发送过来的消息
agent.messenger.on(Constant.EVENT_WEBPACK_READ_FILE_MEMORY, data =& {
const fileContent = Utils.readWebpackMemoryFile(compiler, data.filePath);
if (fileContent) {
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, {
fileContent,
// agent.logger.error(`webpack server memory file[${data.filePath}] not exist!`);
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, {
fileContent: '',
在worker view 渲染文件读取里面调用如下方法, 获取webpack的编译内容
通过app.messenger.sendToAgent 向agent发送消息
通过app.messenger.on 监听agent发送过来的消息
function readFile(filePath){
return new Promise(resolve =& {
this.app.messenger.sendToAgent(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, {
this.app.messenger.on(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, data =& {
resolve(data.fileContent);
在app/extend/application.js 挂载webpack实例, 以便进行扩展.
const WEBPACK = Symbol('Application#webpack');
module.exports = {
get webpack() {
if (!this[WEBPACK]) {
this[WEBPACK] = new FileSystem(this);
return this[WEBPACK];
针对此方案实现基于了egg的开发webpack server编译插件
关于koa的实现热编译和重启, 可以参考
本地开发内存文件读取与线上运行文件读取分离实现
在egg-view插件开发规范中,我们会在ctx上面挂载render方法, render方法会根据文件名进行文件读取, 模板与数据编译, 从而实现模板的渲染.如下就是controller的调用方式:
exports.index = function* (ctx) {
yield ctx.render('index/index.js', Model.getPage(1, 10));
那我们如何处理从webpack内存读取还是从本地文件读取呢? 其中最关键的一步是根据文件名进行文件读取, 只要view插件设计时, 把文件读取的方法暴露出来,就可以实现本地开发webpack热更新内存存储读取.
vue view engine设计实现:
const Engine = require('../../lib/engine');
const VUE_ENGINE = Symbol('Application#vue');
module.exports = {
get vue() {
if (!this[VUE_ENGINE]) {
this[VUE_ENGINE] = new Engine(this);
return this[VUE_ENGINE];
class Engine {
constructor(app) {
this.app = app;
this.config = app.config.vue;
this.cache = LRU(this.config.cache);
this.fileLoader = new FileLoader(app, this.cache);
this.renderer = vueServerRenderer.createRenderer();
this.renderOptions = Object.assign({
cache: this.cache,
}, this.config.renderOptions);
createBundleRenderer(code, renderOptions) {
return vueServerRenderer.createBundleRenderer(code, Object.assign({}, this.renderOptions, renderOptions));
* readFile(name) {
return yield this.fileLoader.load(name);
render(code, data = {}, options = {}) {
return new Promise((resolve, reject) =& {
this.createBundleRenderer(code, options.renderOptions).renderToString(data, (err, html) =& {
if (err) {
reject(err);
resolve(html);
ctx.render 方法
class View {
constructor(ctx) {
this.app = ctx.app;
* render(name, locals, options = {}) {
// 我们通过覆写app.vue.readFile即可改变文件读取逻辑
const code = yield this.app.vue.readFile(name);
return this.app.vue.render(code, { state: locals }, options);
renderString(tpl, locals) {
return this.app.vue.renderString(tpl, locals);
module.exports = View;
服务器view渲染插件实现
通过webpack实例覆写app.vue.readFile 改变从webpack内存读取文件内容.
if (app.vue) {
app.vue.readFile = fileName =& {
const filePath = path.isAbsolute(fileName) ? fileName : path.join(app.config.view.root[0], fileName);
if (/\.js$/.test(fileName)) {
// webpack 实例是有[egg-webpack]插件挂载上去的.
return app.webpack.fileSystem.readServerFile(filePath, fileName);
return app.webpack.fileSystem.readClientFile(filePath, fileName);
app.messenger.on(app.webpack.Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, data =& {
if (data.state) {
const filepath = app.config.webpackvue.build.manifest;
const promise = app.webpack.fileSystem.readClientFile(filepath);
promise.then(content =& {
fs.writeFileSync(filepath, content, 'utf8');
webpack + vue 编译插件实现
说明: 在最开始的实现时,一直在摸索, 为了尽快满足项目, 整个功能是在一个插件中实现的.
随着业务稳定下来, 就对这边个插件进行了分离和重新实现, 才有了上面的三个插件, 目地是让webpack编写更简单, 扩展更容易.
egg+webpack+vue项目实战
用egg-init 初始化一个项目和安装依赖后, 我们添加 @hubcarl/egg-view-vue 和 egg-view-vue-ssr依赖, 添加egg-webpack 和 egg-webpack-vue 开发依赖
npm i @hubcarl/egg-view-vue --save
npm i @hubcarl/egg-view-vue --save
npm i egg-webpack --save-dev
npm i egg-webpack-vue --save-dev
config/plugin.js
exports.vue = {
enable: true,
package: '@hubcarl/egg-view-vue'
exports.vuessr = {
enable: true,
package: 'egg-view-vue-ssr'
exports.webpack = {
enable: true,
package: 'egg-webpack'
exports.webpackvue = {
enable: true,
package: 'egg-webpack-vue'
config.default.js
exports.view = {
cache: false
exports.static = {
router: '/public', // 请求进来的前缀,避免和应用的 router 冲突,默认是 `/public`
// maxAge: 3600 * 24 * 180, // maxAge 缓存,默认 1 年
dir: path.join(app.baseDir, 'public') // 静态文件目录,默认是 `${baseDir}/app/pulbic`
config.local.js
const webpackConfig = {
baseDir: path.resolve(__dirname, '../'),
port: 8090,
path: 'public',
publicPath: '/public/',
prefix: 'static',
entry: [path.join(baseDir, 'app/web/page')],
commonsChunk: ['vendor']
webpack: {
styleLoader: 'vue-style-loader',
loaderOption: {
includePaths: [path.join(baseDir, 'app/web/asset/style')]
exports.webpack = {
port: webpackConfig.webpackvue.build.port,
clientConfig: require(path.join(app.baseDir, 'build/client')),
serverConfig: require(path.join(app.baseDir, 'build/server'))
exports.webpackvue = webpackConfig.webpackvue;
package.json 增加如下命令
"scripts": {
"build-dev": "NODE_ENV=development node build",
"build-prod": "NODE_ENV=production node build",
"start": "WORKERS=1 NODE_ENV=development node index.js",
"start-prod": "WORKERS=1 && NODE_ENV=production node index.js",
编写webpack配置
在项目根目录下增加build文件夹, 然后分别编写client和server webpack配置文件
client/dev.js
const ClientBaseBuilder = require('./base');
class ClientDevBuilder extends ClientBaseBuilder {
constructor() {
this.setEggWebpackPublicPath();
this.setDevTool('eval-source-map');
this.setCssExtract(false);
module.exports = new ClientDevBuilder().create();
sever/dev.js
const ServerBaseBuilder = require('./base');
class ServerDevBuilder extends ServerBaseBuilder {
constructor() {
this.setEggWebpackPublicPath();
module.exports = new ServerDevBuilder().create();
命令行编译文件到磁盘
增加 build.js 文件
const EggWebpackVue = require('egg-webpack-vue');
const clientConfig = require('./client');
const serverConfig = require('./server');
EggWebpackVue.EasyWebpack.build([clientConfig, serverConfig]);
npm run build-dev
npm run build-prod
client 文件默认编译到public目录下,
server 编辑结果默认到 app/view` 目录下
npm start 会自动通过egg-webpack 启动编译流程, 无需单独运行npm run build-dev 或 npm run build-prod. 启动流程效果如下:
访问: http://127.0.0.1:7001
基于Vue多页面和单页面服务端渲染同构工程骨架项目
基于vue + axios 多页面服务器客户端同构入口: http://127.0.0.1:7001
基于vue + vuex + vue-router + axios 单页面服务器客户端同构入口: http://127.0.0.1:7001/app
关于性能和优化以及问题 (单独总结)
egg+webpack+vue 依赖
egg view plugin for vue
vue server side render solution for egg-view-vue
webpack dev server plugin for egg, support read file in memory and hot reload
egg webpack building solution for vue
programming instead of configuration, webpack is no longer complex

我要回帖

更多关于 vue 多页面 的文章

 

随机推荐