起因

最近在做一个手机站点,服务器用的Google Cloud,但是用户多是国内的。所以在提升页面加载和响应速度这块,是很重视的。

一开始是想使用manifest.json来做Application Cache, 不过后面发现Google和FireFox都有提到打算废弃这个标准并建议大家改用Service Worker。

可是我要做到站点是文章居多的偏新闻性质的,而且交互也不多,用Service Worker来做PWA,感觉有点大材小用。所以想找一些更好实践,而且对于css和js资源的缓存和快速加载有意义的方案。

prefetch和preload就出现在眼前了。

关于prefetch和preload

具体的技术介绍,这里就不详述,大家可以看看MDN的这篇文章

这里说一下preload和prefetch的区别:

preload是通知浏览器尽快的去加载当前页面现在或者将来需要的一些资源文件(css,js,image,viedo,audio,fonts等等),这个加载跟页面解析是并行的,不会阻塞页面本身的加载。

prefetch是一种预测,预测访问当前页面A的用户很可能会访问页面B,所以在页面A提前告知浏览器去加载页面B的资源。这个提前加载的优先级最低,不会干扰页面的正常资源的加载解析,也不会干扰preload。

兼容性

preload在pc上,chrome和firefox支持比较好, eage是还在开发状态,safari的最新版本也已经支持了。在手机端的支持就不是很好,Andriod 需要5之后的webview才支持,国内的UC,QQ和Baidu都不支持;iOS是最新的11.3开始支持

具体参看:caniuse preload

prefetch的兼容性,相对好点,除了MacOS和iOS上的safari不支持之外,其他浏览器都支持了。

具体参看:caniuse prefetch

另外,preload和prefetch都是申明性质的,所以就算不支持,也不会影响现有页面的任何功能

关于站点的webpack配置

为了方便js和css资源的缓存,在nginx上是配置了很长的过期时间,所以对应的,css和js的编译后的文件名都做了hash后缀。这样,线上的更新,都是文件的替换而不是刷新缓存。

为了方便的实现html里面css和js资源文件能在编译后自动更新路径,使用了HtmlWebpackPlugin来实现html的编译和输出

而在HtmlWebpackPlugin本身也有插件机制,其中一个由GoogleChromeLabs开发的插件PreloadWebpackPlugin,就能方便的给编译的html文件添加相关资源的preload和pretch

具体实现(针对多页面多Entry情况)

如果你是一个单页面应用,那么一切都很简单,就不多说了。这里主要说一下在处理多页面的输出时的注意点

首先,说一下我的几个module的版本:

"webpack": "^4.10.2",
"html-webpack-plugin": "^3.2.0",
"preload-webpack-plugin": "^3.0.0-alpha.3",

PreloadWebpackPlugin,我一开始用的是正式版的v2.3.0,可是我的webpack是4.x版本,有点不兼容。按照官方issues上的解决方案,改为安装了preload-webpack-plugin@latest,然后才可以的。

HtmlWebpackPlugin多页面配置

首先,你需要配置好HtmlWebpackPlugin,不同的页面使用不同的template,编译到不同的filename,同时还要指定哪些chunks需要加载到页面里面。

我的站点是目前包括四个页面index.html, book.html, content.html和search.html,对应配置如下:

    new HtmlWebpackPlugin({
      title: 'index',
      template: './src/pages/index/index.html',
      filename: 'index.html',
      chunks: ['index']
    }),
    new HtmlWebpackPlugin({
      title: 'book',
      template: './src/pages/book/book.html',
      filename: 'book.html',
      chunks: ['book']
    }),
    new HtmlWebpackPlugin({
      title: 'content',
      template: './src/pages/content/content.html',
      filename: 'content.html',
      chunks: ['content']
    }),
    new HtmlWebpackPlugin({
      title: 'search',
      template: './src/pages/search/search.html',
      filename: 'search.html',
      chunks: ['search']
    }),

完成这一步之后,编译,dist目录应该有对应的四个文件,里面的资源也能正确加载。接下来就是配置preload了

preload配置

PreloadWebpackPlugin这个插件更适用于单页面应用,对于多页面应用,在配置上比较繁琐一点。

首先,需要使用excludeHtmlNames来反向排除所有页面,只关联你要配置的那个页面,

再然后,使用include选项来明确指定哪些资源要被preload

最后,每一个页面对应添加一个PreloadWebpackPlugin的实例

还有一点需要补充说明一下,在webpack配置的plugins里面,PreloadWebpackPlugin的配置需要在HtmlWebpackPlugin之后,不然PreloadWebpackPlugin找不到HtmlWebpackPlugin提供的hooks,会报错。

