普通视图

发现新文章,点击刷新页面。
昨天 — 2025年4月18日首页

Node.js Web开发进阶:Stream、HTTP模块与文件上传全解析

作者 林夕HT
2025年4月18日 15:23

一、Stream的读写操作

在 Node.js 中,Stream 是处理大数据文件和持续数据流的强大工具。它允许你以更高效的方式读取和写入数据,避免一次性加载大量内容到内存中。

1. 什么是 Stream

Stream 是 Node.js 中用于处理流数据的接口。它们是 EventEmitter 的实例,有四种基本类型:

  • Readable:可读流,例如 fs.createReadStream()
  • Writable:可写流,例如 fs.createWriteStream()
  • Duplex:可读可写流,例如 TCP 套接字(例如net.Socket)。
  • Transform:在读写过程中可以修改或转换数据的流,例如 zlib 压缩流(zlib.createDeflate())。

2、创建可读流(Readable Stream)

const fs = require('fs');



fs.readFile('input.txt', (err, data) => {
  if (err) return console.error(err); 
  console.log('读取到数据:', data);
});
//一次性读取我们都知道这样也可以上文读取,但是有缺点
// 缺点一:没有办法精准控制从哪里读取,读取什么位置
// 缺点二:读取到某一个位置的,暂停读取, 恢复读取,
// 缺点三:文件非常大的时候,多次读取

//后面参数可选可不选
const readStream = fs.createReadStream('input.txt',{
  start: 2, // 开始读取的位置
  end: 22, // 结束读取的位置
  highWaterMark: 3 // 读取几个字节回调一次,可以做为精准回调,默认是64kb
});

readStream.on('open', (fd) => {
  console.log('通过流将文件打开~',fd);
});

readStream.on('data', chunk => {
  console.log('读取到数据:', chunk.toString());
});

readStream.on('end', () => {
  console.log('读取完毕');
});

readStream.on('error', err => {
  console.error('读取出错:', err);
});
image.png

后续不再赘述fs操作了。

3、创建可写流(Writable Stream)

const writeStream = fs.createWriteStream('output.txt');
// const writeStream = fs.createWriteStream('output.txt', {
//   flags: "a+",// a+表示追加内容
//   start: 1, // 开始写入的位置
// });
// 这里也可以自行传参数查看效果;

writeStream.write('写入一些内容\n');
// end方法:
// 操作一:将最后的内容写入到文件中,并且关闭文件
//-操作二:关闭文件
writeStream.end('最后一行\n');

writeStream.on('finish', () => {
  console.log('写入完成');
});

writeStream.on('error', err => {
  console.error('写入出错:', err);
});
// writeStream.on('close', () => {
//   console.log('流写入关闭');
// });
// writeStream.close();

4、读写结合:文件复制操作

const readStream = fs.createReadStream('input.txt');
const writeStream = fs.createWriteStream('output.txt');

readStream.pipe(writeStream);

使用 .pipe() 方法可以将可读流中的数据自动传递给可写流。

5、进阶:Transform 流

Transform 流是 Duplex 流的一种,允许在数据传输过程中对数据进行转换。

const { Transform } = require('stream');

const upperCaseTr = new Transform({
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  }
});

process.stdin.pipe(upperCaseTr).pipe(process.stdout);

Transform 流示例:文件中每行添加行号

const fs = require('fs');
const readline = require('readline');
const { Transform } = require('stream');

let lineNumber = 1;

const lineNumberTransform = new Transform({
  transform(chunk, encoding, callback) {
    const lines = chunk.toString().split('\n').map(line => `${lineNumber++}: ${line}`);
    this.push(lines.join('\n'));
    callback();
  }
});

fs.createReadStream('input.txt')
  .pipe(lineNumberTransform)
  .pipe(fs.createWriteStream('numbered_output.txt'));

二、http模块web服务

什么是Web服务器?

  • 当应用程序(客户端)需要某一个资源时,可以向一台服务器,通过Http请求获取到这个资源;
  • 提供资源的这个服务器,就是一个Web服务器;
  • 开源的Web服务器:Nginx、Apache(静态)、ApacheTomcat(静态、动态)、Node.js

1、引入 http 模块并创建服务器


const http = require('http');
//http.createServer会返回服务器的对象;
//底层其实使用直接 new Server 对象。
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, World!');
});
//监听端口时,监听1024以上的端口,666535以下的端口;因为这里和字节 2进制有关
server.listen(3000, () => {
  console.log('服务器已启动:http://localhost:3000');
});

说明:

  • 可以创建多个服务器

  • http.createServer() 创建一个 HTTP 服务实例。

  • req 是请求对象,包含客户端请求的所有信息。

  • res 是响应对象,用于向客户端返回数据。

  • server.listen() 启动服务器监听指定端口。

提示

可以全局安装nodemon工具npm install nodemon -g以做到修改后不需要再进行重启node执行;可以直接使用nodemon XXX.js来运行即可

2、request 请求对象解析

req 对象包含了客户端请求的所有信息:

const server = http.createServer((req, res) => {
  //request对象中包含哪些信息?
  console.log('请求方法:', req.method);
  console.log('请求路径:', req.url);
  console.log('请求头:', req.headers);
  
  res.end('已收到请求');
});

常用属性包括:

  • req.method: 请求方法(如 GET、POST)
  • req.url: 请求路径(包括查询字符串)
  • req.headers: 请求头对象
  • 根据这一特性我们可以根据不同请求路径或者方式去做不同的返回值操作执行数据内容。

3、response 响应对象解析

res 对象用来返回响应数据给客户端:

res.writeHead(200, { 'Content-Type': 'text/html' });
res.write('<h1>Hello Web</h1>');
res.end();

常用方法:

  • res.writeHead(statusCode, headers): 设置状态码与响应头
  • res.write(data): 向响应体写入数据
  • res.end([data]): 结束响应,data 为可选

4、根据不同 URL 返回不同内容

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    res.end('欢迎来到首页');
  } else if (req.url === '/about') {
    res.end('这是关于页面');
  } else {
    res.statusCode = 404;
    res.end('页面未找到');
  }
});

这一操作可以根据不一样请求参数进行分割处理展示,node中模块引入url,解析参数可以用URLSearchParams(queryString弃用了)

5、处理 POST 请求数据

POST 请求中的数据是通过流的方式传递的,需要监听 dataend 事件:

//request时象本质是上是一个readable可读流
const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/submit') {
    let body = '';

    req.on('data', chunk => {
      body += chunk;
    });

    req.on('end', () => {
      console.log('接收到的数据:', body);
      res.end('数据接收成功');
    });
  }
});

6、静态文件服务示例

const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  const filePath = path.join(__dirname, req.url === '/' ? '/index.html' : req.url);
  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.writeHead(404);
      res.end('文件未找到');
    } else {
      res.writeHead(200);
      res.end(data);
    }
  });
});

7、总结

使用 Node.js 原生的 http 模块可以快速搭建小型 Web 服务,虽然比不上 Express 那样方便,但足够应对基础场景,并能帮助我们更深入理解 Web 服务的底层原理。

三、axios node中使用

Http Node请求

1. http.request —— 更灵活,支持 GET / POST / PUT 等所有方法

const http = require('http');

const options = {
  hostname: 'jsonplaceholder.typicode.com',
  port: 80,
  path: '/posts/1',
  method: 'GET'
};

const req = http.request(options, res => {
  let data = '';
  res.on('data', chunk => data += chunk);
  res.on('end', () => console.log(JSON.parse(data)));
});

req.on('error', error => console.error(error));
req.end();

2. http.get —— 简化 GET 请求写法的语法糖

const http = require('http');

http.get('http://jsonplaceholder.typicode.com/posts/1', res => {
  let data = '';
  res.on('data', chunk => data += chunk);
  res.on('end', () => console.log(JSON.parse(data)));
}).on('error', err => console.error(err));

✅ 适合只发 GET 请求时使用,不支持 POST、PUT、DELETE。

🛠 1、安装 axios

npm install axios

📦 2、基本使用方式

1. 发起 GET 请求

const axios = require('axios');

axios.get('https://jsonplaceholder.typicode.com/posts/1')
  .then(res => {
    console.log(res.data);
  })
  .catch(err => {
    console.error('请求失败', err.message);
  });

2. 发起 POST 请求

axios.post('https://jsonplaceholder.typicode.com/posts', {
  title: 'Hello',
  body: 'World',
  userId: 1
})
.then(res => {
  console.log(res.data);
})
.catch(err => {
  console.error('POST 请求出错', err.message);
});

⚙️ 3、带参数的请求

GET 请求携带参数:

axios.get('https://jsonplaceholder.typicode.com/posts', {
  params: {
    userId: 1
  }
})
.then(res => {
  console.log(res.data);
});

POST 请求设置 headers:

axios.post('https://example.com/api', { name: '张三' }, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token'
  }
})

🧪 4、async/await 写法

const axios = require('axios');

async function getData() {
  try {
    const res = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
    console.log(res.data);
  } catch (err) {
    console.error('请求出错', err.message);
  }
}

getData();

📤 5、文件上传(multipart/form-data)

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

const form = new FormData();
form.append('file', fs.createReadStream('./test.jpg'));

axios.post('https://example.com/upload', form, {
  headers: form.getHeaders()
})
.then(res => console.log('上传成功', res.data))
.catch(err => console.error('上传失败', err));

📁 6、设置超时 / 自定义配置

axios({
  method: 'get',
  url: 'https://example.com/api',
  timeout: 3000, // 3秒超时
  headers: {
    'X-Custom-Header': 'value'
  }
})

🔁 7、axios 实例(适合封装)

const instance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: { 'X-Token': 'abc123' }
});

instance.get('/user').then(res => console.log(res.data));

四、文件上传的细节分析


📌 1、文件上传的基本流程

  1. 前端使用表单或 JS 提交文件(multipart/form-data
  2. 后端接收请求并解析上传的文件内容
  3. 存储文件到指定目录(本地或云存储)
  4. 返回文件上传结果或链接

使用原生 httpfs


const http = require('http');
const fs = require('fs');
const path = require('path');

http.createServer((req, res) => {
  if (req.method === 'GET') {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(`
      <form method="POST" enctype="multipart/form-data">
        <input type="file" name="file" /><br/>
        <button type="submit">上传</button>
      </form>
    `);
  } else if (req.method === 'POST') {
    let data = '';
    req.setEncoding('binary');

    req.on('data', chunk => {
      data += chunk;
    });

    req.on('end', () => {
      // 获取 boundary 分隔符
      const boundary = '--' + req.headers['content-type'].split('boundary=')[1];
      const parts = data.split(boundary);
      const filePart = parts[1];

      // 提取文件名
      const match = filePart.match(/filename="(.+)"/);
      const filename = match && match[1] ? match[1] : 'upload.bin';
      const start = filePart.indexOf('\r\n\r\n') + 4;
      const end = filePart.lastIndexOf('\r\n');
      const fileData = filePart.substring(start, end);

      fs.writeFile(path.join(__dirname, filename), fileData, 'binary', err => {
        if (err) {
          res.writeHead(500);
          res.end('上传失败');
        } else {
          res.writeHead(200);
          res.end('上传成功,文件名:' + filename);
        }
      });
    });
  }
}).listen(3000, () => {
  console.log('Server running at http://localhost:3000');
});


🧰 2、常用 Node.js 文件上传模块

✅ 推荐使用 multer

npm install multer

📤 3、Express + Multer 实现文件上传

1. 创建 upload.js

const express = require('express');
const multer = require('multer');
const path = require('path');

const app = express();

// 配置存储位置和文件名
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, './uploads'); // 上传目录
  },
  filename: function (req, file, cb) {
    const ext = path.extname(file.originalname);
    cb(null, Date.now() + ext); // 文件名
  }
});

const upload = multer({ storage });

app.post('/upload', upload.single('file'), (req, res) => {
  // req.file 就是上传的文件信息
  res.json({
    message: '上传成功',
    filename: req.file.filename
  });
});

app.listen(3000, () => console.log('服务已启动,端口3000'));

🖥 4、前端上传方式

1. 表单方式

<form action="http://localhost:3000/upload" method="POST" enctype="multipart/form-data">
  <input type="file" name="file" />
  <button type="submit">上传</button>
</form>

2. Axios 上传文件

const formData = new FormData();
formData.append('file', fileInput.files[0]);

axios.post('http://localhost:3000/upload', formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
})
.then(res => console.log('上传成功', res.data))
.catch(err => console.error('上传失败', err));

🧠 5、上传细节与注意事项

细节 描述
文件大小限制 limits: { fileSize: 5 * 1024 * 1024 }
多文件上传 upload.array('files', maxCount)
文件类型校验 fileFilter: (req, file, cb) => {...}
跨域 后端需设置 Access-Control-Allow-Origin
文件夹不存在处理 可用 fs.mkdirSync('uploads', { recursive: true }) 创建

🛠 6、常见问题排查

问题 原因
req.file 为 undefined 表单名错误或未使用 multipart/form-data
文件未保存 destination 路径不存在或无权限
文件名乱码 可以使用 Buffer.from(filename, 'binary').toString('utf8') 转码
大文件上传失败 默认大小限制过小,可通过 limits 配置放宽

Node.js 学习第一天:入门指南

作者 Mintopia
2025年4月18日 11:14

引言

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,让 JavaScript 可以在服务器端运行。它使得开发者可以使用 JavaScript 构建高性能、可扩展的网络应用程序。在第一天的学习中,我们将完成环境搭建,并了解 Node.js 的基本概念和简单应用。

一、环境搭建

1. 下载与安装

Node.js 的官方网站是nodejs.org/ ,在这里你可以找到适合你操作系统的安装包。对于大多数开发者来说,建议下载长期支持(LTS)版本,因为它更加稳定。

安装过程非常简单,按照安装向导的提示一步一步进行即可。安装完成后,你可以通过以下命令来验证是否安装成功:

node -v

如果安装成功,该命令会输出 Node.js 的版本号。同时,Node.js 自带了包管理工具 npm(Node Package Manager),你可以使用以下命令验证 npm 的安装:

npm -v

2. 集成开发环境(IDE)选择

有许多 IDE 可以用于 Node.js 开发,以下是一些常见的选择:

  • Visual Studio Code:这是一个免费且功能强大的开源 IDE,拥有丰富的插件生态系统,对 Node.js 开发提供了很好的支持。
  • WebStorm:由 JetBrains 开发的专业 JavaScript IDE,具备智能代码提示、调试工具等高级功能,但它是付费软件。

二、Node.js 基本概念

1. 单线程与事件驱动

Node.js 采用单线程、事件驱动的架构。单线程意味着在同一时间只能执行一个任务,但通过事件驱动和非阻塞 I/O 操作,Node.js 可以高效地处理大量并发请求。

例如,当一个 Node.js 程序发起一个文件读取请求时,它不会等待文件读取完成,而是继续执行后续的代码。当文件读取完成后,会触发一个事件,Node.js 会处理这个事件。

2. 模块系统

Node.js 使用模块系统来组织代码。一个模块就是一个独立的 JavaScript 文件,它可以包含变量、函数和类等。Node.js 内置了许多核心模块,如fs(文件系统模块)、http(HTTP 服务器模块)等。

以下是一个简单的模块示例:

// math.js
function add(a, b) {
    return a + b;
}
function subtract(a, b) {
    return a - b;
}
module.exports = {
    add: add,
    subtract: subtract
};

在另一个文件中,我们可以引入并使用这个模块:

// main.js
const math = require('./math');
const result1 = math.add(5, 3);
const result2 = math.subtract(5, 3);
console.log('加法结果:', result1);
console.log('减法结果:', result2);

3. 全局对象

在 Node.js 中,有一些全局对象可以在任何模块中使用。其中最常用的是global对象,它类似于浏览器中的window对象。

以下是一些常见的全局对象和方法:

  • console:用于在控制台输出信息,如console.log()、console.error()等。
  • __dirname:表示当前模块所在的目录路径。
  • __filename:表示当前模块的文件路径。
console.log('当前目录:', __dirname);
console.log('当前文件:', __filename);

三、第一个 Node.js 程序

1. 创建一个简单的 HTTP 服务器

使用 Node.js 的http模块,我们可以轻松创建一个简单的 HTTP 服务器。以下是一个示例代码:

const http = require('http');
const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Hello, World!\n');
});
const port = 3000;
server.listen(port, () => {
    console.log(`服务器正在监听端口 ${port}`);
});

将上述代码保存为server.js,然后在终端中运行以下命令:

node server.js

打开浏览器,访问http://localhost:3000,你将看到页面上显示Hello, World!。

2. 代码解释

  • http.createServer():创建一个 HTTP 服务器实例,它接受一个回调函数作为参数,该回调函数在每次收到 HTTP 请求时都会被调用。
  • res.statusCode:设置 HTTP 响应的状态码,这里设置为 200 表示请求成功。
  • res.setHeader():设置 HTTP 响应的头部信息,这里设置响应内容的类型为纯文本。
  • res.end():结束响应并发送响应内容。
  • server.listen():让服务器开始监听指定的端口。

四、总结

在第一天的学习中,我们完成了 Node.js 的环境搭建,了解了 Node.js 的基本概念,包括单线程与事件驱动、模块系统和全局对象。同时,我们还创建了一个简单的 HTTP 服务器。这些知识是学习 Node.js 的基础,在后续的学习中,我们将深入探讨 Node.js 的更多功能和应用。

希望你通过今天的学习,对 Node.js 有了一个初步的认识,并能够顺利运行第一个 Node.js 程序。在接下来的学习中,你可以尝试修改代码,添加更多的功能,进一步熟悉 Node.js 的开发。

昨天以前首页

Node.js 对前端技术有利的知识点

作者 Mintopia
2025年4月17日 11:21

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它让 JavaScript 可以在服务器端运行。下面为你介绍一些 Node.js 对前端技术有利的知识点:

1. 前端构建工具

自动化任务

像 Grunt 和 Gulp 这类构建工具,可借助 Node.js 来自动化完成前端开发里的重复性任务,例如文件压缩、代码合并、编译预处理样式表等。以 Gulp 为例,它能让开发者用 JavaScript 代码来定义任务流程,从而提高开发效率。

const gulp = require('gulp');
const uglify = require('gulp-uglify');
// 定义一个压缩JavaScript文件的任务
gulp.task('compress', function () {
    return gulp.src('src/js/*.js')
      .pipe(uglify())
      .pipe(gulp.dest('dist/js'));
});

打包工具

Webpack 和 Rollup 等打包工具也是基于 Node.js 构建的。它们可以将多个模块打包成一个或多个文件,优化资源加载。Webpack 还支持各种加载器和插件,能处理不同类型的文件,如 CSS、图片等。

2. 前端开发服务器

本地开发环境

Node.js 能够搭建本地开发服务器,像 Express 和 Koa 这类框架就很适合创建轻量级的服务器。在开发过程中,服务器可以实时响应代码的变化,自动刷新页面,提升开发体验。

const express = require('express');
const app = express();
// 静态文件服务
app.use(express.static('public'));
const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

代理服务器

在开发过程中,可能会遇到跨域问题。Node.js 服务器可以作为代理服务器,转发请求,解决跨域问题。

3. 前端包管理

npm 和 yarn

npm(Node Package Manager)是 Node.js 的默认包管理器,yarn 则是 Facebook 推出的另一个包管理器。它们可以方便地管理前端项目的依赖,快速安装、更新和删除第三方库。例如,使用npm install命令可以安装项目所需的所有依赖。

4. 实时通信

WebSocket

Node.js 配合 WebSocket 库(如 Socket.IO)可以实现实时通信功能。在前端开发中,实时通信常用于聊天应用、实时数据展示等场景。

const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
// 监听客户端连接事件
io.on('connection', (socket) => {
    console.log('A user connected');
    // 监听客户端发送的消息
    socket.on('chat message', (msg) => {
        io.emit('chat message', msg);
    });
    // 监听客户端断开连接事件
    socket.on('disconnect', () => {
        console.log('A user disconnected');
    });
});
const port = 3000;
http.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

5. 同构应用开发

服务器端渲染(SSR)

Node.js 允许在服务器端运行 JavaScript 代码,这使得实现服务器端渲染成为可能。像 Next.js(用于 React)和 Nuxt.js(用于 Vue)这类框架,就可以利用 Node.js 在服务器端渲染页面,提高首屏加载速度和搜索引擎优化(SEO)效果。

6. 前端测试

测试框架

Node.js 上有许多前端测试框架,如 Jest、Mocha 和 Jasmine 等。这些框架可以帮助开发者编写和运行单元测试、集成测试等,确保代码的质量。例如,Jest 是一个功能强大的 JavaScript 测试框架,它可以自动发现和运行测试用例。

function sum(a, b) {
    return a + b;
}
test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
});

