I have WebPack React project which I'm testing on my "staging" server.
Now its time to release it on "production" server.
I'm using server.json file which consists with server info such as api keys, api address, and so on.
What I want is to use different server.json for "production" and "staging".
And when I use production-server.json, there would be no traces of staging-server.json in my bundle.
src
- config
-- config.js
-- production-server.json
-- staging-server.json
maybe something like: yarn build-staging, yarn build-production
You should use environment variables and webpack's DefinePlugin. Additionally, you can use node-config to automatically load a json configuration file based on your NODE_ENV.
package.json
"scripts": {
"build:dev": "NODE_ENV=development start-something",
"build": "NODE_ENV=production start-something"
}
project config structure
config
default.json
{ "api": "https://api.mysite.com/v1" }
staging.json
{ "api": "http://localhost:8000/v1" }
webpack config
// node-config will load your staging.json or default.json file here
// depending on what NODE_ENV is
const config = require('config');
plugins: [
// inject window.CONFIG into your app
new webpack.DefinePlugin({
CONFIG: JSON.stringify(config)
})
]
Then in your react code you will have access to environment-specific config
componentDidMount() {
// in prod: https://api.mysite.com/v1/user/some-user-id
// in staging: http://localhost:8000/v1/user/some-user-id
return axios(`${CONFIG.api}/user/${this.props.userId}`).then(whatever...)
}
If you're on windows use cross-env to set your environment variable.
Using node-config isn't the only way to do this, there are several, but I find it pretty easy, unless you're working with electron.
edit
Since node-config uses nodejs it is typically used in front end projects in conjunction with webpack. If you are unable to to integrate it with webpack you don't need to use node-config at all, I would do something like this:
project structure
config
default.json
development.json
test.json
index.js
src
...etc
config files
// default.json, typically used for production
{
"api": "https://api.mysite.com/v1"
}
// development.json
{
"api": "http://localhost:8000/v1"
}
// index.js
// get process.env via babel-plugin-transform-inline-environment-variables
import production from './default.json';
import development from './development.json';
const { NODE_ENV: env } = process.env;
const config = {
production,
development
};
export default config[env];
Related
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;
},
};
I have an application where locally (without pm2) all the environment variables in the .env file work just fine using dotenv.
But on the server where I'm using pm2 to run the app, the environment variables remain undefined.
The pm2 commands I'm using to run the app on server are:
pm2 start myapp/app.js
pm2 startup
pm2 save
dotenv will read .env file located in the current directory.
When you call pm2 start myapp/app.js it won't search for myapp/.env.
.env // It will try to load this, which doesn't exist
myapp/
app.js
So you have two solutions
use path option:
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '.env') });
Or call your script from inside myapp/
pm2 start app.js
A good pattern here is to remove dotenv from your code and "require" it on the command line. This makes your code nicely transportable between any environment (including cloud-based) - which is one of the main features of environment variables.
Note: you will still need to install dotenv in your project via npm when running it on a server.
a) code up your .env file alongside your script (e.g. app.js)
b) to run your script without pm2:
node -r dotenv/config app.js
c) in pm2.config.js:
module.exports = {
apps : [{
name : 'My Application',
script : 'app.js',
node_args : '-r dotenv/config',
...
}],
}
and then
pm2 start pm2.config.js
note: the use of dotenv/config on the command line is one of the best practices recommended by dotenv themselves
edit 2021: for completeness - as my answer has got some ticks, I wanted to add a 4th option to the list:
d) combined pm2/env config
module.exports = { apps : [{
name : 'My Application',
script : 'app.js',
env : {
PORT: 5010,
DB_STRING: 'mongodb://localhost:27017',
...
},
}]};
This will be useful if you are treating your pm2.config as environmental configuration and outside of git etc. It just negates the need for a separate .env, which may suit you. It negates the need for dotenv completely as pm2 injects the env variables into your script's process
you have kill you pm2 process first
try
pm2 kill
then restart pm2 using
pm2 start app.js
I had the same problem but it wasnt explained clearly so here is the solution based on
github user vmarchaud comment.
This also fixes the issue people had with #Andy Lorenz solution.
In my case i wanted to create an ecosystem file for multiple apps but i was keep getting
Error: Cannot find module 'dotenv/config'
The solution was easy.
You have to declar cwd, aka the project folder where the dotenv/config will be read from.
module.exports = {
apps: [{
name: 'app1 name',
script: 'app1.js',
cwd: '/path/to/folder/',
exec_mode: 'fork_mode',
node_args: '-r dotenv/config',
}, {
name: 'app2 name',
script: 'app2.js',
cwd: '/path/to/folder/',
instances: 'max',
exec_mode: 'cluster',
node_args: '-r dotenv/config',
}],
};
You can parse .env using dotenv lib end set them manually in ecosystem.config.js
ecosystem.config.js:
const { calcPath, getEnvVariables } = require('./helpers');
module.exports = {
apps: [
{
script: calcPath('../dist/app.js'),
name: 'dev',
env: getEnvVariables(),
},
],
};
helpers.js:
const path = require('path');
const dotenv = require('dotenv');
const fs = require('fs');
function calcPath(relativePath) {
return path.join(__dirname, relativePath);
}
// this function will parce `.env` file but not set them to `process.env`
const getEnvVariables = () => {
const envConfig = dotenv.parse(fs.readFileSync(calcPath('.env')));
const requiredEnvVariables = ['MODE'];
for (envVariable of requiredEnvVariables) {
if (!envConfig[envVariable]) {
throw new Error(`Environment variable "${envVariable}" is not set`);
}
}
return envConfig;
};
None of this worked for me because I was using cluster mode.
I installed dotenv as dev dependency at the root (I was using yarn workspaces too).
Then I did this:
require('dotenv').config({ path: 'path/to/your/.env' })
module.exports = {
apps: [
{
name: 'app',
script: 'server/dist/index.js',
instances: 2,
exec_mode: 'cluster',
instance_var: 'APP_INSTANCE_SEQ',
// listen_timeout: 10000,
// restart_delay: 10000,
}
]
}
I use a much simpler version of #Marcos answer:
.env
app.js
for example we need to store token in .env file and pass it right to app.js:
inside .env
token=value
inside app.js:
require('dotenv').config();
console.log(process.env.token)
Also, don't forget. If you add .env file to .gitignore and then git pull you repo on VPS or smth, you need to copy .env file manually, otherwise your app won't work.
And in some cases it's important in what area you are using your config, so make sure that NODE_ENV=production string is added to your .env file.
After all you could use pm2 start app.js right from your app's folder.
This was my project setup..
/src/app.ts
which than compiled into dist folder.
/dist/app.js
my .env file was outside dist folder so it wasn't accessible.
this is the command i tried.
pm2 start app.js --env=.env
In a static folder I have config.js
module.exports = {
config: {
urls: {
auth: localhost
}
}
}
I run npm run build and send the output (dist folder) to the client to deploy in their production environment. I want the client to be able to edit the value of auth.
config is currently configured as a external file in webpack:
const config = require(path.join(paths.STATIC, 'config.js'))
externals: [{
appsetting: JSON.stringify(config)
}]
How do I make config.js recognize changes post webpack build?
How about something like this, using axios:
function readConfig () {
return axios.get('./static/config.js').then((response) => {
return response.data
});
}
readConfig().then((config) => {
// Do stuff
});
And make sure config.js is copied to the static/ folder.
Create an entry file in webpack.config for config.js and import/require config.js in other files where you consume the config.
My project structure is as follows:
- workspace
- customPackage
- customIndex.js
- myProject
- index.js
- myProject2
- index.js
In dev, I want to import the package from my local workspace like following:
//index.js
import something from '../customePackage/customIndex.js'
where as in production I need the import to work from npm modules like the following:
//index.js
import something from 'customPackage';
The purpose being to be able to use the local changes in the package(without going through the commit cycle). Finally after testing the package can be pushed and used normally via npm package.
How to do this in an efficient way without having to make code changes every time?
You can use Resolve#alias with Webpack:
resolve: {
alias: {
"customPackage": process.env.NODE_ENV === "production" ?
"customPackage" :
path.resolve(__dirname, "../customePackage/customIndex.js")
}
}
Then in your source, you only need to do:
import something from 'customPackage';
And it will point to the correct package. Obviously you need to set the NODE_ENV environment variable, or change that depending on your build environment.
if you are already using webpack you can make two different entry points:
entry: {
bundle: './Scripts/index.tsx',
bundle2: './Scripts/index2.tsx'
},
output: {
publicPath: "/js/",
path: path.join(__dirname, '/wwwroot/js/'),
filename: '[name].js'
},
then in index you import main module and in index2 import your test module. So you will have different bundle files bundle.js and bundle2.js.
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