262020.2

自己写一个 livereoload 中间件

在项目中,我们经常需要一个自动刷新的功能。无论你使用 webpack-dev-server 还是其他工具,不够,市面上的工具都稍微有点复杂,会另外起一个端口,用于服务端和客户端相互通信。这在某些条件下反而不符合要求,有些情况下,我们只有一个端口可以使用,只能在当前服务基础上进行处理。我专门写了一个 express 的中间件来实现。

const chokidar = require('chokidar'); // 除了其他依赖,还需要依赖这个,用于监听文件变动

// 开启保存自动刷新功能
if (livereload) {
	app.use(createLiveReload({
		matchUrls: [/^\/some/, '/index.html'],
		renderFile: path.join(__dirname, 'www/index.html'),
		watchFiles: path.join(__dirname, 'www/**/*'),
                isReady: () => true,
	}))
}

/**
 * 创建一个 livereload 中间件
 * @param {*} options
 */
function createLiveReload(options) {
	const { matchUrls, renderFile, watchFiles, intervalTime = 1000, isReady } = options

	let latest = Date.now()
	let modified = latest

	chokidar.watch(watchFiles, {
		ignored: /(.*\.(map)$)|(\/vendor\/)/,
	}).on('all', () => {
		if (typeof isReady === 'function' && !isReady()) {
			return
		}
		modified = Date.now()
	})

	return function(req, res, next) {
		const { method, originalUrl } = req

		if (method.toLowerCase() !== 'get') {
			next()
			return
		}

		const scriptUrl = '/_live-reload.js'
		const url = URL.parse(originalUrl)
		const { pathname } = url

		if (pathname === scriptUrl) {
			if (modified > latest) {
				const content = `window.location.reload(true)`
				latest = modified
				res.end(content)
			}
			else {
				const content = `
					var currentScript = document.currentScript;
					setTimeout(function() {
						// 移除老的脚本
						currentScript.parentNode.removeChild(currentScript)
						// 插入新的脚本
						const script = document.createElement('script')
						script.src = '/_live-reload.js?v=' + Date.now()
						document.body.appendChild(script)
					}, ${intervalTime})
				`
				res.setHeader('content-type', 'application/json; charset=utf-8')
				res.end(content)
			}
		}
		else if (matchUrls.some(item => item instanceof RegExp ? item.test(pathname) : item === pathname)) {
			fs.readFile(renderFile, (err, data) => {
				if (err) {
					res.sendStatus(404);
				}
				else {
					const html = data.toString()
					const body = html.indexOf('</body>')
					const script = `<script src="${scriptUrl}"></script>`
					if (body > -1) {
						const content = html.replace('</body>', script + '</body>')
						res.end(content)
					}
					else {
						const content = html + script
						res.end(content)
					}
				}
			})
		}
		else {
			next()
		}
	}
}

实现原理,就是在前端界面不断的移除,新增脚本,脚本的内容由服务端输出,在服务端文件变化时,脚本内容为 window.location.reload(true) 从而刷新页面。

需要注意两个点:

  • 由于它会直接读取文件后立即渲染,所以,在 express 的路由列表中,要注意其顺序,放在 app.use(express.static(...)) 后面,这样可以保证静态文件都可以被访问到,放在其他所有动态路由的前面,这样能保证通过 matchUrls 匹配到的 url 能够使用 renderFile 进行渲染
  • 你可能需要根据不同的 url 渲染不同的文件,此时,你要多次调用 createLiveReload 来实现多个中间件实例,当然,这个时候你要保证 matchUrls 的顺序是正确的。

这就是简单的自刷新中间件了。

09:29:34 已有0条回复
242020.2