完成上面这一步之后的配置如下:

    new HtmlWebpackPlugin({
      title: 'index',
      template: './src/pages/index/index.html',
      filename: 'index.html',
      chunks: ['index']
    }),
    new HtmlWebpackPlugin({
      title: 'book',
      template: './src/pages/book/book.html',
      filename: 'book.html',
      chunks: ['book']
    }),
    new HtmlWebpackPlugin({
      title: 'content',
      template: './src/pages/content/content.html',
      filename: 'content.html',
      chunks: ['content']
    }),
    new HtmlWebpackPlugin({
      title: 'search',
      template: './src/pages/search/search.html',
      filename: 'search.html',
      chunks: ['search']
    }),
    new PreloadWebpackPlugin({
      rel: 'preload',
      excludeHtmlNames: ['book.html', 'content.html', 'search.html'],
      include: ['index']
    }),
    new PreloadWebpackPlugin({
      rel: 'preload',
      excludeHtmlNames: ['index.html', 'content.html', 'search.html'],
      include: ['book']
    }),
    new PreloadWebpackPlugin({
      rel: 'preload',
      excludeHtmlNames: ['index.html', 'book.html', 'search.html'],
      include: ['content']
    }),
    new PreloadWebpackPlugin({
      rel: 'preload',
      excludeHtmlNames: ['index.html', 'book.html', 'content.html'],
      include: ['search']
    }),

可以看到,在excludeHtmlNames的配置上比较繁琐,如果页面多的话,最好写个函数来实现过滤。

prefetch的配置

一个PreloadWebpackPlugin实例同时只能干一件事情,根据rel字段区分,要么是preload,要么是prefetch。所以,还得新增PreloadWebpackPlugin的实例。

不过不着急,先想好如何配置prefetch。在我的这个站点里面,在index页面,大部分的链接都是指向的book页面,所以可以在index页面prefetch book页面的资源。同样的还有,在book页面prefetch content页面的资源。

定好之后,就是配置了,跟preload的配置比较类似,需要区分的就是rel和include这两个选项

rel需要改成prefetch,这不用多说。include,这个时候需要改成你打算prefetch的页面的资源,而不是当前页面的资源。

最终的配置如下:

    new HtmlWebpackPlugin({
      title: 'index',
      template: './src/pages/index/index.html',
      filename: 'index.html',
      chunks: ['index']
    }),
    new HtmlWebpackPlugin({
      title: 'book',
      template: './src/pages/book/book.html',
      filename: 'book.html',
      chunks: ['book']
    }),
    new HtmlWebpackPlugin({
      title: 'content',
      template: './src/pages/content/content.html',
      filename: 'content.html',
      chunks: ['content']
    }),
    new HtmlWebpackPlugin({
      title: 'search',
      template: './src/pages/search/search.html',
      filename: 'search.html',
      chunks: ['search']
    }),
    new PreloadWebpackPlugin({
      rel: 'preload',
      excludeHtmlNames: ['book.html', 'content.html', 'search.html'],
      include: ['index']
    }),
    new PreloadWebpackPlugin({
      rel: 'prefetch',
      excludeHtmlNames: ['book.html', 'content.html', 'search.html'],
      include: ['book']
    }),
    new PreloadWebpackPlugin({
      rel: 'preload',
      excludeHtmlNames: ['index.html', 'content.html', 'search.html'],
      include: ['book']
    }),
    new PreloadWebpackPlugin({
      rel: 'prefetch',
      excludeHtmlNames: ['index.html', 'content.html', 'search.html'],
      include: ['content']
    }),
    new PreloadWebpackPlugin({
      rel: 'preload',
      excludeHtmlNames: ['index.html', 'book.html', 'search.html'],
      include: ['content']
    }),
    new PreloadWebpackPlugin({
      rel: 'preload',
      excludeHtmlNames: ['index.html', 'book.html', 'content.html'],
      include: ['search']
    }),

以上,就能顺利的实现,在webpack编译过程中,在html页面里面自动配置上preload和prefetch。希望能帮到大家

重构



最新在对之前开发的一个美食类的小程序《今天吃什么星人》做重构。这个是我做的头一款小程序,基于labrador框架来开发的。小程序开发上架之后,就没再动过。后面大家发现这款小程序慢慢有点人气,打算拿出来再优化优化看看

大家重新设计了首页和美食页,然后轮到我这边来开发。

从初次开发到这次打算改版,已经又进行过三个小程序的开发,也算积累了不少经验。labrador, mpvue, wepy 这三款比较流行的框架都有接触和使用,其中最喜欢的算是mpvue了。

这次的改版,改动没多大,主要在UI上,所以是没打算做架构上的大改动。可是重构的时候遇到一些编译上的问题解决不了,而labrador已经被开发者放弃,好几个月没更新了。再想想这个项目后续可能还会有不少的改动。最后还是决定使用mpvue来改写一遍。

重构的时候,考虑到需要参照着之前的代码来改,就没用git checkout --orphan这样的方式新建分支,而是单独新建了一个同级文件夹来放置新代码。这样新旧代码可以同时参看,也可以使用微信开发者工具同时预览效果对比

代码提交

很快,整个项目重构完成了。这个时候,想往之前的项目里面新建一个new分支,把当前的新版的master分支提交过去。

一开始执行的git push;git push --tags, 提示No configured push destination

这才想起来,忘记配置项目的远程分支了:git remote add origin git@git.coding.net:xxxxx.git

然后再执行git push,提示被reject了。

直接git push,默认是推送到origin的master分支,这两个版本的分支进度不一样,所以被拒绝了。

调整命令,改成git push origin new:master。意思是推送本地的master分支到origin端的new分支

推送成功,到coding的后台一看,也有新分支了。代码算是初步提交成功

后续优化

接下来,执行git branch -u origin/new 使得本地的master分支能跟踪origin端的new分支,这样pull的时候,能顺利拉取。

完成之后,再尝试了一次git push,发现还是reject,原来是我的全局git配置里面配置了git.default 为 current。其作用是设定git push的默认行为是把当前分支推送到origin端同名的分支。所以在当前场景下,就会被reject

但是我又不想修改全局的配置。好在git是可以按项目来单独配置并覆盖全局配置的

执行git config push.default upstream,这样就使得git push的默认行为是推送到跟踪的远端分支

再执行git push,提示Everything up-to-date,成功!

参考资料:

  1. https://stackoverflow.com/questions/948354/default-behavior-of-git-push-without-a-branch-specified
  2. https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E8%BF%9C%E7%A8%8B%E5%88%86%E6%94%AF
  3. https://gist.github.com/seanbuscay/5877413

很多程序员,包括我,会在写代码的时候,习惯性的用TODO, FIXME, HACK等作为一些注释的前缀。这样方便自己和后来者预先知道相关注释和代码的状况

不过这样的备注只有是以后恰巧看到这段代码的时候才能看到,没法在一次发版或者空闲的时候,有一个直观的界面能看到这个项目里面所有这样的关键词。

这个时候一个简单的自定义命令兴许能帮到你:

command Todo Ack! 'TODO\|FIXME'

效果如下:

不过要想使这条命令生效,需要一些环境准备工作

Ack

首先我们需要安装Ack或者Ag,这两个都是命令行下的一个全局搜索工具,比系统自带的grep要快很多的。

这里的安装以Ag为例,因为这个是基于C来重写的Ack并新增了很多特性,效率也是比Ack快了很多的。

# OSX
brew install the_silver_searcher
# Archlinux
pacman -S the_silver_searcher
# Ubuntu
apt-get install silversearcher-ag

在安装完成之后,就可以在shell里面测试一下

ag TODO ./

Ack.vim

Ack.vim是一款插件,用来方便在Vim里面使用Ack/Ag, 然后通过Quickfix来提供搜索结果

不过这款插件,默认是使用的Ack作为搜索引擎,为了能用上我们刚刚安装的Ag,需要在~/.vimrc里面作一些配置:

if executable('ag')
  let g:ackprg = 'ag --vimgrep'
endif

这段配置是引用的官方仓库的Readme

不过也看很多人是用的

if executable('ag')
  let g:ackprg = 'ag --nogroup --nocolor --column'
endif

本人测试之后,发现二者并没有什么区别。可能对于Ag来说 --vimgrep是等同于--nogroup --nocolor --column 的。

这个时候在Vim的命令行里面输入

:Ack! TODO 

是可以实现上图所示效果的,而且还可以通过

:AckAdd FIXME

这样的方式来附加其它要查看的。

不过这样的方式,命令还是有点长。所以配置一个alias来替代上面的命令,是一个比较符合懒人的想法

~/.vimrc里面新增

command Todo Ack! 'TODO\|FIXME'

然后在Vim的命令行里面输入:

:Todo

就能实现列出当前项目里面的所有TODO,FIXME了,而且可以根据自己的需要来扩展这样的关键字,像我就扩展为如下代码:

command Todo Ack! 'TODO|FIXME|CHANGED|BUG|HACK'
command Debug Ack! 'NOTE|INFO|IDEA'

区分了Todo和Debug两种功能。

高亮

Vim里面会自动的高亮TODOFIXME,但是不会高亮CHANGED这样的关键字,所以需要手动的来指定一下,在~/.vimrc里面添加如下配置:

if has("autocmd")
  " Highlight TODO, FIXME, NOTE, etc.
  if v:version > 701
    autocmd Syntax * call matchadd('Todo',  '\W\zs\(TODO\|FIXME\|CHANGED\|BUG\|HACK\)')
    autocmd Syntax * call matchadd('Debug', '\W\zs\(NOTE\|INFO\|IDEA\)')
  endif
