npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@holix/static

v0.0.3

Published

Static file middleware for @holix/router with pattern matching and blacklist support

Readme

@holix/static

静态文件服务中间件,用于 @holix/router 的自定义协议路由。支持文件过滤、缓存控制、目录列表等功能。

特性

  • ✅ 基于 glob 模式的黑名单/白名单文件过滤
  • ✅ 智能缓存控制(ETag、Cache-Control)
  • ✅ 目录列表自动生成
  • ✅ 路径遍历攻击防护
  • ✅ 符号链接控制
  • ✅ 自定义 MIME 类型
  • ✅ 404/错误回调处理
  • ✅ 40+ 内置 MIME 类型

安装

pnpm add @holix/static

基本使用

import path from 'node:path'
import { createRouter } from '@holix/router'
import { createStaticMiddleware } from '@holix/static'

const router = createRouter()
const staticDir = path.resolve(__dirname, './public')

// 创建静态文件中间件
const staticMiddleware = createStaticMiddleware({
  root: staticDir,
  prefix: '/_static', // 可选,URL 前缀
})

// 注册中间件
router.use(staticMiddleware)

// 现在可以访问:
// holix://app/_static/style.css
// holix://app/_static/images/logo.png

配置选项

StaticMiddlewareOptions

interface StaticMiddlewareOptions {
  /** 静态文件根目录(绝对路径) */
  root: string

  /** URL 路径前缀,例如 '/_static' 或 '/assets' */
  prefix?: string

  /** 是否启用缓存(默认: true) */
  cache?: boolean

  /** 缓存最大时长(秒),默认: 31536000 (1 年) */
  maxAge?: number

  /** 黑名单模式列表(支持 glob 语法) */
  blacklist?: string[]

  /** 白名单模式列表(支持 glob 语法,优先级高于黑名单) */
  whitelist?: string[]

  /** 是否启用目录列表(默认: false) */
  index?: boolean

  /** 索引文件名(默认: ['index.html']) */
  indexFiles?: string[]

  /** 是否跟随符号链接(默认: false) */
  followSymlinks?: boolean

  /** 自定义 MIME 类型映射 */
  mimeTypes?: Record<string, string>

  /** 404 处理函数 */
  onNotFound?: (ctx: ExtendedRouteContext, filePath: string) => void

  /** 错误处理函数 */
  onError?: (ctx: ExtendedRouteContext, error: Error) => void
}

使用示例

1. 基本静态文件服务

import { createStaticMiddleware } from '@holix/static'

const middleware = createStaticMiddleware({
  root: '/path/to/public',
})

2. 使用 URL 前缀

const middleware = createStaticMiddleware({
  root: '/path/to/public',
  prefix: '/_static',
})

// 访问: holix://app/_static/style.css
// 映射到: /path/to/public/style.css

3. 文件过滤 - 黑名单

const middleware = createStaticMiddleware({
  root: '/path/to/public',
  blacklist: [
    '**/.git/**', // 屏蔽 .git 目录
    '**/.env*', // 屏蔽 .env 文件
    '**/config.json', // 屏蔽配置文件
  ],
})

默认黑名单 (自动包含):

  • **/.git/**
  • **/.svn/**
  • **/node_modules/**
  • **/.env*
  • **/package-lock.json
  • **/pnpm-lock.yaml
  • **/yarn.lock

4. 文件过滤 - 白名单

白名单优先级高于黑名单。只有匹配白名单的文件才允许访问。

const middleware = createStaticMiddleware({
  root: '/path/to/public',
  whitelist: [
    '**/*.js', // 只允许 JS 文件
    '**/*.css', // 只允许 CSS 文件
    '**/*.png', // 只允许 PNG 图片
    '**/*.jpg', // 只允许 JPG 图片
  ],
})

5. 启用目录列表

const middleware = createStaticMiddleware({
  root: '/path/to/public',
  index: true, // 启用目录列表
  indexFiles: ['index.html', 'index.htm'], // 优先查找的索引文件
})

6. 禁用缓存(开发模式)

const middleware = createStaticMiddleware({
  root: '/path/to/public',
  cache: false, // 禁用缓存
})

7. 自定义缓存时长

const middleware = createStaticMiddleware({
  root: '/path/to/public',
  cache: true,
  maxAge: 86400, // 缓存 1 天 (单位: 秒)
})

8. 自定义 MIME 类型

const middleware = createStaticMiddleware({
  root: '/path/to/public',
  mimeTypes: {
    '.md': 'text/markdown',
    '.ts': 'text/typescript',
  },
})

9. 错误处理回调

const middleware = createStaticMiddleware({
  root: '/path/to/public',
  onNotFound: (ctx, filePath) => {
    console.log('File not found:', filePath)
    ctx.res.statusCode = 404
    ctx.res.setHeader('Content-Type', 'application/json')
    ctx.res.end(JSON.stringify({ error: 'File not found' }))
  },
  onError: (ctx, error) => {
    console.error('Static file error:', error)
    ctx.res.statusCode = 500
    ctx.res.end('Internal Server Error')
  },
})

Glob 模式语法

