How to use environment variables at build time in a VueJS project - javascript

I'm trying to use an environment variable during the build stage of a CI job for a VueJS app. I'm using GitLab CI, and one of the environment variables that is made available is CI_COMMIT_SHORT_SHA,
build:
image: node:latest
stage: build
variables:
CI_COMMIT_SHORT_SHA: "$CI_COMMIT_SHORT_SHA"
artifacts:
paths:
- dist/
script:
- npm install --progress=false
- npm run build
- echo "Build Complete"
Here's how I'm trying to use this variable in Vue:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>This is a static site that is served with a CloudFront distribution in front of an S3 bucket.</p>
<p>The site is updated through a GitLab CI/CD pipeline.</p>
<p>Commit ref: {{ commit }}</p>
<p>Using cache invalidation</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return {
commit: process.env.CI_COMMIT_SHORT_SHA
}
}
}
</script>
I'm not seeing this variable come through. Is there something else I need to do in order to access the environment variable and display it in my component?

As mentioned in https://cli.vuejs.org/guide/mode-and-env.html#using-env-variables-in-client-side-code
Only variables that start with VUE_APP_ will be statically embedded into the client bundle with webpack.DefinePlugin. You can access them in your application code:
console.log(process.env.VUE_APP_SECRET)

If you're using webpack.config, you can set up DefinePlugin in a similar way.
In your webpack.config.js you would use a new plugin,
new webpack.DefinePlugin({
/*
JSON.stringify(yourconfig) is highly recommened
to avoid overwriting existing keysother process.env
*/
'process.env': JSON.stringify(config.prod), // or config.dev
}),
Where config.prod / config.dev is something like
let config = {};
config.prod = require('./config/prod.env'); // imports ./config/prod.env.js
config.dev = require('./config/dev.env');
at the top of the file,
and the prod.env.js and dev.env.js files look something like
'use strict';
module.exports = {
VUE_APP_MODE: 'MYMODE'
};
If you wanted to match the vue process more closely,you could filter out the object keys using RegExp /^VUE_APP_.*/.
Then in the data section of your .vue file you can include these by using:
data: {
VUE_APP_MODE: process.env.VUE_APP_MODE
}

After some research it seemed that the vue-cli-service build command only looks into the dot-files in the root of your project, and only processes these variables starting with VUE_APP_ (in various .env files)
You could set all the variables in the Gitlab CI options and then copy those variables to the .env file. Now, when vue-cli builds the project, it injects these values in the transpiled scripts.
You could issue a command like this before you build the project:
env | grep 'VUE_APP_' > .env
I also use a staging environment that is built when pushing into the staging branch. Therefore I have these variables set into Gitlab:
VUE_APP_VAR1=foo
VUE_APP_VAR2=bar
VUE_ACCEPT_VAR1=foo
VUE_ACCEPT_VAR2=bar
Since vue-cli wants the variables to start with VUE_APP_ I do a replace with sed:
env | grep 'VUE_ACCEPT_' | sed 's/VUE_ACCEPT_/VUE_APP_/' > .env

Related

Ignore variable dependency of node_module webpack

