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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@risemaxi/envault

v0.0.3

Published

Build-time validated, typed environment variables for any bundler

Readme

@risemaxi/envault

Build-time validated, typed environment variables for any bundler.

envault validates process.env against a Standard Schema at build time, then exposes the validated values through a virtual module. The schema library never enters your bundle, and a missing or invalid variable fails the build instead of crashing at runtime.

Works with Zod, Valibot, ArkType, Effect Schema, TypeBox — anything implementing the Standard Schema ~standard interface.

envault moves validation to the build step. The schema runs once during bundling; only the validated, typed values reach the output.

Install

npm install -D @risemaxi/envault

Quick start

1. Define a schema

// env.schema.ts
import { z } from "zod";

export default z.object({
  API_URL: z.string().url(),
  SECRET: z.string().min(1),
  PORT: z.coerce.number().default(3000),
});

2. Add the plugin

// vite.config.ts
import envault from "@risemaxi/envault/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [envault({ schemaFile: "./env.schema.ts" })],
});

3. Import your env

import { env } from "virtual:env";

console.log(env.API_URL); // string
console.log(env.PORT); // number

A ./env.d.ts is generated next to your config so env is fully typed from the schema's output.

Bundlers

Every integration takes the same options. Import the one for your bundler:

import envault from "@risemaxi/envault/vite";
import envault from "@risemaxi/envault/rollup";
import envault from "@risemaxi/envault/rolldown";
import envault from "@risemaxi/envault/webpack";
import envault from "@risemaxi/envault/rspack";
import envault from "@risemaxi/envault/esbuild";
import envault from "@risemaxi/envault/bun";
// webpack.config.js
const envault = require("@risemaxi/envault/webpack");

module.exports = {
  plugins: [envault({ schemaFile: "./env.schema.ts" })],
};

The default export of @risemaxi/envault is the Vite plugin:

import envault from "@risemaxi/envault";

Expo / Metro

// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const { withEnvConfig } = require("@risemaxi/envault/metro");

const config = getDefaultConfig(__dirname);

module.exports = withEnvConfig(config, {
  schemaFile: "./env.schema.ts",
});

withEnvConfig preserves any existing resolver.resolveRequest (including Expo's defaults) and delegates every other module to it.

Validation runs when the virtual module is first bundled, not when the Metro config is loaded. Tools that merely load your config without bundling — such as EAS Build's fingerprint and config-resolution steps, which run before your project's env vars are injected — therefore don't trip validation on env that isn't present yet.

Metro's resolver is synchronous, so an inline schema passed to withEnvConfig must validate synchronously. Use schemaFile for asynchronous schemas.

Passing a schema directly

Instead of a file path, pass the schema object inline:

import { z } from "zod";
import envault from "@risemaxi/envault/vite";

const schema = z.object({
  API_URL: z.string(),
  PORT: z.coerce.number().default(3000),
});

export default {
  plugins: [envault({ schema })],
};

With an inline schema no .d.ts is generated (there is no file path to reference). Type virtual:env yourself, or use schemaFile to get types for free.

How it works

  1. For a schemaFile, envault loads the schema in a child process with jiti, so .ts/.js schemas and their imports work the same in any project — CommonJS or ESM, any Node ≥ 18 — and validation runs in the child. The schema library stays out of the build process.
  2. For an inline schema, validation runs directly in the build.
  3. If validation fails, the build fails, listing every missing/invalid variable.
  4. On success the virtual module is generated as export const env = { ... } with the validated values inlined. Keys are JSON-quoted, so even names like MY-VAR are reachable via env["MY-VAR"].
  5. A .d.ts is written so env is typed directly from the schema's output.

No schema-library code or validation logic ends up in the final bundle.

Where env values come from

envault validates the environment your bundler/runtime provides, using each one's native env loading:

  • Vite loads .env files into import.meta.env, not process.env, so envault reads them through Vite's own loader (honoring your mode and envDir). .env, .env.local, and .env.[mode] work with no extra setup.
  • Bun and Expo / Metro load .env files into process.env, which envault reads directly.
  • Rollup, Rolldown, webpack, Rspack, and esbuild have no built-in .env loading. Populate process.env yourself — real environment variables (CI, shell) or a loader like dotenv (import "dotenv/config") before the build.

Real process.env values always take precedence over values from .env files.

Options

| Option | Type | Default | Description | | --------------- | ------------------ | --------------- | ------------------------------------------------------------------- | | schema | StandardSchemaV1 | — | A Standard Schema object. Mutually exclusive with schemaFile. | | schemaFile | string | — | Path (from project root) to a module that default-exports a schema. | | virtualModule | string | "virtual:env" | The virtual module specifier to import from. | | dtsFile | string | "./env.d.ts" | Where to write the generated .d.ts. |

schema and schemaFile are mutually exclusive; provide exactly one.

Supported schema libraries

Any library implementing Standard Schema v1:

Exported utilities

import {
  envault, // Vite plugin factory (also the default export)
  vite,
  rollup,
  rolldown,
  webpack,
  rspack,
  esbuild,
  bun, // per-bundler factories
  validateEnv, // (schema, env) => Promise<ValidatedEnv> — async, in-process
  validateEnvSync, // (schema, env) => ValidatedEnv — sync, throws on async schemas
  loadSchemaEnv, // (schemaFile, env) => ValidatedEnv — load + validate in a child process
  clearSchemaCache, // () => void — drop the loadSchemaEnv cache
  generateModule, // (env) => string — the virtual module source
  generateDts, // (schemaImport, virtualModule?) => string — the .d.ts source
} from "@risemaxi/envault";

@risemaxi/envault/metro additionally exports withEnvConfig.

License

MIT