Blog

Yoki


  • 首页

  • 归档

  • 搜索

node的基本调试

发表于 2019-08-22

vscode调试

断点

无须多言

模式

两种模式

  • 一种是通过node的launch,也就是直接启动项目的入口。这种方式最为直接,但是实际上并不是很方便,因为每一次调试都需要重新启动项目。
  • 另一种是通过attach的方式,使用node-inspector进行调试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/build/app.js",
"sourceMaps": true,
"outFiles": [ // 该选项指定了map文件的寻找路径
"build"
]
},
{
"type": "node",
"request": "attach",
"name": "附加",
"port": 5858,
"timeout": 10000,
"restart": true,
"address": "localhost",
"sourceMaps": true,
"outFiles": [
"build"
]
}
]
}

参考资料

NodeJS的代码调试和性能调优

关于移动端rem适配方案

发表于 2019-08-16

基本概念

  • 物理像素(physical pixel):一个物理像素是显示器(手机屏幕)上最小的物理显示单元,pp 只与硬件设备有关。屏幕可视宽为 320 像素,非 retina 屏的 pp 是 320,retina 屏的则至少为 640
  • 设备独立像素(device pixel ratio ):dpr是个虚拟单位,在 web 世界中也称为 css 像素[逻辑像素],屏幕可视宽为 320 像素,dip 也就是 320 像素(window.devicePixelRatio )
  • 设备像素比(density-independent pixel):设备像素比 = 物理像素 / 设备独立像素 // 在某一方向上,x方向或者y方向

部分机型示例

  • iphone5/SE
    • 独立像素(CSS像素):320*568
    • 物理像素(分辨率):640*1136
    • 设备像素比(dpr):2
  • iPhone 6/7/8
    • 独立像素(CSS像素):375*678
    • 物理像素(分辨率):750*1334
    • 设备像素比(dpr):2
  • iPhone 6/7/8 Plus
    • 独立像素(CSS像素):414*736
    • 物理像素(分辨率):1920*1080(1242x2208)
    • 设备像素比(dpr):3

关系

pt和px的关系就是—— 1pt(独立像素) 里面有几个像素(物理像素)点 (比如 1pt里面有1个px,也可以有2个,3个,分别对应上图的@1x,@2x,@3x)

  • 所以为什么设计稿640px和750px要除以2,就是因为设计师给的640px和750px是物理像素, 而我们在浏览器模拟调试移动端的时候看到的像素是独立像素。
  • 可能会有这样一个疑问,1pt里面像素点越多肯定越清晰,那为什么没有看到设计师给1242px的设计稿呢?   听一个大神说是因为人用肉眼睛能分辨出的最大分辨率就是@2x即750*1334,而@3x已经超过人肉眼分辨的极限了,没必要。

适配方案

  • 禁止缩放

    1
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
  • 引用淘宝的flexible.js方案

  • 使用postcss-pxtorem在构建过程中将px转为rem

1
2
3
4
5
6
7
postcss: [require('postcss-pxtorem')({
rootValue: 37.5, // 设计稿是750px
propList: ['*'],
unitPrecision: 3, // 指定`px`转换为`rem`单位值的小数位数(很多时候无法整除)
selectorBlackList: ['.ignore ','.hairlines'], // 指定不转换为`rem`单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
minPixelValue: 2 // 设置要替换的最小像素值(大于或等于`2px`才转换为`rem`单位),你也可以设置为你想要的值
})]

其他

处理1像素边框

在rem方案中,如果没有特殊处理,1px的线条在 dpr 大于 1 的设备会比较粗。根本原因是1px可能会占用两个物理像素点或以上。
可以采用 transform: scale(0.5);来解决,在上面的selectorBlackList添加’.ui-hairline’

相关资料

  • 移动WEB的硬知识

nuxt初步

发表于 2019-08-16

介绍

SSR与CSR的相爱相杀

首先看一下传统的web开发,传统的web开发是,客户端向服务端发送请求,服务端查询数据库,拼接HTML字符串(模板),通过一系列的数据处理之后,把整理好的HTML返回给客户端,浏览器相当于打开了一个页面。这种比如我们经常听说过的jsp,PHP,aspx也就是传统的MVC的开发。

SPA应用,到了Vue、React,单页面应用优秀的用户体验,逐渐成为了主流,页面整体式javaScript渲染出来的,称之为客户端渲染CSR。SPA渲染过程。由客户端访问URL发送请求到服务端,返回HTML结构(但是SPA的返回的HTML结构是非常的小的,只有一个基本的结构)。客户端接收到返回结果之后,在客户端开始渲染HTML,渲染时执行对应javaScript,最后渲染template,渲染完成之后,再次向服务端发送数据请求,注意这里时数据请求,服务端返回json格式数据。客户端接收数据,然后完成最终渲染。

SPA虽然给服务器减轻了压力,但是也是有缺点的:

  • 首屏渲染时间比较长:必须等待JavaScript加载完毕,并且执行完毕,才能渲染出首屏。
  • SEO不友好:爬虫只能拿到一个div元素,认为页面是空的,不利于SEO。

