Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing env variables from index.html #3105

Closed
6 tasks done
edikdeisling opened this issue Apr 23, 2021 · 37 comments · Fixed by #12202
Closed
6 tasks done

Accessing env variables from index.html #3105

edikdeisling opened this issue Apr 23, 2021 · 37 comments · Fixed by #12202
Labels
enhancement New feature or request feat: html

Comments

@edikdeisling
Copy link
Contributor

edikdeisling commented Apr 23, 2021

Describe the bug

I try to insert html tag by environment variable condition and use vite-plugin-html for it. But Vite behaviour is weird

Reproduction

https://github.com/xSorc/test-vite-index-env

This code works only in build

<% if (import.meta.env.VITE_SHOW_TAG === '1') { %>
  works
<% } %>

During server i have this error

[vite] Internal server error: Cannot use 'import.meta' outside a module while compiling ejs
If the above error is not helpful, you may want to try EJS-Lint:
https://github.com/RyanZim/EJS-Lint
Or, if you meant to create an async function, pass `async: true` as an option.

This code works only in dev server

// vite.config.ts
require('dotenv').config();
// index.html
<% if (process.env.VITE_SHOW_TAG === '1') { %>
  works
<% } %>

But during build this doesn't work, because Vite replace process.env with this:

<% if (({}).VITE_SHOW_TAG === '1') { %>
  works
<% } %>
  1. The only option that works in both server and build is this:
// vite.config.ts
require('dotenv').config();
// index.html
<% if (process.env['VITE_SHOW_TAG'] === '1') { %>
  works
<% } %>

I suppose that first or second variant should works

System Info

Output of npx envinfo --system --npmPackages vite,@vitejs/plugin-vue --binaries --browsers:

  System:
    OS: Windows 10 10.0.19042
    CPU: (6) x64 Intel(R) Core(TM) i5-8600K CPU @ 3.60GHz
    Memory: 9.13 GB / 15.94 GB
  Binaries:
    Node: 14.15.3 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.10 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 6.14.9 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.19041.423.0), Chromium (90.0.818.42)
    Internet Explorer: 11.0.19041.1
  npmPackages:
    vite: 2.2.1 => 2.2.1

Used package manager:


Before submitting the issue, please make sure you do the following

@nihalgonsalves
Copy link
Member

nihalgonsalves commented Apr 23, 2021

You should use ejs template variables to have something more stable:

diff --git a/vite.config.ts b/vite.config.ts
index aaf065b..8cdf521 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,12 +1,12 @@
 // @ts-ignore
import dotenv from 'dotenv';
 
-dotenv.config();
+const { parsed } = dotenv.config();
 
 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [reactRefresh(), injectHtml()],
+  plugins: [reactRefresh(), injectHtml({ injectData: parsed })],
})
diff --git a/index.html b/index.html
index 7e1616f..8c7fce7 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
     <title>Vite App</title>
   </head>
   <body>