使用 micromatch 进行模式匹配:

// 基本通配符
'*.js' // 匹配任何 .js 文件
'script.js' // 精确匹配

// 目录通配符
'**/*.css' // 匹配所有子目录中的 .css 文件
'src/**/*.ts' // 匹配 src 下所有 .ts 文件

// 字符集
'file[0-9].txt' // 匹配 file0.txt, file1.txt, ...
'*.{js,ts}' // 匹配 .js 或 .ts 文件

// 否定模式
'!test/**' // 排除 test 目录

缓存策略

1. ETag 缓存

中间件自动为每个文件生成 ETag(基于文件修改时间和大小):

ETag: "5f7b8c9d-1234"

客户端下次请求时携带 If-None-Match,如果 ETag 匹配则返回 304 Not Modified

2. 不可变缓存

对于包含 hash 的文件(如 bundle-abc123.js),自动设置不可变缓存:

Cache-Control: public, max-age=31536000, immutable

3. 普通缓存

其他文件使用标准缓存:

Cache-Control: public, max-age=31536000

安全特性

1. 路径遍历防护

自动检测并阻止路径遍历攻击:

GET /../../../etc/passwd  ❌ 返回 400
GET /%2e%2e%2f%2e%2e%2f  ❌ 返回 400

2. 符号链接控制

默认不跟随符号链接,防止访问根目录外的文件:

createStaticMiddleware({
  root: '/var/www',
  followSymlinks: false, // 默认值
})

3. 默认黑名单

自动屏蔽敏感文件和目录(.git, .env, node_modules 等)。

内置 MIME 类型

支持 40+ 常见 MIME 类型:

| 扩展名 | MIME 类型 | |--------|-----------| | .html, .htm | text/html | | .css | text/css | | .js, .mjs | application/javascript | | .json | application/json | | .png | image/png | | .jpg, .jpeg | image/jpeg | | .svg | image/svg+xml | | .woff2 | font/woff2 | | .mp4 | video/mp4 | | ... | ... |

完整列表请参考源码中的 DEFAULT_MIME_TYPES

最佳实践

开发环境

createStaticMiddleware({
  root: './public',
  cache: false, // 禁用缓存,便于调试
  index: true, // 启用目录列表
})

生产环境

createStaticMiddleware({
  root: './dist',
  cache: true, // 启用缓存
  maxAge: 31536000, // 1 年缓存
  blacklist: [ // 严格的黑名单
    '**/.git/**',
    '**/.env*',
    '**/config/**',
  ],
})

Electron 应用

import path from 'node:path'
import { app } from 'electron'

const staticDir = app.isPackaged
  ? path.join(process.resourcesPath, 'public')
  : path.join(__dirname, '../public')

createStaticMiddleware({
  root: staticDir,
  prefix: '/_assets',
  cache: true,
})

与 router-ssr 集成

配合 @holix/router-ssr 使用,服务打包后的 chunk 文件:

import { createRouter } from '@holix/router'
import { createSSRMiddleware } from '@holix/router-ssr'
import { createStaticMiddleware } from '@holix/static'
import path from 'node:path'

const router = createRouter()

// 1. 静态文件中间件(服务 _chunks)
router.use(createStaticMiddleware({
  root: path.resolve(__dirname, './dist'),
  cache: true,
}))

// 2. SSR 中间件(处理路由)
router.use(createSSRMiddleware({
  routes: [...],
  framework: 'vue',
}))

访问流程:

  1. holix://app/about → SSR 渲染 HTML
  2. holix://app/_chunks/about-C5TTlyQe.js → 静态文件服务

工具函数

包提供了一些实用工具函数:

import {
  formatFileSize,
  getExtension,
  isHiddenFile,
  isSafePath,
  matchPatterns,
} from '@holix/static'

// 匹配 glob 模式
matchPatterns('src/index.ts', ['**/*.ts']) // true

// 检测路径安全性
isSafePath('../../../etc/passwd') // false

// 格式化文件大小
formatFileSize(1024) // '1.00 KB'

// 获取扩展名
getExtension('script.js') // '.js'

// 检测隐藏文件
isHiddenFile('.gitignore') // true

API 参考

createStaticMiddleware(options)

创建静态文件中间件。

  • 参数: StaticMiddlewareOptions
  • 返回: StaticMiddleware 函数

matchPatterns(path, patterns)

检查路径是否匹配 glob 模式列表。

  • 参数:
    • path: 要检查的路径
    • patterns: glob 模式数组
  • 返回: boolean

isSafePath(path)

检查路径是否安全(无路径遍历)。

  • 参数: path - 要检查的路径
  • 返回: boolean

formatFileSize(bytes)

格式化文件大小为人类可读格式。

  • 参数: bytes - 字节数
  • 返回: string (例如: '1.50 MB')

getExtension(filename)

获取文件扩展名(小写)。

  • 参数: filename - 文件名
  • 返回: string (例如: '.js')

isHiddenFile(filename)

检查文件是否为隐藏文件(以 . 开头)。

  • 参数: filename - 文件名
  • 返回: boolean

License

MIT