I have built a library that I want to use in a Next.JS project. Within this library a certain dependency is using an import via a string passed into a require statement within the source code where the import is taking place. This is causing webpack to not recognize the import. I don't want to change code within any node_modules as this is not a preferred approach but how can I ensure that my project using the library I built is able to compile and run?
Within file_using_string_passed_into_require_to_get_import.js:
let importName = "./potential_import_A.js"
if(condition){
importName = "./potential_import_B.js"
}
module.exports = require(importName)
This is the folder structure:
Project/
| node_modules
| my-library
| node_modules
| library-dependency
| file_using_string_passed_into_require_to_get_import.js
| potential_import_A.js
| potential_import_B.js
To create a local (unpublished) library package
Create a 'my-library' folder (outside your current project dir).
Do npm init (Folder must include the 'package.json' )
Include source code (potential_import_A), exporting any desired functions.
In the actual project folder:
cd into the folder of the project that needs to use your library.
Run npm install --save local/path/to/my-library.
The --save will add the package to your dependencies in the project's package.json file, as it does with 3rd party published packages. It will also add a copy of the source code to the node modules folder of the project, as always.
Importing your new library:
import/require the package as you would normally, from any project.
For example
import { myFunction } from "my-library"
I got it to work by excluding node_modules from the webpack build. Since I am using Next.JS this is within my next.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
webpack: (
config,
{
buildId, dev, isServer, defaultLoaders, nextRuntime, webpack,
},
) => {
if (isServer) {
config.target = 'node';
config.node = {
__dirname: true,
global: true,
__filename: true,
};
config.externals = [nodeExternals()], // in order to ignore all modules in node_modules folder
config.externalsPresets = {
node: true, // in order to ignore built-in modules like path, fs, etc.
};
}
return config;
},
};

Application modularity with Vue.js and local NPM packages

I'm trying to build a modular application in Vue via the vue-cli-service. The main app and the modules are separated projects living in different folders, the structure is something like this:
-- app/package.json
/src/**
-- module1/package.json
/src**
-- module2/package.json
/src**
The idea is to have the Vue app completely agnostic about the application modules that can be there at runtime, the modules themself are compiled with vue-cli-service build --target lib in a local moduleX/dist folder, pointed with the package.json "main" and "files" nodes.
My first idea (now just for development speed purposes) was to add the modules as local NPM packages to the app, building them with a watcher and serving the app with a watcher itself, so that any change to the depending modules would (I think) be distributed automatically to the main app.
So the package.json of the app contains dependencies like:
...
"module1": "file:../module1",
"module2": "file:../module2",
...
This dependencies are mean to be removed at any time, or in general be composed as we need, the app sould just be recompiled and everything should work.
I'm trying to understand now how to dynamically load and activate the modules in the application, as I cannot use the dynamic import like this:
import(/* webpackMode: "eager" */ `module1`).then(src => {
src.default.boot();
resolve();
});
Because basically I don't know the 'module1', 'module2', etc...
In an OOP world I would just use dependency injection retrieving classes implementing a specific interface, but in JS/TS I'm not sure it is viable.
There's a way to accomplish this?
Juggling with package.json doesn't sound like a good idea to me - doesn't scale. What I would do:
Keep all available "modules" in package.json
Create separate js file (or own prop inside package.json) with all available configurations (for different clients for example)
module.exports = {
'default': ['module1', 'module2', 'module3'],
'clientA': ['module1', 'module2', 'module4'],
'clientB': ['module2', 'module3', 'module4']
}
tap into VueCLI build process - best example I found is here and create js file which will run before each build (or "serve") and using simple template (for example lodash) generate new js file which will boot configured modules based on the value of some ENV variable. See following (pseudo)code (remember this runs inside node during build):
const fs = require('fs')
const _ = require('lodash')
const modulesConfig = require(`your module config js`)
const configurationName = process.env.MY_APP_CONFIGURATION ?? 'default'
const modules = modulesConfig[configurationName]
const template = fs.loadFileSync('name of template file')
const templateCompiled = _.template(template)
const generatedJS = templateCompiled({ `modules`: modules })
fs.writeFileSync('bootModules.js', generatedJS)
Write your template for bootModules.js. Simplest would be:
<% _.forEach(modules , function(module) { %>import '<%= module %>' as <%= module %><% }); %>;
import bootModules.js into your app
Use MY_APP_CONFIGURATION ENV variable to switch desired module configuration - works not just during development but you can also setup different CI processes targeting same repo with just different MY_APP_CONFIGURATION values
This way you have all configurations at one place, you don't need to change package.json before every build, you have simple mechanism to switch between different module configurations and every build (bundle) contains only the modules needed....
In an OOP world I would just use dependency injection retrieving classes implementing a specific interface, but in JS/TS I'm not sure it is viable.
Why not?
More than this, with JS/TS you are not restricted to use classes implementing a specific interface: you just need to define the interface (i.e. the module.exports) of your modules and respecting it in the libraries entries (vue build lib).
EDIT: reading comments probably I understood the request.
Each module should respect following interface (in the file which is the entry of the vue library)
export function isMyAppModule() {
return true;
}
export function myAppInit() {
return { /* what you need to export */ };
}
Than in your app:
require("./package.json").dependencies.forEach(name => {
const module = require(name);
if(! module.isMyAppModule || module.isMyAppModule() !== true) return;
const { /* the refs you need */ } = module.myAppInit();
// use your refs as you need
});

