Rahul
Rahul

Reputation: 1123

Add script into HTML using vite and vue 3

I have one js file which needs to be put in the public directory and needs to add it in the final production build as a text/javascript.

I have checked the options in vite config but couldn't find anything useful. The files I add contain a global JSON object and can be accessed directly.

To achieve this, I tried this solution.

vite.config.ts

import { fileURLToPath, URL } from "url";
import path from 'path';
// import test from "./src/assets/test.js"
import test from "./public/test.js"

import { defineConfig , loadEnv} from "vite";
import vue from "@vitejs/plugin-vue";
import { loadingScript } from 'vite-plugin-loading-script'

export default defineConfig(({ command, mode }) => {
  // Load env file based on `mode` in the current working directory.
  // Set the third parameter to '' to load all env regardless of the `VITE_` prefix.
  const env = loadEnv(mode, process.cwd(), '')
  return {
    // vite config
    define: {
      __APP_ENV__: JSON.stringify(env.VITE_REDIRECT_URL),
      __TEST__: test,
    },
    plugins: [vue()],
    server: {
      hmr: {
        overlay: false,
      },
    },
    resolve: {
      alias: {
        "@": fileURLToPath(new URL("./src", import.meta.url)),
      },
    },
    build: {
      // rollupOptions: {
      //   external: ['__APP_ENV__'],
      //   output: {
      //     globals: {
      //       __APP_ENV__: JSON.stringify(env.VITE_REDIRECT_URL),
      //     }
      //   }
      // }
    }
  }
});

test.js

export default {
    REDIRECT_URL: "https://example.com/",
    API_URL: "https://example.com/",
};

with the above changes, I got the console.log('__TEST__', __TEST__) as expected JSON object but it doesn't work with the production build.

Upvotes: 1

Views: 5340

Answers (2)

Parzival
Parzival

Reputation: 51

If I understood you correctly, you want to inject the vars REDIRECT_URL and API_URL into your app. First off, in our case we stopped using .env, process.env, etc, as it didn't work in the production build (perhaps we were using it incorrectly). Our app uses Vite 3.

TL;DR

The solution for us was to mix different techniques that could solve both scenarios:

  1. during local development;
  2. serving the production app.

Let's use the API_URL as an example. Change your test.js file to look like this:

test.js

window.__RUNTIME_CONFIG__ = {
    API_URL: 'https://example.com/'
};

Your test.js file should be inside the public folder.

What we are doing here, is to inject a new global object to the window called __RUNTIME_CONFIG__ (you could name it whatever you want).

Now make sure that you are including this file in your index.html:

index.html

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

<head>
    <script src="test.js"></script>
    ...
</head>
...

</html>

Simple enough right?

console.log

The real deal

At this point your problem (in its basic form) should be solved but let's be honest, that's not fancy at all and there's nothing magical there. We could add some extra sauce:

  • Typescript support
  • Multiple environments (local, STG, PRD, etc)
  • Dynamic variables such as adding a version number.

Typescript

If you would like this to be typed-safe you could add a globals.ts file with the following content:

export {};

declare global {
    interface Window {
        __RUNTIME_CONFIG__: {
            API_URL: string;
        };
        __PACKAGE_JSON_VERSION__: string;
    }
}

And an envVars.ts file with the following content:

export const envVars = {
    getVars: () => {
        if (!window.__RUNTIME_CONFIG__.API_URL) {
            throw new Error('Environment variables misconfiguration.');
        }

        return {
            API_URL: window.__RUNTIME_CONFIG__.API_URL
        };
    }
};
const { API_URL } = envVars.getVars();
export { API_URL };

Your folder structure will end up something like this:

folder structure

Now you can use it something like this:

import axios from 'axios';
import { API_URL } from 'src/domain';

export const axiosBase = axios.create({
    baseURL: API_BASE_URL
});

Multiple environments

Now things start to get a little more tricky but before we dive into the juicy stuffs, I want to make things clear first:

There's no such thing as "environment" or "env vars" when we deal with client side applications. The "environment" is each of your users using their own devices. We'll use the word "environment" for the sake of simplicity and just to mention different types of servers (local environment, QA environment, production environment, etc).

In our case we use runtime env configurations (runtime-env.js), for you it would be your test.js file. Create one test-{env}.js per environment and locate them inside your public folder. Adjust your test-{env}.js files accordingly:

multiple environments

Now you will face 2 scenarios:

  1. local development;
  2. serving the production app.

For scenario 1. modify your package.json file and add the following script:

"predev": "bash -c \"cp public/runtime-env-local.js public/runtime-env.js\"",

assuming you start your project using yarn dev. If you use the yarn start command instead, simply change predev to prestart.

For scenario 2. the answer is: it depends, but the main idea is to be able to copy one of the test-{env}.js files into test.js because that is what your index.html file is expecting. We use docker for all of our applications, so what we did was to create a .sh bash file to do that before the CMD command is executed.

env.sh

#!/bin/bash

cp "test-$RUNTIME_ENV.js" "test.js"

Dockerfile

FROM nginx:stable-alpine
...
# Copy .env file and shell script to container
COPY env.sh /usr/share/nginx/html/env.sh

# Make the shell script executable
RUN chmod +x /usr/share/nginx/html/env.sh

CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""]

Then we spin up our docker like this:

docker build -t my-awesome-app .
docker run -d -p 5174:80 -e RUNTIME_ENV=production --name my-awesome-app my-awesome-app

Hope this helps someone else in the future.

Upvotes: 0

Muratkhan Ghalamat
Muratkhan Ghalamat

Reputation: 17

maybe you can try including the js file to the html in the public directory

Upvotes: 0

Related Questions