前言

最近在找工作,把自己的以前做的项目梳理一下,于是找了几个比较有代表性的项目,分别由不用文章给大家介绍一下,这篇文章讲的是MingleJS,是我个人独立开发的一个项目,针对toB中后台系统组件化开发的一个解决方案,已在公司内部技术部内部推行使用。

下面分为几个板块来详细介绍一下它

  • What? MingleJS是什么?
  • Why? 为什么要开发这样一个东西,它解决了什么样的问题?
  • How? 如何去使用它?

MingleJS 是什么?

描述:融汇WUI的思想,实现的一套开箱即用的前端框架/组件库

面向用户:后端开发工程师【,全栈开发工程师

应用场景:toB 中后台系统

特点:无需打包编译等过程,直接在html中引入minglejs就能使用

功能:其内部集成了 Antd 组件库,组件提供了简易的API,以及交互规则,模版渲染,流程控制,循环列表等

创作背景 (为什么开发?)

在上一家公司的技术部里,toB中后台系统的开发模式是前后端不分离,所有的内部后台系统的项目都是基于后端的项目中的,后端通过在模版里去调用前端的组件,配置几个参数,一个页面视图和交互就都构建好了(组件支持的相对较完善)

这套框架就是WUI,是公司自己的内部框架和组件库了,是一套以CommonJS + jQuery 实现的一套内部UI框架,组件规则里包含了交互逻辑, 入职后一直在它上面去做开发和维护,因为是以前的上古项目,维护异常痛苦,于是萌生出了把WUI重构的想法

然后就开始对框架的使用和一些组件/功能模块去进行剖析,用在了哪些系统,一些列边界情况的考虑等等,研究了好几天,因为代码做了太多的兼容处理,导致阅读起来成本巨高,我放弃了,于是准备自己重新写一套框架。保留和WUI同样的模式,让后端去使用前端的组件库,团队模式还是保持不变。

找我们老大聊之后,于是就开始了。

它解决了什么样的问题?

  • 由于团队中toB系统是前后端不分离的模式,后端MVC架构中的View这一层,需要前端去辅助实现,它提供了,开箱即用,无需打包编译的特点,后端直接在script中引入minglejs,就能在项目中去使用

  • 几乎无需编写视图代码,真正实现配置化,还有配套可视化代码生成器,能够快速构建视图

项目展示

功能介绍

  • 模版解析
    • 文本解析,属性解析, 拓展运算符 等 <{var}>
    • 事件解析 @[event] 例如:@click="handleClick"
    • 流程控制 w-if w-else
    • 循环数据 w-foreach
    • 指令 w-readonly

有时间再补充, 未完待续。。

JS手写一个路由模块

Hash 模式

原理:通过 window.onhashchange监听到页面url地址的hash值变化,改变页面内容

实现要求:浏览器端自行可以实现,不用服务器端配置

本质上就是检测 url 的变化,截获 url 地址,然后解析来匹配路由规则。

这种 #。后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。另外每次 hash 值的变化,还会触发 hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。

废话不多说直接上代码完成最小化实现

在线演示地址

html

<body>
<a href="#/page1">page1</a>
<a href="#/page2">page2</a>
<a href="#/page3">page3</a>
<a href="#/page4">page4</a>
<div class="container"></div>
</body>
</html>
<script src="./router.js"></script>
<script>
let container = document.querySelector('.container');
let router = window.__router__;
router.route('/page1', function () {
container.innerHTML = 'page1';
})
router.route('/page2', function () {
container.innerHTML = 'page2';
})
router.route('/page3', function () {
container.innerHTML = 'page3';
})
router.route('/page4', function () {
container.innerHTML = 'page4';
})
</script>

router.js

