Модули и среда исполнения — Примеры

Пример 1 — Одна зависимость, два стиля модулей

Стартовый помощник загружает файл конфигурации. Сначала его написали с функцией, которая загружает модуль по имени, а потом импортировали из более нового файла с явными импортами — и теперь инициализация выполняется дважды. Покажите оба стиля рядом и остановите проект на одном.

// config.cjs
const fs = require('node:fs')

module.exports = {
  load: () => JSON.parse(fs.readFileSync('config.json', 'utf8'))
}

Решение

// config.mjs
import { readFile } from 'node:fs/promises'

export const load = async () => JSON.parse(await readFile('config.json', 'utf8'))

Стиль с явными импортами разрешается до запуска программы: инструменты видят привязки, а модуль вычисляется один раз для каждого импортёра. Модуль же, загруженный одним способом и затребованный другим, может вместо этого инициализироваться дважды. При этом динамическая форма остаётся верным инструментом там, где загрузка должна быть условной, — и именно это делает её цель невидимой для статического анализа. Выберите один стиль на зависимость и держитесь его.

Пример 2 — Чтение файла в память, чтобы его отправить

Эндпоинт отдаёт большой файл выгрузки. Маленькие файлы проходят, а на файле продакшен-размера процесс убивают: всё тело держится в памяти до того, как успевает записаться хоть что-то. Переделайте эндпоинт так, чтобы данные уходили кусками.

import { createServer } from 'node:http'
import { readFile } from 'node:fs/promises'

createServer(async (req, res) => {
  const body = await readFile('./export.csv')    // весь файл, сразу
  res.writeHead(200, { 'content-type': 'text/csv' })
  res.end(body)
}).listen(3000)

Решение

import { createServer } from 'node:http'
import { createReadStream } from 'node:fs'

createServer((req, res) => {
  const source = createReadStream('./export.csv')
  source.on('error', (err) => res.destroy(err))
  res.on('error', (err) => source.destroy(err))
  res.writeHead(200, { 'content-type': 'text/csv; charset=utf-8' })
  source.pipe(res)
}).listen(3000)

Куски приходят и записываются по мере чтения, поэтому память остаётся ровной, каким бы большим ни стал файл, и у каждой стороны перекачки есть собственный путь ошибки. Однако пропущенные обработчики дорого обходятся: сбойный поток тихо застревает или течёт вместо громкого падения. Указывайте кодировку и на границе — кракозябры порождает несоответствие той, в которой данные прочитаны, и той, в которой записаны.