-  <% if (process.env.VITE_SHOW_TAG === '1') { %>
+  <%if (VITE_SHOW_TAG === '1') { %>
   works
   <% } %>
   <script type="module" src="/src/index.tsx"></script>

(though I'd probably pass in required variables explicitly)

@edikdeisling
Copy link
Contributor Author

@netchampfaris makes sense, thank you!

But should Vite be more definite in this case and have same behavior for dev\prod while working with index.html?
I think this issue may be closed if working with env variables inside index.html is forbidden

@nihalgonsalves
Copy link
Member

Maybe @patak-js can weigh in.

The define plugin doesn't handle this case during dev:

https://github.com/vitejs/vite/blob/5fe9a69/packages/vite/src/node/plugins/define.ts#L16

And the import analysis plugin, which does, is always executed after the plugins:

https://github.com/vitejs/vite/blob/5fe9a69/packages/vite/src/node/plugins/index.ts#L66

So the plugins do not see the rewritten env. Is this something we want to enable?

@nihalgonsalves nihalgonsalves added the p2-edge-case Bug, but has workaround or limited in scope (priority) label Apr 23, 2021
@patak-dev
Copy link
Member

I think it makes sense for import.meta.env to work during dev, I couldn't find an explanation why this should be avoided.

Just for reference, looks like there are plugins available also https://github.com/lxs24sxl/vite-plugin-html-env

@patak-dev patak-dev added the bug label Apr 23, 2021
@IndexXuan
Copy link

https://github.com/IndexXuan/vite-plugin-html-template
same like vue-cli public/index.html syntax(lodash.template + like html-webpack-plugin injected vars)
examples:https://github.com/IndexXuan/vue-enterprise-boilerplate/blob/feature/vite/public/index.html#L11

@nihalgonsalves nihalgonsalves changed the title Using process.env inside index.html Accessing env variables from index.html May 17, 2021
@zardoy
Copy link

zardoy commented Jul 5, 2021

I think this issue may be closed if working with env variables inside index.html is forbidden

But what about a more regular case, if I just simply need to access env variable. For example Snowpack and CRA support this: <title>%SOME_ENV%</title>. I also think that this should be documented on the env page

@IndexXuan
Copy link

@zardoy use transform html hook or plugin above.

@darkylmnx
Copy link

In Vue CLI this was something native, and it's kind of weird anyway for it to work on build and not on dev.
If on dev you need things like PayPal, Gmaps or any SDK API key loaded through a script src.
You would need an env var in the index.html file.

<script src="https://www.paypal.com/sdk/js?client-id=<%= import.meta.env.VITE_SOME_KEY %>&disable-funding=credit,card,venmo,sepa,bancontact,eps,giropay,ideal,mybank,p24,sofort"></script>

That's actually my use-case.

I don't think using an external plugin for that is a good solution knowing it works in build mode.

@liho98
Copy link

liho98 commented Aug 11, 2021

yes we need this feature

@cosmith
Copy link

cosmith commented Sep 14, 2021

If you have multiple modes something like this lets you reuse Vite's env parsing:

import {defineConfig, loadEnv} from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import reactRefresh from "@vitejs/plugin-react-refresh";
import {injectHtml} from "vite-plugin-html";

export default defineConfig(({mode}) => {
    const env = loadEnv(mode, "env"); // reuse vite's env parser to inject into our index.html
    console.log(`Using env mode ${mode}`, env);
    return {
        plugins: [reactRefresh(), tsconfigPaths(), injectHtml({injectData: {...env, MODE: mode}})],
        envDir: "env",
        server: {port: 3001},
    };
});

This enables me to use MODE and the various VITE_ variables in my index.html file. Not great but it will do for now!

@nightah
Copy link

nightah commented Sep 28, 2021

There seem to be some shortcomings of the EJS/HTML injection technique.

From what I can tell it doesn't like including double quotes "" and can cause a compile-time error as below:

vite v2.5.10 building for production...
✓ 0 modules transformed.
[vite:build-html] Unable to parse {"file":"/srv/repos/authelia/web/index.html","line":16,"column":23}
14 |  </head>
15 |  
16 |  <body data-basepath=""{{.Base}}"" data-rememberme='{{.RememberMe}}' data-resetpassword='{{.ResetPassword}}' data-theme='{{.Theme}}'>
   |                        ^
17 |    <noscript>You need to enable JavaScript to run this app.</noscript>
18 |    <div id="root"></div>
file: /srv/repos/authelia/web/index.html
error during build:
Error: Unable to parse {"file":"/srv/repos/authelia/web/index.html","line":16,"column":23}
14 |  </head>
15 |  
16 |  <body data-basepath=""{{.Base}}"" data-rememberme='{{.RememberMe}}' data-resetpassword='{{.ResetPassword}}' data-theme='{{.Theme}}'>
   |                        ^
17 |    <noscript>You need to enable JavaScript to run this app.</noscript>
18 |    <div id="root"></div>

I've attempted to use the unescaped EJS tag as using the escaped HTML tag will result in a non-desirable output.
Is there a recommended way to work around this issue? Should this potentially be filed as a separate/different bug?

@idleberg
Copy link

@nightah You should probably post the EJS tags you're using in your template and how you inject data. I've noticed similar problems, maybe our two use-cases can provide some solid background for this issue.

@nightah
Copy link

nightah commented Sep 29, 2021

@idleberg a completely reproducible case can be found here with the following relevant portions:

vite.config.ts
https://github.com/authelia/authelia/blob/f4d095a7b225ef87626b54fc0d97e4b5eb41859b/web/vite.config.ts#L31-L35

index.html
https://github.com/authelia/authelia/blob/f4d095a7b225ef87626b54fc0d97e4b5eb41859b/web/index.html#L15

It's worthwhile noting that during the dev mode with vite the output appears to be as expected. That is to say that the index.html appears to render correctly when I view the source of the page:

<body data-basepath="" data-rememberme="true" data-resetpassword="true" data-theme="light">

Which mirrors the configuration: https://github.com/authelia/authelia/blob/82f1847e75b9fbb1c26527fb38ec1b787b582a7d/web/.env.development#L2-L5

If I instead try to perform a production build here's the output I see from the HTML:

<body data-basepath={{.Base}} data-rememberme={{.RememberMe}} data-resetpassword={{.ResetPassword}} data-theme={{.Theme}}>

This is similar to the configuration here however with the small key difference that it's consuming the quotes surrounding the EJS tags.

I've also tested to confirm that the issue isn't specifically relating to the values being rendered in the EJS tags, if I copy the value from .env.production and run it in dev mode I get the correct output.

I thought perhaps I could get around this issue by including explicit double quotes "" surrounding said values, however, that leads to the issue I described in #3105 (comment).

@nightah
Copy link

nightah commented Oct 11, 2021

Okay so I've got to the bottom of this, the consumption of the quotes isn't a Vite issue, it's a problem that manifests from vite-plugin-html.

If I don't use that plugin Vite's output of the index.html post-production build is identical with the input index.html.

For clarity here's an output index.html with vite-plugin-html performing env replacements with EJS tags:

<!doctype html><html lang=en><head><base href={{.BaseURL}}><meta property=csp-nonce content={{.CSPNonce}} /><meta charset=utf-8 /><meta name=viewport content="width=device-width,initial-scale=1"/><meta name=theme-color content=#000000 /><meta name=description content="Authelia login portal for your apps"/><link rel=manifest href=./manifest.json /><link rel=icon href=./favicon.ico /><title>Login - Authelia</title><script type=module crossorigin src=./static/js/index.829172be.js></script><link rel=modulepreload href=./static/js/vendor.d0bc79df.js><link rel=stylesheet href=./static/css/index.393eb37d.css></head><body data-basepath={{.Base}} data-rememberme={{.RememberMe}} data-resetpassword={{.ResetPassword}} data-theme={{.Theme}}><noscript>You need to enable JavaScript to run this app.</noscript><div id=root></div></body></html>

You'll notice in the above extract that almost all sets of double quotes "" have been consumed along with the transformation.

Output with Vite and the transformIndexHtml Vite hook performing env replacements per solution described further down:

<!DOCTYPE html>
<html lang="en">

<head>
  <base href="{{.BaseURL}}">
  <meta property="csp-nonce" content="{{.CSPNonce}}" />
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Authelia login portal for your apps" />
  <link rel="manifest" href="./manifest.json" />
  <link rel="icon" href="./favicon.ico" />
  <title>Login - Authelia</title>
  <script type="module" crossorigin src="./static/js/index.dd4afd07.js"></script>
  <link rel="modulepreload" href="./static/js/vendor.88e06a7b.js">
  <link rel="stylesheet" href="./static/css/index.6d03b4c9.css">
</head>

<body data-basepath="{{.Base}}" data-rememberme="{{.RememberMe}}" data-resetpassword="{{.ResetPassword}}" data-theme="{{.Theme}}">
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  
</body>

</html>

For those that are looking at a solution similar to CRA or others where you can just template %VARNAME% in your index.html and have it replaced during the dev server and production builds you can use the following which leverages part of what @cosmith provided above along with Vite's transformIndexHtml hook:

import {defineConfig, loadEnv} from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import reactRefresh from "@vitejs/plugin-react-refresh";

export default defineConfig(({mode}) => {
    const env = loadEnv(mode, "env");

    const htmlPlugin = () => {
        return {
            name: "html-transform",
            transformIndexHtml(html: string) {
                return html.replace(/%(.*?)%/g, function (match, p1) {
                    return env[p1];
                });
            },
        };
    };

    return {
        plugins: [htmlPlugin(), reactRefresh(), tsconfigPaths()],
    };
});

Just remember that only variables that are prefixed with VITE_ are passed across as per the Vite documentation.

@idleberg feel free to give this a try and let me know if it resolves your problem.

@hornta
Copy link

hornta commented Nov 11, 2021

I don't know if this is related but I give it a try.

I'm trying to use a environment variable in my index.html by passing it like this:

<script async src="https://www.googletagmanager.com/gtag/js?id=import.meta.env.VITE_ANALYTICS_ID"></script>

but this gives bad html output because vite adds double quotes around the interpolated value so that my html end up looking like:

<script async src="https://www.googletagmanager.com/gtag/js?id="G-XXXXXX""></script>

Is there any way to disable Vite adding double quotes around these injected variables?
Thanks

@theonelucas
Copy link

I don't know if this is related but I give it a try.

I'm trying to use a environment variable in my index.html by passing it like this:

<script async src="https://www.googletagmanager.com/gtag/js?id=import.meta.env.VITE_ANALYTICS_ID"></script>

but this gives bad html output because vite adds double quotes around the interpolated value so that my html end up looking like:

<script async src="https://www.googletagmanager.com/gtag/js?id="G-XXXXXX""></script>

Is there any way to disable Vite adding double quotes around these injected variables? Thanks

Vite doesn't do that. Check you .env file and make sure there's no double quotes around your property value, e.g.: PROP=VALUE

@piotr-cz
Copy link

piotr-cz commented Dec 22, 2021

Solution mentioned in #3105 (comment) worked great
until v2,7.5 which now stops with an error during build when parser encounters href="%VITE_ENV_VAR%":

[vite:build-html] Unable to parse HTML; URI malformed
 at "xxx/index.html"

Invoking plugin before Vite core plugins with enforce: 'pre' doesn't help

This is probably a side-effect of #6199 which makes html plugin use [decodeURI] for assets:

https://github.com/poyoho/vite/blob/95b86b1cd367e0ce7adade3368350ec3ee5d4ee6/packages/vite/src/node/plugins/html.ts#L293

Workaround: use # to wrap variable placeholder as it is then evaluated as value excluded form checks

@piotr-cz
Copy link

piotr-cz commented Dec 22, 2021

Update: using enforce: 'pre' inside plugin worked. Full code:

// vite.config.ts
import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => ({
  plugins: [
    htmlPlugin(loadEnv(mode, '.')),
  ]
}))

/**
 * Replace env variables in index.html
 * @see https://github.com/vitejs/vite/issues/3105#issuecomment-939703781
 * @see https://vitejs.dev/guide/api-plugin.html#transformindexhtml
 */
function htmlPlugin(env: ReturnType<typeof loadEnv>) {
  return {
    name: 'html-transform',
    transformIndexHtml: {
      enforce: 'pre' as const,
      transform: (html: string): string =>
        html.replace(/%(.*?)%/g, (match, p1) =>
          env[p1] ?? match
        ),
    }
  }
}

@nightah
Copy link

nightah commented Dec 22, 2021

I haven't seen any issues upgrading to v2.7.5 or v2.7.6 for that matter with the aforementioned method.
I have not needed to include enforce: 'pre'.

@carlmathisen
Copy link

I installed vite-plugin-ejs to be compatible with @vue/cli.

import { ViteEjsPlugin } from 'vite-plugin-ejs';
export default defineConfig({
  plugins: [ViteEjsPlugin]
});

Then I could use <% %> in index.html to access env vars like before.

@linzebingo
Copy link

linzebingo commented Mar 2, 2022 via email

@bluwy
Copy link
Member

bluwy commented Mar 6, 2022

This doesn't look like an issue with Vite, but rather the html plugin vite-plugin-ejs, vite-plugin-html, etc. Vite doesn't support accessing environment variables in HTML, other than inside the script tag. If it's needed to use env variables in HTML, the transformIndexHtml hook would do the trick.

@bluwy bluwy added enhancement: pending triage feat: html p2-to-be-discussed Enhancement under consideration (priority) and removed bug p2-edge-case Bug, but has workaround or limited in scope (priority) labels Mar 6, 2022
@bluwy bluwy added this to Team Board Mar 6, 2022
@bluwy bluwy moved this to Discussing in Team Board Mar 6, 2022
@matthiassommer
Copy link

Finally, I got it working with this commit.

@Tusko
Copy link

Tusko commented Jun 4, 2022

the simpliest way
https://www.npmjs.com/package/vite-plugin-html-env

@TheDutchCoder
Copy link

TheDutchCoder commented Jul 15, 2022

I had to change the function to this:

function htmlPlugin (env: ReturnType<typeof loadEnv>) {
  return {
    name: 'html-transform',
    transformIndexHtml: {
      enforce: 'pre' as const,
      transform: (html: string) => {
        return html.replace(/<%=(.*?)%>/g, (match, p1) => env[p1] ?? match)
      },
    },
  }
}

In order to also support things that output a full tag:

<!DOCTYPE html>
<html lang="en">

<head>
  <%=VITE_CONTENT_SECURITY_POLICY%>
</head>

Edit: looks like this doesn't work in dev mode 😢

@lritter79
Copy link

I'm using process.env in the index.html file of my react app with Vite, and for some reason, it's actually working in prod. No plugin, or anything.
Here's the code:

<%if (process.env.NODE_ENV === 'production') { %>
   <script
     id="ze-snippet"
     src="https://static.zdassets.com/ekr/snippet.js?key=foobar"
   >
  </script>
<% } %>

Is this technically correct since it's working, or is there a better way to do this?

@johnsmithjsjs
Copy link

johnsmithjsjs commented Nov 9, 2022

I needed conditional logic right inside HTML to add <noscript> tag in production and staging, but not locally, so vite-plugin-ejs worked great for me.

vite.config.ts:

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { ViteEjsPlugin } from 'vite-plugin-ejs'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    ViteEjsPlugin((viteConfig) => ({
      // viteConfig is the current Vite resolved config
      env: viteConfig.env,
    })),
    vue(),
  ],
  // ...
})