为了解决如上两个问题,出现了SSR解决方案,后端渲染出首屏的DOM结构返回,前端拿到内容带上首屏,后续的页面操作,再用单页面路由和渲染,称之为服务端渲染(SSR)。

SSR渲染流程是这样的,客户端发送URL请求到服务端,服务端读取对应的url的模板信息,在服务端做出html和数据的渲染,渲染完成之后返回html结构,客户端这时拿到的之后首屏页面的html结构。所以用户在浏览首屏的时候速度会很快,因为客户端不需要再次发送ajax请求。并不是做了SSR我们的页面就不属于SPA应用了,它仍然是一个独立的spa应用。

SSR是处于CSR与SPA应用之间的一个折中的方案,在渲染首屏的时候在服务端做出了渲染,注意仅仅是首屏,其他页面还是需要在客户端渲染的,在服务端接收到请求之后并且渲染出首屏页面,会携带着剩余的路由信息预留给客户端去渲染其他路由的页面。

渲染流程

从服务端到客户端的一个过程,主要查看官网。

注意:vue的生命周期created和beforeCreated在客户端和服务端同时执行。

布局思想

  • layouts
  • pages
  • components

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

const config = {
server: {
host: 'localhost',
port: 16700 // default: 3000
},
mode: 'universal',
// 源码目录,分层使之有客户端和服务端
srcDir: 'client/',
// 打包目录
buildDir: 'nuxt-dist',
env: {
baseUrl: process.env.NODE_ENV === 'production' ? 'linker.sz.datastory.com.cn/m' : 'linker.sz.datastory.com.cn'
},
router: {
base: process.env.NODE_ENV === 'production' ? '/m/' : '',
middleware: 'permission'
},
/*
** Headers of the page
*/
head: {
title: '数说领客',
meta: [{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1,minimum-scale=1, maximum-scale=1' },
{ hid: 'description', name: 'description', content: process.env.npm_package_description || '' }],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
// eslint-disable-next-line array-bracket-newline
script: [
// flexible
{ src: '//g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js', type: 'text/javascript', charset: 'utf-8' },
// 接入微信js
{ src: '//res.wx.qq.com/open/js/jweixin-1.4.0.js', type: 'text/javascript', charset: 'utf-8' },
{ src: '//cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js', type: 'text/javascript', charset: 'utf-8' }
// eslint-disable-next-line array-bracket-newline
]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: ['~/assets/styles/app.less'],
/*
** Plugins to load before mounting the App
*/
plugins: [{ src: '~plugins/vant', ssr: false },
{ src: '~/plugins/proxy.ts' },
{ src: '~/plugins/dayjs.ts', ssr: true },
{ src: '~/plugins/ramda.ts', ssr: true },
{ src: '~/plugins/throttle-debounce.ts', ssr: true },
{ src: '~/plugins/echarts.ts', ssr: false }],
/*
** Nuxt.js modules
*/
// eslint-disable-next-line array-bracket-newline
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
'@nuxtjs/pwa',
'@nuxtjs/eslint-module',
'@nuxtjs/style-resources'],
styleResources: {
less: ['./assets/styles/variable.less']
},

/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {
proxy: true
},
/**
* proxy中间件
*/
proxy: {
'/mock': {
target: 'https://easy-mock.com/mock/5cc7f9cba978170c5029aaeb/linker',
changeOrigin: true,
pathRewrite: {
'^/mock': ''
}
},
'/api': {
target: 'http://linker.sz.datastory.com.cn'
// pathRewrite: {
// '^/api': '/'
// }
}
},
/*
** Build configuration
*/
build: {
analyze: true,
/*
** You can extend webpack config here
*/
extend(config, ctx) {
if (ctx.isClient) {
config.output.filename = ctx.isDev ? '[name].js' : '[name].[chunkhash].js'
config.output.chunkFilename = ctx.isDev ? '[name].js' : '[name].[chunkhash].js'
}
},
optimization: {
splitChunks: {
chunks: 'all',
automaticNameDelimiter: '.',
maxAsyncRequests: 7,
cacheGroups: {
ramda: {
test: /node_modules[\\/]ramda/,
chunks: 'all',
priority: 20,
name: true
},
echarts: {
test: /node_modules[\\/]echarts/,
chunks: 'all',
priority: 20,
name: true
}
}
},
runtimeChunk: 'single'
},
splitChunks: {
layouts: false,
pages: true,
commons: true
},
filenames: {
app: ({ isDev, isClient, isServer }) => isDev ? '[name].js' : '[name].[chunkhash].js',
chunk: ({ isDev, isClient, isServer }) => isDev ? '[name].js' : '[name].[chunkhash].js',
css: ({ isDev }) => isDev ? '[name].css' : '[contenthash].css',
img: ({ isDev }) => isDev ? '[path][name].[ext]' : 'img/[hash:7].[ext]',
font: ({ isDev }) => isDev ? '[path][name].[ext]' : 'fonts/[hash:7].[ext]',
video: ({ isDev }) => isDev ? '[path][name].[ext]' : 'videos/[hash:7].[ext]'
},
babel: {
// eslint-disable-next-line array-bracket-newline
plugins: [
// 按需导入
['component', {
libraryName: 'vant',
libraryDirectory: 'es',
style: false
}, 'vant'],
// nuxt-property-decorator
['@babel/plugin-transform-runtime'],
// nuxt-property-decorator
['@babel/plugin-proposal-decorators', { legacy: true }],
//
['@babel/plugin-proposal-class-properties', { loose: true }]

// eslint-disable-next-line array-bracket-newline
]
},
postcss: [require('postcss-pxtorem')({
rootValue: 37.5, // 设计稿是750px
propList: ['*'],
selectorBlackList: [] // 排除转换van
})],
publicPath: '/dist/client/'

}
}
module.exports = config

部署

Nginx

假设域名为a.com,该域名映射到pc端网站(vue的spa)。a.com/m映射到服务端(nuxt启动的服务,移动端网站)

  • nginx配置如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
upstream gateway_svr {
server sznode1:16800;
}


upstream h5_nuxt_ssr {
#h5移动端渲染
server 127.0.0.1:16700;
keepalive 64;
}


map $sent_http_content_type $expires {
"text/html" epoch;
"text/html; charset=utf-8" epoch;
default off;
}

server {
listen 80;
server_name linker.sz.datastory.com.cn;

client_max_body_size 10M;

# 0代表是pc端,1是移动端微信
set $wechatMobile_request 0;

if ($http_user_agent ~* MicroMessenger){
set $wechatMobile_request 1;
}

# pc端静态资源
location ~* ^/static/.* {
if ($request_uri ~ "[&\?]max_age=([0-9]+)") {
add_header "Cache-Control" "max-age=$1";
}
root /home/linker-frontend/dist;
}
# h5移动端
location ~* ^/dist/.* {
root /home/linker-h5-frontend-root/nuxt-dist;
}




location /mock/ {
root html;
proxy_connect_timeout 300s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
send_timeout 600s;
index index.html index.htm index.php index.jsp index.do default.do default.jsp default.html default.php;
proxy_next_upstream error http_503 http_504;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_pass https://easy-mock.com/mock/5cc7f9cba978170c5029aaeb/linker/;
}

location /api/ {
root html;
proxy_connect_timeout 300s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
send_timeout 600s;
index index.html index.htm index.php index.jsp index.do default.do default.jsp default.html default.php;
proxy_next_upstream error http_503 http_504;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_pass http://gateway_svr/;
}

location /m/ {
expires $expires;

proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 1m;
proxy_connect_timeout 1m;
proxy_pass http://h5_nuxt_ssr;
}



location / {
root /home/linker-frontend/dist;
index index.html index.htm index.php index.jsp index.do default.do default.jsp default.html default.php;
try_files $uri $uri/ /index.html;

}

}
  • 同时nuxt配置router的base为/m

守护进程

pm2使用入门

pm2可使进程常驻

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
apps: [{
name: 'h5-nuxt-ssr',
cwd: './',//当前工作路径
script: 'npm',// 实际启动脚本
args: ['start', '--max-memory-restart 500M'],/参数
autorestart: true,
watch: ['nuxt-dist', 'client', 'server', 'nuxt.config.ts'],// 监控变化的目录,一旦变化,自动重启
watch_delay: 1000,
ignore_watch: ['node_modules'],// 从监控目录中排除
watch_options: {
followSymlinks: false,
usePolling: true
},
error_file: './logs/app-err.log',//错误日志
out_file: './logs/app-out.log'//输出日志
}]
}