endif

上面这段代码是摘抄的,具体的功能就是匹配TODO等关键字,然后加上语法高亮。

综上,一个简单好用的TODO管理器就已经完成了。

参考资料

上文里面的绝大部分代码都是从以下资料里面摘抄拼凑的,对于Vim,我还只是一个懵懵懂懂的拿来主义者。

  1. https://coderwall.com/p/prfnnw/vim-to-do-list
  2. https://stackoverflow.com/questions/6577579/task-tags-in-vim
  3. http://harttle.com/2015/12/21/vim-search.html
  4. https://github.com/mileszs/ack.vim
  5. http://betterthanack.com/

之前安装过Docker Toolbox来实现在Mac上面跑Docker,后来Docker又出了一个Docker for Mac,好奇心一起就卸了Docker Toolbox,装了它。

愿意尝试的原因是Docker Toolbox主要是用VirtualBox来放container,Mac环境里面相关的docker命令其实都是代理,最终的实现是在VirtualBox里面。而Docker for Mac用的是macOS的一个框架HyperKit来实现的,不需要使用VirtualBox来做中间代理,性能上会有很大的提升。当然,前提是你的系统是升级到了macOS10.11。

顺利安装和启动Docker for Mac之后,在命令行运行docker info命令,会出现如下错误。

could not read CA certificate "/Users/{{username}}/.docker/ca.pem": open /Users/{{username}}/.docker/ca.pem: no such file or directory

在网上找了不少解决方式,

bash_profile

尝试了下这个网址里面说的https://github.com/boot2docker/osx-installer/issues/126
我的用户目录下没有.bash_profile的文件,我是用的zsh,所以在.zsh_rc找了找(安装Docker Toolbox是很久之前的事情了,根本不记得当初怎么安装的),意外的也找到了当初配置的一些DOCKER_*的环境变量。注释,重启zsh,再次运行docker info,还是不行。

docker machine

然后又继续尝试上面网址里面,后续给的方法:

docker-machine regenerate-certs default

 egenerate TLS machine certs?  Warning: this is irreversible. (y/n): y
 Regenerating TLS certificates
 Host does not exist: "default"

失败。

既然是说default不存在,那我就来创建一个

查看了docker-machine相关的命令,刚好有一个create命令,执行了一下:

docker-machine create default
 Creating CA: /Users/{{username}}/.docker/machine/certs/ca.pem
 Creating client certificate: /Users/{{username}}/.docker/machine/certs/cert.pem
 Running pre-create checks...
 Error with pre-create check: "exit status 126"

搜了一下关于exit status 126 的相关错误,说的是没安装VirtualBox,这时候我觉得不对劲了,因为跑Docker for Mac是不需要VirtualBox的啊。

正式看了一下docker machine是什么东西,原来是一个用来方便的管理多种类型的docker主机的一个工具,可以是虚拟机,本地主机和云平台,具体可以看看这个链接:https://yeasy.gitbooks.io/docker_practice/content/machine/usage.html

顺便推荐一下这本电子书《Docker —— 从入门到实践》

看完docker machine的作用,可以看的出来,这和我现在想做的事情和遇到的问题,没有太大干系。我是安装完了Docker的环境,然后在执行docker命令的时候出现了问题,不是在已经有container的时候在维护上出了问题。

环境变量

这时候我开始回到官网来追根溯源了,很幸运的找到一篇深入比较Docker for Mac和Docker Toolbox的文章:Docker for Mac vs. Docker Toolbox

这个对于Docker for Mac和Docker Toolbox各自的实现机制说的十分透彻,同时还意外的有一个Setting up to run Docker for Mac章节,说了如何配置Docker for Mac的环境,在你已经或者曾经安装过Docker Toolbox的情况下。

按照上面说的执行命令

env | grep DOCKER

顺利的找到了所有的DOCKER_*的环境变量

对对应的变量执行 unset

 unset DOCKER_TLS_VERIFY
 unset DOCKER_CERT_PATH
 unset DOCKER_MACHINE_NAME
 unset DOCKER_HOST

再次执行

env | grep DOCKER

保证环境变量已经清除。

然后执行

docker info

一个正常的信息列表出来了

Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Server Version: 17.03.1-ce
Storage Driver: overlay2
 Backing Filesystem: extfs
 Supports d_type: true
 Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: bridge host ipvlan macvlan null overlay
Swarm: inactive
Runtimes: runc
Default Runtime: runc
...

以上就是整个debug的过程。其实这么折腾,主要还是自己不是很了解docker,得抽空再好好看看上面说的那本电子书才行了。

从一个Electron项目说起

这两天在折腾用Electron来写一个弹幕助手,方便用PC直播的主播能够看到用户发的弹幕并且进行回复和相关管理操作。