Then inside index.html:

<% if (env.VITE_APP_ENV !== 'local') { %>
  <script>console.log('production or development')</script>
<% } else { %>
  <script>console.log('local')</script>
<% } %>


<% if (env.VITE_APP_ENV === 'production') { %>
  <script src="https://widget.intercom.io/widget/<%= env.VITE_APP_INTERCOM_APP_ID %>"></script>
<% } %>


<body>
  <% if (env.VITE_APP_ENV !== 'local' ) { %>
    <!-- <Google Tag Manager (noscript)> -->
    <noscript>
      <iframe src="https://www.googletagmanager.com/ns.html?id=..."
        height="0"
        width="0"
        style="display:none;visibility:hidden"
      ></iframe>
    </noscript>
    <!-- </Google Tag Manager (noscript)> -->
  <% } %>
</body>

@johnsmithjsjs
Copy link

P.S. vite-plugin-html-env didn't work for me because it's currently full of bugs. For example, having <!-- comments --> inside your index.html leads to some lines being randomly removed from the output. It's cool though, it allows you write something like this:

<script vite-if="import.meta.env.VITE_APP_ENV === local">console.log('local')</script>
<script vite-else>console.log('production or development')</script>

P.P.S. transformIndexHtml didn't work too because it's just a simple replacer, i needed if/else logic.