今天在处理 express 的 req.headers 时遇到坑,特地记录下来。在 req.query, req.cookies, req.headers 这三个对象,按理应该按照客户端传过来的数据进行处理,但是唯独 req.headers 是全小写的,无论你客户端传过来的是大写还是小写,这里接收到的都是小写。这是为什么呢?原来,这和 http 协议还有关系,http/1.0 时是大小写敏感的,也就是说 Token 和 token 是两个不同的 key,到了 http/1.1 大小写又不敏感了,这两个 key 是同一个 key,再到 http/2.0 协议要求 HEADER 的 key 必须全部小写,RFC7504,我也是醉了。因为 http/1.1 和 http/2.0 是当下最广泛的 http 协议,所以 express 索性全部用小写,以兼容后面的版本。

那么怎么解决这个问题呢?使用 req.get() 即可,req.get 是大小写不敏感,遵循 http/1.1 的规则,这样就可以避免大小写带来的问题。至于 cookies 等其他参数,则是大小写敏感的。

21:00:36 已有0条回复

MAC 软件破解版集锦

202020.2

折腾免费的CDN就是跟自己过不去

因为把淘小品搬到一个便宜的服务商,打算重新拾起免费CDN的主意。早些年,接触过加速乐、安全宝、360网站卫士等曾经火及一时的网站服务,如今只剩下百度云加速一家。而且,去看它的功能,真心觉得是从站长角度出发,做的非常好。再想想百度网盘,也是在这样的大浪淘沙之下,如今是国内网盘第一。想起小马哥在年会上曾经说过,互联网行业是长跑。

于是我屁颠屁颠去折腾了百度云加速的CDN服务,看似一切都OK了,图片也通过自己写的插件转成CDN地址了,一切顺利。接下来,我打算切https了。我所用的主机服务商提供一键安装ssl证书的功能。于是赶紧给主站安装上。www域名顺利https,开心😄

接下来就是修改CDN域名为https,百度云加速也提供了证书申请的服务。开心申请下来了。但是经过长时间的折腾,发现访问CDN的https时,一直提示证书无效。经过反反复复的折腾之后,我终于找到了症结所在。原来,由于我们的域名没有备案,所以在百度云加速,虽然一切的一切都是正确设置,但实际,它是直接解析到源站的,没有备案的域名,无法享受其服务。后来又去折腾了一下cloudflare,完全没有百度云加速好用啊,放弃。所以,我折腾那么久,全是白折腾。

最后,撤掉了它的服务,直接将CDN域名解析到主机,重新申请了一个SSL,这次申请时,包含了之前使用的CND域名,然后就顺利都用上了https,只是没有走CDN,一切都走主机流量,怕是有一天流量用完。之所以另外起一个域名提供图片访问,是为了避免主站http一次请求过多。

所以,这么多年的教训是,折腾免费的东西,都是跟自己过不去。折腾国内的东西,永远绕不过备案这一问题。

13:37:02 已有0条回复
152020.2

吹 React Hooks,垃圾!

React Hooks 中不乏一些优秀的思想,例如通过第二个参数告知 hooks 函数依赖,当依赖发生变化的时候,才会执行对应的处理。这在之前使用 reselect 中早就体验过,不是什么高深的技术。但莫名其妙的,Hooks 被吹上了天,认为它是下一代数据管理方式,我只想送一个字:呸!

我唯一觉得 Hooks 有些价值的,是官方博客中提到一点:有利于打包时,让产出物文件体积更小。这是个事实,因为压缩程序可以用 a() 替换一个命名超长的函数,比方 IwillFuckYouReactHooksFunction(),而如果这个函数名是一个 class 的方法名,则无法被优化(理论上是可以被优化的,对于私有方法,如果确保知会在当前 class 中使用,那么优化成 _a() 也完全没有问题,因为外部不可能用到嘛)。而 React.Component 的生命周期函数莫名其妙整那么长,刚入门的同学背都要背半天,就是罪魁祸首。

除此之外,Hooks 一无是处。

在一些情况下,确实让写的代码量减少了。但是你确定在撰写代码阶段节省的时间,没有在调试阶段还回来?Hooks 是一个异类,遵循其思想创建的函数叫“状态函数”,就像 Date.now() 一样,鬼知道它会返回什么结果。你自认为能控制被隐藏起来的状态,因为社区告诉你“你不需要关心状态,而是关心什么时候需要产生影响”。我就想说,你确定你不需要关心状态?摸着黑心问一问自己吧。当你需要关心一下的时候,你的状态在哪里?在 React 内部的堆栈中,你根本摸都摸不到。

