入坑 Node.js 1

news/2025/2/14 3:14:14/
http://www.w3.org/2000/svg" style="display: none;">webkit-tap-highlight-color: rgba(0, 0, 0, 0);">

原文:https://blog.iyatt.com/?p=14717

前言

前面刚刚对 Spring Boot 有了个概念,再来学学 Node.js,顺便当学 JavaScript,为后面入前端做准备。

环境

Node.js 20.12.2

官方 API 文档:https://nodejs.org/docs/latest/api/
CommonJS:https://nodejs.org/api/modules.html
ECMAScript Modules:https://nodejs.org/api/modules.html

模块导入方式

分为 CommonJS(CJS)和 ECMAScript Modules(ESM)。

CJS 使用 require 导入,使用 modules.export 或 exports 导出。ESM 使用 import 导入,使用 export 导出。

CJS 在运行时加载模块,导入和导出是同步的。ESM 是静态加载,在代码解析的时候进行,导入和导出操作是异步的。

扩展名使用 .js 时默认识别为 CJS,扩展名使用 .mjs 时默认识别为 ESM。Node.js 的早期版本只支持 CJS,后面的开始支持 ESM,新项目可以直接使用 ES。

本篇实践以 ESM 进行,下面展示两种方式的对比。
下面的例子会在 8080 端口创建一个 http 服务,展示字符串“Hello World!”,可以通过浏览器访问:http://localhost:8080

CJS

js">const http = require('node:http');const hostname = '127.0.0.1';
const port = 8080;const server = http.createServer((req, res) => {res.statusCode = 200;res.setHeader('Content-Type', 'text/plain');res.end('Hello World!\n');
});server.listen(port, hostname, () => {console.log(`Server running at http://${hostname}:${port}/`);
});

https://img-blog.csdnimg.cn/img_convert/9ee923fe9ff54fe96693d1ac5a4ad64a.png" alt="file" />

ESM
导入指定模块

js">import { createServer } from 'node:http';const hostname = '127.0.0.1';
const port = 8080;const server = createServer((req, res) => {res.statusCode = 200;res.setHeader('Content-Type', 'text/plain');res.end('Hello World!\n');
});server.listen(port, hostname, () => {console.log(`Server running at http://${hostname}:${port}/`);
});

变量修饰

变量修饰有三种:var、let 和 const
const 和一般编程语言里一样,表示常量,声明时必须初始化,且不可再次赋值,具有块级作用域。如果 const 修饰的是一个对象或数组,虽然不能更换变量索引的对象或数组,但是可以修改对象属性或数组元素。
let 相当于一般编程语言里的局部变量,声明时可以初始化也可以不初始化,后期可以再次赋值,也是块级作用域,比如在大括号内声明的只能在大括号内访问。
var 与另外两种不同,如果在函数外声明,则为全局变量,整个程序中都可以访问。在函数内声明,则仅在函数内可访问。还可以多次声明,后续声明覆盖前面的声明。

同步与异步

Node.js 提供的很多函数都分为同步和异步两个版本,同步版函数通常名字多一个 Sync。
同步可以这样理解:你要泡茶,得先烧水,在烧水得过程中就在旁边等着,直到水烧开了,才倒水泡茶。
异步:同样泡茶,开始烧水,但是你不在旁边等着,跑去看电视了,等水烧好了,再回来倒水泡茶。
异步执行的时候,如果一个操作会花一些实践,那么就不会干等着,会去先执行别的任务。如果是同步就会等着完成一件再做另外一件。从性能来说,异步的性能更高,不会让计算机闲着,但是现实不是总能异步的,如果后续的操作都依赖前面的工作结果,就必须采用同步,等待完成后得到结果才能执行别的任务。应用中根据实际需要来决定使用同步还是异步。
下面用写文件来展示同步和异步