class Router {
routes = {};

constructor() {
// 监听hash值变化
window.addEventListener('hashchange', () => this._render());
window.addEventListener('load', () => this._render());
}

// 每次改变路由会执行注册路由触发的回调函数
_render() {
// location.hash "#/page1"
let hash = location.hash.slice(1) || '/';
this.routes[hash] && this.routes[hash]();
}

// 注册路由
route(path, callback) {
this.routes[path] = callback
}
}

window.__router__ = new Router();

HTML5 History 模式

原理:xxx

14年后,因为HTML5标准发布。多了两个 API,pushState 和 replaceState,通过这两个 API 可以改变 url 地址且不会发送请求。同时还有 onpopstate 事件。通过这些就能用另一种方式来实现前端路由了,但原理都是跟 hash 实现相同的。用了 HTML5 的实现,单页路由的 url 就不会多出一个#,变得更加美观。但因为没有 # 号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求。为了避免出现这种情况,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面。具体可以见:

// TODO 未完待续

Tips

最近公司在推小游戏这块的业务,所以就有了这么一个需求,要求在oppo/vivo小游戏上,接入一套SDK给研发调用,主用于收集用户数据(注册,登录,游戏在线时常上报,等一系列用户行为记录),然后需在游戏项目(用的ts)中去编写代码,于是就有了这个想法,把SDK做成一个npm模块,以后维护这个模块就好了,便于维护和管理。

网上看了很多资料,最后总结出这篇文章,在这里谢谢各位大佬

1 . 初始化NPM包

npm init -y			// 初始化package.json

2 . 安装typescript

npm install typescript -D

3 . 配置 tsconfig.json

tsc --init			// 初始化tsconfig.json

修改配置为

{
"compilerOptions": {
"target": "es5", // 指定ECMAScript目标版本
"module": "commonjs", // 指定模块化类型
"declaration": true, // 生成 `.d.ts` 文件
"outDir": "./dist", // 编译后生成的文件目录
"strict": true // 开启严格的类型检测
}
}

4 . 编写模块

这里把之前写好的SDK模块拿过来

然后在跟目录下新建 index.ts 如下:

5 .

NPM | NRM

查看那些npm包可以更新

npm -g outdated		// 加上 -g 代表全局 global

查看当前用户

npm whoami

更新npm模块

安装npm-check-updates模块

// 全局安装 npm-check-updates
npm install -g npm-check-updates

检查可更新模块

npm-check-updates
//也可以使用简写
ncu

模块更新

ncu -u && npm install
//或者
npm update 模块名

查看全局包的位置

npm root -g

发布 | 更新 npm模块

npm publish

撤回已发布的npm包

操作限制

  1. 根据规范,只有在发包的24小时内才允许撤销发布的包( unpublish is only allowed with versions published in the last 24 hours)
  2. 即使你撤销了发布的包,发包的时候也不能再和被撤销的包的名称和版本重复了(即不能名称相同,版本相同,因为这两者构成的唯一标识已经被“占用”了)
npm unpublish dalan_ui_pc
npm unpublish dalan_ui_pc --force 强制撤销

nrm 添加源

nrm add dl_npm http://npm.superdalan.com/

npm自动更新版本号

参考链接

https://blog.csdn.net/znyaiw/article/details/80199457

版本号递增规则:
- 主版本号( major ):做了不兼容修改或颠覆式的重写
- 次版本号( minor ):向下兼容的功能性新增
- 修订号( patch ):向下兼容的问题修正

npm version patch -m '修复xxxx'

背景介绍

自己在部署单页面应用(Single Page Applacation)的时候发现了一些问题

因为在很久以前 , 都是把项目打包好丢给后端or运维去部署,什么nginx配置,Jenkins配置,钩子,自动化脚本都不用管,直到有一天我在 KVM 机器 Linux上用 nginx 部署的时候,才发现一个问题…

为啥项目部署好后,一切都正常使用,但是一刷新就404了… 咋回事???为了弄懂其中原理. 于是就有了下面这篇文章 👇🏻

本文用Vue的项目进行举例 , React Angular 等其他支持 SPA 的框架同理

Vue路由模式

