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 🙏

© 2024 – Pkg Stats / Ryan Hefner

vite-env-only

v2.3.0

Published

Minimal Vite plugin for for isolating server-only and client-only code

Downloads

41,335

Readme

ci workflow

vite-env-only

Minimal Vite plugin for for isolating server-only and client-only code.

Install

npm install -D vite-env-only

Setup

// vite.config.ts
import { defineConfig } from "vite"
import envOnly from "vite-env-only"

export default defineConfig({
  plugins: [envOnly()],
})

Options

denyImports

Configures validation of import specifiers that should not be present on the client or server. Validation is performed against the raw import specifier in the source code.

{
  denyImports?: {
    client?: Array<string | RegExp>
    server?: Array<string | RegExp>
  }
}

For example:

// vite.config.ts
import { defineConfig } from "vite"
import envOnly from "vite-env-only"

export default defineConfig({
  plugins: [
    envOnly({
      denyImports: {
        client: ["fs-extra"],
      },
    }),
  ],
})

denyFiles

Configures validation of files that should not be present on the client or server. Validation is performed against the resolved and normalized root-relative file path.

{
  denyFiles?: {
    client?: Array<string | RegExp>
    server?: Array<string | RegExp>
  }
}

For example:

// vite.config.ts
import { defineConfig } from "vite"
import envOnly from "vite-env-only"

export default defineConfig({
  plugins: [
    envOnly({
      denyFiles: {
        client: [
          // Deny all files with a `.server` suffix
          /\.server\.(\w+)?$/,
          // Deny all files nested within a `.server` directory
          /(^|\/).server\//,
          // Deny a specific file
          "src/secrets.ts",
        ],
      },
    }),
  ],
})

Macros

serverOnly$

Marks an expression as server-only and replaces it with undefined on the client. Keeps the expression as-is on the server.

For example:

import { serverOnly$ } from "vite-env-only"

export const message = serverOnly$("i only exist on the server")

On the client this produces:

export const message = undefined

On the server this produces:

export const message = "i only exist on the server"

clientOnly$

Marks an expression as client-only and replaces it with undefined on the server. Keeps the expression as-is on the client.

For example:

import { clientOnly$ } from "vite-env-only"

export const message = clientOnly$("i only exist on the client")

On the client this produces:

export const message = "i only exist on the client"

On the server this produces:

export const message = undefined

Dead-code elimination

This plugin eliminates any identifiers that become unreferenced as a result of macro replacement.

For example, given the following usage of serverOnly$:

import { serverOnly$ } from "vite-env-only"
import { readFile } from "node:fs"

function readConfig() {
  return JSON.parse(readFile.sync("./config.json", "utf-8"))
}

export const serverConfig = serverOnly$(readConfig())

On the client this produces:

export const serverConfig = undefined

On the server this produces:

import { readFile } from "node:fs"

function readConfig() {
  return JSON.parse(readFile.sync("./config.json", "utf-8"))
}

export const serverConfig = readConfig()

Type safety

The macro types capture the fact that values can be undefined depending on the environment.

For example:

import { serverOnly$ } from "vite-env-only"

export const API_KEY = serverOnly$("secret")
//           ^? string | undefined

If you want to opt out of strict type safety, you can use a non-null assertion (!):

import { serverOnly$ } from "vite-env-only"

export const API_KEY = serverOnly$("secret")!
//           ^? string

Why?

Vite already provides import.meta.env.SSR which works in a similar way to these macros in production. However, in development Vite neither replaces import.meta.env.SSR nor performs dead-code elimination as Vite considers these steps to be optimizations.

In general, its a bad idea to rely on optimizations for correctness. In contrast, these macros treat code replacement and dead-code elimination as part of their feature set.

Additionally, these macros use function calls to mark expressions as server-only or client-only. That means they can guarantee that code within the function call never ends up in the wrong environment while only transforming a single AST node type: function call expressions.

import.meta.env.SSR is instead a special identifier which can show up in many different AST node types: if statements, ternaries, switch statements, etc. This makes it far more challenging to guarantee that dead-code completely eliminated.

Prior art

Thanks to these project for exploring environment isolation and conventions for transpilation: