赞
踩
原package.json文件中scripts字段的配置字段为:
在终端运行
npm run dev
可能会报错
‘.’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
解决方法就是修改package.json文件中dev指令:
"dev": "nodemon bin/www",
修改之后package.json文件中scripts字段的配置字段为:
能不能像vue-cli启动前端项目时在控制台(终端)输出服务器地址和启动时间:
使用console.time()
和console.timeEnd()
组合即可输出代码运行时间(计算是的他俩之间同步代码的运行时间,点这里查看使用方法)
var app = require("../app");
var debug = require("debug")("demo:server");
var http = require("http");
console.time("Time");
/**
......
此处省略好多字
*/
console.timeEnd("Time");
使用console.log()
可以输出带颜色的文字,甚至可以加上一点点小样式。点这里查看console家族的趣事。
console.log("给你点%c颜色", "color: yellow; ");
其中 %c 之后的字符就会应用color: yellow;
样式。
但是这个只是在浏览器控制台才会起效果,而node的console输出是在命令行(终端)。问题不大,可以这样写:
console.log("%s\x1B[31m%s\x1B[0m", "给你点", "颜色");
麻!拆开来看:
%s:字符串占位符,输出时会将console.log剩下的参数匹配替换进来(相当于console.log('给你点\x1B[31m颜色\x1B[0m')
)
\x1B[31m:ANSI转义码(ANSI escape code) 它将被终端拦截并指示它切换到红色。
\x1B[0m:ANSI转义码(ANSI escape code)表示重置终端颜色,使其在此之后不再继续成为所选颜色。
也可以安装colors-console插件
npm i -D colors-console
/bin/www.js:
const colors = require('colors-console')
console.log('给你点' + colors('red', '颜色'))
ANSI转义码对照表:
ANSI转义码 | colors-console插件对应参数 | 描述 |
---|---|---|
\x1B[0m | 重置样式 | |
\x1B[1m | bright | 字体颜色:亮色(应该是粗体) |
\x1B[2m | grey | 字体颜色:灰色 |
\x1B[3m | italic | 斜体 |
\x1B[4m | underline | 下划线 |
\x1B[7m | reverse | 反向 |
\x1B[8m | hidden | 隐藏 |
\x1B[30m | black | 字体颜色:黑色 |
\x1B[31m | red | 字体颜色:红色 |
\x1B[32m | green | 字体颜色:绿色 |
\x1B[33m | yellow | 字体颜色:黄色 |
\x1B[34m | blue | 字体颜色:蓝色 |
\x1B[35m | magenta | 字体颜色:品红 |
\x1B[36m | cyan | 字体颜色: 青色 |
\x1B[37m | white | 字体颜色:白色 |
\x1B[40m | blackBG | 字体背景:黑色 |
\x1B[41m | redBG | 字体背景:红色 |
\x1B[42m | greenBG | 字体背景:绿色 |
\x1B[43m | yellowBG | 字体背景:黄色 |
\x1B[44m | blueBG | 字体背景:蓝色 |
\x1B[45m | magentaBG | 字体背景:品红 |
\x1B[46m | cyanBG | 字体背景:青色 |
\x1B[47m | whiteBG | 字体背景:白色 |
如果在引入插件有灰色三个小点的提示,那不是错误,不影响运行,只是提示没找到声明文件(.d.ts)。
如果看着不得劲,在终端尝试安装其声明文件(.d.ts)
npm i --save-dev @types/colors-console
若没有找到该声明文件,则可以在根目录新建一个koa.d.ts(koa只是文件名,随便取名)。添加如下声明代码:
declare module "colors-console";
ANSI转义码(ansi escape code):
ANSI是国际标准,所以(应该是)兼容所有平台。
以下是原文摘抄,原文在这里
\x1B表示ACSII执行escape命令,也可写作\033。
原文图片
\x1B加上 [ ,他们一起组成的部分通常被称为CSI (Control Sequence Introducer)。
ansi escape code的语法:
0x1B + "[" + <params> + <fn>
因此: \x1b[0;1;34m 可以理解为 m(0, 1, 34); 同样,\x1b[A 可以理解为: A()。
<fn>可用的函数 :
函数名 | 描述 |
---|---|
A | 将光标向上移动n行 |
B | 将光标向下移动n行 |
C | 将光标向前移动n个字符 |
D | 将光标向后移动n个字符 |
E | 将光标向下移到到n行的行首 |
F | 将光标向上移到到n行的行首 |
G | 将光标移动到当前行中的第n列 |
H | 将光标移动到第m行,第n列(以左上为起点) |
J | 清除部分屏幕。0、1、2和3有各种特定的功能 |
K | Clear part of the line. 0, 1, and 2 have various specific functions |
S | 将窗口向上滚动到n行 |
T | 将窗口向下滚动到n行 |
s | 保存当前光标位置以供u函数使用 |
u | 将光标设置回s函数最后保存的位置 |
f | 和G函数一样 |
m | 设置图形模式(SGR) |
m函数参数说明:
参数值 | 描述 |
---|---|
0 | 重置:关闭所有属性 |
1 | 粗体 |
3 | 斜体 |
4 | 下划线 |
30–37 | 从0-7的基本颜色板中设置文本颜色,即前景色 |
38;5;n | 从256色调色板中将文本颜色设置为n,例如:\x1b[38;5;34m |
38;2;r;g;b | 将文本颜色设置为rgb,例如:\x1b[38;2;255;255;0m |
40–47 | 从0-7的基本颜色板中设置背景色 |
48;5;n | 从256色调色板中将背景色设置为n,例如:\x1b[48;5;34m |
48;2;r;g;b | 将背景色设置为rgb,例如:\x1b[48;2;255;255;0m |
90–97 | 从0-7的明亮调色板中设置文本颜色 |
100–107 | 从0-7的明亮调色板中设置背景色 |
本地服务器地址就是localhost(或者127.0.0.1),只有本机能够访问。
局域网服务器地址是本机的IP(IPv4),只有连接此局域网的设备可以访问。
vue-cli的Network就是用的该IP:
查看本机IP地址:
有两种方法查看-图形化界面查看和命令行查看
图形化界面查看步骤:桌面-网络(鼠标右击)-属性-已连接的网路(鼠标左击)- 详细信息
图形化界面查看的缺点是:不同版本的Window系统查看方式可能略微不同。
所以推荐命令行查看本机IP地址:
在终端(cmd)输入如下指令:
ipconfig
即可。
其中的IPv4地址即是以后node服务器的局域网地址。
获取本机的IP地址需要用到node的os模块,具体代码如下:
var os = require("os"); function getIPAdress() { var interfaces = os.networkInterfaces(); for (var devName in interfaces) { var iface = interfaces[devName]; for (var i = 0; i < iface.length; i++) { var alias = iface[i]; if ( alias.family === "IPv4" && alias.address !== "127.0.0.1" && !alias.internal ) { return alias.address; } } } }
此方法只适用本地开发使用,打包发布到服务器后会失效。
os.networkInterfaces()的返回值格式为:
{
'以太网 2': [
{
address: '',//用于指定分配的网络地址,即IPv4或IPv6
netmask: '',//指定IPv4或IPv6网络掩码
family: 'IPv6',//值为IPv4或IPv6之一
mac: '',//指定网络接口的MAC地址
internal: false,//如果接口为环回一则为true,否则为false
cidr: '',//用于指定分配的IPv4或IPv6地址以及CIDR表示法中的路由前缀。如果网络掩码无效,则将其设置为null
scopeid: 4//指定IPv6的作用域ID
}
]
}
var app = require("../app");
var http = require("http");
var port = normalizePort(process.env.PORT || "3000");
var server = http.createServer(app.callback());
server.listen(port);
normalizePort(process.env.PORT || "3000");
这段代码就是获取端口号:先从环境变量中取,没有的话,默认3000。
normalizePort()是koa-generator中引入的,他的作用就是规范化端口的值,其定义是:
/** * Normalize a port into a number, string, or false. */ function normalizePort(val) { var port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; }
server.listen(port);
用于监听端口,也可以这样写server.listen(port,host);
port是端口,host是主机ip地址。
server.address().address
返回的也是该值)的连接,否则接收基于IPv4地址(默认host为0.0.0.0,server.address().address
返回的也是该值)的连接。并且127.0.0.1也可连接。若host有指定地址,那么只该指定的host可连接。server.listen(port)
相当于server.listen(port,'::')
或server.listen(port,'0.0.0.0')
。process.env
属性返回一个包含用户环境信息的对象。
修改环境变量的方法有两种,第一种是在运行中修改:
设置或新增process.env的属性:
process.env.PORT = 3000;//设置的值会自动转成字符串'3000'
删除process.env的属性:
delete process.env.PORT;
注意:在Windows系统下,环境变量是不区分大小写的:
process.env.TEST === process.env.test;//true
第二种是在package.json中设置命令:
{
"name": "server2",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "set PORT=5252 && nodemon bin/www localhost"
}
}
这两种方式都是临时修改环境变量,当程序关闭变量也都会还原。
若要永久,需要在操作系统中设置,比如window系统:
此电脑(鼠标右击)- 属性 - 高级系统设置 - 环境变量
-process.argv
返回node启动时的命令行参数数组。
比如:
修改package.json中的start命令:
{
"scripts": {
"start": "node bin/www --param1 --param2=5152"
}
}
在/bin/www.js中打印命令行参数:
console.log(process.argv);
启动服务器
npm run start
会得到如下输出:
[
'E:\\Node\\install\\node.exe',
'F:\\test\\vue_node\\hello-node\\server2\\bin\\www',
'--param1',
'--param2=5152'
]
命令行参数约定是以 --
(两个破折号) 作为前缀。
(nodemon指令同理,因为其也是执行node指令)
解析命令行参数可以手写:
function getProcessArgv(){
const argvs = process.argv.splice(2);
const r = {};
argvs.forEach((item)=>{
const v = item.split('=');
const key = v[0].replace(/^-+/gim,"");
const value = v[1] || true;
r[key] = value;
});
return r;
}
或者安装Yargs插件
npm i yargs@6.0.0
使用方法:
//node指令: node bin/www --name=xiaoyang --age=18
var argv = require('yargs').argv;
console.log(argv.name,argv.age);//xiaoyang 18
注意!使用npm run指令时,使用参数需要加--
,这是npm传递参数给script的方法。
比如:
package.json
"scripts": {
"start": "node bin/www"
},
在终端执行:
npm run start -- --port=5152
相当于执行:
node bin/www --port=5152
如果npm run 中的参数与package.json中相应指令中的参数名一样,不会覆盖。
比如:
"scripts": {
"start": "node bin/www --port=5153"
},
在终端执行:
npm run start -- --port=5152
相当于执行:
node bin/www --port=5153 --port=5152
process.argv的值为:
[
'E:\\Node\\install\\node.exe',
'F:\\test\\vue_node\\hello-node\\server2\\bin\\www',
'--port=5153',
'--port=5152'
]
yargs插件的返回值为:
{
port: [ 5153, 5152 ]
}
实现此功能需要遍历路由目录,需要加载node的fs模块和path模块。
遍历路由目录:
const Koa = require("koa"); const app = new Koa(); var fs = require("fs"); var path = require("path"); //要遍历的文件夹所在的路径 var routesDir = path.resolve("routes/"); //根据文件路径读取文件,返回文件列表 function loadRoutes(dirPath) { fs.readdir(dirPath, { withFileTypes: true }, function (err, files) { if (err) { console.warn(err, "读取文件夹错误!"); } else { //遍历读取到的文件列表 files.forEach(function (dirent) { const currentPath = path.join(dirPath, dirent.name); if (dirent.isDirectory()) { //文件夹 loadRoutes(currentPath); } else if (dirent.isFile()) { //文件 const relativePath = path.relative(__dirname, currentPath); const router = require(relativePath); app.use(router.routes(), router.allowedMethods()); } }); } }); }
path.resolve([…paths])
该方法将路径或路径片段的序列解析为绝对路径。
给定的路径序列从右到左处理,每个后续的 path 会被追加到前面,直到构建绝对路径。
如果在处理完所有给定的 path 片段之后,还没有生成绝对路径,则使用当前工作目录(__dirname);如果没有传入 path 片段,则 path.resolve() 将返回当前工作目录的绝对路径(相当于__dirname)。
path.resolve('/foo','/bar', './baz');
// 返回: '/bar/baz'
path.resolve('/foo/bar', './baz');
// 返回: '/foo/bar/baz'
path.resolve('/foo/bar', '/tmp/file/');
// 返回: '/tmp/file'
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
// 如果当前工作目录是 F:/test/vue_node/hello-node/server2,
// 则返回 'F:/test/vue_node/hello-node/server2/wwwroot/static_files/gif/image.gif'
path.join([…paths])
使用特定于平台的分隔符作为定界符将所有给定的 path 片段连接在一起,然后返回规范化生成的路径。如果连接的路径字符串是零长度字符串,则将返回 ‘.’,表示当前工作目录。返回的路径类型与其第一个参数的路径类型一致:第一个参数为绝对路径,则返回绝对路径;第一个参数为相对路径,则返回相对路径。
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// 返回: '/foo/bar/baz/asdf'
path.join('foo', 'bar', 'baz/asdf', 'quux', '..');
// 返回: 'foo/bar/baz/asdf'
path.relative(from, to)
path.relative() 方法根据当前工作目录返回从 from 到 to 的相对路径。 如果 from 和 to 都解析为相同的路径(在分别调用 path.resolve() 之后),则返回零长度字符串。
如果零长度字符串作为 from 或 to 传入,则将使用当前工作目录而不是零长度字符串。
path.relative('C:\\orandea\\test\\aaa', 'C:\\orandea\\impl\\bbb');
// 返回: '..\\..\\impl\bbb'(Window系统返回反斜杠 \ )
__dirname
当前模块的目录名。
例如,在 F:/test/vue_node/hello-node/server2/app.js 中使用__dirname,其值则为 F:/test/vue_node/hello-node/server2
__filename
当前模块的目录名。
例如,在 F:/test/vue_node/hello-node/server2/app.js 中使用__filename,其值则为 F:/test/vue_node/hello-node/server2/app.js
fs.readdir(path[, options], callback)
读取目录的内容。
path:读取的目录路径(绝对路径或相对路径)、Buffe类或URL类:<string> | <Buffer> | <URL>。
callback:回调函数,有两个参数 (err, files),其中 files 是目录中文件或目录信息的数组(<string>[] | <Buffer>[] | <fs.Dirent>[]),默认返回的是文件或目录名称数组。
options:【可选】,<Object>
<fs.Dirent>对象
由于路由是可以分几个文件的(/routes/),因此不可避免会碰到不同路由文件有相同的路由地址,这会照成路由冲突,解决办法就是使用命名空间:以/routes/ 为根目录,路由文件的绝对路径就是该文件中所有路由的命名空间。
比如:/routes/index.js 的命名空间是 /index。/routes/login/index.js 的命名空间是 /login/index。
然后用路由的prefix()方法设置路由命名空间:
/routes/index.js:
const router = require("koa-router")();
router.prefix("/index");
//请求路径为:/index
router.get("/", async (ctx, next) => {
ctx.body = "koa2";
});
//请求路径为:/index/string
router.get("/string", async (ctx, next) => {
ctx.body = "koa2 string";
});
可以把 路由目录自动加载 的loadRoutes()方法做成插件:新建 /plugins/ 目录,用于存放插件文件。同时可以把添加命名空间的代码也移到这个插件中,而不用分散到各个路由文件中。
/plugins/loadRoutes.js
var fs = require("fs"); var path = require("path"); //要遍历的文件夹所在的路径 var routesDir = path.resolve("routes/"); //根据文件路径读取文件,返回文件列表 function loadRoutes(app, dirPath) { fs.readdir(dirPath, { withFileTypes: true }, function (err, files) { if (err) { console.warn(err, "读取文件夹错误!"); } else { //遍历读取到的文件列表 files.forEach(function (dirent) { const currentPath = path.join(dirPath, dirent.name); if (dirent.isDirectory()) { //文件夹 loadRoutes(app, currentPath); } else if (dirent.isFile()) { //文件 const relativePath = path.relative(__dirname, currentPath); const router = require(relativePath); //命名空间 const routerNamespace = path .relative(routesDir, currentPath) .replace(/\.js$/, ""); router.prefix("/" + routerNamespace); //Note: prefix always should start from / otherwise it won't work. app.use(router.routes(), router.allowedMethods()); } }); } }); } module.exports = function (app) { loadRoutes(app, routesDir); };
app.js
const load = require("./plugins/loadRoutes.js");
//加载全部路由
load(app);
一般情况node代码不需要打包,直接放到node服务器,即可运行。如果你的代码需要保护,防止别人滥用你的代码,可以考虑webpack打包,以增加别人滥改你代码的成本。
可以使用@vercel/ncc插件打包:
安装:
npm i @vercel/ncc
打包命令:
ncc build app.js -m -o dist
打包之后生成dist目录:
修改服务器入口文件(\bin\www.js)项目的引入:
将var app = require("../app");
修改为var app = require("../dist/index.js");
即可。
你可能会遇到这种情况,明明写了路由,但是在请求时却是Not Found。原因可能是:
router.get("/", async (ctx, next) => {
await ctx.render("index", {
title: "Hello Koa 2!",
});
next();
});
ctx.render会解析pug文件为html,但是我们打包的时候并没有加载Pug解析器,所以代码会报错,从而Not Found。
不用纠结,因为本项目是前后端分离,后端只负责返回数据就好:
router.get("/", async (ctx, next) => {
ctx.body = "Hello Koa 2!";
next();
});
有个小插曲:我在百度查 打包node项目 时,看到了有@vercel/ncc、@zeit/ncc、pkg (前两个是webpack打包成单文件,pkg是编译成二进制文件)。于是就挑了一个,过几天写这篇文章时,忘了安装的是哪个了,只记得是全局安装,项目中的package.json文件无迹可查,还好有npm list -g --depth 0
。
{ "name": "server2", "version": "0.1.0", "private": true, "scripts": { "start": "node bin/www", "dev": "nodemon bin/www --port=5152", "prd": "pm2 start bin/www", "build": "ncc build app.js -m -o dist", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "debug": "^4.1.1", "koa": "^2.7.0", "koa-bodyparser": "^4.2.1", "koa-convert": "^1.2.0", "koa-json": "^2.0.2", "koa-logger": "^3.2.0", "koa-onerror": "^4.1.0", "koa-router": "^7.4.0", "koa-static": "^5.0.0", "koa-views": "^6.2.0", "pug": "^2.0.3", "yargs": "^6.0.0" }, "devDependencies": { "colors-console": "^1.0.3", "nodemon": "^1.19.1" } }
var app = require("../app"); var debug = require("debug")("demo:server"); var http = require("http"); var colors = require("colors-console"); var os = require("os"); var argv = require("yargs").argv; console.time("Time"); //命令行参数:--port if (argv.port && /^\d+$/.test(argv.port)) { process.env.PORT = argv.port; } var port = normalizePort(process.env.PORT || "3000"); var server = http.createServer(app.callback()); server.listen(port); server.on("error", onError); server.on("listening", onListening); //获取本机IPv4 function getIPAdress() { var interfaces = os.networkInterfaces(); for (var devName in interfaces) { var iface = interfaces[devName]; for (var i = 0; i < iface.length; i++) { var alias = iface[i]; if ( alias.family === "IPv4" && alias.address !== "127.0.0.1" && !alias.internal ) { return alias.address; } } } } //规范化接口格式 function normalizePort(val) { var port = parseInt(val, 10); if (isNaN(port)) { return val; } if (port >= 0) { return port; } return false; } function onError(error) { if (error.syscall !== "listen") { throw error; } var bind = typeof port === "string" ? "Pipe " + port : "Port " + port; switch (error.code) { case "EACCES": console.error(bind + " requires elevated privileges"); process.exit(1); break; case "EADDRINUSE": console.error(bind + " is already in use"); process.exit(1); break; default: throw error; } } // function onListening() { console.log("Node running at:"); console.log("- Local: ", colors("cyan", `http://localhost:${port}/`)); console.log("- Network:", colors("cyan", `http://${getIPAdress()}:${port}/`)); console.timeEnd("Time"); debug("Listening on " + bind); }
declare module "colors-console";
var fs = require("fs"); var path = require("path"); const defaultOptions = { extname: [".js"], //要加载的文件扩展名,非此扩展名不加载 root: "routes/", //要遍历的路由文件的根目录 //不添加命名空间的文件或目录,相对于root的路径,比如['login/'],表示/routes/login/ 下的所有文件 prefixIgnore: ["main.js"], }; //要遍历的文件夹所在的路径 const routesDir = path.resolve(defaultOptions.root); //根据文件路径读取文件,返回文件列表 function loadRoutes(app, dirPath) { fs.readdir(dirPath, { withFileTypes: true }, function (err, files) { if (err) { console.warn(err, "读取文件夹错误!"); } else { //遍历读取到的文件列表 files.forEach(function (dirent) { const currentPath = path.join(dirPath, dirent.name); if (dirent.isDirectory()) { //文件夹 loadRoutes(app, currentPath); } else if (dirent.isFile()) { //文件 const relativePath = path.relative(__dirname, currentPath); const extname = path.extname(relativePath); //文件扩展名 if (defaultOptions.extname.includes(extname)) { const router = require(relativePath); const extnameReg = new RegExp(defaultOptions.extname.join("|")); const prefixIgnore = defaultOptions.prefixIgnore.some((s) => { const ignoeSrc = path.resolve(defaultOptions.root, s); return currentPath.indexOf(ignoeSrc) === 0; }); if (!prefixIgnore) { //命名空间 const routerNamespace = path .relative(routesDir, currentPath) .replace(extnameReg, ""); router.prefix("/" + routerNamespace); //Note: prefix always should start from / otherwise it won't work. } app.use(router.routes(), router.allowedMethods()); } } }); } }); } module.exports = function (app, opt = {}) { Object.assign(defaultOptions, opt); loadRoutes(app, routesDir); };
const Koa = require("koa"); const app = new Koa(); const json = require("koa-json"); const onerror = require("koa-onerror"); const bodyparser = require("koa-bodyparser"); const logger = require("koa-logger"); const load = require("./middleware/loadRoutes.js"); onerror(app); // middlewares app.use( bodyparser({ enableTypes: ["json", "form", "text"], }) ); app.use(json()); app.use(logger()); app.use(require("koa-static")(__dirname + "/public")); // logger app.use(async (ctx, next) => { const start = new Date(); await next(); const ms = new Date() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); }); //加载路由 load(app); // error-handling app.on("error", (err, ctx) => { console.error("server error", err, ctx); }); module.exports = app;
参考资料:
CSDN:nodejs遍历文件夹下所有文件
百度:查看电脑IP地址的CMD命令是多少?老王教你如何使用,很简单
知乎:node.js express模板初学遇到.address().address为"::"为什么?
CSDN:node.js获取计算机本地ip
CSDN:node环境实现console输出不同颜色
CSDN:ANSI转义代码(ansi escape code)
博客园:nodejs 修改端口号 process.env.PORT(window环境下)
ncc - koa 后台源码加密打包工具 @vercel/ncc - webpack node打包更正规
nodejs 几个方便的打包工具
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。