10 个 Node.js 最佳实践:来自 Node 专家的启示,由客座作者 Azat Mardan 撰写。SitePoint 特邀嘉宾文章旨在为您带来来自网络社区的著名作家和演讲者的高质量优质内容。
在我之前的文章 10 Tips to Become a Better Node Developer in 2017 中,我介绍了 10 条 Node.js 建议、技巧和技术,您可以将它们应用到您的代码中。这篇文章继续介绍 10 个最佳实践,以帮助您将 Node 技能提升到一个新的水平。这就是我们将要介绍的内容:
- 使用 npm scripts — 当您可以使用 npm scripts和 Node 更好地组织它们时,停止编写 bash 脚本。例如,
npm run build
、start
和test
。当 Node 开发人员看一个新项目时,npm scripts就像是唯一的入口。 - 使用 env 环境变量 — 使用
process.env.NODE_ENV
来设置development
, 或者production
环境。一些框架也会使用这个变量,所以请按照约定配置。 - 理解 event loop 事件循环 —
setImmediate()
不是立即执行的, 而nextTick()
也不是下一次执行. 使用setImmediate()
或者setTimeout()
将 CPU 密集型任务放到下一个事件循环周期。 - 使用功能继承 — 通过像一些最高产的 Node 贡献者所做的那样,只使用功能继承,避免陷入盲目的辩论和调试和理解原型继承或类的脑筋急转弯。
- 适当地命名 — 给出有意义的名称,作为文档。此外,请不要使用大写文件名,如果需要,请使用破折号。文件名中的大写不仅看起来很奇怪,而且会导致跨平台问题。
- 考虑不使用 JavaScript — ES6/7 是可悲的补充,它诞生于 6 年的会议,当时我们已经有了更好的 JavaScript,称为 CoffeeScript。如果您想更快地发布代码并停止浪费时间讨论 var/const/let、分号、类和其他参数,请使用它。
- 提供原生代码 — 使用转译器时,提交本机 JS 代码(构建的结果),以便您的项目可以在没有构建的情况下运行。
- 使用 gzip — 呵呵!
npm i compression -S
和健全的日志记录 - 不是太多也不是太少,具体取决于环境。npm i morgan -S
。 - 扩大规模 — 从 Node 开发的第一天开始就开始考虑集群和无状态服务。使用 pm2 或 strongloop 的集群控制。
- 缓存请求 — 通过将它们隐藏在静态文件服务器(如 nginx)和/或请求级缓存(如 Varnish 缓存和 CDN 缓存)之后,最大限度地利用 Node 服务器。
使用 npm Scripts
现在,为build、test以及最重要的启动应用程序创建 npm scripts几乎是一种标准。这是 Node 开发人员在遇到新的 Node 项目时首先考虑的地方。有些人 (1, 2, 3, 4)甚至放弃了 Grunt、Gulp 等,转而使用更底层但更可靠的 npm scripts。我完全可以理解他们的论点。考虑到 npm scripts具有 pre 和 post 钩子,您可以达到非常复杂的自动化水平:
代码语言:javascript复制"scripts": {
"preinstall": "node prepare.js",
"postintall": "node clean.js",
"build": "webpack",
"postbuild": "node index.js",
"postversion": "npm publish"
}
通常在前端开发时,您希望运行两个或多个监视进程来重新构建您的代码。例如,一个用于 webpack,另一个用于 nodemon。您可以使用 &&
执行此操作,因为第一个命令不会退出。但是,有一个更加方便的模块名为 concurrently,它可以生成多个进程并同时运行它们。
另外,在本地安装webpack、nodemon、gulp、Mocha等命令行工具,避免冲突。例如,您可以指向 ./node_modules/.bin/mocha
或将此行添加到您的 bash/zsh 配置文件(PATH!):
export PATH="./node_modules/.bin:$PATH"
使用 Env 环境变量
即使在项目的早期阶段也要使用环境变量,以确保不会泄露敏感信息,并从一开始就正确构建代码。此外,一些库和框架(我知道 Express 肯定会这样做)会引入 NODE_ENV
之类的信息来修改它们的行为。将其设置为production
。设置您的 MONGO_URI
和 API_KEY
值。您可以创建一个 shell 文件(例如 start.sh
)并将其添加到 .gitignore
:
NODE_ENV=production MONGO_URL=mongo://localhost:27017/accounts API_KEY=lolz nodemon index.js
Nodemon 还有一个配置文件,您可以在其中放置环境变量示例:
代码语言:javascript复制{
"env": {
"NODE_ENV": "production",
"MONGO_URL": "mongo://localhost:27017/accounts"
}
}
理解 Event Loop 事件循环
强大而聪明的event loop事件循环是 Node 如此快速和出色的原因,它利用了所有浪费在等待输入和输出任务完成的时间。因此,Node 擅长优化 I/O 密集型系统。
如果您需要执行 CPU 密集型操作(例如,计算、密码散列或压缩),那么除了为这些 CPU 任务生成新进程之外,您可能还想使用 setImmediate()
或setTimeout()
将任务进行延迟— 他们的回调中的代码将在下一个事件循环周期中继续。nextTick() 在同一个循环上工作,与名称相反。啊!这里要注意。
这是来自于事件循环的 Bert Belder 的图表。他清楚地知道事件循环是如何工作的!
使用功能性继承
JavaScript 支持原型继承,即对象从其他对象继承。class
运算符也被添加到 ES6 的语言中。但是,与功能继承相比,它过于复杂。大多数 Node 专家更喜欢后者的简单性。它通过简单的函数工厂模式实现,不需要使用prototype
、new
或 this
。当您更新原型(导致所有实例也发生变化)时没有隐式影响,因为在功能继承中每个对象都使用自己的方法副本。
参考一下 TJ Holowaychuk 的代码,他是 Express、Mocha、Connect、Superagent 和许多其他 Node 模块背后的高产天才。Express 使用函数继承(完整源代码):
代码语言:javascript复制exports = module.exports = createApplication;
// ...
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
app.request = { __proto__: req, app: app };
app.response = { __proto__: res, app: app };
app.init();
return app;
}
客观地说,核心 Node 模块大量使用原型继承。如果您遵循该模式,请确保您知道它是如何工作的。您可以在此处阅读有关 JavaScript 继承模式的更多信息。
适当地命名
这个很明显。好名字可以作为文档。你更喜欢哪一个?
代码语言:javascript复制const dexter = require('morgan')
// ...
app.use(dexter('dev')) // When is the next season?
当我只看 app.use()
时,我不知道 dexter
在做什么。一个不同的更有意义的名字怎么样:
const logger = require('morgan')
// ...
app.use(logger('dev')) // Aha!
同样,文件名必须正确反映内部代码的用途。如果您查看 Node 的 lib
文件夹(GitHub 链接),其中包含与平台捆绑的所有核心模块,那么您会看到文件/模块的清晰命名(即使您对所有核心模块都不是很熟悉):
events.js
fs.js
http.js
https.js
module.js
net.js
os.js
path.js
process.js
punycode.js
querystring.js
内部模块用下划线标记(_debugger.js、_http_agent.js、_http_client.js),就像代码中的方法和变量一样。这有助于警告开发人员这是一个内部接口,如果您正在使用它,那么您就只能靠自己了——如果它被重构甚至被删除,请不要抱怨。
考虑不使用 JavaScript
嗯?你刚才读对了吗?但到底是什么?是的。这是正确的。即使有了 ES6 和 ES2016/ES7 添加的两个特性,JavaScript 仍然有它的怪癖。除了 JavaScript 之外,您或您的团队只需很少的设置就可以从中受益。根据专业水平和应用程序的性质,您最好使用提供强类型的 TypeScript 或 Flow 。另一方面, Elm 或 ClojureScript是纯函数式的。CoffeeScript 是另一个经过实战考验的好选择。你也可以看看 Dart 2.0 。
如果您只需要几个宏(宏允许您构建您想要的语言),而不是一门全新的语言,那么请考虑使用 Sweet.js ,它可以做到这一点——允许您编写生成代码的代码。
如果您采用非 JavaScript 路线,请仍然包含您的编译代码,因为一些开发人员可能对您的语言理解得不够好,无法正确构建它。例如,VS Code 是最大的 TypeScript 项目之一,可能在 Angular 2 之后,并且代码使用 TypeScript 为 Node 的核心模块添加类型。在 VS Code 仓库(链接)的 vscode/src/vs/base/node/
中,您可以看到熟悉的模块名称,例如 crypto
、process
等,但带有 ts
扩展名。仓库中还有其他 ts
文件。但是,它们还包括带有原生 JavaScript 代码的 vscode/build
。
理解 Express 的中间件
Express 是一个伟大且非常成熟的框架。它的出色之处在于允许无数其他模块配置其行为。因此,您需要了解最常用的中间件,并且需要知道如何使用它。那么为什么不拿出我的笔记 my Express cheat sheet。我在那里列出了主要的中间件模块。例如, npm i compression -S
将通过缩小响应来降低下载速度。 logger('tiny')
或 logger('common')
将分别提供更少(dev)或更多(prod)日志。
纵向扩展
Node 非常擅长异步,因为它的非阻塞 I/O 并且它使这种异步编码方式保持简单,因为只有一个线程。这是一个尽早开始扩展的机会,甚至可能使用第一行代码。有一个核心cluster
模块,它可以让你垂直扩展而不会出现太多问题。但是,更好的方法是使用 pm2 或者 StrongLoop’s cluster control 的集群控制之类的工具。
例如,这是您可以开始使用 pm2 的方式:
代码语言:javascript复制npm i -g pm2
然后您可以启动同一服务器的四个实例:
代码语言:javascript复制pm2 start server.js -i 4
对于 Docker,pm2 >2版本具有 pm2-docker
。所以你的 Dockerfile 看起来像这样:
# ...
RUN npm install pm2 -g
CMD ["pm2-docker", "app.js"]
官方的 Alpine Linux pm2 映像位于 Docker Hub 中。
缓存请求
这是一个 DevOps 最佳实践,它可以让您从 Node 实例中获得更多的性能(您可以使用 pm2 等获得多个实例,见上文)。可行的方法是让 Node 服务器执行应用程序的工作,例如发出请求、处理数据和执行业务逻辑,并将静态文件的流量卸载到另一个 Web 服务器,例如 Apache httpd 或 Nginx。同样,您可能应该使用 Docker 进行设置:
代码语言:javascript复制FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
我喜欢使用 Docker compose 让多个容器(nginx、Node、Redis、MongoDB)相互协作。例如:
代码语言:javascript复制web:
build: ./app
volumes:
- "./app:/src/app"
ports:
- "3030:3000"
links:
- "db:redis"
command: pm2-docker app/server.js
nginx:
restart: always
build: ./nginx/
ports:
- "80:80"
volumes:
- /www/public
volumes_from:
- web
links:
- web:web
db:
image: redis
总结
在这个开源软件的时代,没有理由不向公开的可信和经过测试的代码学习。您无需进入内部圈子即可进入。学习永无止境,我相信很快我们将根据我们将经历的失败和成功拥有不同的最佳实践。他们是有保证的。
最后,我想写一篇关于软件如何吞噬世界以及 JavaScript 如何吞噬软件的文章……有很多很棒的东西,比如每年的标准发布、大量的 npm 模块、工具和会议……但相反,我会谨慎地结束。
我看到越来越多的人追逐下一个新框架或语言。这是闪亮的物体综合症。他们每周学习一个新库,每个月学习一个新框架。他们强迫性地查看 Twitter、Reddit、Hacker News 和 JS Weekly。他们使用 JavaScript 世界中压倒性的活动来拖延。他们有空的公共 GitHub 历史记录。
学习新事物是好的,但不要将其与实际构建东西混淆。重要的是什么以及支付你的薪水实际上是在建造东西。停止过度工程。你不是在构建下一个 Facebook。Promise vs. generators vs. async await 对我来说是没有意义的,因为当有人在讨论中回复一个线程时,我已经写了我的回调(并且使用 CoffeeScript 比普通的 ES5/6/7 快 2 倍!)。
最后的最佳实践是使用最佳实践,最好的就是掌握基础知识。阅读源代码,尝试代码中的新事物,最重要的是自己编写大量代码。现在,在这一点上,停止阅读并发布重要的代码!
- END -