要讲清楚这个问题,首先得从路由模式说起

vue-router分为hashhistory模式

hash模式 (vue-router默认模式)

url的表现形式为http://blog.chrischen.top#home

这种方式有些缺点:

  • 比较难看
  • 使用location.search 获取不了query后面的参数

history模式

url表现形式为 http://blog.chrischen.top/home

history 模式 解决了hash模式上面的一些问题,同时还有其他的一些优点
这里不细讲 history模式的区别,不然跑题了

但如果要使用history模式,我们需要在服务器上进行额外配置。本文将讨论如何配置以及为什么要这样配置。

history模式的配置方法

首先要将mode设置为history

const router = new VueRouter({
mode: 'history',
routes: [...]
})

然后设置后端(这里采用的nginx):

location / {
try_files $uri $uri/ /index.html;
}

然后就…… 没了!显然官方的教程讲的比较简略,并且我们参照这个教程实际上还是会遇到一些问题。

history模式的配置实践及原理

只配置前端的情况

首先,我们将mode设置为history,但不配置后端。然后,假如我们的路由是长这个样子的:

const routes = [
{path: '/home', component: Home},
{path: '/', redirect: '/home'}
];

我们用nginx部署项目,然后在地址栏输入http://localhost:8080(这里配置的端口是8080),你会发现地址栏之后会变为http://localhost:8080/home,并且看起来一切正常,似乎路由也可以正常切换而不会发生其他问题(实际上会发生问题,后面会进行讨论)。看起来好像不需要按官网告诉我们的那样配置后端也能实现history模式,但如果你直接在地址栏输入http://localhost:8080/home,你会发现你获得了一个404页面。

那么http://localhost:8080为什么可以(部分)正常显示呢?道理其实很简单,你访问http://localhost:8080时,静态服务器(这里是nginx)会默认去目标目录(这里为locationroot所指定的目录)下寻找index.html(这是nginx在端口后没有额外路径时的默认行为),目标目录下有这个文件吗?有!然后静态服务器返回给你这个文件,配合vue-router进行转发,自然可以(部分)正常显示。
但如果直接访问http://localhost:8080/home,静态服务器会去目标目录下寻找home文件,目标目录下有这个文件吗?没有!所以自然就404了。

配置 Nginx

为了达到直接访问http://localhost:8080/home也可以成功的目的,我们需要对后端(这里即nginx)进行一些配置。

首先想想,要怎样才能达到这个目的呢?