@patak-dev patak-dev moved this from Discussing to P2 - 5 in Team Board Dec 15, 2022
@arsenitem
Copy link

Thanks @johnsmithjsjs , your solution worked perfectly

@kyle-villeneuve
Copy link

If you're already using vite-plugin-html there is a very simple solution which seems to work quite well.

vite.config.ts

createHtmlPlugin({
  // add environment variables to build
  inject: { data: Object.assign({ mode }, process.env) },
})

This will allow you to use environment variables in index.html like so. The following code block will print out all injected variables.

<script>
  console.log(<%- JSON.stringify(locals, null, 2) -%>)
</script>

@patak-dev
Copy link
Member

We discussed this feature with the team and decided the use case is typical enough to justify having built-in support in Vite core.

We're considering providing interpolation of env variables only, and using the proposed syntax: %VITE_KEY%. For other use cases discussed in this issue, like conditionals we would suggest adopting a full-fledge HTML pre-processor or creating inline plugins for your projects.

@patak-dev patak-dev added enhancement New feature or request and removed enhancement: pending triage labels Feb 23, 2023
@patak-dev patak-dev moved this from P2 - 5 to Approved in Team Board Feb 23, 2023
@bluwy bluwy removed the p2-to-be-discussed Enhancement under consideration (priority) label Feb 26, 2023
@PunkFleet
Copy link

PunkFleet commented Mar 4, 2023

We discussed this feature with the team and decided the use case is typical enough to justify having built-in support in Vite core.

We're considering providing interpolation of env variables only, and using the proposed syntax: %VITE_KEY%. For other use cases discussed in this issue, like conditionals we would suggest adopting a full-fledge HTML pre-processor or creating inline plugins for your projects.

Is this already released in version 4.1.4?

@isik-dev
Copy link

isik-dev commented Mar 7, 2023

@Birddle The PR is yet to be merged => #12202, so no.

@lorand-horvath
Copy link

@patak-dev Thanks for adding support for this in vite@4.2 #12202 (comment)

@patak-dev
Copy link
Member

@lorand-horvath the shoutout goes to @bluwy for implementing it ❤️

@github-actions github-actions bot locked and limited conversation to collaborators Mar 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request feat: html
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.