最好配置脚本在package.json里

  • “pm2-start”: “yarn build && pm2 start pm2.config.js”
  • “pm2-delete”: “pm2 delete h5-nuxt-ssr”,
  • “pm2-monit”: “pm2 monit h5-nuxt-ssr”

开机自动启动

我们希望直接通过服务器重启之后能自动启动

  • 通过pm2 save保存当前进程状态。
  • pm2 startup

使用shell脚本来自动部署项目到服务器

发表于 2019-04-30

前言

我们可以通过shell远程执行一些命令,来达到我们部署系统的目的。这样我们就可以不用sftp可视化界面来拖动,省去了这一步。

远程执行

一般我们是通过ssh来登录服务器

简单的命令

1
ssh user@remoteNode "cd /home ; ls"
  • 双引号,必须有。如果不加双引号,第二个ls命令在本地执行
  • 分号,两个命令之间用分号隔开

复杂的命令

远程执行的内容在“<< eeooff ” 至“ eeooff ”之间,在远程机器上的操作就位于其中

1
2
3
4
5
6
ssh user@remoteNode > /dev/null 2>&1 << eeooff
cd /home
touch abcdefg.txt
exit
eeooff
echo done!

需要注意以下

  • << eeooff,ssh后直到遇到eeooff这样的内容结束,eeooff可以随便修改成其他形式。
  • 重定向目的在于不显示远程的输出了
  • 在结束前,加exit退出远程节点

部署脚本

先使用tar 命令压缩文件,减少文件上传的大小,过去了服务器再解压

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 移除本地压缩文件
rm -rf dist.tar.gz

# 压缩文件,其中 dist为要上传的文件所在目录
tar -zcvf dist.tar.gz dist/