主要的开发任务就是对PC站点已有功能的搬迁和调整。

在最后实现退出功能的时候,希望是在菜单栏上有一个退出按钮,用户点击就能直接退出。

下面是mainProcess里面配置菜单的代码,针对退出登录按钮注册了事件,点击的时候,通知对应的web页去实现退出登录功能。

const template = [
    {
        label: '操作',
        submenu: [
            {
                label: '关闭助手',
                role: 'quit'
            },
            {
                label: '退出登录',
                click: function(){
                    mainWindow.webContents.send('main-process-message', '{action: "quit-login"}');
                }
            }
        ]
    }
];

然后是renderProcess这边监听事件并对应处理

const ipc = require('electron').ipcRenderer;
ipc.on('main-process-message', (event, data) => {
    switch(data.action) {
        case "quit-login": 
            user.quit();
            console.log('quit');
            break;
    }
});

一个意外的错误

项目这块,renderProcess的代码都是用webpack进行编译的。

在renderProcess里面引入electron模块之后,立马就报了一个问题:

ERROR in ./~/.1.6.2@electron/index.js
Module not found: Error: Can't resolve 'fs' in '/xxx/electron-quick-start/node_modules/.1.6.2@electron'
@ ./~/.1.6.2@electron/index.js 1:9-22
@ ./js/index.js

上网看了一圈,发现github上的webpack项目里面有人遇到了类似的问题:

Error: Cannot resolve module ‘fs’ with electron and js

然后底下有人给出了解决方式,在webpack.config.js里面加上target: 'electron'就可以的。

试了一下,发现果然是可以的。

深入了解target属性

解决问题之后,出于好奇,就去webpack官网看了target配置的文档

webpack可以为js的各种不同的宿主环境提供编译功能,为了能正确的进行编译,就需要开发人员在配置里面正确的进行配置。

默认情况下,target的值是web,也就是为类浏览器的环境提供编译。

完整的target属性值及对应作用的列表如下:

拷贝自https://github.com/webpack/webpack.js.org/edit/master/content/configuration/target.md

target Description
async-node Compile for usage in a Node.js-like environment (uses fs and vm to load chunks asynchronously)
~~atom~~ Alias for electron-main
~~electron~~ Alias for electron-main
electron-main Compile for Electron for main process.
electron-renderer Compile for Electron for renderer process, providing a target using JsonpTemplatePlugin, FunctionModulePlugin for browser environments and NodeTargetPlugin and ExternalsPlugin for CommonJS and Electron built-in modules.
node Compile for usage in a Node.js-like environment (uses Node.js require to load chunks)
node-webkit Compile for usage in WebKit and uses JSONP for chunk loading. Allows importing of built-in Node.js modules and nw.gui (experimental)
web Compile for usage in a browser-like environment (default)
webworker Compile as WebWorker

所以,把target设为electron,也就能正确的对Electron环境下的代码进行编译了,不过我的代码是写在renderProcess里面的,所以把targets设置为electron-render才是更加合理的选择。

在13年的时候, 写过一篇博文–关于数据备份. 里面讲到了我悲惨的数据丢失和找回的经历, 也说了一些对数据备份的方案. 关于云备份, 只是大致的说了点.

就在今天, 先是群里面有人发了张照片, 说金山快盘要关闭了

kuaipan-down

一开始是有点不相信的, 因为我从快盘刚出现的时候, 基本就开始使用了, 看着它一个平台一个平台的支持, 性能也是一天天的好, 给的空间也是一天天的大. 中间自己还提过不少的反馈. 而且是金山公司的. 怎么可能突然就要关闭了.

kuaipan-storage

然后就访问官网, 还真是要关闭了, 而且所属公司, 也不知道什么时候变成迅雷了…

对数据的分级

所以我现在就得重新考虑我这几十个G的数据的何去何从了. 也算是来一次”重构”吧.

首先, 就得对数据做一个划分, 不能再跟以前一样, 所有的数据都是大杂烩的放一起了, 存储是要钱的(后面会说到选择收费的云存储)

按隐私,重要性,可维护性,体积来划分:

  • 很隐私的文件: 不能被他人看到, 也不能被政府等第三方随意抽查
  • 比较隐私的文件: 可以接受被第三方做安全和和合法性的检查, 但是不能被抓取和利用文件中的内容. 比如一些工作文件,系统备份
  • 不是隐私文件: 虽然不是隐私文件, 但是也想能够收集起来, 方便查阅
  • 很重要的文件: 绝对不能丢失
  • 比较重要的文件: 平时收集的一些网上的文档和程序. 丢失很可惜, 但是也是可以再次收集的
  • 方便维护的文件: 数量相对比较少, 而且划分的维度不多. 比如手机照片, 基本一个文件夹, 可劲的塞进去都行
  • 不方便维护的文件: 比如媳妇做设计收集的图片和psd, 各种分类各种划分, 人工维护不方便. 还有系统的备份, 手机的备份
  • 大文件: 体积在200M以及往上的文件 软件和视频
  • 中等文件: 1M到20M 多是音乐和图片
  • 小文件: 个人文档, 代码等等