异步
从执行结果可以看到,使用异步写文件,写文件这个操作会花费“较多”时间,但是主线程不会等着它完成,而是先去执行后面的打印“hello world”,在打印这个操作完成以后,写文件的动作才完成。

js">import { writeFile } from 'node:fs';writeFile('test.txt', 'hello world', (err) =>
{if (err){console.error(err);return;}console.log('写入成功');
});console.log('hello world');

https://img-blog.csdnimg.cn/img_convert/b6fa523e117d12e20a850b4fefe95a25.png" alt="file" />

同步
同步写文件,在执行写文件的时候就会阻塞主线程,直到完成以后才能继续往下执行。

js">import { writeFileSync } from 'node:fs';try
{writeFileSync('test.txt', 'hello world');console.log('写入成功');
}
catch (err)
{console.error(err);process.exit(1);
}console.log('hello world');

https://img-blog.csdnimg.cn/img_convert/187799345b20198dc7882d573b149a24.png" alt="file" />

文件操作 fs

上面同步与异步举例使用的写文件操作,这里就略过了。

换行符

在不同的操作系统中,默认的换行符是不一样的。
Windows:\r\n(回车符+换行符)
Unix/Linux/macOS:\n(换行符),其中早期的 macOS 采用的换行符是 \r(回车符)
要保证良好的跨平台性,就不要指定某一种,但是自己写每种情况又显得多余,因为 Node.js 提供了换行符。像下面这样导入 EOL就行,这是一个换行符字符串。

js">import { EOL from 'os';

追加文件

专用文件追加函数

js">import { writeFileSync, appendFileSync, appendFile } from 'fs';
import { EOL } from 'os';try
{writeFileSync('test.txt', 'hello world' + EOL); // 写入文件appendFileSync('test.txt', 'hello Node.js' + EOL); // 同步追加文件
}
catch (err)
{console.error(err);process.exit(1);
}appendFile('test.txt', 'hello hello' + EOL, err => // 异步追加文件
{if (err){console.error(err);return;}console.log('写入成功');
});

写文件追加模式

js">import { writeFileSync } from 'fs';
import { EOL } from 'os';try
{writeFileSync('test.txt', 'hello world' + EOL); // 写入文件writeFileSync('test.txt', 'hello Node.js' + EOL, { flag: 'a' }); // 追加文件console.log('写入成功');
}
catch
{console.error(err);process.exit(1);
}

流式写文件

类似一般编程语言里的打开文件操作,打开后会创建一个操作文件的句柄,通过句柄来读写文件,最后关闭句柄。

js">import { createWriteStream } from 'fs';
import { EOL } from 'os';const ws = createWriteStream('test.txt');ws.on('finish', () => // 监听写入完成事件
{console.log('写入文件成功');
});ws.on('error', (err) => // 监听写入错误事件
{console.error('写入文件失败:', err);return;
});// 写入文件
ws.write('hello' + EOL);
ws.write('world' + EOL);// 结束写入
ws.end();

读文件

js">import { readFileSync, readFile } from 'node:fs';// 同步
try
{const data = readFileSync('test.txt');console.log(data.toString());
}
catch(err)
{console.error(err);
}// 异步
readFile('test.txt', (err, data) =>
{if (err){console.error(err);process.exit(1);}console.log(data.toString());
});

流式读文件

按缓存大小读取

js">import { createReadStream } from 'node:fs';var rs = createReadStream('test.txt');rs.on('data', (data) =>
{console.log(data.toString());
});rs.on('error', (error) =>
{console.log(error);
});rs.on('end', () =>
{console.log('(读取完成)');
});

按行读取

js">import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';var rs = createReadStream('test.txt');
const rl = createInterface(
{input: rs,crlfDelay: Infinity
});rl.on('line', (line) => {console.log(line);
});rl.on('error', (error) => {console.log(error);
});rl.on('close', () => {console.log('(读取完成)');
});

复制文件

使用一个 69M 的视频文件测试

一次性复制

