Snowpack: Inject ENV variables into build

Created on 29 May 2020  路  13Comments  路  Source: snowpackjs/snowpack

Original Discussion: https://www.pika.dev/npm/snowpack/discuss/253
/cc @jiay0u, @FredKSchott

Currently, Snowpack has no good way to read env variables into your frontend application. Things like process.env.NODE_ENV won't work, since process is a Node thing that doesn't exist in the browser.

We should support either import.meta.env.FOO (Node-style) or import.meta.getEnv('FOO') (Deno style). Since this is a build-time transformation that gets converted to a string (and not an actual function call) I think Node-style makes the most sense.

The easiest way to test this would be as a 3rd party plugin that implements this in a transform() function. If we can get that working, then we can decide to integrate it into Snowpack directly.

Most helpful comment

Also for anyone who's blocked by this today, you could support this via a Babel plugin and the old process.env concept: https://babeljs.io/docs/en/babel-plugin-transform-node-env-inline

All 13 comments

See https://create-react-app.dev/docs/adding-custom-environment-variables/ for how CRA does it.

https://github.com/vitejs/vite seems to have first-party support for .env files, which is also interesting. I'd love to see a plugin that supports this as well.

How would you go about this?

I can easily see how a transform plugin would add this in to a file's content, however how would you go about determining which file to add it to. You surely don't want to add it to every file that is transformed?

I think the easiest way would be a REGEX find and replace to replace all references to import.meta.env with the actual string env value. This is what Webpack and others do today.

  • FIND: /import\.meta\.env\.(.*)/gm (.* isn't right, but you get the gist :)
  • REPLACE: the value of process.env.$1 when dev/build is run.

The plugin would implement something like:

{
  transform({ contents, urlPath, isDev }) {
      if (!urlPath.endsWith(".js")) {
        return null;
      }
      // DO FIND AND REPLACE, AND RETURN
}

Also for anyone who's blocked by this today, you could support this via a Babel plugin and the old process.env concept: https://babeljs.io/docs/en/babel-plugin-transform-node-env-inline

An alternate interface for consideration:

// snowpack.config.js

module.exports = {
  scripts: {
    'mount:src': 'mount ./src --to /',
  },
  plugins: ['@snowpack/plugin-env'],
}
// your app code
import env from '@snowpack/plugin-env'

console.log(env.NODE_ENV)

When the plugin is asked to transform itself it will more or less return:

export default {
  // process.env outputted here
  NODE_ENV: 'development'
}

This seems more explicit and less "magical", however I guess the main downside is that it departs from webpack's convention, which I guess means some lock in. But it is lock in to a plugin not snowpack itself.

This is a really interesting approach, although there are two issues that we'd need to solve for:

  1. I don't think we have any examples of a plugin also being a frontend web dependency, and it's kind of messing with my brain. I'm not opposed to it, but it feels like a more advanced discussion of how can a plugin provide its own dependency into the app.
  2. When we officially support this, we can't inject all process.env out into a module like that due to the high likelihood of leaking private keys, data, etc. Whatever we implement would need to be whitelisted to only include the keys actually used in your app (which is another reason that the find-replace system fits this model well).

Next.js just has an env key on their config file. https://nextjs.org/docs/api-reference/next.config.js/environment-variables

Next also uses the NEXT_PUBLIC_ prefix, which is making me rethink whether we should too. I'd feel terrible if someone used some private key in their app without realizing that it was going out to the world.

Maybe we just go full "dotenv" support and build that into Snowpack instead of pulling from process.env directly. That feels a bit safer, since anything in your system ENV probably isn't meant for a public website. Also https://github.com/motdotla/dotenv is actually really easy to integrate, in my past experience.

Yeah I'm not sure 1. is a problem in itself but I can see how it could cause confusion.

  1. is definitely an issue, however the plugin options could easily be used to specify the variables, especially as the snowpack config can be a js file and has access to process.env. So you could have something like:
module.exports = {
  scripts: {
    'mount:src': 'mount ./src --to /',
  },
  plugins: [
    ['config/index', { 
      NODE_ENV: process.env.NODE_ENV
    }]
  ],
}

I'm also a fan of dotenv and would like to see that support OOB as well. However I wonder if this would cause problems for some code bases if they are already using a .env file out of the same code base to run a node server and it has a bunch of not for public secrets in it.

Ah you're right, that must be why they still use NEXT_PUBLIC_ and REACT_APP_ even in the .env files.

If we do the same, then it would be fine to use one .env for both server and client since we could filter out anything that doesn't start with SNOWPACK_PUBLIC/SP_PUBLIC_/PUBLIC before passing it to the build-time transformer.

I'm interested in this topic.

Okay, took a stab at this: https://github.com/pikapkg/snowpack/pull/394

There are some open questions in the TODO section, would love any feedback.

Merged via #394

Was this page helpful?
0 / 5 - 0 ratings

Related issues

FredKSchott picture FredKSchott  路  6Comments

wenyanqi picture wenyanqi  路  5Comments

devongovett picture devongovett  路  4Comments

FredKSchott picture FredKSchott  路  6Comments

FredKSchott picture FredKSchott  路  4Comments