按范围分: 个人文件和公司文件.

按类型分: 图片, 文档, 软件, 字体, 音乐, 视频, 备份, 代码

这些划分不是独立的, 四个维度可以综合的来描述某一类文件.

云存储的分级

云存储的公司很多, 需要一个好的筛选维度去进行划分, 这样才好按照自己的需要进行选择.

按隐私, 持久, 价格和便捷性来划分:

  • 隐私程度: 存储的内容不会被云存储公司自身或者政府翻阅的可能性 国外的大于国内的 要翻墙的大于不要翻墙的
  • 持久: 这个云存储能保证有效的时间, 像这次金山快盘的突然关闭, 来几次会让人崩溃, 所以要挑选能长久有效的云存储. 一般来说, 收费的大于免费的, 价格高的大于价格低的
  • 价格: 今天看了下. Dropbox(€9.99/月 1TB) > GoogleDrive ($1.99/月 100GB) > OneDrive(¥15/月 50GB) ~ 坚果云(30GB起 增量1GB/月 不封顶 ¥16.66/月) > 百度云盘(免费, 存储按T算, 基本算不封顶) = 微云(免费, 存储按T算, 基本算不封顶) = 360云盘(免费, 存储按T算, 基本算不封顶)
  • 便捷性: 速度,是否需要翻墙,对各个系统的支持,对文件的格式的支持. 这个基本上对系统的支持, 对文件格式的支持都差不多. 所以主要看速度和是不是需要翻墙. 个人感觉 OneDrive > 国内云 > Dropbox > GoogleDrive

最终的筛选

从以上的划分, 最后决定如下

  • 针对很隐私很重要的文件(手机的日常照片,视频; 个人的一些重要的有纪念意义的文档), 不论体积和数量, 放Dropbox, 尽量使用它的免费空间. 针对邮件, 邮件附件, 基于Google系列产品构建的文件, 使用GoogleDrive.
  • 针对比较隐私的文件. 对不方便维护的Mac系统备份, 采用自购的移动硬盘, 用Mac的TimeMachine. 对手机, 则使用iPhone的备份到Mac功能. 对工作文件, 比较重要, 往往不方便维护, 是小文件或者中文件, 比如设计用的字体, psd, 图片; 以前早起的没有版本系统的一些没有太大实际意义, 纪念意义更重的代码. 用OneDrive, 如果空间不够, 可以增加容量.
  • 针对比较随意的内容, 比如一些软件备份, 一些电影资源, 一些音乐(ape等高清资源或者一般的mp3) 就用百度云盘, 速度不错, 短期不会倒闭, 而且有算法能对重复内容做识别, 可以免上传.

希望通过这样的划分方式能比较方便又安全的进行个人数据的管理. 如果大家有更好的方案, 也期待分享交流.

一点小广告:

如果大家想用OneDrive而且没有注册过, 可以用这个链接https://onedrive.live.com?invref=031414c742a7d364&invscr=90 进行访问, 这样我也能得到0.5G的奖励^_^

本文参考自:Git-与其他系统-迁移到-Git

也建议阅读之前,先看看此文章,本文主要是对里面方法的一个补充和完善,保证可用性

第一步:把之前svn的提交者的信息映射为git需要的

在本地的svn目录,执行以下命令:

svn log ^/ --xml | grep "^<author" | sort -u | perl -pe 's/<author>(.*?)</author>/$1 = /' > users.txt

显而易见,本方法要求主机上安装了grep,sort 和 perl.

最后得到的user.txt 是所有svn提交者的name。

在对应等号后面加上Email地址。

最终变成如下格式:

schacon = Scott Chacon 
selse = Someo Nelse 

第二步:使用svn2git来导入

在一个新的打算放置导入成功之后的git项目的目录(最后也把上一步的users.txt拷贝到此处)

前往github上面的svn2git项目,安装这个工具,能比较方便的进行svn到git的导入

执行以下命令:

svn2git svn://yourdomain.com/your/path/to/svn --username xingwang --verbose --authors users.txt

解释一下几个参数:
--username: 设定你在svn里面的用户名,方便执行svn co
--verbose: debug模式,能够了解svn2git执行了哪些命令,方便出问题的时候调试
--authors: 用来设定提交者信息,也就是上一步生成的users.txt

  • 服务器环境:ubuntu 14.04, nginx/1.4.6

  • 执行命令:sudo nginx -t -c /etc/nginx/conf.d/default.conf

  • 命令目的:查看新修改的nginx是否有错误,避免上线导致服务器出错