js">import { readFileSync, writeFileSync } from 'node:fs';try
{const data = readFileSync('test1.mp4');writeFileSync('test2.mp4', data);
}
catch(error)
{console.error(error);
}console.log(process.memoryUsage().rss / 1024 / 1024);

使用内存 106M
https://img-blog.csdnimg.cn/img_convert/8006ad2dcab993d852350ab2a4f23878.png" alt="file" />

流式复制

js">import { createReadStream, createWriteStream } from 'node:fs';const rs = createReadStream('test1.mp4');
const ws = createWriteStream('test2.mp4');rs.on('data', (chunk) => {ws.write(chunk); 
});
// 也可以使用管道
// rs.pipe(ws);rs.on('error', (err) => {console.errot(err);
});console.log(process.memoryUsage().rss / 1024 / 1024);

使用内存 36M
在读写的文件较大时,使用流式读写会比较节省内存,默认缓冲区大小为 64KB,一次性最多读入 64KB 到内存,等取出后才能继续读取。
https://img-blog.csdnimg.cn/img_convert/ac3a28086ca31876faa81782d10652f9.png" alt="file" />

其它文件操作

如重命名文件/移动文件,创建文件夹,删除文件夹,查看文件信息等等,参考文档:https://nodejs.org/api/fs.html

路径 path

在 CJS 中可以直接使用 __dirname__filename 获取文件所在目录和文件自身路径的,但是 ESM 中不可用。

CJS

js">console.log(__dirname);
console.log(__filename);

https://img-blog.csdnimg.cn/img_convert/8d3baa56251551b1d47ff2fdb3a71211.png" alt="file" />

ESM
获取目录和路径的实现参考

js">import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';const __filename = fileURLToPath(import.meta.url);
var __dirname = dirname(__filename); // 方法一:已知文件全名的情况下
var __dirname = resolve(); // 方法二:未知文件全名的情况下console.log(__dirname);
console.log(__filename);

https://img-blog.csdnimg.cn/img_convert/05dd6f4e8fcd8f39c24d1efad69baedd.png" alt="file" />

路径拼接

在不同的操作系统下路径连接符号不同,在 Windows 下是反斜杠,在 Linux 下是斜杠。通过 Node.js 的路径拼接函数就能根据所在平台进行处理,保证跨平台性。
获取操作系统路径分割符

js">import { sep } from 'node:path';console.log(sep);

https://img-blog.csdnimg.cn/img_convert/a12438a0f2934a29c9b76814b208998a.png" alt="file" />

拼接路径

js">import { resolve } from 'node:path';const path1 = resolve('D:', 'hello', 'world', 'test.txt');
console.log(path1);const path2 = resolve('hello', 'world', 'test.txt');
console.log(path2);

https://img-blog.csdnimg.cn/img_convert/983bcb3c1e55da4fd9427523e56eba39.png" alt="file" />

路径解析

js">import { parse, resolve } from 'node:path';const path = resolve('index.mjs');
const parseObject = parse(path);
console.log(parseObject);

返回结果是一个对象,包含了根目录,目录,文件名,扩展名,纯文件名
https://img-blog.csdnimg.cn/img_convert/17bc20901fc364543701ce70acb52830.png" alt="file" />

其它函数

文档:https://nodejs.org/api/path.html

http_410">Web 服务 http

web__412">简单的 web 服务器

js">import { createServer } from 'node:http';const server = createServer((req, res) =>
{res.setHeader('Content-Type', 'text/html;charset=UTF-8');res.end('你好,世界!');
})const port = 80;
server.listen(port, () =>
{console.log(`服务器运行在 http://localhost:${port}/`);
});

https://img-blog.csdnimg.cn/img_convert/75ab6055183619e62e83f51dba613e06.png" alt="file" />

获取请求

