Minimal Vite plugin for for isolating server-only and client-only code.
npm install -D vite-env-only
// vite.config.ts
import { defineConfig } from "vite"
import envOnly from "vite-env-only"
export default defineConfig({
plugins: [envOnly()],
})
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"],
},
}),
],
})
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",
],
},
}),
],
})
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"
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
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()
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
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.
Thanks to these project for exploring environment isolation and conventions for transpilation: