Node.js运行时:ES模块、文件与JSON命令行项目
前置:JavaScript值与函数、集合、模块。Node.js运行JavaScript,提供文件、进程等API;没有网页document,不能把浏览器app.mjs直接交给node执行。Node属于Web Base运行时入口,不混进JavaScript语言规则或HTML标准库。
环境与模块边界
本批使用Node24.21.0,属于24.x LTS教学路径,核对官方发布周期后选用已有兼容环境;不因Current版本更高就要求迁移。执行 node --version、node -p "process.execPath"检查实际程序。本项目不依赖npm包,package.json设置 type:module,.mjs也明确表示ES模块。相对import需带扩展名;node:fs/promises属于运行时标准接口,不是浏览器内置,也无需npm install。
CommonJS使用require/module.exports,ES模块使用import/export;不要因“require is not defined”就随机改全局配置,先确认文件扩展名、package.json和运行方式。浏览器模块URL与Node模块解析不是同一个机制。
完整JSON统计CLI
路径 examples/node/reading-report,保存四个源码/数据文件和package.json即可运行。输入为数组,每项有非空title字符串及done布尔值;结果为总条数与已完成条数。读取小文件全量进内存,UTF-8 JSON不带BOM;非法JSON、错误结构和缺文件均失败,不输出空统计冒充成功。
package.json
{"name":"reading-report","private":true,"type":"module","engines":{"node":">=24"},"scripts":{"test":"node --test","start":"node cli.mjs sample.json"}}report.mjs
export function summary(notes){
if(!Array.isArray(notes)||notes.some(n=>!n||typeof n.title!=='string'||!n.title.trim()||typeof n.done!=='boolean'))throw new Error('expected notes with title and boolean done');
return {total:notes.length,done:notes.filter(n=>n.done).length};
}cli.mjs
import {readFile} from 'node:fs/promises';
import {summary} from './report.mjs';
const args=process.argv.slice(2);
if(args.length!==1){console.error('usage: node cli.mjs FILE');process.exitCode=2;}
else{
try {const notes=JSON.parse(await readFile(args[0],'utf8'));console.log(JSON.stringify(summary(notes)));}
catch(error){console.error('error: '+error.message);process.exitCode=1;}
}sample.json
[{"title":"HTML","done":false},{"title":"CSS","done":true}]在目录执行 node cli.mjs sample.json,预期输出:
{"total":2,"done":1}readFile返回Promise,await等待本次文件结果,不等于把CPU密集计算自动并行。JSON.parse只解析语法,summary还需检查业务字段;done为字符串"false"应拒绝,不能按真值把它计为完成。相对文件名相对当前工作目录,不是脚本目录。成功写stdout并退出0;缺参数退出2;文件/JSON/结构错误写stderr并退出1。process.exitCode允许输出正常刷新,不使用突然exit跳过异步清理。
node --test与诊断
report.test.mjs(完整函数与进程测试)
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {spawnSync} from 'node:child_process';
import {summary} from './report.mjs';
test('统计正常与空输入,拒绝不完整结构',()=>{
assert.deepEqual(summary([{title:'HTML',done:false},{title:'CSS',done:true}]),{total:2,done:1});
assert.deepEqual(summary([]),{total:0,done:0});
for(const value of [{},[null],[{title:'x',done:'false'}],[{title:' ',done:false}]])assert.throws(()=>summary(value));
});
test('CLI区分成功、参数错误、文件错误与非法JSON',()=>{
const run=(args)=>spawnSync(process.execPath,['cli.mjs',...args],{cwd:import.meta.dirname,encoding:'utf8'});
const good=run(['sample.json']);assert.equal(good.status,0);assert.equal(good.stdout.trim(),'{"total":2,"done":1}');
assert.equal(run([]).status,2);assert.equal(run(['missing.json']).status,1);
assert.equal(run(['package.json']).status,1);
const bad=run(['report.mjs']);assert.equal(bad.status,1);assert.match(bad.stderr,/error:/);
});执行 node --test 或 npm test,预期两组测试通过。测试真实启动cli检查正常、无参数、缺文件、非数组JSON和非法JSON。执行 node cli.mjs missing.json后用PowerShell的$LASTEXITCODE或Bash的$?检查1;ERR_MODULE_NOT_FOUND先检查import路径和大小写,ENOENT先检查文件路径和工作目录。安装了另一个运行时或包不能自动修复拼错的路径。
练习与自检
给summary增加未完成计数,为空数组和全部完成写断言。再把Node输出与浏览器阅读记录页的总数含义作比较,说明为何Node不能访问页面的localStorage,也不能把本地文件权限当浏览器同源策略。
参考解法
先验证字段,再返回open:total-done;空数组三个计数均为0,全部完成时open为0。Node进程处理传入文件,浏览器按源隔离存储;两个环境没有自动共享同一份数据。可以显式导出JSON再作为CLI输入,需另行设计导出按钮,不能假装Node自动读到浏览器状态。本批不实现业务HTTP API和账号同步。
后续:包发布、依赖锁定、安全更新、异步服务器与部署另开工作包。本批只验证纯标准库CLI,不承诺完整Node后端课程。依据:Node发布周期、包与模块、文件接口、测试。
深入课程已接续:流背压与取消。基础内容与旧锚点保留,按当前路线进入完整机制和实践。
继续阅读
- 排错
- 运行时与模块