js">import { createServer } from 'node:http';
import { parse } from 'node:url';const server = createServer((req, res) =>
{console.log('-'.repeat(100));console.log('请求 URL:' + req.url);console.log('请求方法:' + req.method);console.log('http 版本:' + req.httpVersion);console.log('请求头:' + JSON.stringify(req.headers));console.log(parse(req.url, true));console.log('-'.repeat(100));// 回复客户端res.setHeader('Content-Type', 'text/html;charset=UTF-8');res.end('你好,世界!');
});const port = 80;
server.listen(port, () =>
{console.log(`服务器运行在 http://localhost:${port}/`);
});

访问:http://localhost/submit?s1=123&s2=abc
https://img-blog.csdnimg.cn/img_convert/da0442d0a5835f7b592b37fdf6f53e53.png" alt="file" />
服务器端获取
https://img-blog.csdnimg.cn/img_convert/d3d63daa443b71e642030f68875b6ef5.png" alt="file" />

另外一种解析方式

js">import { createServer } from 'node:http';const server = createServer((req, res) =>
{console.log('-'.repeat(100));console.log('请求 URL:' + req.url);console.log('请求方法:' + req.method);console.log('http 版本:' + req.httpVersion);console.log('请求头:' + JSON.stringify(req.headers));let url = new URL(req.url, `http://${req.headers.host}`);console.log('pathname: ' + url.pathname);console.log('search: ' + url.search);console.log('searchParams: ' + url.searchParams);console.log(url.searchParams.get('s1') + ' ' + url.searchParams.get('s2'));console.log('-'.repeat(100));// 回复客户端res.setHeader('Content-Type', 'text/html;charset=UTF-8');res.end('你好,世界!');
})const port = 80;
server.listen(port, () =>
{console.log(`服务器运行在 http://localhost:${port}/`);
});

https://img-blog.csdnimg.cn/img_convert/1db2710a8a11516dabed8567f7b96e46.png" alt="file" />

应用

请求

js">import { createServer } from 'node:http';const server = createServer((req, res) => 
{let { method } = req;let { pathname } = new URL(req.url, `http://${req.headers.host}`);res.setHeader('Content-Type', 'text/html; charset=utf-8');console.log(method, pathname);if (method === 'GET' && pathname === '/login'){res.end('登录页面');}else if (method === 'GET' && pathname === '/register'){res.end('注册页面');}else{res.statusCode = 404;res.end('Not Found');}
});const port = 80;
server.listen(port, () =>
{console.log(`服务器运行在 http://localhost:${port}/`);
});

https://img-blog.csdnimg.cn/img_convert/92512f73027ba61588198e8d459a31bd.png" alt="file" />
https://img-blog.csdnimg.cn/img_convert/8744f0b63a8d039d889c1721394e28f9.png" alt="file" />
https://img-blog.csdnimg.cn/img_convert/56cc72f43be52735eabb183f8dbc24b1.png" alt="file" />

响应

加载 html 文件作为响应内容

index.mjs

js">import { createServer } from 'node:http';
import { readFileSync } from 'node:fs';const server = createServer((req, res) => 
{let data = readFileSync('index.html');res.setHeader('Content-Type', 'text/html; charset=utf-8');res.end(data);
});const port = 80;
server.listen(port, () =>
{console.log(`服务器运行在 http://localhost:${port}/`);
});

index.html