执行结果:

nginx: [emerg] "server" directive is not allowed here in /etc/nginx/conf.d/default.conf:1
nginx: configuration file /etc/nginx/conf.d/default.conf test failed

default.conf里面的内容:

server {
    listen                      80; 
    server_name                 localhostmanager.com www.shuizhongyueming.com;
    root                        /var/www/www.shuizhongyueming.com;

    location / { 
        index   index.html index.php;
    }   

    # 媒体资源文件
    # TODO: 用一个static.hostmanager.com 之类的域名放置
    location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$ {
         expires      30d;
    }   

    # 前端代码
    # TODO: 用一个fe.hostmanager.com之类的域名放置
    location ~ .*.(js|css)$ {
         expires      1h; 
    }   
}

一开始以为是语法错误,可是复制了官方文档里面的一个简单PHP站点配置之后,还是报同样的错误,所以基本算排除了语法错误

上网Google的结果,大多说的是server的配置应该放在http里面

可是我的default.conf 是在/etc/nginx/nginx.conf 里面,在http模块下include的,不应该有任何的错误才对

最后自己根据网上这些答案猜测,是我进行语法检测的对象有问题。

要检测现有的修改过的Nginx配置是否有错误,不是单单检测那个修改过的扩展的.conf文件。而是不管任何时候,始终都是去检测主文件/etc/nginx/nginx.conf,只有这样,才能顺利的在对应的模块加载扩展的.conf文件。

这样一来保证了配置的前后语境的正确性,二来,这样才是真正的检测(完全和实际运行情况相符)

所以正确的检测修改的Nginx的语法是否错误的命令应该是:sudo nginx -t -c /etc/nginx/nginx.conf,然后一个欣喜的结果就会是:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

希望这个小的发现能帮助到大家。

Images can't contain alpha channels or transparencies

今天给一个已经reject了N多次的APP做再一次的上传和提交。因为一些问题,需要重新上传图片。这算是iTunes Connect更新之后,头一回做图片上传的操作,发现果真方便不少。

首先已有的图片展示速度提升很多,点击图片可以全屏查看,鼠标放图片上会在左上角有个红色的删除按钮。最棒的是上传图片的时候,可以拖进去,而且还是批量的。比老版的点击一次上传一张要方便多了。而且图片上传的速度也是快的惊人。But 图片上传失败了,就像上面那个图片。

stackoverflow上面找到了答案,得使用Photoshop重新生成图片,然后去掉alpha值,也就是透明度。

我的操作流程是这样的:

  • 在Photoshop中打开图片
  • alt+ctrl+shift+s(或者文件=>存储为web所用格式)
  • 去掉透明度复选框的选中
  • 存储

去掉透明度复选框的选中

这样再次生成的图片,就可以顺利的上传到iTunes connect上面了

本文是翻译的https://help.github.com/articles/using-pull-requests/ 。旨在帮助大家了解GitHub上面如何给别人的仓库提交变更,英文好的同学可以查看上述网址,获取最新的内容

Pull Request可以让你通知别人,你已经推送了一个修改到GitHub的版本库。在pull request被发送之后,感兴趣的成员可以review这些代码变动,讨论可能需要的修改,甚至在必要的时候在这之后push一些commits

本指南详细描述了从发送一个pull request(假设的),使用各种代码审查工具和管理工具,到最后完成变更的过程。

协同开发模式的快速笔记

在GitHub上面有两种流行的协同开发模式:

Fork & Pull

Fork & Pull模式让任何人都可以fork(复制的意思)一个已经存在的版本库并且提交变更到他们自己fork出来的版本库上,而不需要获得源版本库的访问权限。变更必须通过项目维护者才能放入源版本库。这种模式降低了给新贡献者带来的麻烦,在开源项目非常流行,因为它使人们能够独立工作而不需要前期的协调。

共享版本库模式

共享版本库模式在小团队和组织合作的私人项目中更为普遍。每个人都有push到单一共享版本库或者用来隔离变更的顶级分支的权限。

Pull requests 在 Fork & Pull 模式 中更加有用,因为它提供了一种方法去提醒项目维护者,在他的fork里面有变更. 但是,在共享版本库模式中,他们同样有用,当人们启动代码审查或者在合并到主分支之前讨论所有变更的时候

开始之前

本指南假设您有一个GitHub的帐户 ,你已经Fork了一个的现有版本,并push了您的更改。想要获得关于Fork和推送变更的帮助,请参阅文章——Forking a Repo

启动Pull Request

在下面的例子中,codercat在Fork自Octocat的Spoon-Knife版本库中完成了一些工作,在他的Fork中push了一个commit,并希望有人来审查和合并。