# 上传到服务器(需要输入密码,如果已经进行过私钥配置,则不用),其中/home/savoygu/gusaifei 为上传文件所在目录
scp -r dist.tar.gz root@47.93.xxx.xxx:/home/savoygu/gusaifei

# 登录到服务器(需要输入密码,如果已经进行过私钥配置,则不用)
# 服务器环境开启
ssh root@47.93.xxx.xxx << EOF

# 进入目标目录
cd /home/savoygu/gusaifei
# 解压
sudo tar -zxvf dist.tar.gz --strip-components 1
# 移除线上压缩文件
sudo rm -rf

exit
EOF
# 服务器环境结束
echo 上传完成!

前端如何搭建一个成熟的脚手架

发表于 2019-03-25

前言

有了之前的基础,我们现在可以讲讲一个成熟的脚手架是怎么做了。vue-cli作为vue的脚手架,给如此多的前端开发者使用,已经算是成熟了吧。
这里我们参考vue-cli的源码,基于rollup和typescript一步步搭建。

开始

以下我们的命令仍然是ds~,模板是ds-cli-lib-template

目录结构

1
2
3
4
5
6
7
8
9
10
11
├─ bin            # 打包文件目录
│ ├─ ds.js # package.json里的bin字段引用文件
├─ src
│ ├─ lib # 具体命令目录
│ ├─ list # ds list
│ ├─ init # ds init
│ ├─ utils # 工具函数
├─ main.ts # 入口文件
├─ typings # typescript类型文件目录
├─ rullup.config.js # rollpu构建配置
├── test # 测试用例

编写构建配置

现如今,webpack用来开发应用(热更新hmr,代码拆分等),rollup用来开发类库(简单易上手,打包后代码能读懂,至于其他的特性webpack基本已支持)。
现在来明确我们的需求

  • 使用typescript编写模块代码
  • 打包成umd模块规范的代码
  • 可以引用commonjs规范的包(因为历史原因,大多数包都不是ES模块规范)
  • 压缩打包代码,减少体积
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//rollup.config.js
import typescript from "rollup-plugin-typescript2";
import commonjs from 'rollup-plugin-commonjs'
import { uglify } from 'rollup-plugin-uglify'
export default {
//入口文件
input: "src/main.ts",
output: [
{
banner: "#!/usr/bin/env node",
/**
* 头部插入这段代码
* */
name: "ds",
file: "bin/ds.js",
//打包成umd模块规范
format: "umd"
}
],
plugins: [
typescript(),
commonjs({
include: "node_modules/**",
extensions: ['.js', '.ts']
}),
uglify()
],
};

npm脚本命令(“scripts”字段)

1
2
3
4
{
"clean": "rm -rf ./bin && mkdir bin",
"build": "npm run clean && rollup --config"
}

编写入口文件

是一些非常基础的东西,我们一般不放很复杂的逻辑在入口文件里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const cmd = require('commander');
const config = require('../package.json');

//这里cli-init.ts和cli-list.ts我们可以简单导出一个函数,如
// export default function(...args) {
// console.log('init')
// }
import init from './lib/init/cli-init';
import list from './lib/list/cli-list';

const command= {
init,
list
};

//map对应的type,从而执行
function exec(type, ...args) {
config.debug = args[0].debug;
command[type](...args);
}

cmd
.usage('<command>')
.version(config.version)
.description('欢迎使用ds-cli');

cmd
.command('init')
.description('初始化组件模板')
.action((...args) => exec('init', ...args));

cmd
.command('list')
.description('查看线上组件模板')
.action((...args) => exec('list', ...args));

cmd.command('help')
.description('查看帮助')
.action(() => cmd.help());

// 解析输入的参数
cmd.parse(process.argv);
if (!cmd.args.length) {

cmd.help();
}

我们打包到bin文件夹下后,配置一下package.json的bin字段为bin/ds.js,然后发布npm试一下命令。如果失败,请重新审视上述流程。

初始化模板

我们对模板的要求

  • 文件目录必须含有template文件夹,并且所需模板文件放在该目录下
  • 文件名命名规范是ds-cli-‘name’-template,方便脚手架拉取
  • 可用meta.js提高自定义程度(所谓动态化模板)

期望命令

ds init

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if(当前目录下构建){
询问一下是不是当前目录,是的话进入run函数
}else{
进入主流程run函数
}
//run函数
function run(){
if(模板路径是本地文件路径){
//支持本地模板如ds init /usr/webpack test
if(路径存在){
//动态构造模板到你的目录如test
generate()
}else{
//报错日志
}
}else{
//1.检查当前process的node版本,大于6才可以用
//2.检查当前package.json的版本,跟远程仓库的版本比较一下。如果不一样,就提醒一下用户有新版本
//3.下载远程仓库到本地(本地一般存放用户目录里的.ds-template文件夹下),然后执行generate函数
}
}

动态模板