<!DOCTYPE html>
<html lang="zh"><head><meta charset="UTF-8"><title>表格</title><style>td{padding: 20px 40px;}table tr:nth-child(odd){background-color: #f11212;}table tr:nth-child(even){background-color: #5b0af1;}table, td{border-collapse: collapse;}</style></head><body><table border="1"><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>4</td><td>5</td><td>6</td></tr><tr><td>7</td><td>8</td><td>9</td></tr><tr><td>10</td><td>11</td><td>12</td></tr></table><script>let tds = document.querySelectorAll('td');tds.forEach(item => {item.onclick = function(){item.style.backgroundColor = '#000000';}})</script></body>
</html>

点击单元格变色
https://img-blog.csdnimg.cn/img_convert/4203095ba6a48100a9a40023b90939bb.png" alt="file" />

js__603">html、css、js 拆分

index.html

<!DOCTYPE html>
<html lang="zh"><head><meta charset="UTF-8"><title>表格</title><link rel="stylesheet" href="index.css"></head><body><table border="1"><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>4</td><td>5</td><td>6</td></tr><tr><td>7</td><td>8</td><td>9</td></tr><tr><td>10</td><td>11</td><td>12</td></tr></table><script src="index.js"></script></body>
</html>

index.css

td{padding: 20px 40px;
}
table tr:nth-child(odd){background-color: #f11212;
}
table tr:nth-child(even){background-color: #5b0af1;
}
table, td{border-collapse: collapse;
}

index.js

js">let tds = document.querySelectorAll('td');
tds.forEach(item => {item.onclick = function(){item.style.backgroundColor = '#000000';}
})

main.mjs

js">import { createServer } from 'node:http';
import { readFileSync } from 'node:fs';const server = createServer((req, res) => 
{var { pathname } = new URL(req.url, `http://${req.headers.host}`);if (pathname === '/'){res.setHeader('Content-Type', 'text/html; charset=utf-8');let html = readFileSync('index.html', 'utf-8');res.end(html);}else if (pathname.endsWith('.css')){res.setHeader('Content-Type', 'text/css; charset=utf-8');let css = readFileSync(pathname.slice(1), 'utf-8');res.end(css);}else if (pathname.endsWith('.js')){res.setHeader('Content-Type', 'application/javascript; charset=utf-8');let js = readFileSync(pathname.slice(1), 'utf-8');res.end(js);}else{res.statusCode = 404;res.end('404 Not Found');}
});const port = 80;
server.listen(port, () =>
{console.log(`服务器运行在 http://localhost:${port}/`);
});
部署静态资源站

用的我主页的源码,主页地址:https://iyatt.com
文件结构如图
https://img-blog.csdnimg.cn/img_convert/726ab74d3c454306a81a59a14af3a80c.png" alt="file" />

下面是 Node.js 代码

js">import { createServer } from 'node:http';
import { readFile } from 'node:fs';
import { extname, resolve } from 'node:path';const root = resolve('homepage'); // 网站根目录
const mimeTypes = { // 支持的文件类型和对应的MIME类型(开发中可以使用第三方模块)'.html': 'text/html; charset=utf-8','.css': 'text/css','.js': 'application/javascript','.png': 'image/png','.jpg': 'image/jpeg','.gif': 'image/gif','.ico': 'image/x-icon',
};const server = createServer((req, res) => 
{const { pathname } = new URL(req.url, `http://${req.headers.host}`);if (req.method !== 'GET') { // 只处理 GET 请求res.statusCode = 405;res.end('<h1>405 Method Not Allowed</h1>');return;}if (pathname === '/') { // 访问根目录跳转 index.htmlres.statusCode = 301;res.setHeader('Location', '/index.html');res.end();}else {const ext = extname(pathname);readFile(resolve(root, pathname.slice(1)), (err, data) => {if (err) {switch (err.code) {case 'ENOENT': { // 文件不存在res.statusCode = 404;res.end('<h1>404 Not Found</h1>');break;}case 'EPERM': { // 权限不足res.statusCode = 403;res.end('<h1>403 Forbidden</h1>');break;}default: { // 其他错误res.statusCode = 500;res.end('<h1>500 Internal Server Error</h1>');break;}}}else {if (mimeTypes[ext]) { // 设定已知的 Content-Typeres.setHeader('Content-Type', mimeTypes[ext]);}else { // 未知的产生下载行为res.setHeader('Content-Type', 'application/octet-stream');}res.end(data);}});}
});const port = 80;
server.listen(port, () =>
{console.log(`服务器运行在 http://localhost:${port}/`);
});

正常访问
https://img-blog.csdnimg.cn/img_convert/760e762f5a751582df0c4f8897ca5f07.png" alt="file" />

访问资源中的一张图片
https://img-blog.csdnimg.cn/img_convert/9d188f473bdfff03e09b9e55b058dbf7.png" alt="file" />

找不到文件
https://img-blog.csdnimg.cn/img_convert/a5c40b1c189d57a22d3239e0aebc54e9.png" alt="file" />

没有权限访问文件
https://img-blog.csdnimg.cn/img_convert/17eb31eee91ea2c736b67e34d7e939ca.png" alt="file" />

下载行为
https://img-blog.csdnimg.cn/img_convert/624a974336ba015f850b6cfee3e57e27.png" alt="file" />

模块

基于 ESM 的模块导出

导出

自定义模块实现 1
针对单个函数、变量导出,在要导出的函数和变量前加上 export

modules.mjs

js">export function testFunction1() {console.log('测试函数1');
}export function testFunction2() {console.log('测试函数2');
}export const testConstant = '这是一个常量';

自定义模块实现 2
集中导出,使用 export {} 把要导出的函数、变量放进去

modules.mjs

js">function testFunction1() {console.log('测试函数1');
}function testFunction2() {console.log('测试函数2');
}const testConstant = '这是一个常量';export { testFunction1, testFunction2, testConstant }

使用模块
index.mjs

js">export function testFunction1() {console.log('测试函数1');
}export function testFunction2() {console.log('测试函数2');
}export const testConstant = '这是一个常量';

https://img-blog.csdnimg.cn/img_convert/5ce44c378e02b5f6a21cf786b4754d8c.png" alt="file" />

别名

给要导出的内容设置别名,使用集中导出

modules.mjs

js">function testFunction1() {console.log('测试函数1');
}function testFunction2() {console.log('测试函数2');
}const testConstant = '这是一个常量';export { testFunction1 as test1, testFunction2 as test2, testConstant as test }

index.mjs

js">import { test1, test2, test } from "./modules.mjs";console.log(test);
test1();
test2();

默认导出

前面的普通导出,在导入使用的时候需要添加一个括号,而默认导出可以不用添加括号。只是在一个模块中只允许一个默认导出,使用方法在普通导出的基础上把 export 换成 export default 就行。如果是设置一个变量为默认导出不能直接在 const/var/let 前写,要额外写导出。比如

js">const testConstant = '这是一个常量';
export default testConstant;

下面将一个函数默认导出
modules.mjs

js">export function testFunction1() {console.log('测试函数1');
}export default function testFunction2() {console.log('测试函数2');
}

使用
如果一次性导入多个,默认导出的必须写在前面
index.mjs

js">import testFunction2, { testFunction1 } from "./modules.mjs";testFunction1();
testFunction2();

包管理工具

Node.js 的官方包管理工具是 npm,也有一些第三方的包管理工具,比如 yarn。
关于 npm 的官方说明:https://nodejs.org/en/learn/getting-started/an-introduction-to-the-npm-package-manager

包安装或依赖安装

安装包使用命令

npm install
# 或
npm i

安装指定包可以在命令后跟上包名,搜索包可前往:https://www.npmjs.com/
如果要全局安装就加上参数 -g,一般是命令工具采用全局安装的方式,这样不管在什么路径下都能使用,可以参考要安装的东西的文档决定什么方式安装。使用命令查看全局安装路径

npm root -g

https://img-blog.csdnimg.cn/img_convert/d46ddd009331e8b88ef91214d9cff85f.png" alt="file" />