在传统的hash模式中(http://localhost:8080#home),即使不需要配置,静态服务器始终会去寻找index.html并返回给我们,然后vue-router会获取#后面的字符作为参数,对前端页面进行变换。

类比一下,在history模式中,我们所想要的情况就是:输入http://localhost:8080/home,但最终返回的也是index.html,然后vue-router会获取home作为参数,对前端页面进行变换。那么在nginx中,谁能做到这件事呢?答案就是try_files

大意就是它会按照try_files后面的参数依次去匹配root中对应的文件或文件夹。如果匹配到的是一个文件,那么将返回这个文件;如果匹配到的是一个文件夹,那么将返回这个文件夹中index指令指定的文件。最后一个uri参数将作为前面没有匹配到的fallback。(注意try_files指令至少需要两个参数)

拿我自己的网站举个例子:

location / {
root /work/apps/blog.chrischen.top;
index index.html;
try_files $uri $uri/ /index.html;
}

$uri是nginx中的变量,比如我访问的网址是http://localhost:8080/home,那么它就代表的/home

blog.chrischen.top这个目录中,没有子目录,只有一个index.html和一些压缩后的名称是hash值的.js文件。当我们请求http://localhost:8080/home这个地址时,首先查找有无home这个文件,没有;再查找有无home目录,也没有。所以最终会定位到第三个参数从而返回index.html,按照这个规则,所有路由里的url路径最后都会定位到index.htmlvue-router再获取参数进行前端页面的变换,至此,我们已经可以通过http://localhost:8080/home这个地址进行成功地访问了。
$uri这个参数的作用其实是匹配那些.js文件用的,而$uri/在这个例子中并没有多大用,实际上是可以去掉的。

最后总结

其原理就是用nginx把项目代理的SPA的根目录,并设置如果访问到其他不存在的页面,则会自己跳转到index.html页面,等于变相的把 blog.chrischen.top/home
后面的部分不做处理,然后就可以通过前端路由匹配规则进行匹配

Nginx+Https配置

TLS或传输层安全( transport layer security),它的前身是SSL(安全套接字层secure sockets layer),是Web协议用来包裹在一个受保护,加密封装正常通道。
采用这种技术,服务器和客户端之间可以安全地进行交互,而不用担心消息将被拦截和读取。证书系统帮助用户在核实它们与连接站点的身份。

步骤1:创建SSL证书

sudo mkdir /etc/nginx/ssl
sudo openssl req -x509 -nodes -days 36500 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt

创建了有效期100年,加密强度为RSA2048的SSL密钥key和X509证书文件。

参数说明:

req: 配置参数-x509指定使用 X.509证书签名请求管理(certificate signing request (CSR)).”X.509” 是一个公钥代表that SSL and TLS adheres to for its key and certificate management.
-nodes: 告诉OpenSSL生产证书时忽略密码环节.(因为我们需要Nginx自动读取这个文件,而不是以用户交互的形式)。
-days 36500: 证书有效期,100年
-newkey rsa:2048: 同时产生一个新证书和一个新的SSL key(加密强度为RSA 2048)
-keyout:SSL输出文件名
-out:证书生成文件名
它会问一些问题。需要注意的是在common name中填入网站域名,如wiki.xby1993.net即可生成该站点的证书,同时也可以使用泛域名如*.xby1993.net来生成所有二级域名可用的网站证书。
整个问题应该如下所示:

Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:New York
Locality Name (eg, city) []:New York City
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Bouncy Castles, Inc.
Organizational Unit Name (eg, section) []:Ministry of Water Slides
Common Name (e.g. server FQDN or YOUR name) []:your_domain.com
Email Address []:admin@your_domain.com

步骤2: 修改Nginx配置

server {
listen 80;
listen 443 ssl;
ssl_certificate "/Users/chrischen/server/nginx/nginx.crt";
ssl_certificate_key "/Users/chrischen/server/nginx/nginx.key";
server_name "wui-test.local.aidalan.com";
server_name "test-wui.local.aidalan.com";

root "/Users/chrischen/dalan/wui-test.local.aidalan.com";
index index.html index.htm;

location / {
autoindex on;
}

#主要配置这个,包含 在server里面
# location ~ .*\.(php)?$ {
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# include fcgi.conf;
# }

access_log off;

default_type 'text/html';
charset utf-8;

set $ACAO '*';
add_header 'Access-Control-Allow-Origin' '$ACAO';
}

参考链接 https://segmentfault.com/a/1190000004976222

想来搞前端也快2年时间来,想起在刚开始写Javascript的时候,总是能遇见一些奇奇怪怪的问题, 和一些让人 WTF的操作,后面越来越深入的了解到这门语言后,一些令人摸不着头脑东西也就明白为什么会这样了,
这里也是记录一些好玩好用的JavaScript的片段,也算是一种输出吧


将多维数组变成一维数组

let str = [1,2,[3,4,5,6,7],[8,[10,9]]];

// 方法1
str.join().split(','); // ["1", "2", "3", "4", "5", "6", "7", "8", "10", "9"]
str.join(',').split(','); // ["1", "2", "3", "4", "5", "6", "7", "8", "10", "9"]

// 方法2 - es6
str.flat(Infinity); // ["1", "2", "3", "4", "5", "6", "7", "8", "10", "9"]

判断是否空对象

let isEmptyObject = function(obj) {
for(let k in obj) {
return false;
}
return true;
};

// 更加简单的办法
let isEmptyObj = Object.keys(obj).length === 0;

判断是否是空数组

let arr = [];
Array.isArray(arr) && arr.length !== 0;

向下取整的几种简写方式

~~2.33			// 2

2.33 | 0 // 2

2.33 >> 0 // 2

2.33 << 0 // 2

2.33 ^ 0 // 2

取随机字符串

原理就是将随机转成字符串(11 ~ 36)进制字符串(10进制以上开始出现字母),然后截取去掉小数位

let str = Math.random().toString(16).substring(2);
let str2 = Math.random().toString(32).substring(2);
console.log(str) // 02f3a8227c04b
console.log(str2) // hs9lje959eg

一行代码评级组件

用法:”★★★★★☆☆☆☆☆”.slice(5 - num, 10 - num); num的值在1 ~ 5之间

上面的方法只能实现整数,那如何实现小数评级呢?

<div class="box" data-attr="after"></div>
.box:after{ content: attr(data-attr); color: #0C68F7; }
$('.after').attr('data-attr','★★★★☆')		// 把刚出的字符串动态设置在这里

查看 css ::after (:after) 属性👉🏿:Web 开发技术CSS(层叠样式表)::after (:after)

统计字符串中相同字符出现的次数

let str = 'abcdaabc';

let result = arr.split('').reduce((p, k) => (p[k]++ || (p[k] = 1), p), {});

console.log(result); //{ a: 3, b: 2, c: 2, d: 1 }

😂立即执行函数的几种写法…..

( function() {}() );
( function() {} )();
[ function() {}() ];
~ function() {}();
! function() {}();
+ function() {}();
- function() {}();
delete function() {}();
typeof function() {}();
void function() {}();
new function() {}();
new function() {};
var f = function() {}();
1, function() {}();
1 ^ function() {}();
1 > function() {}();

DOM调试模式

[].forEach.call($$("*"),function(a){
a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)
})

1. 安装lrzsz(需要装 brewhome 🤏🏻)

brew install lrzsz

2. 系统配置

创建文件

cd /usr/local/bin

vi iterm2-recv-zmodem.sh

vi iterm2-send-zmodem.sh

创建好两个文件后分别添加内容:

iterm2-recv-zmodem.sh

#!/bin/bash
# Author: Matt Mastracci ([email protected])
# AppleScript from http://stackoverflow.com/questions/4309087/cancel-button-on-osascript-in-a-bash-script
# licensed under cc-wiki with attribution required
# Remainder of script public domain

osascript -e 'tell application "iTerm2" to version' > /dev/null 2>&1 && NAME=iTerm2 || NAME=iTerm
if [[ $NAME = "iTerm" ]]; then
FILE=`osascript -e 'tell application "iTerm" to activate' -e 'tell application "iTerm" to set thefile to choose folder with prompt "Choose a folder to place received files in"' -e "do shell script (\"echo \"&(quoted form of POSIX path of thefile as Unicode text)&\"\")"`
else
FILE=`osascript -e 'tell application "iTerm2" to activate' -e 'tell application "iTerm2" to set thefile to choose folder with prompt "Choose a folder to place received files in"' -e "do shell script (\"echo \"&(quoted form of POSIX path of thefile as Unicode text)&\"\")"`
fi

if [[ $FILE = "" ]]; then
echo Cancelled.
# Send ZModem cancel
echo -e \\x18\\x18\\x18\\x18\\x18
sleep 1
echo
echo \# Cancelled transfer
else
cd "$FILE"
/usr/local/bin/rz -E -e -b
sleep 1
echo
echo
echo \# Sent \-\> $FILE
fi

iterm2-send-zmodem.sh

#!/bin/bash
# Author: Matt Mastracci ([email protected])
# AppleScript from http://stackoverflow.com/questions/4309087/cancel-button-on-osascript-in-a-bash-script
# licensed under cc-wiki with attribution required
# Remainder of script public domain

osascript -e 'tell application "iTerm2" to version' > /dev/null 2>&1 && NAME=iTerm2 || NAME=iTerm
if [[ $NAME = "iTerm" ]]; then
FILE=`osascript -e 'tell application "iTerm" to activate' -e 'tell application "iTerm" to set thefile to choose file with prompt "Choose a file to send"' -e "do shell script (\"echo \"&(quoted form of POSIX path of thefile as Unicode text)&\"\")"`
else
FILE=`osascript -e 'tell application "iTerm2" to activate' -e 'tell application "iTerm2" to set thefile to choose file with prompt "Choose a file to send"' -e "do shell script (\"echo \"&(quoted form of POSIX path of thefile as Unicode text)&\"\")"`
fi
if [[ $FILE = "" ]]; then
echo Cancelled.
# Send ZModem cancel
echo -e \\x18\\x18\\x18\\x18\\x18
sleep 1
echo
echo \# Cancelled transfer
else
/usr/local/bin/sz "$FILE" -e -b
sleep 1
echo
echo \# Received $FILE
fi

将文件写好后保存好,使用如下命令添加权限

chmod 777 iterm2-*  	# 把以上两个文件都添加权限(这里的*代表正则匹配的规则)

iterm2 设置快捷命令

点击 iTerm2 的设置界面 Perference-> Profiles -> Default -> Advanced -> Triggers 的 Edit 按钮,加入以下配置

Regular expression Action Parameters
rz waiting to receive.**B0100 Run Silent Coprocess /usr/local/bin/iterm2-send-zmodem.sh
**B00000000000000 Run Silent Coprocess /usr/local/bin/iterm2-recv-zmodem.sh

配置好后如图

img

使用方法

rz 上传功能 :在bash中,也就是iTerm2终端输入rz 就会弹出文件选择框,选择文件 choose 就开始上传,会上传到当前目录

rz

sz 下载功能 :sz fileName(你要下载的文件的名字) 回车,会弹出窗体 我们选择要保存的地方即可

sz <filename>

微信小游戏提审发布流程

导入项目

点击上传,覆盖体验版

WX20200206-164738@2x

上传代码

WX20200206-164809@2x

等编译好代码,自动上传

WX20200206-165338@2x

上传成功

WX20200206-165638@2x WX20200206-165648@2x

登陆微信公众平台

https://mp.weixin.qq.com/

点击提交审核,然后会出来很多选择框

WX20200206-165918@2x

WX20200206-170543@2x

WX20200206-170553@2x

WX20200206-170600@2x

填写游戏内容介绍(首次提审需要)∂

一些游戏特色,玩法,介绍,游戏背景 等 图文描述

填好提交后,会显示审核中(后来发现不用通过这个审核可以直接提交代码,填写好就可以)

Lark20200206-170400

Lark20200206-170756

提交审核

点击提交审核,然后会出来很多选择框

WX20200206-170128@2x

WX20200206-170031@2x

WX20200206-170158@2x

提交成功,正在审核

Lark20200206-171725

现象

git status查看有改动但未提交的文件时总只显示数字串,显示不出中文文件名,非常不方便

git log 却能正确显示

寻找原因

了解后,由于Git在默认设置下,中文文件名在工作区状态输出,中文名不能正确显示,而是显示为八进制的字符编码

解决问题

将 Git的配置项 core.quotepath 设置为false, quotepath 表示引用路径 加上–global代表全局配置

git config --global core.quotepath false

输入命令则自动写入到 ~/.gitconfig 文件中

当然也可以直接修改 .gitconfig 进行配置等等,还可以设置alias等一些列配置

git全局配置文件 Mac 一般存放在 ~/.gitconfig

效果

这样设置完成后,一般都能看见中文了,如果还是乱码,可以看看 shell 终端 bash ,zsh 等配置 是否设置支持中文显示

0%