Keys defined in dev.env.js are not accessible with webpack in vuejs

The template is based on vuejs-webpack, and the build, config files are here, I have not modified any of these files.
Based on Environment Variables the keys defined in dev.env.js file must be accessible when running npm run dev in the app.
This is the content of my dev.env.js:
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
SAMPLE: '"XX"',
AUTH_URL: '"http://localhost:3030"'
})
And when I try to access AUTH_URL in App.vue like this process.env.AUTH_URL, I receive undefined.
It seems to me whatever is defined in dev.env.js file will never become accessible when running npm run dev
Use the webpack.DefinePlugin to define the variables you wish to share with your front end. Webpack its self does not expose process to the browser as this is a node js function.
const dev_env = require('dev.env.js)
plugins: [
new webpack.DefinePlugin({
'process.env' : {
NODE_ENV: JSON.stringify(dev_env.NODE_ENV)
}
})
]
Some variation of the above should work for you.
If your run "npm run dev", then your new environment variable is only available after a restart of npm (Ctrl + C and again 'npm run dev').

Setting environment variables in Gatsby

I used this tutorial: https://github.com/gatsbyjs/gatsby/blob/master/docs/docs/environment-variables.md
Steps I followed:
1) install dotenv#4.0.0
2) Create two files in root folder: ".env.development" and ".env.production"
3) "follow their setup instructions" (example on dotenv npm docs)
In gatsby-config.js:
const fs = require('fs');
const dotenv = require('dotenv');
const envConfig =
dotenv.parse(fs.readFileSync(`.env.${process.env.NODE_ENV}`));
for (var k in envConfig) {
process.env[k] = envConfig[k];
}
Unfortunately, when i run gatsby develop, NODE_ENV isn't set yet:
error Could not load gatsby-config
Error: ENOENT: no such file or directory, open 'E:\Front-End Projects\Gatsby\sebhewelt.com\.env.undefined'
It works when I set it manually:
dotenv.parse(fs.readFileSync(`.env.development`));
I need environment variables in gatsby-config because I put sensitive data in this file:
{
resolve: `gatsby-source-contentful`,
options: {
spaceId: envConfig.CONTENTFUL_SPACE_ID,
accessToken: envConfig.CONTENTFUL_ACCESS_TOKEN
}
}
How to make it work?
PS: Additional question - As this made me think, I know I shouldn't put passwords and tokens on github, but as netlify builds from github, is there other safe way?
I had a similar issue, I created 2 files in the root ".env.development" and ".env.production" but was still not able to access the env file variables - it was returning undefined in my gatsby-config.js file.
Got it working by npm installing dotenv and doing this:
1) When running gatsby develop process.env.NODE_ENV was returning undefined, but when running gatsby build it was returning 'production' so I define it here:
let env = process.env.NODE_ENV || 'development';
2) Then I used dotenv but specify the filepath based on the process.env.NODE_ENV
require('dotenv').config({path: `./.env.${env}`});
3) Then you can access your variables for your config:
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-contentful`,
options: {
spaceId: `${process.env.CONTENTFUL_ID}`,
accessToken: `${process.env.CONTENTFUL_TOKEN}`,
},
},
],
}
You should only use env files when you're comfortable checking those into git. For passwords/tokens/etc. add them to Netlify or whatever build tool you use through their dashboard.
These you can access in gatsby-config.js & gatsby-node.js via process.env.ENV_VARIABLE.
You can't access environment variables added this way in the browser however. For this you'll need to use .env.development & .env.production.
I really dislike the .env.production file pattern, our build system sets up and uses env variables and having extra build steps to write those into a file is weird. But Gatsby only whitelists GATSBY_ of the env vars, with no obvious way of adding your own.
But doing that isn't so hard, you can do it by adding something like this in the gatsby-node.js file:
exports.onCreateWebpackConfig = ({ actions, getConfig }) => {
const config = getConfig();
// Allow process.env.MY_WHITELIST_PREFIX_* environment variables
const definePlugin = config.plugins.find(p => p.definitions);
for (const [k, v] of Object.entries(process.env)) {
if (k.startsWith("MY_WHITELIST_PREFIX_")) {
definePlugin.definitions[`process.env.${k}`] = JSON.stringify(v);
}
}
actions.replaceWebpackConfig(config);
};
After doing a few searches, I found that we can set environment variables through netlify website, here are the steps:
Under your own netlify console platform, please go to settings
Choose build & deploy tab (can be found on sidebar)
Choose environment sub-tab option
Click edit variables and add/put your credentials in
Done!

Angular2 global configuration file

I've got an Angular2 project with this structure :
client/ // Angular2 client
app/
app.component.ts
...
main.ts
...
server/ // API
server.js
config/ // config files
webpack.config.js
...
I'd like to have all constants and parameters of the Angular2 app (like the url to the API...) in the config directory, with all other config files.
How can I perform it in Angular2 ? As the config folder is outside the client folder, is it a good practice to import something that is outside, with many "../../../" ?
Also I wanted to use dependency injection, but is there anything less heavy ?
And how can I avoid to import manually the file in each component/module I want to use it ?
Thx
What I used is probably the not best approach but, I'm using webpack to inject global variables with the DefinePlugin plugin:
I use the .env file on root, to store the variables, I have a .env.TST, .env.PRD and replace it with deployment script
webpack.common.js
var preEnv = require('../.env');
var envVars = {};
for(var propertyName in preEnv) {
envVars[propertyName] = '"'+preEnv[propertyName]+'"';
}
...
plugins: [
new webpack.DefinePlugin(
envVars
)
]
...
Example .env file
module.exports = {
"APIURL": "https://localhost/MyAPI/",
"PUBLIC_URL": "https://localhost:3000/",
"BASE_PATH": "/"
"ENV": "dev"
}
And you will have APIURL as a global variable
Additionaly I added a file into the typings, to prevent warnings:
typings/typings.d.ts
declare var APIURL: string;
declare var PUBLIC_URL: string;
declare var ENV: string;
declare var BASE_PATH: string;
I hope it helps to someone
Inside you package.json you can write an npm script for every environment. that you are going to support:
"scripts": {
"build:dev": "webpack --config config/webpack.dev.js",
"build:prod": "webpack --config config/webpack.prod.js"
}
Then in every config/webpack.xxxx.dev script you can declare global variables using Webpack DefinePlugin || ExtendedDefinePlugin. It is vital to understand that this plugin allows you to have your global server-side node.js variables, become available as global client-side .js variables.
This is how your webpack.dev.js might look like
new DefinePlugin({
'ENV': JSON.stringify('Development'),
'URL': JSON.stringify('http://...')
})
...
This is how your webpack.prod.js might look like
new DefinePlugin({
'ENV': JSON.stringify('Prod'),
'URL': JSON.stringify('http://...')
})
...
Finally is your .ts files you can then write
declare var URL: string; //keeps the compiler happy
let configUrl: string = URL;
link to example github project: ng2a.frontend

Categories

Resources