如果不使用 -g 参数,默认安装是在当前工作路径下创建一个文件夹 node_modules,并在里面放置安装的东西。另外在工作路径下会产生一个 package-lock.json 文件,里面会记录安装的包的名字、版本、地址、校验信息。在发布自己开发的软件的时候通常不打包 node_modules 文件夹,可以极大地缩小打包体积,在用户使用这个软件的时候可以通过上面的安装命令来自动完成依赖安装,安装的时候不需要指定包名,会读取 package-lock.json 文件获取开发者使用的依赖。
站在软件开发者的角度,对于使用的依赖又分普通依赖和开发依赖,默认安装是标注为普通依赖,即使用 -S 参数,使用 -D 参数安装的则为开发依赖。开发者编写一个软件安装的普通依赖,发布出去,使用 npm i 自动安装依赖会同样安装。而开发依赖一般只是用于开发者测试使用,用户运行开发者编写的软件并不依赖,可以不需要安装,开发者使用 -D 安装这些依赖,则发布出去,用户安装依赖时就不会安装这些依赖。(下图是文档原文)
https://img-blog.csdnimg.cn/img_convert/c8a41f421f007044f894d991e526019c.png" alt="file" />

简单来说,如果开发者编写一个软件用到的某些依赖的功能是要集成到编写的软件中,这种依赖开发者就要安装为普通依赖,也可以叫做生产依赖。同时另外存在一些依赖,它们不是软件功能的组成,但是是开发者进行开发需要使用的工具或者测试框架,只是开发者需要,软件运行本身不用,开发者就要把这些依赖作为开发依赖安装。

创建一个项目

创建一个文件夹,终端工作路径切换到文件夹下,执行

npm init

默认项目名会使用文件夹的名称,但是项目名称不能用中文,如果文件夹含有中文,就自行设置英文名称,也可以直接设置其它名称
https://img-blog.csdnimg.cn/img_convert/7fade58fafef372c2c8bb31ed2b00e6e.png" alt="file" />

上面的命令就是引导创建一个 package.json 文件
https://img-blog.csdnimg.cn/img_convert/069bb8a022db7ac92f80f1cb96c101fe.png" alt="file" />

配置命令别名

我写了一个源文件 index.mjs

js">console.log('Hello, world!');

修改 package.json
中 scripts 部分,添加了两个别名 server 和 start 和别名对应执行的命令
https://img-blog.csdnimg.cn/img_convert/bdc03cb7a4a082bf7795507719c03b6a.png" alt="file" />

就可以使用 npm run 别名 的方式执行,其中 start 这个别名特殊,可以直接通过 npm start 执行
https://img-blog.csdnimg.cn/img_convert/d3406e5d5ec1addb9866183f1b65db0b.png" alt="file" />

在项目极其复杂,运行时添加参数较多的情况下,通过别名可以更方便的运行

发布包

npm 源站注册一个账号:https://www.npmjs.com/

然后创建一个示例演示发布
创建一个包名为 iyatt-package
https://img-blog.csdnimg.cn/img_convert/36256f03675621b8f8704116a11e8de1.png" alt="file" />

编写源码
index.mjs

js">export function add(num1, num2) {return num1 + num2;
}

如果修改过 npm 源站的,在进行发布操作的时候要换回官方的源站才行,镜像站不支持发布包。

npm 登录注册的账号

npm login

发布

npm publish

https://img-blog.csdnimg.cn/img_convert/385909d18ef8de9c3dfcd6d7ea35aebe.png" alt="file" />

npm 源站上就能搜到了
https://img-blog.csdnimg.cn/img_convert/2fd300cc1f88f7b7c56f85980a4858f8.png" alt="file" />

可以执行命令从源站下载安装这个包
https://img-blog.csdnimg.cn/img_convert/50d4dd1568d16e677345c5a69d761992.png" alt="file" />

写一段代码测试包调用

js">import { add } from 'iyatt-package';console.log(add(1, 2));

https://img-blog.csdnimg.cn/img_convert/b9bc4d65826d58dcd1b18492f3137705.png" alt="file" />

如果后面要发布新版本的包,把 package.json 里的版本改一下,再执行发布命令就可以。
如果要删除发布的包可以到 npm 源站上操作,更为方便。