7. 前端脚手架工具

Yeoman 和 create-react-app

Node.js 可以用于创建前端脚手架工具,快速生成项目模板。Yeoman 是一个通用的脚手架工具,它可以根据不同的生成器生成各种类型的项目。而 create-react-app 则是专门为 React 项目设计的脚手架工具,它可以帮助开发者快速搭建一个新的 React 项目。

🛠️ Unindex:推荐一款自动化索引文件生成工具

作者 雷王
2025年4月17日 11:19

📖 开发背景

作为一名前端开发者,在维护组件库和工具函数目录时,总会遇到下面的困扰:

  1. 每次新增组件/工具文件时,都需要手动更新 index.* 文件做引入或重新导出
  2. 基于构建工具的 import.meta.glob 的导入在 Re-Export 时会丢失对应的类型
  3. 不同的语言文件对索引文件的引入方式不同,但内容大体上仍然属于重复性工作

查找了 npm 暂时没有发现一个能同时满足以下需求的工具:

  • 支持可视化配置
  • 可定制生成规则
  • 支持监听模式
  • 可脱离构建工具直接运行

"既然找不到合适的轮子,那就自己造一个!" -- 于是 unindex 诞生了!

✨ unindex 的核心特点

1. 极简配置,开箱即用

// 最简单的配置
export default defineConfig({
  // 定义入口目录
  entryDir: 'src/hooks'
})

只需指定目录,unindex 就会自动生成规范的导出文件,未配置 outFile 时将尝试从匹配的文件列表中进行获取扩展名。

2. 深度定制能力

export default defineConfig({
  entryDir: 'src/hooks',
  glob: {
    patterns: '**/*.ts',
  },
  // 更改生成的代码片段
  codeGenerator: ({ file, relativePath }) => {
    return `export { default as use${pascalCase(file.slice(file.lastIndexOf('/') + 1).replace(/\.tsx?$/, ''))} } from './${relativePath}';`
  },
  // 更改输出内容
  contentGenerator: ({ codes }) => {
    return `\
/* Generated by unindex */
${codes.join('\n')}
`
  }
})

3. 多场景支持

  • 组件库开发:自动导出所有组件
  • 工具函数集:统一管理工具方法
  • 文档系统:自动生成目录索引
  • 样式管理:自动导入 CSS/SCSS 文件
// 支持配置项数组
export default defineConfig([
  {
    entryDir: 'src/hooks',
    glob: {
      // 需要匹配的文件
      patterns: '**/*.ts',

      // 忽略以 `_` 开头的文件和目录
      // ignore: '**/_*',

      // 仅忽略以 `_` 开头的文件
      ignore: '**/_.*',
    },
  },
  {
    entryDir: 'src/styles',
    glob: {
      patterns: '**/*.css',
    },
    // 更改生成的代码片段
    codeGenerator: ({ relativePath }) => `@import './${relativePath}';`,
  },
  {
    entryDir: 'docs/pages',
    // 更改输出的文件名
    outFile: 'Home.md',
    glob: {
      patterns: '**/*.md',
    },
    codeGenerator: ({ file, relativePath }) => `[${file}](./${relativePath})`,
    // 更改输出的内容
    contentGenerator: ({ codes }) => {
      return `\
# 目录

${codes.map((code) => `- ${code}`).join('\n')}
`
    },
  },
])

4. 支持监听模式

export default defineConfig({
  // 定义入口目录
  entryDir: 'src/hooks',
+  watch: true, // 优先级大于命令行 `--watch` 参数
})
npx unindex --watch

文件新增、删除、重命名时自动重新生成。

🚀 快速入门

  1. 安装:
# npm
npm i -D unindex

# yarn
yarn add -D unindex

# pnpm
pnpm add -D unindex
  1. 创建配置文件 unindex.config.[js,ts,mjs,cjs,mts,cts,json]
import { defineConfig } from 'unindex'

export default defineConfig({
  entryDir: 'src/hooks',
  glob: {
    patterns: '**/*.{ts,tsx}',
    ignore: '**/*.spec.*'
  }
})
  1. 运行:
# 命令行
npx unindex

# 或添加到 package.json
{
  "scripts": {
    "unindex": "unindex"
  }
}

结语

你的项目是否也在为导出文件烦恼?试试 unindex,让工具代替重复劳动!

项目已开源,欢迎 Star 和贡献代码!

项目地址: github.com/l246804/uni…

Node.js 如何检测 script 脚本是在项目本身运行

作者 Legend80s
2025年4月15日 11:34

假设有一个脚本 check.mjs 如何检测它自己是在自身项目内运行?

💭 背景

postinstall 运行时机有两个:被其他项目安装 npm i foo 或自己项目运行 npm i。如果想让自己项目安装时不运行或者做一些特殊操作,则需要检测脚本是否被自身运行。

假设有 package.json

{
  "scripts": {
     "postinstall": "node ./scripts/check.mjs"
  }
}

check.mjs 的 isRunInItself 如何写?有三种方式:

📁 方式 1:cwd 和 dirname 比较

原理:cwd 为运行时路径,dirname 为其磁盘路径,如果脚本 check.mjs 被自身运行,则二者相同,否则不同。

假设 check.mjs 的路径为 /temp/foo/check.mjs,它被 bar 项目依赖,bar 路径为 /workspace/bar

自身运行
  • cwd: /temp/foo/
  • dirname: /temp/foo/
被安装后运行
  • cwd: /temp/foo/node_modules/foo
  • dirname: /workspace/bar

故代码可以这样写:

// check.mjs
/**
 * @returns {boolean}
 */
function isRunInItself() {
  // 获取当前工作目录
  const currentDir = process.cwd()

  // 获取 foo 包的根目录
  const __dirname = import.meta.dirname
  const fooRootDir = path.resolve(__dirname, '..')

  if (currentDir === fooRootDir) {
    log('正在本包目录安装,跳过检测...')

    return true
  }

  return false
}

📦 方式 2:检测当前项目 package.json name

原理:检查当前运行目录的 package.json name

/**
 * @returns {boolean}
 */
function isRunInItself() {
  // so if name in package.json in the current dir is @neural/utils then skip check
  if (fs.existsSync('./package.json')) {
    const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'))

    if (packageJson.name === PACKAGE_NAME) {
      // log('Skip check because package name is @neural/utils')

      return true
    }
  }

  return false
}

读取 json 也可以直接用更高级的写法,利用 Node.js v18.20.0 引入的 import attributes

const { default: packageJson } = await import('./package.json'), { with: { type: 'json' } })

// 如果只需要 name 可以再解构下
const { default: { name } } = await import('./package.json'), { with: { type: 'json' } }) 

方式 3:🗝️ 环境变量获取 package.json name

原理:利用 Node.js 鲜为人知的一个隐藏知识点。Node.js 在运行时会将 package.json 的字段注入环境变量。

Node.js 在运行时会将 package.json 文件中的字段注入到环境变量中。例如,如果 package.json 文件中包含以下内容:

{
  "name": "foo",
  "version": "1.2.5"
}

在运行时,Node.js 会将以下环境变量设置为:

  • npm_package_name 为 "foo"
  • npm_package_version 为 "1.2.5"

这些环境变量可以在脚本中通过 process.env 访问[9]

—— 官方文档

// package.json
{
  "name": "foo"
  "scrpts": {
    "say:name": "echo What is the pkg name? $npm_package_name"
  }
}

npm run say:name

❯ npm run say:name 

> foo@0.0.20 say:name
> echo What is the pkg name? $npm_package_name

What is the pkg name? foo

注意:如果是 Windows 则环境变量需要改成 %npm_package_name%

bun 兼容性是真的好,Windows 下也无需改。bun say:name

❯ bun say:name 
$ echo What is the pkg name? $npm_package_name
What is the pkg name? foo

那检测逻辑怎么写呢。有两种写法。

  1. 直接在 package.json 中判断(但是兼容性不好)

Linux:

// package.json
"postinstall": "[ $npm_package_name = foo ] && echo '被自身运行无需 check' || echo 被其他项目安装后执行"

Windows:

// package.json
"postinstall": "[ %npm_package_name% = foo ] && echo '被自身运行无需 check' || echo 被其他项目安装后执行"
  1. 写到 Node.js 脚本。
/**
 * @returns {boolean}
 */
function isRunInItself() {
  return process.env.npm_package_name === 'foo'
}

🧠 总结

最严谨是用方法1,最简单则使用方法3。

❌
❌