大家也看到了,其实最重要的就是generate函数~

  • 而如果我们去掉这一步generate模板的话,其实就是相当于下载一个静态模板,如果我们对于用户自定义没要求的话,其实可以跳过这一步。
  • generate函数里面用到了metalsmith,这个就相当于我们之前用的gulp,通过不断地编写中间件来优化打包后的结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function generate(){
    const opts = getOptions(name, templatePath) as meta; // 获取meta.js配置,存到opts里
    // 我们把所需文件放在源文件的template目录下,其他一些如测试放在外面。初始化一下metalsmith
    const metalsmith = Metalsmith(path.join(templatePath, 'template')) //我们约定,将模板所有文件放在ds-cli-lib-template/template里
    //中间件
    metalsmith.use(askQuestions(opts.prompts)) // 询问问题,将信息存metalsmith.metadata()
    .use(filterFiles(opts.filters)) // 通过问题交互过滤掉不需要的文件
    .use(renderTemplateFiles()); // 模板里面可以使用handlebar语法,作为占位符,我们这里重新渲染模板文件

    // 源目录打包到目标目录to
    metalsmith.clean(false)
    .source('.')
    .destination(to)
    .build((err, files) => {
    done(err);
    });
    }
  • 我们在模板(如ds-cli-lib-template)目录下需要构造meta.js,自定义我们所需的字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    module.exports={
    //会通过中间件把这些字段存在metalsmith.metadata(),方便接下来的中间件调用
    prompts:{
    //格式可以参考https://github.com/SBoudrias/Inquirer.js/#question
    name: {
    type: 'string',
    required: true,
    message: 'Project name',
    },
    author: {
    type: 'string',
    message: 'Author',
    },
    description: {
    type: 'string',
    required: false,
    message: 'Project description',
    default: '构建一个lib',
    },
    lint: {
    "type": "confirm",
    "message": "是否用tslint"
    },
    },

    filters: {
    //当上面prompts的lint为false的时候,就过滤掉文件
    "tslint.json": "lint",
    "tsconfig.json": "lint"
    }
    }

前端工程师的儒家修养

发表于 2019-02-26

技术路线怎么走

到高级阶段之后,其实已经不分前后端了。因为前后端领域的互通之处会越来越多。

  • 初级前后端
  • 中级前后端
  • 高级程序员
  • 技术专家/架构师

技术人员的能力模型

技能是看得到的部分,当你和一些资深开发人员接触时,往往能直观感受到的是技能。但是他们真正资深的原因,在于眼界、思维和心态(看不到的部分)。

技能

眼界

  • 思考职业规划
  • 抬高视角,不要过分沉迷具体技术,纷繁技术的背后是对称和统一
  • 多出去走走
  • 好奇心是技术进步的原动力。
  • 如果你是个资深程序员,去看看 Java 世界、Go 的世界、Python 的世界。

思维

对软件的思考
  • 软件的本质,是对现实的的建模
    • 历史数据+发展规律=预测未来
    • 软件的变化本质是现实世界的变化
    • 思考为什么我们能对现实世界建模?
  • 软件的挑战,来自于应对世界的变化
    • 为应对变化,我值得提前投入吗,投入多少?
理解世界
  • 抽象
    • 世界变化很快,但事物的本质是稳定的
    • 为什么要抽象,以不变应万变
  • 分层
    • 当我们听歌的时候,不会关心电是怎么来的

心态

  • 认定方向,勇敢前进
  • 大气一点,对手不在眼前,技术进步那么快

35 岁危机

中年危机

35 岁的危机来自 25 岁的迷茫

  • 尽早投资自己,多看书,多学一个技能如做饭
  • 给予时间与自由,比给钱有用
  • 给能者多一些挑战而不是能者多劳

前端如何搭建一个的简单脚手架

发表于 2019-02-18

前言

这里先简单了解一下基础知识,接下来会基于typescript和rollup参考写一个较为完整的脚手架(动态模板),以下只能拉取静态模板。

基础

脚手架发布和安装

这里假设我们的脚手架名字是ds-cli,以下都用这个名字。我们在命令行使用脚手架命令为ds

  • 我们怎么样让用户通过npm或者yarn全局安装ds-cli之后,在终端能执行ds命令呢?答案就在package.json的bin字段
  • 当我们bin字段指向我们的目标文件main.js,同时目标文件开头具有

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //这里是main.js
    #!/usr/bin/env node

    // --这种用法是为了防止操作系统用户没有将node装在默认的/usr/bin路径里。当系统看到这一行的时候,
    // 首先会到env设置里查找node的安装路径,再调用对应路径下的解释器程序完成操作。
    //这里是package.json
    "bin": {
    "ds": "./main.js"
    }
  • 这样当我们发布上npm,别人下载下来后,就可以直接使用ds命令了。

解析命令行

  • 如何轻松便捷的读取命令行里的参数呢?这里我们使用tj大神的commander
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const cmd = require("commander");

    // 比如我们想执行ds init **的命令,想出现“初始化组件模板”的描述
    // action是执行这个命令后续的回调,...args是后面**的参数
    cmd
    .command('init')
    .description('初始化组件模板')
    .action((...args) => {});

    //解析命令行
    cmd.parse(process.argv);

用户交互

我们通过询问用户来获得一定的交互,这样可以知道用户需要什么

  • 采用inquirer询问项目描述,作者
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //比如我们在上面那个action里面搞事情,即ds init之后问用户
    ...action((...args)=>{
    inquirer
    .prompt([
    {
    name: "description",
    message: "请输入项目描述"
    },
    {
    name: "author",
    message: "请输入作者名称"
    }
    ]).then(answers=>{
    //在这里获得上面的答案
    console.log(answers.description,answers.author)
    })
    })

拉取远程的仓库

  • download-git-repo可拉取远程仓库
1
2
3
4
5
6
7
8
9
10
const download = require("download-git-repo");
// 第一个git地址,第二个name是git clone下来后的名字...
download(
"https://github.com/yokiyokiyoki/vue-fullpage.git#master",
name,
{ clone: true },
err => {
...
}
);

简单的模板替换

  • 我们通过询问交互后,肯定内部做了些改变。这里我们可以把package.json的作者和描述简单改了
  • 可以使用handbars,模板语法简单

    1
    2
    3
    4
    5
    //这个是通过download-git-repo拉下来的package.json
    {
    "author":"{{author}}",
    "description":"{{description}}"
    }
  • 然后我们通过读取文件字符串给handbars编译一下拿到的变量,再写入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //通过询问拿到的answers
    const meta = {
    name,
    description: answers.description,
    author: answers.author
    };
    const fileName = `${name}/package.json`;
    //判断一下是否有这个文件
    if (fs.existsSync(fileName)) {
    const content = fs.readFileSync(fileName).toString();
    const result = handlebars.compile(content)(meta);
    fs.writeFileSync(fileName, result);
    }

终端的一些效果

  • 高亮终端打印出来的信息:chalk。如chalk.green(‘成功了’),chalk.red(‘失败了’)
  • 终端加载效果:ora,有个loading效果

    1
    2
    3
    const spinner = ora("正在下载模板...");
    spinner.start();
    spinner.succeed();
  • 打印日志的特殊标志:log-symbols

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env node

const cmd = require("commander");
const download = require("download-git-repo");
const inquirer = require("inquirer");
const handlebars = require("handlebars");
const fs = require("fs");
const ora = require("ora");
const chalk = require("chalk");
const symbols = require("log-symbols");

cmd
.version("0.0.1", "-v, --version")
.command("init <name>")
.action(name => {
if (fs.existsSync(name)) {
// 错误提示项目已存在,避免覆盖原有项目
console.log(symbols.error, chalk.red("项目已存在"));
return;
}
inquirer
.prompt([
{
name: "description",
message: "请输入项目描述"
},
{
name: "author",
message: "请输入作者名称"
}
])
.then(answers => {
download(
"https://git.datatub.com:Uranus/general-template#master",
name,
{ clone: true },
err => {
const spinner = ora("正在下载模板...");
spinner.start();
if (!err) {
spinner.succeed();
const meta = {
name,
description: answers.description,
author: answers.author
};
const fileName = `${name}/package.json`;
if (fs.existsSync(fileName)) {
const content = fs.readFileSync(fileName).toString();
const result = handlebars.compile(content)(meta);
fs.writeFileSync(fileName, result);
}
console.log(symbols.success, chalk.green("项目初始化完成"));
} else {
spinner.fail();
console.log(symbols.error, chalk.red(`拉取远程仓库失败${err}`));
}
}
);
});
});
//解析命令行
cmd.parse(process.argv);
  • 上面是main.js文件,然后修改一下package.json的字段(bin字段修改,模板变量)
  • 发布npm,自己下载下来体验一下

分支规范使用(git flow)

发表于 2019-01-08

安装方式

  • mac 下,brew install git-flow-avh(该模型比较好,具有 bugfix 分支)

使用方式

  • 前期工作:需要在 wiki 上建立相关发布日志,记录每次版本
  • changelog(该记录一般为产品或项目在迭代周期开始之前指定之需求)
  • git flow init(初始化)

场景 1

产品迭代周期的开始——产品经理提出了一堆新功能(可以说一个功能【jira】对应一个 feature 分支),新建 feature 分支到完成发布的生命周期如下

1.git flow feature start 180827-radar109(新建分支 feature/180827-radar109 并切换)

2.git flow feature publish 180827-radar109 (将该版本推到远程仓库,便于小伙伴获取)

3.git flow feature finish 180827-radar109(完成该功能分支,并删除。同时切回 develop 分支)

4.发布测试平台交由提测,假设测试通过,直接跳到 7。测试不通过有 bug,跳到 5。

5.git flow bugfix start 180827-radar109(新建分支 bugfix/180827-radar109 并切换,也许测试提出了新的 jira 来修改,这里不一定是 109)

6.与 2-3 步骤类似

7.每天中午自己的分支拉一遍 develop 分支(防止自己的 feature/bugfix 分支不是最新)

8.产品经理提出的所有新功能,小伙伴全部完成之后。跟产品经理确认这个周期不再有新的功能,同时他在灰度测试平台确认可以发布线上,该周期基本结束。

9.git flow release start 产品版本(从 develop 分支新建 release/产品版本)

10.git flow release publish 产品版本(发布到远程仓库,便于大家进行小修小改,无需进行 bugfix 分支,这是因为之前 4 步骤针对每个 feature 已经提测,这里理论上只是小修改如文案不到位之类)