而 React 是什么?基于状态的 UI 渲染引擎。社区总结的一句话是“一个状态对应一个界面”,你现在控制不了状态,然后跟我说,你可以完全掌控界面的变化。拉倒吧。

我可以不负责任的说:状态是函数的天敌!

我从来不在一个暴露的函数中使用状态(例如在函数中引用函数外的变量),虽然偶尔会在函数内创建一个私有函数使用函数内部的一个状态来加快写码,但是我很清楚,这个状态和函数都是用两下就会销毁的。React Hooks 反其道而行,又开心的说,我们拥抱函数式组建,又诡异的宣称,你可以在函数式组件里面操作和使用状态(但是你拿不到状态)。如果你不是经常去看源码,你会严重怀疑自己是不是进入了童话世界当起反派,明明理想那么美好,为啥我总是干坏事……

吹吧!下一代主流!

吹吧!最理想的 React 编程方式!

吹吧!……

11:10:27 已有0条回复
132020.2

在家办公习惯监控

112020.2

git 撤销上一个 commit

在一些时候,我们不小心提交了一个 commit,在 push 之前,我们希望将这个 commit 撤销,重新提交。这个时候,你可以使用下面这条命令:

git reset --soft HEAD^

--soft 和 --mixed 都可以使用。这样就可以撤销上一条 commit,而且我们还可以利用类似都命令撤销前几条,例如:

git reset --soft HEAD~2

表示撤销 2 条。~ 后面都数字就是撤销的 commit 数。

19:51:44 已有0条回复

linux 下以二进制的方式安装 nodejs

在一些linux环境下,安装最新的nodejs比较麻烦,因为受到linux源的影响,往往直接通过yum/apt安装的都不是想要的版本,因此,必须自己去选择对应的版本来安装。但是,nodejs官方又不会为所有的操作系统打包,而编译安装常常又会遇到依赖问题。所以在一些情况下,我们需要在linux机器上,直接下载官方构建好的产出结果来运行。

例如,我们现在需要安装v10.x版本,我们通过wget下载对应的-linux-x64.tar.gz文件,然后通过tar命令将其解压到/usr/local目录下,/usr/local 目录是用于存放各种软件程序的地方。然后将文件夹重命名为 /usr/local/node10 这样的文件夹名字。

tar zxvf node-v10.9.0-linux-x64.tar.gz // 解压文件夹
sudo mv node-v10.9.0-linux-x64 /usr/local/node10 // 移动+重命名

接下来,我们要将 node 的可执行文件目录加入到环境变量中:

## /etc/profile
export PATH=$PATH:/usr/local/node10/bin

接下来,是将路径加入到 sudo 中:

## /etc/sudoers
secure_path = ...:/usr/local/node10/bin

这样处理之后,就可以使用 sudo npm i -g 等命令了。

如果你的 sudo 需要代理,还需要:

## /etc/sudoers
Defaults env_keep += "http_proxy https_proxy no_proxy"

这样 sudo npm i 才能通过代理进行安装。

完成上面步骤之后,重启服务器(重启是为了让配置对所有用户生效,也可以不用重启,在 bashrc 中增加环境变量,并通过 source 命令使之生效)。

15:34:42 , 已有0条回复
042020.2

因为疫情,只能窝在家里,体验了小姨子的 MacBook Air 11,特别小,屏幕边框还是特别宽那种,虽然小是小了些,但是真的特别小巧,而且特别轻,这才是我理想中可以随身携带的手提电脑,和一本书的大小差不多,重量上还不及一些厚书。放在书包里可以方便办公。当初我买了一台 18 年 MacBook Pro 13 寸,现在想来,其实不值,如果追求性能,应该选 15 寸,如果追求轻薄,应该选 11 寸 air。

19:00:31 已有0条回复