Node.js 规约主要包含编码风格、安全规约、最佳实践等几个部分,目的是给业务同学提供研发过程中的实质性规范和指导。其中编码风格 follow eslint-config-egg。
- 1.1【推荐】使用 Node.js 内置的全局变量。eslint: node/prefer-global
// bad
const { Buffer } = require('buffer');
const b = Buffer.alloc(16);
// good
const b = Buffer.alloc(16);
// bad
const { URL } = require('url');
const u = new URL(s);
// good
const u = new URL(s);
// bad
const { URLSearchParams } = require('url');
const u = new URLSearchParams(s);
// good
const u = new URLSearchParams(s);
// bad
const { TextEncoder } = require('util');
const u = new TextEncoder(s);
// good
const u = new TextEncoder(s);
// bad
const { TextDecoder } = require('util');
const u = new TextDecoder(s);
// good
const u = new TextDecoder(s);
// bad
const process = require('process');
process.exit(0);
// good
process.exit(0);
// bad
const console = require('console');
console.log('hello');
// good
console.log('hello');
- 1.2【推荐】使用模块内支持的
promises
API。eslint: node/prefer-promises
Node.js 从 v11.14.0 开始支持 require('dns').promises
和 require('fs').promises
API。
// bad
const dns = require('dns');
const fs = require('fs');
function lookup(hostname) {
dns.lookup(hostname, (error, address, family) => {
// ...
});
}
function readData(filePath) {
fs.readFile(filePath, 'utf8', (error, content) => {
// ...
});
}
// good
const { promises: dns } = require('dns');
const { promises: fs } = require('fs');
async function lookup(hostname) {
const { address, family } = await dns.lookup(hostname);
// ...
}
async function readData(filePath) {
const content = await fs.readFile(filePath, 'utf8');
// ...
}
- 1.3【推荐】如无特殊需求,模块引用声明放在文件顶端,注意引用顺序。eslint: import/order
如无特殊需求(如动态 require),模块引用声明需要放在文件顶端。引用顺序如无特殊需求,按以下顺序来引入依赖:node 内置模块、npm 包、本地文件或其他,几类文件代码块之间各空一行,每类文件代码块中的引用顺序按照字典排序,如有解构引用情况,字典序以解构的第一个为准,解构内部按照字典排序。
// bad
const Car = require('./models/car');
const moment = require('moment');
const mongoose = require('mongoose');
const fs = require('fs');
const http = require('http');
const { Foo, Bar } = require('tool');
const note = require('note');
// good
const fs = require('fs');
const http = require('http');
const { Bar, Foo } = require('tool');
const moment = require('moment');
const mongoose = require('mongoose');
const note = require('note');
const Car = require('./models/car');
// bad
import Car from './models/car';
import moment from 'moment';
import mongoose from 'mongoose';
import fs from 'fs';
import http from 'http';
import { Foo, Bar } from 'tool';
import note from 'note';
// good
import fs from 'fs';
import http from 'http';
import { Bar, Foo } from 'tool';
import moment from 'moment';
import mongoose from 'mongoose';
import note from 'note';
import Car from './models/car';
- 1.4【推荐】抛出异常时,使用原生
Error
对象。eslint: no-throw-literal
// bad
throw 'error';
throw 0;
throw undefined;
throw null;
const err = new Error();
throw 'an ' + err;
const err = new Error();
throw `${err}`
// good
throw new Error();
throw new Error('error');
const err = new Error('error');
throw err;
try {
throw new Error('error');
} catch (err) {
throw err;
}
1.5【推荐】线上环境尽量不要使用 fs/child_process
模块的 sync
方法,如 fs.readFileSync()
、cp.execSync()
等。
这样会阻塞 Node.js 应用的进程,导致不能继续处理新的请求,或当前正在处理的请求超时。推荐使用 require('fs').promises
方式或使用 mz。
// bad
const fs = require('fs');
function test() {
fs.readFileSync('./somefile', 'utf-8');
}
// good
const { promises: fs } = require('fs');
async function test() {
await fs.readFile('./somefile', 'utf-8');
}
// good
const fs = require('mz/fs');
async function test() {
await fs.readFile('./somefile', 'utf-8');
}
- 2.1【强制】在客户端隐藏错误详情。
错误提示有可能会暴露出敏感的系统信息,容易被利用去做进一步的攻击。
- 2.2【强制】隐藏或伪造技术栈和框架标识。
隐藏或伪造 X-Powered-By 响应头,应用广泛的框架多有公开的漏洞,防止标识露出被恶意利用。
- 2.3【强制】JSONP 跨域接口必须严格校验访问来源。
配置域名白名单,防止通过 JSONP 接口获取到敏感信息的风险。
- 2.4【强制】禁止使用从参数或明文 cookie 中获取的用户标识进行敏感信息查询输出。
防止未授权访问/越权访问。
- 2.5【强制】防止 SQL 注入。
含有用户输入内容的 SQL 语句必须使用预编译模式。若用户输入无法使用预编译模式(输入为表名/字段名等内容),需要对用户输入进行转义/过滤之后再拼接到 SQL 中。
- 2.6【推荐】定期检查过期依赖和依赖漏洞升级。
检测依赖,对于有漏洞或者过期的依赖要及时升级或替换。
- 2.7【推荐】用户上传文件不允许至服务器本地,需要上传到 OSS 等服务。
任意文件上传漏洞,防止用户上传恶意文件,入侵服务器。
- 2.8【推荐】服务端 URL 重定向需要设置白名单。
若需要对用户输入内容作为目标 URL 进行重定向,需要对其进行域名白名单校验,不允许跳转至白名单外的域名。
- 2.9【推荐】对接口入参严格校验。
使用 jsonschema 或 joi 校验入参,减少意外输入造成的程序报错或崩溃,同时也能减少脏数据形成。
- 3.1【推荐】应用不应该有状态。
使用外部数据存储。保证即使结束某个应用实例也不会影响数据和服务。
- 3.2【推荐】尽量不要用 Node.js 应用去托管前端静态文件。
应该把前端静态文件放到 CDN,当静态文件的访问量很大的时候,可能会阻塞其他服务的执行。
- 3.3【推荐】把 CPU 密集型任务委托给反向代理。
Node.js 应用不合适做 CPU 密集型任务(例如 gzip,SSL),请尽量把这类任务代理给 nginx 或其他服务。
- 3.4【推荐】使用
async/await
,尽量避免使用回调函数。
async/await
可以让你的代码看起来更简洁,可以规避掉回调地狱的问题,并且使异常处理也变得清晰简单。
- 3.5【推荐】使用
util.promisify
处理回调函数,使其返回Promise
。
const util = require('util');
const fs = require('fs');
const stat = util.promisify(fs.stat);
async function callStat() {
const stats = await stat('.');
console.log(`This directory is owned by ${stats.uid}`);
}
-
3.6【推荐】使用 Node.js 原生
Promise
,而不是三方库如bluebird
。 -
3.7【推荐】在类方法中返回
this
方便链式调用。
class Jedi {
jump() {
this.jumping = true;
return this;
}
setHeight(height) {
this.height = height;
return this;
}
}
const luke = new Jedi();
luke.jump()
.setHeight(20);
- 3.8【推荐】使用 阿里云 Node.js 性能平台 作为应用的性能监控工具。
阿里云 Node.js 性能平台提供 Node.js 应用性能监控、管理及报警,性能快照远程截取与调优, 安全与依赖更新提示,异常日志与慢 HTTP 日志等功能,能有效帮助开发者监控和排查 Node.js 应用性能问题。
- eslint-config-ali:本规约配套的 ESLint 规则包,可使用
eslint-config-ali/node
或eslint-config-ali/typescript/node
引入本文介绍的规则