11.release 版本发布正式平台,一天时间缓冲进行 UAT 测试,若无问题第二天进行第 11 步骤,若有问题直接针对该 release 分支修改

12.git flow release finish 产品版本(完成 release 分支,并合并到 develop 和 master 分支,同时使用该版本名字打 tag)

13.在 wiki 某角落,写下该周期之发布记录。同时使用 release 脚本运行记录。

14.发布平台使用 master 分支,发布所有线上机器。

场景 2

产品经理或者客户提出线上有 bug,需要立刻修改的作法如下

  • 需要注意,hotfix 大多是紧急情况,测试人员有可能不到位,这里更多依赖自测,然后直接发布 master

    1.git flow hotfix start 版本名字-专有名字(可能有多个 hotfix 给多个小伙伴修改,这里会从 master 切出分支 hotfix/版本名字-专有名字)

    2.git flow hotfix publish 版本名字-专有名字(push 到远程仓库,万一小伙伴突然有事或者电脑挂了,其他小伙伴可以接力改)

    3.git flow hotfix finish 版本名字-专有名字(合并到 master 分支,并对 hotfix 版本打 tag,然后删除)

    4.发布平台使用 master 分支,发布所有线上机器。

场景 3

项目经理和客户确认可能只有两个里程碑节点,这时候迭代周期如何确定

1.内部和项目经理制定更为详细的周期节点,或使用石墨管理,或使用 wiki 管理(工具只是手段)每个周期内更细致的需求,多个周期节点的制定有利于发布线上,给项目整体以信心

2.如果两个里程碑节点发布线上(线上只会存活两次正式版本),大家认为已经足够,则无须讨论

3.具体情况具体分析,重要的是人而不是死板的步骤

场景 4

产品在迭代周期中间变更部分需求

1.视需求变更的复杂度而定,如果影响到周期结束时间,可以和产品经理商量修改结束时间,如果时间不能更改,则寻求更多的资源(研发,加班)

2.频繁变更需求则应拒绝,请产品思考清楚

分支规范说明(git flow)

发表于 2019-01-06

背景

  • 由于 git 项目到后期可能会有太多分支(小伙伴忘记删除),且小伙伴不知道哪个分支是有用的
  • 线上紧急 bug 修复不应直接修改 master 代码

git branch 功能很强大,但是没有一套模型告诉我们应该怎样在开发的时候善用这些分支。而 Git Flow 模型就是要告诉我们怎么更好地使用 Git 分支。Git Flow 模型也有相关的插件,可放心食用。

产品迭代周期

可以明确的是,产品开发迭代必须是有一个周期的。这个周期时间,可以由 pm(项目或者产品)指定,不论多短或多长,都应视为一个周期。此概念可帮助该 git-flow 模型执行。

  • 周期时间确立之初,需要把周期结束完结的所有 feature 和 bugfix 确定好(feature 和 bug 都视为一个周期内之需求)
  • 该周期一旦开始,便不能随便插队新功能 feature。新功能应当放入需求池,延续到下一次迭代周期(该项需要和每个 pm 确认,让其知悉)
  • 周期完成后,master 需要打 tag 并发布线上(方便回滚上一个稳定版本),并记录 release note,可追溯
  • 产品版本,如果没有的请开始 push 大家用起来吧

分支说明

Master 以及 Develop 这两个分支是长期分支,他们会一直存活在整个 Git Flow 里,而其它的分支大多会因任务结束(执行 finish 命令)而被自动删除,这样无需担心有冗余分支。

Release 是需要打版本号的分支。例如 1.1.2(待定)。

除上述三种分支,其他分支命名规范统一以:功能分支-年/月/日:feature/180827。

倘若有 jira 链接,假设是 radar(该项目)109,可以附上:feature/180827-radar109

Master 分支

  • 该分支永远是线上稳定可信赖版本
    • 产品经理说线上有问题的时候只能从这个分支切出来 hotfix 分支做热修复
    • 可以在该分支打 Tag
  • 任何人不允许在该分支直接修改并提交代码
    • 只能从别的分支合并到 master

Develop 分支

  • 是所有 feature 的基础分支
    • 产品经理说开发新功能的时候,基于该分支开始切出新的 feature 分支,而最后所有 feature 分支功能完成后,也会合并回 develop 分支

Release 分支

  • 上线前的分支
    • rc1,rc2→master
  • 不允许在该分支上开发新功能
    • 通过询问产品经理,可得知一个周期内的 develop(集齐许多 feature),准备上线了,可以进行 release 发布
    • 若产品经理需要临时上线新功能可以拒绝
    • 可以进行一些 bugfix
  • 为什么不用 develop 作为 release 分支
    • 在发布周期内,还可以在 develop 开发新功能
    • 一个团队可以忙于发布,另一个团队可以忙于开发下一个周期的新功能
  • 需要写 release note
    • 在相关 wiki 上记录版本发布内容

Hotfix 分支

  • 线上出现故障的时候,紧急修复的分支
    • 产品经理说线上某个问题必须修一下,那么从 Master 分支开一个 Hotfix 分支出來进行修复,Hotfix 分支修复完成之后,会合并回 Master 分支,也同时会合并一份到 Develop 分支。

