vercel / storage

Vercel Postgres, KV, Blob, and Edge Config
https://vercel.com/storage
Apache License 2.0
513 stars 56 forks source link

feature: Support Vite by falling back from `process.env` to `import.meta.env` #108

Closed elliott-with-the-longest-name-on-github closed 1 year ago

elliott-with-the-longest-name-on-github commented 1 year ago

In all of our packages, we support no-config on platforms that use process.env. Vite does not -- it uses import.meta.env, with a catch. To avoid exposing system env vars to your build process, Vite requires its env vars to be prefixed with VITE_.

This means, for example, that the following code in Postgres:

export function postgresConnectionString(
  type: ConnectionStringType = 'pool',
): string | undefined {
  let connectionString: string | undefined;

  switch (type) {
    case 'pool': {
      connectionString = process.env.POSTGRES_URL;
      break;
    }
    case 'direct': {
      connectionString = process.env.POSTGRES_URL_NON_POOLING;
      break;
    }
    default: {
      const _exhaustiveCheck: never = type;
      const str = _exhaustiveCheck as string;
      throw new VercelPostgresError(
        'invalid_connection_type',
        `Unhandled type: ${str}`,
      );
    }
  }

  if (connectionString === 'undefined') connectionString = undefined;
  return connectionString;
}

could support Vite by doing:

export function postgresConnectionString(
  type: ConnectionStringType = 'pool',
): string | undefined {
  let connectionString: string | undefined;

  switch (type) {
    case 'pool': {
-      connectionString = process.env.POSTGRES_URL;
+      connectionString = process.env?.POSTGRES_URL ?? import.meta.env?.VITE_POSTGRES_URL;
      break;
    }
    case 'direct': {
-      connectionString = process.env.POSTGRES_URL_NON_POOLING;
+      connectionString = process.env?.POSTGRES_URL_NON_POOLING ?? import.meta.env?.VITE_POSTGRES_URL_NON_POOLING;
      break;
    }
    default: {
      const _exhaustiveCheck: never = type;
      const str = _exhaustiveCheck as string;
      throw new VercelPostgresError(
        'invalid_connection_type',
        `Unhandled type: ${str}`,
      );
    }
  }

  if (connectionString === 'undefined') connectionString = undefined;
  return connectionString;
}

We could also potentially enhance the error by detecting if the user is running Vite by doing something like:

if (import.meta.env.MODE !== undefined) {
  // enhance error with a "You might need to prefix your environment variables with `VITE_`" message
}

One note of caution: The ideal experience here would also require some level of integration with build & deploy + our examples in the storage dashboard. Ideally, connecting a store to a project that's running on Vite would automatically prefix its env vars with VITE_.

elliott-with-the-longest-name-on-github commented 1 year ago

cc #99 and #107 -- this would allow both of these use cases to be zero-config.

dferber90 commented 1 year ago

This sounds like the way to go :thumbs:

Not sure what would happen to import.meta.env when compiling for cjs tho

correttojs commented 1 year ago

When running console.log(import.meta?.env?.VITE_KV_REST_API_URL); inside the package, right before reading process.env.KV_REST_API_URL, it logs undefined (note that it's calling the index.js file and not the .cjs). The same log works inside a +page.server.ts file.

The only workaround I found is to define the env variables inside the vite.config.ts file.

export default ({ mode }) => {
  process.env.VITE_KV_REST_API_URL = "..."
  return defineConfig({
    plugins: [sveltekit()],
  });
};
elliott-with-the-longest-name-on-github commented 1 year ago

I'm silly, we can't do this. The reason Vite only reads VITE_ variables into import.meta.env is so that they don't accidentally get bundled into client code. This would inevitably expose variables at some point. Instead, I added docs to our packages:

https://github.com/vercel/storage/pull/117

@correttojs Not sure if there's a good place on front to put some sort of setup instructions like this, but basically one of those two solutions is what people need to do. (The first one is pretty close to what you're doing here.)