版本管理

用于管理 Node.js 版本的工具挺多的,比如 nvm 和 n 等,其中 n 不支持 Windows,Windows 下推荐使用 nvm-windows: https://github.com/coreybutler/nvm-windows

需要前往项目页 Release 下载安装包,项目页上有使用说明,可以用于升级 Node.js,在多个版本之间切换等等。

如果是 Linux 可以使用 n 来管理,安装也方便,直接使用 npm

npm i -g n

npm 源站上有 n 命令的使用说明:https://www.npmjs.com/package/n


http://www.ppmy.cn/news/1431043.html

相关文章

CentOS 7虚拟机配置过程中所需组件的安装(二)

1.安装net-tools组件&#xff08;解决无 ifconfig&#xff09; # yum install net-tools 2.安装gcc、c编译器以及内核文件 # yum -y install gcc gcc-c kernel-devel 验证安装成功 3.安装nano&#xff08;文本编辑器&#xff09; # yum install nano

K8s: Ingress对象, 创建Ingress控制器, 创建Ingress资源并暴露服务

Ingress对象 1 &#xff09;概述 Ingress 是对集群中服务的外部访问进行管理的 API 对象&#xff0c;典型的访问方式是 HTTPIngress-nginx 本质是网关&#xff0c;当你请求 abc.com/service/a, Ingress 就把对应的地址转发给你&#xff0c;底层运行了一个 nginx但 K8s 为什么不…

Github 2024-04-22 开源项目日报Top10

根据Github Trendings的统计,今日(2024-04-22统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目4C++项目2Go项目2JavaScript项目1TypeScript项目1非开发语言项目1Zig项目1免费编程书籍和学习资源清单 创建周期:3762 天协议类型:…

数据库服务类--Redis--未授权访问终端Getshell

免责声明:本文仅做技术交流与学习. 目录 前提条件: windows上开启redis服务: Linux上创建&开启redis服务: 操作: 1-连接靶机redis 2-写入webshell 3-访问后门 redis--->webshell Redis未授权访问漏洞复现与利用 - 知乎 (zhihu.com) 前提条件: 端口开放(6379) 目录…

Reactor 模式

目录 1. 实现代码 2. Reactor 模式 3. 分析服务器的实现具体细节 3.1. Connection 结构 3.2. 服务器的成员属性 3.2. 服务器的构造 3.3. 事件轮询 3.4. 事件派发 3.5. 连接事件 3.6. 读事件 3.7. 写事件 3.8. 异常事件 4. 服务器上层的处理 5. Reactor 总结 1…

【Redis(9)】Spring Boot整合Redis,实现分布式锁,保证分布式系统中节点操作一致性

在上一篇系列文章中&#xff0c;咱们利用Redis解决了缓存穿透、缓存击穿、缓存雪崩等缓存问题&#xff0c;Redis除了解决缓存问题&#xff0c;还能干什么呢&#xff1f;这是今天咱们要接着探讨的问题。 在分布式系统中&#xff0c;为了保证在多个节点间操作的一致性&#xff0…

jmeter5.4.1源码编译(IDEA)问题解决

问题现象&#xff1a;最近想更深入的研究下jmeter5.4.1的原理及功能具体实现&#xff0c;从官网down了个源码&#xff0c;在本地使用IDEA工具导入项目、编译时&#xff0c;报以下错误&#xff1a; class jdk.internal.loader.ClassLoaders$PlatformClassLoader cannot be cast…

物联网配网工具多元化助力腾飞——智能连接,畅享未来

随着物联网技术的迅猛发展&#xff0c;智能插座、蓝牙网关作为其中常见的智能物联设备&#xff0c;无论是功能还是外观都有很大的改进&#xff0c;在智能化越来越普遍的情况下&#xff0c;它们的应用场景也在不断拓宽。对于智能设备而言&#xff0c;配网方式的选择对于设备的成…