Bugfix 分支

  • 线上出现故障的时候,但是并不需要立刻修复
  • 新功能开发完毕,合到 develop 分支之后发现有 bug

Feature 分支

  • 功能分支
    • 从 develop 来,最终完成后会合并回 develop

commit规范

发表于 2018-10-19

背景

团队如果采用 Angular 的提交规范,有方便的插件,可放心食用。

  • 对 git commit -m “不知道提交些什么”——这样的 commit 说拒绝。
  • 生成易读的 commit,可以方便回退版本以及 code-review。

使用说明

第一种方式(并非自定义,使用 ng 团队)

无法定义自己的模块/组件(scopes),比如项目具体业务的页面

  • 确保全局安装或者该项目已经有了 commitizen ,命令:npm i -g commitizen / npm i -D commitizen
  • 查看 package.json,看看是否有 config 选项,并且里面 commitizen 选项,如果有则跳过第三个步骤。
  • 安装 adapter 配置 commit message,例如,要使用 Angular 的 commit message 格式,可以安装 cz-conventional-changelog,执行commitizen init cz-conventional-changelog --save --save-exact
  • git commit以后提交使用git cz

第二钟方式

自定义程度较高,可以有自己想要的模块

  • 确保全局安装或者该项目已经有了 commitizen 和 cz-customizable
  • 查看 package.json,看看是否有 config 选项,并且里面 commitizen 选项,如果有则跳过第三个步骤。
  • 该项目下手动新建.cz-config.js文件,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
"use strict";
module.exports = {
// 不要更改types, 只允许出现这几种
types: [
{
value: "wip",
name: "wip:大版本功能开发阶段性提交"
},
{ value: "feat", name: "feat:完整新功能提交" },
{ value: "fix", name: "fix:修复bug" },
{
value: "build",
name: "build:打包,准备发布"
},
{ value: "docs", name: "docs:只修改了文档相关的文件,例如修改README.md" },
{
value: "style",
name:
"style:代码风格、不影响代码功能的更改,例如修改空格缩进,换行规范等"
},
{
value: "refactor",
name: "refactor:既不修复错误也不添加新功能的代码更改,例如重构"
},
{
value: "chore",
name:
"chore:对非业务性代码进行修改,例如包管理器,构建过程或辅助工具的变动"
}
],
// 按照项目模块, 自行配置
scopes: [{ name: "品牌监测" }, { name: "活动监测" }, { name: "kol监测" }],
// 可以根据匹配的类型不同, 显示不一样的scope,
scopeOverrides: {
fix: [{ name: "merge" }],
chore: []
},
allowCustomScopes: true,
//重要的改动要声明
allowBreakingChanges: ["feat", "fix"]
};
  • 在 package.json 下面添加
1
2
3
4
5
6
7
{
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
}
}
  • git commit以后提交使用git cz

git hook 检查

鉴于 vscode 有很方便的提交 git 的图形化按钮,有些童鞋可能会一不小心就提交了自己的信息

  • git 拥有提交工作流钩子(git hooks),我们在本地用 commit-msg 钩子搞事情~

commit-msg 钩子

简介

  • 它会在用户输入提交信息之后被调用。这适合用来提醒开发者他们的提交信息不符合团队的规范。

使用

  • 为了简化使用 git hook,这里直接使用了 husky 插件(继承了 git hook 所有钩子),让 hook 使用更简单~(npm i husky -D)
使用 ng 的提交规范
  • 安装@commitlint/config-conventional和@commitlint/cli(检查)
    • 新建.commitlintrc.js,如下
1
2
3
4
5
"use strict";
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {}
};

针对自定义的 Adapter

  • 需要安装 commitlint-config-cz(自定义) 和@commitlint/cli(检查)
  • 新建commitlint.config.js,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const disabled = 0
// let warning=1
const error = 2
"use strict";
module.exports = {
extends: ["cz"],
//新建rule规则,避免提交空的message可以通过审查
//rule由name和配置数组组成,如:'name:[0, 'always', 72]',数组中第一位为level,可选0,1,2,0为disable,1为warning,2为error,第二位为应用与否,可选always|never,第三位该rule的值
//具体可查看https://commitlint.js.org/#/reference-rules
rules: {
'type-enum': [error, 'always', ['feat', 'fix', 'refactor', 'docs', 'chore', 'style', 'build']],
'type-empty': [error, 'never'],
'scope-empty': [error, 'never'],
'scope-enum': [disabled]
}
};
  • package.json 里面添加
1
2
3
4
5
"husky": {
"hooks": {
"commit-msg": "commitlint -c -e $GIT_PARAMS"
}
}

自动生成 CHANGELOG

  • 我们可以借助 standard-version(很多特性)这样的工具, 自动生成 CHANGELOG
  • npm i -S standard-version
  • package.json 里面添加
1
2
3
"script": {
"release": "standard-version"
}
  • npm run release 生成 CHANGELOG
12…9
Yoki

Yoki

云在青天水在瓶

82 日志
21 标签
© 2019 Yoki
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4