当前位置: 首页 > 科技观察

用 Babel 和 Nodemon 搭建一个功能齐全的 Node.js 开发环境

时间:2023-03-17 12:19:05 科技观察

用Babel和Nodemon搭建全功能的Node.js开发环境,但是没有系统的总结和回顾,所以为了更深入的研究和回顾我的nodejs和数据可视化之路,笔者花了两个月的时间做彻底审查。Node.js是一个事件驱动的I/O服务器端JavaScript环境。基于谷歌的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。很多朋友可能或多或少接触过nodejs。笔者首先总结了nodejs的应用领域:从上图可以看出,nodejs的应用前景还是很广阔的。前几年流行的IOT物联网技术,nodejs在该领域也有一定的贡献。因此,作为一名前端工程师(国际上称为Front-endengineer),想要在未来有更大的想象空间,node是必备技能之一。话不多说,接下来笔者将带大家一步步搭建高可用的nodejs开发环境,让大家更快更好的上手nodejs开发工作。你将学习如何配置eslint管理项目代码规范,如何使用babel7配置nodejs支持最新的es语法,如何使用nodemon实现node程序的自动重启,如何划分node目录结构,实现一个nodegeneral服务类Xoa实现经典MVC架构管理。根据笔者的工作和管理经验,衡量一个前端项目管理的好坏,往往有以下几个衡量点:还原度和功能完整性两个方面可以通过完善的测试系统进行控制,以及可扩展性。代码对于可维护性和可读性的考核,首先需要组长制定相应的代码规范和规则,最大程度保证同一个项目不同模块的一致性。比如注解规范,格式规范,目录结构和文件命名等。其次,从整体上看,如果公司有多个项目,或者多个项目会相互连接,这个时候我们要衡量和设计从整个前端架构来看,所以前端项目不仅仅是说说而已,它是对企业的长远影响。产品架构和技术架构起着非常重要的作用。因此,制定团队或项目规范可以说是项目开始阶段最关键的一步。1、配置eslint管理项目代码规范用过eslint的朋友都知道,eslint主要是一个javascript代码检测的插件工具。可以约束代码的书写格式和语法规范,比如保持代码的缩进一致,代码末尾是否有分号,使用单引号还是双引号等。我们将创建一个通过一系列的配置,实现完全一致的代码编写风格,对后期的代码管理和维护具有重要意义。说了这么多,让我们看看如何在我们的nodejs项目中使用吧。首先我们可以知道如何在eslint官网下载安装。这里我们使用全局安装:pminstalleslint--global然后我们就可以在项目中生成eslint配置文件了。具体的可选配置文件类型是排他性的。eslintrc的静态json文件,或者动态配置的eslintrc.js文件,这里笔者推荐后者,当前项目下生成配置文件的命令如下:eslint--init这样,通过命令行,我们就可以生成我们想要的eslint配置文件了。首先,作者先上传了一个简单的eslint配置文件:module.exports={"env":{"browser":true,"node":true,//启用node环境"es6":true//启用es6syntax},"extends":"eslint:recommended","globals":{"Atomics":"readonly","SharedArrayBuffer":"readonly"},"parserOptions":{"ecmaVersion":2018,"sourceType":"module"},"rules":{"semi":[2,"never"],//末尾没有分号"eqeqeq":"warn",//require===and!=="no-irregular-whitespace":"warn",//禁止不规则空白"no-empty-pattern":"warn",//禁止使用空解构模式"no-redeclare":"warn",//禁止多重声明同样的变量"quotes":["error","single"],//代码用单引号把字符串包起来"indent":["warn",2],//代码缩进2个空格"no-class-assign":"error",//禁止修改类声明的变量"no-const-assign":"error",//禁止修改变量const声明的文件}};其中规则中键的值分别代表:“off”或0——关闭规则“warn”或1——将规则视为警告(不会影响退出码)“error”或2——将规则视为错误(退出代码为1)你可以在这里使用规则使用市场上现有的规则文件或者你可以根据自己的团队风格制作自己的规则配置,eslint有一个比较全面的规则配置表:当我们配置规则的时候,我们只需要在npmscripts脚本文件中添加执行代码,eslint会自动帮我们校验代码:"scripts":{"start":"eslintsrc&&exportNODE_ENV=development&&nodemon-wsrc"}上面代码中eslintsrc表示对src目录进行eslint语法规则和格式校验。如果我们的代码不符合规范,那么在控制台中就会显示相应的错误。比如我们在代码中写双引号,运行项目时会出现如下错误:2.如何使用babel7配置nodejs支持最新的es语法大家都知道nodejs对es的支持还不够完善,虽然10.0+已经支持大部分es语法,但是最重要的模块化语法(import,export),类(class)和装饰器(Decorator)还不支持。作为一个有追求的前端工程师,为了让代码更优雅简洁,我们有理由使用最新的特性来编写更强大的代码,所以完善的es环境支持是搭建nodejs项目的第二步。没错,为了实现对es语法更全面的支持,babel是我们最好的选择。和eslint类似,写babel的时候也有几种写配置文件的方式。这里我们还是使用js的方式。这样做的好处是可以根据环境动态配置不同的编译方式。我们这里统一使用babel7来介绍如何配置es环境。如果你还在使用babel6或者更低版本,可以查看对应文档的版本进行配置。Babel7在其自己的模块中内置了许多功能。我们首先需要配置环境,即preset-env。我们可以使用@babel/preset-env。对于class和Decorator的支持,我们需要安装@babel/plugin-proposal-class-properties和@babel/plugin-proposal-decorators这两个模块。所以我们一共需要安装以下模块:@babel/cli@babel/core@babel/node@babel/plugin-proposal-class-properties@babel/plugin-proposal-decorators@babel/preset-env关于配置babel的机制在官网也写的很详细。如果你有兴趣,你可以看看。核心是环境(presets)和插件(plugin)机制。官网对preset-env的解释如下:@babel/preset-env是一个智能的自动代码转换工具,可以让我们使用最新的javascript语法。同时,官网还列出了不同配置属性对应的不同功能。为了节省空间,我们直接上传配置好的代码:module.exports=function(api){api.cache(true)constpresets=[['@babel/preset-env',{'targets':{'node':'current'}}]]constplugins=[['@babel/plugin-proposal-decorators',{'legacy':true}],['@babel/plugin-proposal-class-properties',{'loose':true}]]return{presets,plugins}}这也是官方推荐的使用方式。更灵活的配置请参考官网配置。以上两个插件的作用不言而喻。一个用于装饰器属性的编译转换,一个用于类语法的编译转换。最后一步是在package.json的脚本文件中使用我们的babel工具:"scripts":{"start":"eslintsrc&&nodemon-wsrc--exec\"babel-nodesrc\"","build":"babelsrc--out-dirdist"}babel-nodesrc指定需要编译的节点目录为src目录,其他文件和目录不需要编译。通过这样的配置,我们就可以愉快的使用最新的javascript语法开发nodejs项目了。代码写好后,我们执行npmrunbuild将src代码打包编译到dist目录下。编译后的代码如下:"usestrict";var_glob=_interopRequireDefault(require("glob"));var_path=require("path");var_xoa=_interopRequireDefault(require("./lib/xoa.js"));var_config=_interopRequireDefault(require("./config"));function_interopRequireDefault(obj){返回obj&&obj.__esModule?对象:{默认值:对象};}constapp=new_xoa.default();app.use((req,res)=>{console.log(req.url,req.method);});//全局注册业务接口//functionautoRegister(path,)_glob.default.sync((0,_path.resolve)(__dirname,'./routes/*.js')).forEach(item=>{app.use(require(item).default);});//...3.使用方法使用nodemon自动重启node程序非常简单。我们只需要按照官网文档的配置安装使用即可:npminstall--save-devnodemon然后在package.json的脚本文件中配置如下:"scripts":{"start":"eslintsrc&&exportNODE_ENV=development&&nodemon-wsrc--exec\"babel-nodesrc\"","build":"babelsrc--out-dirdist","buildR":"nodedist","test":"echo\"错误:未指定测试\"&&exit1"}nodemon-wsrc表示监听src目录下的文件变化,一旦文件发生变化,node程序会立即重启。我们也可以专门写一个nodemon的配置文件,避免监听特定的文件变化,或者其他自定义配置,如果服务在线,我们也可以使用forever和nodemon来实现持久化,当然主流的方式是pm2.4,如何划分节点目录结构,实现一个节点通用服务类xoa来实现经典MVC架构第四点是本文的核心和重点,目录划分往往考验程序员对项目和架构的理解,对于服务器的目录结构,笔者经验如下:具体目录如下:当然不同的目录还可以进一步细分,这个要看项目的规模,通过项目的结构化设计,团队不同成员可以be有序负责不同模块。这种架构模型参考了传统的MVC模型,但它仍然需要在代码层面进行进一步的控制。接下来笔者将使用原生的javascript实现一个简单的node服务层封装,以实现更方便的node开发。当然,在实际项目中,我们可以使用koa、egg等成熟的框架来开发node应用。这里笔者简单实现一个例子,让大家对节点开发有更深入的了解。我们都知道nodejs有一个http模块可以帮助我们快速创建一个node服务器。代码可能如下所示:import{createServer}from'http'createServer((req,res)=>{res.end('helloworld!')}).listen(3000)这将创建一个简单的服务器。当我们访问localhost:3000时,可以看到页面会显示helloworld!但是如果我们要实现更复杂的功能,比如根据不同的路由处理不同的逻辑,该怎么办呢?可能你会说直接在createServer的回调中根据req.url判断,代码如下:import{createServer}from'http'createServer((req,res)=>{if(req.url==='A'){//A的逻辑}elseif(req.url==='B'){//B的逻辑}elseif(req.url==='C'){//C的逻辑}//...}).listen(3000)但是一旦业务逻辑复杂,路由多了,我们就会写很多ifelse代码,对可维护性是一个很大的伤害。我们希望随着后期业务逻辑越来越复杂,页面路由不断增多,路由和业务逻辑划分,分开管理,可以更容易维护和管理。如何实现这个目标?我们可以参考koa的中间件机制。当我们要注册路由时,只需要这样写:app.use(routeA)这样是不是更优雅?所以我们根据以上需求实现了一个自己的小服务框架。代码实现如下:import{createServer}from'http'classXoa{constructor(){//初始化中间件数组this.middleware=[]}//维护中间件数组use(func){this.middleware.push(func)}//创建服务器实例并执行相应的任务createServer(){constserver=createServer((req,res)=>{//应用中间件this.middleware.forEach((fn)=>fn(req,res)))})returnserver}//服务器监听listen(port=3000,cb){this.createServer().listen(port,cb)}}exportdefaultXoa通过这样的设计,我们可以优雅的使用中间件语法:从'./lib/xoa.js'constapp=newXoa()app.use((req,res)=>{console.log(req.url,req.method)res.end('A')})app.use((req,res)=>{res.end('B')})app.listen(3000)我们再看一个场景,如果我们有很多路由,有负责的页面渲染的route也有负责输出api数据的route,所以我们每一个都要用use,这feels太傻了。作为一个有追求的程序员,这种事情是不允许发生的。我们希望这一切都是自动完成的,自动注册中间件。如何实现?幸运的是,node社区提供了强大的第三方模块glob。我们可以使用glob遍历目录来实现自动注册路由。关于glob的用法这里就不赘述了。用法非常简单。比如我们的路由文件是这样的:我们需要保证路由目录下的路由文件是导出的,那么我们可以在入口文件中实现:importglobfrom'glob'import{resolve}from'path'importXoafrom'./lib/xoa.js'importconfigfrom'./config'constapp=newXoa()//全局注册业务接口glob.sync(resolve(__dirname,'./routes/*.js')).forEach(item=>{app.use(require(item).default)})app.listen(config.serverPort,()=>{console.log(`服务器地址:${config.protocol}//${config.host}:${config.serverPort}`)})通过glob的sync方法,我们可以遍历routes目录,通过require加载路由文件,然后直接注册到app中,这样就不会需要手动一一导入,是不是很简单?(虽然这只是一个最小版本的服务端包,实际项目中还需要进一步的升级和扩展,但希望大家能从设计思路中有所收获)。对于负责的项目,我们可能还会考虑业务逻辑。我们会在服务目录下写我们的服务层代码,在路由文件中使用,可能还会用到数据库模块等,所以这些都是比较有意思的实现。笔者接下来将带大家继续做一个全栈项目,感受节点开发的魅力。