Vue 对渲染做了非常多的优化,经常在可以复用元素的时候会尽量复用元素,避免从头开始渲染。
比如下面的代码示例
1 | <template v-if="loginType === 'username'"> |
上面的代码中通过 loginType
切换两个不同的 template 的时候,因为两个模板使用了相同的元素,Vue 会复用 input
元素,只通过替换 placeholder
来切换。
这样做除了渲染非常快,还可以保留输入框中的文本。你可以自己实际操作试试。
但是这样也会导致一些不符合需求的情况发生,因为不同的状态切换时他总是同一个元素,如果我们有需要手动从操作元素操作 DOM 的时候,则会导致以为情况的发生。
这个时候,我们只需要为元素添加一个 key
属性即可。
1 | <template v-if="loginType === 'username'"> |
这样,每次切换状态时,输入框都讲被重新渲染。
这是最常见的一种父子组件传值的方式。
通过 props
我们可以将值从父组件传递到子组件。
$emit
可以使我们出发一个事件,通过参数将值从子组件传递给父组件。
在元素上定义 ref 属性,再通过 refs 获取。如果 ref 是定义在 DOM 元素上,获取的则是 DOM 元素的引用,如果是用在组件上,获取的则是组件的示例,我们可以通过这个示例直接调用组件的方法和访问组件间的数据。
下面看一个例子。
1 | // 子组件 child.vue |
在子组件中可以用 this.$parent
访问父组件的实例,而子组件的示例则会被推入父组件示例的 $children
数组中。
与 props/\$emit 一样,这两种方式都是用于父子组件的通信,但是 props 的通信方式更加普遍,而且官方也建议节制地使用 $children/$parent, 更多的应该作为应急方案使用。
$parent
property 无法很好的扩展到更深层级的嵌套组件上。Vue2.2 之后提供了两个新的示例选项 provide
和 inject
。父组件通过 provide
来提供变量,子组件通过 inject
来注入变量,父组件借此可以将值注入到深层次的子组件中。
下面看就看一个例子。
1 | // foo.vue |
另外的对于非父子组件之间的复杂状态的管理,建议引入 eventBus 和 Vuex 进行管理。这两种方式将在其他文章中进行介绍。
Vue 2.0 中 v-model 内置的 v-model
指令是一个语法糖,可以拆解为 props:value 和 events:input。只要在组件中提供一个名为 value 的 props,以及名为 input 的自定义事件,满足这两个条件,父组件中就能在子组件上使用 v-model
指令。
1 | <template> |
只需要通过以下方式就能使用 v-model
<counter v-model="val"/>
使用 v-model
指令可以方便的在子组件中同步父组件的数据。2.2 后的版本中,可以定制 v-model
指令的 prop 和 event 名称。参考下面的代码
1 | export default { |
思考一下以下需求:在多语言需求的项目中,我们需要在页面上显示的某一段文案显示一个按钮,并且这个按钮点击后有相应的点击事件触发。如下面代码所示,ID 为 myBtn
的按钮就是在文案中定义的按钮。
{ text: 'I\'m the <button id="myBtn">Button</button>' }
我们可以将整段文案当成 html 渲染,并在组件的 mounted
事件后获取改按钮,并注册点击事件。代码如下所示。那有没有其他方式实现呢,操作 DOM 元素总显得有点不够优雅。
1 | <template> |
答案是有的,我们可以使用组件的实例属性 template
来动态定义组件的模板,组件中使用 this.$options.template
获取和定义模板。上面的例子可以使用下面的方式代替。注意,我们不需要在 Vue 单文件里定义 template 模块了。
1 | <script> |
在上一篇介绍 webpack 升级 webpack 4 版本的时候,在最后提到几个在实际项目中遇到的问题里,有一个是在配合 webpack 升级的过程中,vue-loader 需要对应升级到 15.x,但是这个升级导致原有的用 commonjs 写法去 require
vue 组件时出错了,原因是在 vue-loader 的 14 版本后 vue 文件导出的模块一定是 esModule。详见这个 Issue。
Issue 中尤大大提到的解决方案是可以写一个 Babel 插件去解决这个问题。Babel 大家应该都很熟悉,我们写的 ES6 和 JSX 代码都是靠 Babel 转成浏览器兼容的代码。那 Babel 插件呢,下面开始介绍一下 Babel 插件。
介绍 Babel 插件前,我们先来看看 Babel 转码的三个处理步骤。首先先介绍一下抽象语法树
在 Babel 的处理过程中的每一步都涉及到创建或是操作抽象语法树,亦称 AST。
1 | function square(n) { |
上面的代码可以被表示成下面的树形结构
1 | { |
其中的每一层类似 { type: 'FunctionDeclaration', ... }
的结构都被称为节点(Node),一个 AST 可以由单一的节点或是成百上千个节点构成。 它们组合在一起可以描述用于静态分析的程序语法。
字符串形式的 type 字段表示节点的类型,我们后续的插件就是通过 type 来判断节点进行不同的处理
Babel 转码过程分成三个阶段:分析(parse)、转换(transform)、生成(generate)
其中,分析、生成阶段由 Babel 核心完成,而转换阶段,则由 Babel 插件完成,这也是我们下面介绍的重点
分析
Babel读入源代码,经过词法分析、语法分析后,生成抽象语法树(AST)。
转换
经过前一阶段的代码分析,Babel 得到了 AST。在原始 AST 的基础上,Babel 通过插件,对其进行修改,比如新增、删除、修改后,得到新的 AST。
生成
通过前一阶段的转换,Babel 得到了新的 AST,然后就可以逆向操作,生成新的代码。
代码生成其实很简单:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。
Babel 插件的主要工作就是在转换的步骤中对 AST 中的节点进行新增、删除和修改操作。
Visitor(访问者)
Babel 在递归遍历 AST 语法树时,会访问节点,之所以用访问这个词,是因为有访问者模式这个概念。
访问者(Visitor)是用于 AST 遍历的跨语言模式。简单说他是一个对象,定义了用于在树状结构中获取具体节点的方法。看一下下面的例子
1 | const MyVisitor = { |
这是一个简单的访问者,把它用于遍历中时,每当在树中遇见一个 Identifier 的时候会调用 Identifier() 方法。
如果我们需要在遇到调用表达式(CallExpression)的时候做一些处理,就可以定义一个 CallExpression
回调方法做相应处理
Path(路径)
AST 中有很多节点,每个节点可能有不同的属性,并且节点之间可能存在关联。path 是个对象,它代表了两个节点之间的关联。你可以在 path 上访问到节点的属性,也可以通过 path 来访问到关联的节点(比如父节点、兄弟节点等)
例如,如果有下面这样一个节点及其子节点︰
1 | { |
将子节点 Identifier 表示为一个路径(Path)的话,看起来是这样的:
1 | { |
路径对象中还会包含添加、更新、移动和删除节点等有关的其他方法
例如我们想替换路径中的节点,可以使用 replaceWith
方法,还有很多其他方法,可以通过 Babel 官方文档查看。
前面介绍的是下面开发我们的插件必备的只是,还有其他未提及的插件相关的知识可以看开发手册。开发插件主要是构建 Visitor,有下面两步
但是在构建 Visitor 之前,我们要先分析源文件和目标文件的抽象语法树。通过 AST explorer清晰地看到我们的语法树
回到背景
说回我们编写插件的背景。在 vue-loader 版本升级后,默认的 vue 单文件导出默认变成了 esModule 模块导出。这就导致了之前我们通过 require 方式引入的 vue 组件
const component = require("./component.vue")
必须变成下面的方式引用
const component = require("./component.vue").default
当然我们可以修改所有源码加上 .default
属性调用,但作为一个有追求的程序员,这样显得不够优雅
通过我们前面了解的 Babel 插件的知识,我们可以优雅的处理这个问题
插件思路
首先分析一下转换前的语法树
1 | { |
可以看到 require 调用被转换成 一个 CallExpression,我们需要将 CallExpression 装换成另外一个语句
下面再看看目标代码的语法树
1 | { |
可以看到我们的 CallExpression 被一个叫 MemberExpression 取代了,property
属性是一个名为 default
的 Identifier。同时 MemberExpression 的 object
对应的正是前面的 MemberExpression,内容基本一样。
按照上面的分析,我们可以在 visitor 的访问过程中,处理 CallExpression 的回调方法,判断调用的的方法名称是 require
,且参数是以 .vue
结尾的字符串,我们就可以用一个 MemberExpression 来替换这个表达式。具体我们可以用到 path 中的 replaceWith
方法。
完整的代码如下
1 | module.exports = function({ types: t }) { |
这个插件在是尤大提供后去了解 Babel 的插件机制后试着写出来的,也体会到了 AST 的强大之处。AST 可以做到很多事情,也让我想起来前段时间有个开发者不满微信小程序不能使用 eval 动态执行脚本,自己写了一个运行在小程序中的 JavaScript 的解释器,其中也离不开 AST。上面记录了编写插件的思路过程,希望对有需要的同学有所帮助。
参考
]]>早在 2016 年我就发布过一篇关于在多页面下使用 Webpack + Vue 的配置的文章,当时是我在做自己一个个人项目时遇到了多页面的配置问题,想到别人也可能遇到跟我同样的问题,就把配置的思路分享出来了,传送门在这里。
因为那份配置直到现在还有人在关注,同时最近公司帮助项目升级了 Webpack 4,趁机也把之前的配置也升级了一下,顺手加上了 babel 7 的配置,而且博客荒废了这么久,都快 9102 年了,不能连年均一篇博文都不到,所以有了下面的分享。
下面的配置主要是给在多页面下使用 Webpack 的同学在升级 Webpack 时提供一点思路,多页面的配置思路请点击上面的传送门。
下面代码的地址 https://github.com/cnu4/Webpack-Vue-MultiplePage
设置 mode 构建模式,比如 development 会将 process.env.NODE_ENV 的值设为 development
删除原 extract-text-webpack-plugin 配置,增加 mini-css-extract-plugin 配置
1 | module.exports = { |
这是 webpack 4 一个比较大的变动点,webpack 4 中删除了 webpack.optimize.CommonsChunkPlugin
,并且使用 optimization
中的splitChunk
来替代,下面的配置代替了之前的 CommonsChunkPlugin 配置,同意能提取 JS 和 CSS 文件
1 | module.exports = { |
vue-loader 15 注意要配合一个 webpack 插件才能正确使用
1 | const { VueLoaderPlugin } = require('vue-loader') |
升级到 next
,否则开发下无法正常注入资源文件
压缩的配置也移动到了 optimization 选项下,值得注意的是压缩工具换成了 terser-webpack-plugin,这是 webpack 官方也推荐使用的,估计在 webpack 5 中会变成默认的配置,实测打包速度确实变快了很多。
配置
1 | module.exports = { |
可以使用下面的插件看看打包时间主要耗时在哪
TerserPlugin 压缩插件可以开启多线程,见上面配置
github 的 Demo 中没有引入,有兴趣的同学可以尝试,在一些耗时的 Loader 确实可以提高速度
vue-loader 不支持 HappyPack,官方建议用 thread-loader
1 | const HappyPack = require('happypack'); |
使用 DllPlugn 将 node_modules 或者自己编写的不常变的依赖包打一个 dll 包,提高速度和充分利用缓存。相当于 splitChunks 提取了公共代码,但 DllPlugn 是手动指定了公共代码,提前打包好,免去了后续 webpack 构建时的重新打包。
首先需要增加一个 webpack 配置文件 webpack.dll.config.js
专门针对 dll 打包配置,其中用到 webpack.DllPlugin
。
执行 webpack --config build/webpack.dll.config.js
后,webpack会自动生成 2 个文件,其中vendor.dll.js 即合并打包后第三方模块。另外一个 vendor-mainifest.json 存储各个模块和所需公用模块的对应关系。
接着修改我们的 webpack 配置文件,在 plugin 配置中增加 webpack.DllReferencePlugin
,配置中指定上一步生成的 json 文件,然后手动在 html 文件中引用上一步的 vendor.dll.js 文件。
后面如果增删 dll 中的依赖包时都需要手动执行上面打包命令来更新 dll 包。下面插件可以自动完成这些操作。
安装依赖 autodll-webpack-plugin
AutoDllPlugin 自动同时相当于完成了 DllReferencePlugin 和 DllPlugin 的工作,只需要在我们的 webpack 中添加配置。AutoDllPlugin 会在执行 npm install / remove / update package-name
或改变这个插件配件时重新打包 dll。需要注意的是改变 dll 中指定的依赖包不会触发自动重新打包 dll。
实际打包中生成环境是没问题的,但开发模式下在有缓存的情况下,autodll 插件不会生成新的文件,导致 404,所以在 Demo 中暂时关了这个插件。不过 dll 提前打包了公共文件,确实可以提高打包速度,有兴趣的同学可以研究下开发模式下的缓存问题,欢迎在评论中分享。
1 | module.exports.plugins.push(new AutoDllPlugin({ |
由于项目中是第一次配置 babel,一步到位直接使用新版 7,新版 babel 使用新的命名空间 @babel,如果是老项目升级 babel 7,可以使用工具 babel-upgrade,读一下 升级文档
这里说下上面依赖的作用和升级 babel 7 的改动。
新版中 @babel/runtime 只包含了一些 helpers,如果需要 core-js polyfill 浏览器不支持的 API,可以用 transform 提供的选项 {"corejs": 2}
并安装依赖 @babel/runtime-corejs2
。即使默认的 polyfill 没了,但 @babel/plugin-transform-runtime 依然可以为我们分离辅助函数,减少代码体积
使用 @babel/runtime 的 polyfill 不会污染全局 API,因为不会改动原生对象的原型,它只是创建一个辅助函数在当前作用于生效,所以诸如 [1, 2].includes(1)
这样的语法也无法被 polyfill。如果不是开发第三方库,可以使用 @babel/polyfill,相反他的 polyfill 会影响到浏览器全局的对象原型
@babel/preset-env 提供了一个 useBuiltIns 选项来按需引入 polyfill,而不需要引入全部。另一种方法是直接引用 core-js 包下的特定 polyfill。
现在需要手动安装 @babel/plugin-proposal 开头的依赖是因为 babel 在新版中移除了 stage presets,为的是后续更好维护处于 proposal 阶段的语法。想要使用 proposal 阶段的语法需要单独引用对应的 plugin, 上面的配置只加了几个处于 stage 3 阶段的 plugin,老项目建议使用 babel-upgrade 升级,自动添加依赖
1 | { |
1 | module.exports = { |
下面是我公司项目中遇到的问题,各位升级过程中如果遇到同样的问题可以参考一下解决思路。
webpack4 内置的json-loader 有点兼容性问题,安装 json-loader 依赖和更改配置
解决:
1 | { |
vue-loader 升级到 15.x 后,会导致旧的 commonjs 写法加载有问题,需要使用 require('com.vue').default
的方式引用组件
13的版本还可以设置 esModule,14 以后的版本不能设置了,vue 文件导出的模块一定是 esModule
解决:使用 require('com.vue').default
或者 import
的方式引用组件
esModule option stopped working in version 14 · Issue #1172 · vuejs/vue-loader · GitHub
尤大大建议可以自己写一个 babel 插件,遇到 require vue 文件的时候自动加上 default 属性,这样就不用改动所有代码,我们在项目中也是这样处理的。
scss 中 import 的代码不能被提取到公共 css 中。scss 中的 @import 是使用 sass-loader 处理的,处理后已经变成 css 文件,webpack 已经不能判断是否是同一个模块,所以不能提取到公共的 css 中,但多页面中我们还是希望一些公共的 css 能被提取到公共的文件中。
解决:将需要提取到公共文件的 css 改到 js 中引入就可以,详见下面 issue
mini-css-extract-plugin + sass-loader + splitChunks · Issue #49
mini-css-extract-plugin 的 filename 选项不支持函数,但我们有时候还是希望能单独控制公共 css 文件的位置,而不是和其他入口文件的 css 使用一样的目录格式
解决:使用插件 FileManagerPlugin 在构建后移动文件,等 filename 支持函数后再优化
]]>CDN(内容分发网络)全称是 Content Delivery Network,建立并覆盖在承载网之上、由分布在不同区域的边缘节点服务器群组成的分布式网络,替代传统以 WEB Server 为中心的数据传输模式。
作用是将源内容发布到边缘节点,配合精准的调度系统;将用户的请求分配至最适合他的节点,使用户可以以最快的速度取得他所需的内容,有效解决Internet网络拥塞状况,提高用户访问的响应速度。
传统的网络访问过程图片见上图(图片来自网络,侵删)。首先我们需要知道真实访问目标站点的 IP 地址,这个时候需要对域名进行解析获得 IP 地址。一般本地计算机会请求本地 ISP 的 DNS 服务器,查询是否有缓存记录,如果有则直接访问结果。如果没有,ISP 的 DNS 服务器会向 DNS 根服务器查询,根域名服务器会返回授权服务器地址,告诉他可以通过这个授权服务器找到你要的 DNS 记录。这中间省掉了一个步骤,就是根域名服务器会先跟顶级域名服务器获取授权服务器的地址并向其发起请求。这时 ISP 向授权服务器获得记录后会缓存在本地以供下次访问,接着就是返回给本地计算机。计算机拿到记录里的 IP 地址后,就可以像目标地址发起请求。
这种传统网络访问带来的问题有
CDN 就是在用户和服务器之前增加了一层缓存。要知道计算机界的两大难题,一是给变量起名字,另外一个就是缓存了。缓存可以说在计算机界无处不在。
比如 CPU 为了更快从内存中读取数据,在其自己内部设定了缓存,也就是我们常说的一级、二级甚至多级缓存。计算机内存说起来其实也是 CPU 为了更快获取磁盘的数据的而存在的。再到今天的固态硬盘,为了兼顾速度与成本,大部分固态会使用价格昂贵但速度更快的小容量 SLC 固态作为缓存从而加快访问速度。
所以 CDN 也是作为一层缓存,他的加入有什么作用呢?首先当然是可以减轻源站的压力。再者是可以在跨运营商跨地域的网络中加快访问速度。第三是由于网络请求不会直接到源战,从而也在一定程度上保障了网络安全。
前面说了传统的网络访问过程,那么假如 CDN 后的访问过程是怎样的呢?
首先本地计算机需要向本地 ISP DNS 服务器迭代查询记录,本地 ISP DNS 服务器在递归的向授权服务器获取记录。只是这次授权服务器不再是直接返回域名对应的源站的 IP 地址,而是返回了该域名配置了的对应的 DNS 服务商的智能调度 DNS 服务器的记录。这个域名的记录类型一般是 CNAME 类型。
1 | > nslookup image01.onepLus.cn 8.8.8.8 |
该服务器会根据你的网络情况、地理位置和使用网络供应商等情况,返回一个最合适的 CDN 服务器的节点地址记录。本地 ISP DNS 服务器得到该地址后就会返回给本地计算机,本地计算机再根据该记录对目标 CDN 节点服务器发起请求。
如果该 CDN 节点存在我们想要的资源,即有缓存的话,会直接返回。如果没有,会想源站服务器发起请求并缓存在 CDN 节点服务器,以供下次请求。这里可能会存在多级缓存服务器,如果有的话会一直往上查询直至源站。
回源
回源是指浏览器在发送请求的时候,CDN 节点服务器没有我们需要的资源,一层层的向上请求直到源站服务器。除了 CDN 缓存服务器没有我们要的节点,缓存过期、该资源被配置会不缓存资源都有可能导致回源。过多的回源会导致源站服务器大,极端情况下相当于 CDN 节点不存在。
预取
这里引用一下七牛云的定义
文件预取,也可称为预加热或预缓存,是指新资源提前由 CDN 拉取到 CDN 缓存节点。这样一来,用户第一次访问到该节点时,就可以直接命中缓存,无需重新回源拉取,由此提高用户第一次访问的速度。
一般的 CDN 服务商都会提供可配置预取文件的方法。
适合在运营活动前对静态资源进行预热
缓存
相比于浏览器强制刷新来使浏览器本地缓存失效,开发者需要通过 CDN 服务商提供的“刷新缓存”操作来达到清理 CDN 边缘节点服务器缓存的目的。
这里说一下我在公司直播项目中真实遇到的场景
我在项目中对直播评论的获取进行了改造,从原来的批量获取评论的方式,改成了使用 Websocket 技术从服务器实时推送评论的方式
在做技术方案的时候,由于知道直播页面的静态资源都配置了 CDN,在我心里 CDN 就想一层防火墙一样能挡住直播的大流量,所以就少了对这块的思考。这里的静态资源主要有 JS、CSS 文件和图片等,图片包括了页面上的静态不变的图片和用户的头像图片。
这里提到了用户的头像,每个用户的头像都是不一样的。也正是这里的不一样导致了悲剧的发生。
在改造上线后直播高峰流量来临的时候,我们发现源站服务器基本没办法操作了,服务器压力到了一个很高的水平。通过后台发现正是用户头像的访问占用的很大一部分的网络带宽!但是当时的我们都没有想到,为什么已经配置的 CDN 的用户头像资源还是大量回源到了源站,导致请求激增。
问题就出在这次的评论拉取的方式改成了 Websocket 服务器主动推送的方式,实时性大大增强。我们想象一下,成千上万的用户在同一时间收到了相同的评论,网页渲染评论的时候同时又去请求了相同的头像图片,这个时候大量的 CDN 节点服务器还没有这批实时的用户头像,于是在同一时间都回源到源站的访问图片资源。
由于直播时太过火爆,评论太多(多的时候一秒钟可以有几十条评论),大量的请求同时回源到了源站服务器;同时由于请求太过实时,CDN 节点还没来得及缓存当前的图片,相同的请求又过来了,所以又导致了回源,源源不断的请求到达了源站,正是这个原因导致了流量的激增。
知道了原因,自然可以做出优化方案。
最简单的方案当然是直接删除头像,或者将用户头像简化成几个随机的简单图片,不少友商正是这么做的。
但是知道了原因,我们还是可以想想其他更优的方式。前面提到了预取的概念,那我们可以把所以用户头像都预取到 CDN 节点上吗?答案是理论是当然是可行的,但是一般的 CDN 服务商对每天的资源预取数量都做了限制,跟用户头像不是一个数量级。所以除非是自建的 CDN 网络,这个方案不太可行。
回过头来想想为什么之前批量获取头像的时候为什么没有这个问题。原来我们在批量的时候设置了定时器每个几秒钟到服务器获取一次,这就把用户的访问给错峰开,一旦有了第一个用户访问后的缓存,就不会导致后续的回源。这种错峰的做法,也是在客户端这一侧优化高并发场景下服务器压力的常见方法。所以借鉴这种做法,我们可以优化评论的展示时间,或者优化头像的展示时间,来错峰开流量。
前面介绍了 CDN 的原理,然后又讲了一个实际项目中遇到的关于 CDN 的问题,想说明的是,有时候我们以为我们对某一项技术有了足够的了解,从而缺少了一些思考,但项目的考验会重重的打我们的脸。所以不要以为自己已经有了很深的认识,在什么时候都不能忘了对技术深入的思考。
]]>目前我的博客是部署到 GitHub 和 Coding,出于某些原因国内百度爬虫不能爬去到 GitHub 相关的内容,所以才需要同时部署到国内 Coding。
但每次写完文章都需要部署到两个地方,还是有点繁琐,刚好之前就听过持续集成和相关服务 Travis
,可以用来解决这个问题。下面是具体的配置和步骤,同时也解决了怎么管理博客源码和定制主题源码的问题。
另外 Coding 在去年开始在免费使用其 page 服务的情况下,需要在网站显示有关 Coding 的广告文字。关于如何只在使用 Coding 的服务时才显示那段广告文字,可以查看我博客的源码中的解决方案。
将主题 Fork 到自己的账号下后,在博客源代码根目录使用 git submodule
将主题的仓库作为子仓库
git submodule add git@github.com:cnu4/hexo-theme-apollo.git themes/apollo
进入主题目录,修改主题后按正常流程 git commit
, git push
更新主题的 git 仓库,再到博客根目录 commit
源代码
要求:持续部署到 Github 和 Coding
将 Hexo 源代码 push
到 Github 的仓库,我是将博客的仓库的 source
分支作为源代码仓库
登录 travis
并使用 github 登录后开启上述源代码的仓库
安装 travis cli
工具后登录,使用一下命令生成私钥的加密文件,以下是 Mac 环境下的命令
gem install travis
travis login
travis encrypt-file ~/.ssh/id_rsa --add -r /cnu4/cnu4.github.io
以上命令会自动在 .travis.yml
文件的 before_install
中添加解密得到私钥的代码,并生成文件 id_rsa.enc
创建 .travis
文件夹,在里面新建 ssh_config
文件,填入一下内容,避免第一次连接时的询问
1 | Host * |
将 id_rsa.enc
文件移入 .travis
文件夹
下面是 .travis.yml
配置文件
1 | language: node_js |
在 before_install
中先解密得到私钥并放到 .ssh
目录下,接下来的 npm install
安装 hexo
,然后就是构建和部署命令了
配置中需要注意:
配置中通过 git: submodules: false
使得 travis 不用 https
的方式处理 git 子模块,否则此时会出现克隆时身份验证不通过的情况
通过将代码 git submodule update --init --recursive
添加在 ssh-add ~/.ssh/id_rsa
之后,使用 ssh
处理子模块
注意加密的 ssh 私钥不能有密码保护,也就是创建私钥的时候不要输入密码
接下来写文章后只需要一下命令便可以完成自动部署到 Github 和 Coding
1 | git add . |
在换了新电脑后,使用以下流程
1 | git clone repo_of_source.git # 克隆源码 |
刚学习了 redux 不久,恰好看到一个优秀的 react 项目 github-explorer,该应用使用了 RxJS 去处理数据流,为了巩固学习便有了使用 redux 改写的想法。
应用中使用了自定义个中间件 api,方便编写异步的 action creators。异步 action 可以定义成以下方式
1 | export function loadUserProfileRepos (username) { |
中间件接收到这种形式的action,会处理异步请求并在适当的时候 dispatch types
中的各项。
应用的拉取数据的进度条方面,负责拉取状态 reducer 在接收到诸如 xx_REQUEST
和 xx_RECEIVED
的 actions 后,会更新表示进度条状态的数据。
因为只是巩固redux学习,所以原应用的部分动画效果没有加上。
除了数据流部分,应用大部分都是参考了原应用。
使用了redux后加上的依赖
开发
1 | npm install |
打包
1 | npm run dist |
这篇是 webpack 1.x 的多页面配置,4.x 的版本在这里。
但是多页面配置的思路是一样的,变的是配置,所以可以先看这篇配置,实际使用 4.x 版本的配置
webpack + vue 能很好的完成单页面应用的开发,官方也提供了很多例子和教程。但使用 webpack 能不能用到多页面项目中,同时又能使用 vue 进行模块组件化开发呢?
这里将结合具体的项目,说明一下我是如何配置的。我们希望能在项目里做到
下面 DEMO 的代码地址: https://github.com/cnu4/Webpack-Vue-MultiplePage
下面是我们项目的目录结构
1 | ├─Application (thinkphp 配置下的结构,可以结合自己项目做修改) |
每个页面都是一个文件夹,所需的资源文件也都放在这个文件夹下,不需要这个页面时,也只需要删除这个文件夹。
下面是 index 模块下的 index 页面
1 |
|
上面是 index 页面的 html 模板,我们无需引入任何 css 和 js ,webpack 会自动帮我打包引入。
其中的 app 标签是我们的 vue 组件,webpac k的加载器会帮我们处理 js 文件中引入的 vue 组件,这样就能正确处理这个标签。
下面 index 页面对应的 js 入口文件
1 | import Vue from 'vue' |
先说下 demo 的运行命令
1 | # 首先安装依赖 |
下面是 webpack 的配置文件 webpack.config.js,其中用注释指出了关键配置。
1 | var path = require('path'); |
运行 npm run dev
开发模式运行 demo
根据 webpack 配置文件中 output 的 publicPath 配置项和 HtmlWebpackPlugin 插件的 filename 配置项
demo 中 dev 环境下中分别是 /View 和pathname + ‘.html’
所以 demo 中通过 http://localhost:8080/View/another/index.html
可以访问到 another 模块下的 index 页面
运行 npm run build
打包,可以看到 Application/Home/View 目录下成功生成了按模块分组的 html 文件,这正是项目需要的。
如 Application/Home/View/index 下的 index.html 文件
1 |
|
venders.css 和 venders.js 文件是 webpack 插件帮我们自动生成的公共样式模块和公共 js 模块。打开页面,还能看到其他资源文件也都被正确处理了。
总结一下 webpack 帮我们做了下面几件事