进入到你希望别人Pull变更的版本库中,点击Pull Request按钮。

  1. 切换到您的分支 分支选择下拉
  2. 点击Compare & review按钮 Pull Request 按钮

Pull request可以基于任何分支或者commit发送,但是推荐是从一个顶级分支。这样,在必要的时候,一些跟随的commit可以被push,去更新这个Pull Request。

审查要提交的Pull Request

开始审查之后,你将会进入一个审查页面,在这里你可以概览到所有在你的分支和源版本库的master分支之间的变动。你可以审查在所有commit中的注释,明白哪些文件发生了变动,看到所有在你的分支中的贡献者。
Pull Request 复审页

改变分支范围和目标版本库

默认情况下,Pull Request被认为是基于父版本库的默认分支 。在这种情况下, hubot/Spoon-Knife是contoctocat/Spoon-Knife中Fork出来的。所以这次的Pull Request被假定为基于octocat/Spoon-Knife版本库的master分支。

在绝大多数情况下,默认值将是正确的,但是,如果有任何信息不正确,您可以从下拉列表中更改父版本库和分支。点击顶部的Edit按钮,可以让你换你的头和底座,以及建立各种参考点之间的差别。这里引用可以包括:

  • 添加了tag的release
  • commit的SHA值
  • 分支名称
  • Git的历史标志(如HEAD^1 )
  • 有效的时间引用(如master@{1day} )

欲了解更多信息,请查看our help article on setting up comparisons
Pull Request 选择比较的分支

理解分支范围的最简单的方法是这样的: 基础分支( base branch )是你认为变更应该应用的分支,而头部分支( head branch )是你想被人应用的分支 。

改变基础版本库会导致Pull Request发生时候的通知名单发送变动。所有对基础版本库有push权限的人都会在新的Pull Request产生的时候收到邮件,并且在他们下次登录的时候能够在他们的仪表盘查看该Pull Request。

当您更改分支范围内的任何信息,commit和文件改动的预览区域将更新以显示新的范围。

发送Pull Request

当你准备好提交您的Pull Request,点击Create pull request按钮:
Pull Request 提交

你会被带到一个讨论页面,在这里你可以输入一个标题和说明(可选)。你仍然可以看到,当Pull Request发送时哪些commit将包括在内。

当你你输入好标题和描述,针对commit范围做了必要的自定义,审查了要发送的commit和修改的文件,按Send pull request按钮。
Pull Request 发送按钮

Pull Request会被立即发送。你会被带往一个Pull Request讨论和审核的主页。

在你的Pull Request发送之后,任何push到你的分支的commit会被自动更新上去。如果您需要更多的改变,这尤其有用。

管理Pull Request

所有经由你发送或者审核的Pull Request都可以通过仪表盘的pull request 查看。给自定版本库的Pull Request,对于所有能够访问对应Pull Request页面的人来说,也是可见的。
已有的Pull Request 列表

Pull Request仪表板和版本库的Pull Request列表支持多种筛选和排序控件。用它们来把列表缩小到你感兴趣的Pull Request

审查建议的更改

当您收到一个Pull Request,要做的第一件事就是审查这些建议的更改。Pull Request被紧密地与底层的git仓库集成,所以你可以看到如果Request被接受,哪些commit会被合并:
commit 审核页

您还可以了解所有提交,查看所有文件更改的累计差异。
差异审核页

讨论Pull Request

审查了基本说明,commit,和累积差异后,负责应用更改的人可能有问题或意见。也许是编码风格不匹配项目的指引,或变更缺少单元测试,或者也许一切看起来不错,所有事情都准备就绪。设计这种讨论会面,旨在鼓励和关注这种类型的讨论。
Pull Request 讨论

这种讨论会面从Pull Request的原始标题和描述开始,然后从那里按时间顺序显示额外的活动。任何以下类型的活动,当发生的时候将会被关注:

  • 在Pull Request中的评论。
  • 在Pull Request分支中新提交的commit。
  • 在Pull Request范围内的commit中的存在的文件和行注释。

Pull Request的评论是Markdown语法兼容的,所以你可以嵌入图像,使用预格式化文本块,或者其他Markdown支持的格式。

查看长时间存留的Pull Request

当一个功能或bug修正去了几个月,但从来没有通过或者丢弃,你经常想要仔细看看,是什么阻止这个Pull Request被解决。查看旧的但仍然活跃的Pull Request,可以使得这个工作简化。

您可以从您的版本库的Pull Request页面查看和排序长时间存在的Pull Request。
排序Pull Request

长期存在的Pull Request是至少已经存在了一个多月,并已在过去一个月内有可见活动的。该过滤器将按这样一个排序方式显示那些长期运行的Pull Request:从创作到最新的活动的时间。(完)