I see multiple ways to create config files in Node.js. One uses module.exports in js file, one just use plain json object.
// config1.js
module.exports = {
config_1: "value 1",
config_2: "value 2"
}
// config2.json
{
"config_1": "value 1",
"config_2": "value 2"
}
Is there any advantages of using module.exports in config file? What are the differences?
javascript CommonJS Module
comments
conditionals
loops and such to populate defaults
code to change config based on NODE_ENV or similar
code to look for external files for SSL keys, API credentials, etc
easier to have fallbacks and defaults
JSON file
easy to parse and update with external tools
compatible with pretty much every programming language out there
pure data that can be loaded without being executed
easy to pretty print
JSON could start as the basis and all the code items described above about CommonJS module could live in a config.js module that reads config.json as it's starting point
So I always start with a commonjs module for the convenience, but keep any logic in there simple. If your config.js has bugs and needs tests, it's probably too complicated. KISS. If I know for a fact other things are going to want poke around in my config, I'll use a JSON file.
Thanks #jonathan-ong, looks like config.js (NOT JSON file) works as expected and I could put some comments.
module.exports = {
// Development Environment
development: {
database: {
host: '127.0.0.1',
login: 'dev',
password: 'dev'
}
},
// Production Environment
production: {
database: {
host: '127.0.0.1',
login: 'prod',
password: 'prod'
}
}
};
js files have their own perks as #Peter Lyons mentioned. But if I don't have to access external resources for API keys and etc. I would prefer JSON for config files.
Simple reason being I would not have to touch my code for just for the sake of making changes to config files. I can simply make an API to edit these json files to add,update or remove any configuration key-value pair. Even add whole new config file for a separate environment using an API.
And use config module to consume these configurations
Related
I am looking for a safe way of doing require aliases using gulp.
The idea is similar to what webpack offers with resolve alias.
For example, I put on my source code something like require('#plugin/utils') and it is translated to something of my choice, like require('$/plugins/longer/namespace/utils'). You probably have noticed that it does not match to any actual file path, and that is intentional (they are tiddlywiki files if anyone is interested).
The best thing I found is gulp-replace, but that relies on strings replacements at worst and regular expressions at best.
Ideally I would want something more reliable, something that is AST aware,so I'm sure that I never replace the wrong string (for example, a template string).
I am also using babel, so if there is a babel plugin that I can use I will also be happy about that.
As a last resort I may try to write some babel plugin or a gulp plugin using esprima, but esprima is not up to modern JS standard (doesn't parse object spread) and I would prefer creating another tool.
Thanks in advance
Finally I found a babel module (babel-plugin-module-resolver) that I can integrate with gulp, and with some extra configuration magic with eslint.
So, this is what I added to my gulp pipeline (simplified)
const babelCfg = {
plugins: [[
require.resolve('babel-plugin-module-resolver'),
{
root: [ `${pluginSrc}/**` ],
alias: { '#plugin': `$:/plugins/${pluginNamespace}` },
loglevel: 'silent',
}
]],
};
return gulp
.src(`${pluginSrc}/**/*.js`)
.pipe(babel(babelCfg))
.pipe(gulp.dest(outPath.dist));
That converts all the references to require('#plugin/xxxx') with the proper path.
With some extra configuration you can even make eslint warn you about bad path resolutions.
You need to configure it differently because eslint needs the real path, while the output files needs a special namespace. After installing both eslint-import-resolver-babel-module and eslint-plugin-import this is what you should add to your eslint config to make it work:
plugins:
- import
env:
node: true
browser: true
settings:
import/resolver:
babel-module:
root: ./src/**
alias:
"#plugin": ./src/the/real/path
rules:
import/no-unresolved: [2, { commonjs: true }]
I have implemented the application configuration to be read from JSON file from my angular application so that it is easily configurable post deploment.
We use Octopus deployment tool to deploy the angular application. As you are aware the having he enviornment.ts file gets generated as js file and it
is very difficult to manipulate the settings in Octopus by reading the JS file. Hence I have created config.JSON file that would be outputted to the dist folder and
Octopus deploy can consume the JSON file to make changes whenever needed.
Currently the application is reading values from enviornment ts file which in turn is reading the values set in the Config.JSON file.
I would like to know if my approach is correct ?
Config.JSON
{
"settings": {
"production": false,
"userIdleMinutes": "10",
"corePingIntervalSeconds": "10",
"baseUrl": "http://localhost:57973",
"loginUrl": "/Login",
"adminUrl": "/Admin"
}
}
enviornment.ts
import config from '../assets/config.json'
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: config.settings.production,
baseUrl: config.settings.baseUrl,
loginUrl:config.settings.loginUrl,
adminUrl: config.settings.adminUrl,
userIdleMinutes: config.settings.userIdleMinutes,
corePingIntervalSeconds: config.settings.corePingIntervalSeconds
};
if you are using octopus, instead of doing that, in your environment.prod.ts add some tokens and replace them on each environment by octopus
(right now I don't have access to show you how, but something like below)
environment.prod.ts
export const environment = {
production: #{production},
baseUrl: "#{baseUrl}",
//... rest of your configs
};
then add a step to replace these variables with the variables defined in environment in main.*.js file on each deployment
In ASP.NET Core, the JsonConfigurationProvider will load configuration from appsettings.json, and then will read in the environment version, appsettings.{Environment}.json, based on what IHostingEnvironment.EnvironmentName is. The environment version can override the values of the base appsettings.json.
Is there any reasonable way to preview what the resulting overridden configuration looks like?
Obviously, you could write unit tests that explicitly test that elements are overridden to your expectations, but that would be a very laborious workaround with upkeep for every time you change a setting. It's not a good solution if you just wanted to validate that you didn't misplace a bracket or misspell an element name.
Back in ASP.NET's web.config transforms, you could simply right-click on a transform in Visual Studio and choose "Preview Transform". There are also many other ways to preview an XSLT transform outside of Visual Studio. Even for web.config parameterization with Parameters.xml, you could at least execute Web Deploy and review the resulting web.config to make sure it came out right.
There does not seem to be any built-in way to preview appsettings.{Environment}.json's effects on the base file in Visual Studio. I haven't been able to find anything outside of VS to help with this either. JSON overriding doesn't appear to be all that commonplace, even though it is now an integral part of ASP.NET Core.
I've figured out you can achieve a preview with Json.NET's Merge function after loading the appsettings files into JObjects.
Here's a simple console app demonstrating this. Provide it the path to where your appsettings files are and it will emit previews of how they'll look in each environment.
static void Main(string[] args)
{
string targetPath = #"C:\path\to\my\app";
// Parse appsettings.json
var baseConfig = ParseAppSettings($#"{targetPath}\appsettings.json");
// Find all appsettings.{env}.json's
var regex = new Regex(#"appsettings\..+\.json");
var environmentConfigs = Directory.GetFiles(targetPath, "*.json")
.Where(path => regex.IsMatch(path));
foreach (var env in environmentConfigs)
{
// Parse appsettings.{env}.json
var transform = ParseAppSettings(env);
// Clone baseConfig since Merge is a void operation
var result = (JObject)baseConfig.DeepClone();
// Merge the two, making sure to overwrite arrays
result.Merge(transform, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Replace
});
// Write the preview to file
string dest = $#"{targetPath}\preview-{Path.GetFileName(env)}";
File.WriteAllText(dest, result.ToString());
}
}
private static JObject ParseAppSettings(string path)
=> JObject.Load(new JsonTextReader(new StreamReader(path)));
While this is no guarantee there won't be some other config source won't override these once deployed, this will at least let you validate that the interactions between these two files will be handled correctly.
There's not really a way to do that, but I think a bit about how this actually works would help you understand why.
With config transforms, there was literal file modification, so it's easy enough to "preview" that, showing the resulting file. The config system in ASP.NET Core is completely different.
It's basically just a dictionary. During startup, each registered configuration provider is run in the order it was registered. The provider reads its configuration source, whether that be a JSON file, system environment variables, command line arguments, etc. and builds key-value pairs, which are then added to the main configuration "dictionary". An "override", such as appsettings.{environment}.json, is really just another JSON provider registered after the appsettings.json provider, which obviously uses a different source (JSON file). Since it's registered after, when an existing key is encountered, its value is overwritten, as is typical for anything being added to a dictionary.
In other words, the "preview" would be completed configuration object (dictionary), which is composed of a number of different sources, not just these JSON files, and things like environment variables or command line arguments will override even the environment-specific JSON (since they're registered after that), so you still wouldn't technically know the the environment-specific JSON applied or not, because the value could be coming from another source that overrode that.
You can use the GetDebugView extension method on the IConfigurationRoot with something like
app.UseEndpoints(endpoints =>
{
if(env.IsDevelopment())
{
endpoints.MapGet("/config", ctx =>
{
var config = (Configuration as IConfigurationRoot).GetDebugView();
return ctx.Response.WriteAsync(config);
});
}
});
However, doing this can impose security risks, as it'll expose all your configuration like connection strings so you should enable this only in development.
You can refer to this article by Andrew Lock to understand how it works: https://andrewlock.net/debugging-configuration-values-in-aspnetcore/
You will find instructions to reproduce on your own device at the bottom.
I have a basic Angular project I created using Angular CLI, running on TypeScript 3.1.3, with nothing much added aside a class and a json file.
I created a class ResourcesService with the following command with Angular CLI:
ng generate service Resources
I'm basically using it to load json files, as a mean of localising (instead of using Angular unfinished builtin internationalisation features).
The following is my class, as well as the json file:
ResourcesBundle.json
{
"label.changeLanguage": "Change language",
"label.education": "Education",
"label.experience": "Experiences",
"label.skills": "Skills",
"label.summary": "Summary",
"label.language.english": "English",
"label.language.french": "French"
}
resources.service.ts
import * as resources from '../assets/resources/ResourcesBundle.json';
#Injectable({
providedIn: 'root'
})
export class ResourcesService {
constructor() {}
public getString(label: string): string {
let resource: string = resources[label];
return resource;
}
}
Of course, in order to be able to import the json file that way, I've set "resolveJsonModule": true in tsconfig.json.
The service by itself is working properly. I can inject it and call the getString method, and it's running without any error.
However, no matter what value I pass to the getString method, the returned value is always undefined. I've even tried to hard code the value for label = 'label.summary', but it's still returning undefined. The only time it's working properly is when I write the string directly between the brackets:
let resource: string;
label = 'label.summary';
resource = resources[label]; // resource == undefined
resource = resources['label.summary']; // resource == 'Summary'
Within the TS on VSCode, the content of resources is as following:
label.changeLanguage
label.education
label.experience
label.language.english
label.language.french
label.skills
label.summary
When using console.log(resources), the console was displaying something like this on Firefox:
Object {
label.changeLanguage: "Change language"
label.education: "Education"
label.experience: "Experience"
label.language.english: "English"
label.language.french: "French"
label.skills: "Skills"
label.summary: "Summary"
}
So the json is properly loaded, but it can only be used with hard coded string.
The only other solution I found was to give up the json file and initialise an indexed type directly in the code:
private resources: { [key: string]: string } = {
'label.changeLanguage': 'Change language',
'label.education': 'Education',
'label.experience': 'Experiences',
'label.skills': 'Skills',
'label.summary': 'Summary',
'label.language.english': 'English',
'label.language.french': 'French'
};
However, I don't think that's a good approach, at all...
In the case of the imported json file, why does it always return undefined when I use a variable? Or otherwise, why does it work only with a hard coded string between the brackets?
Edit:
You will find below a stackblitz link to a demo project:
https://stackblitz.com/edit/angular-h2aspf?file=tsconfig.json
If you run it on the browser, it will work properly (the console will properly display Change language).
However, if you download it and run it locally, you will notice that the console will display undefined instead.
To run it locally:
You must have npm and Angular CLI
Download and unzip the stackblitz demo in a folder
Run npm i in the project folder
Run ng serve --open
Open the console on your browser, it should be displaying undefined, instead of the expected value (Change language on stackblitz)
Edit:
According to a comment on the Angular CLI issue, a workaround is to set "esModuleInterop": true in tsconfig.json, and to change the import statement from:
import * as resources from '../assets/resources/ResourcesBundle.json';
To this:
import resources from '../assets/resources/ResourcesBundle.json';
Original answer:
After checking multiple times on different devices, I think this is a bug directly related to Angular (current version: 7.0.2).
To take the example I gave in the question again:
https://stackblitz.com/edit/angular-h2aspf?file=tsconfig.json
On the browser, this demo is outputting Change language on the console.
On locale device:
Download and unzip the stackblitz demo in a folder
Run npm i in the project folder
If you run with ng serve, you will notice undefined in the web browser console
Stop Angular, then run again with ng serve --prod. The web browser console is now properly outputting Change language
I've opened the following issues for Angular and Angular CLI projects on GitHub for this problem:
Angular: Issue #26785: Imported json file as indexed type always giving undefined when Angular is not running in production mode
Angular CLI: Issue #12781: Imported json file as indexed type always giving undefined, but not when running ng serve --prod
I am currently learning Meteor and I found out something that intrigued me.
I can load HTML and CSS assets from a JS file using the import statement.
import '../imports/hello/myapp.html';
import '../imports/hello/myapp.css';
import * as myApp from '../imports/hello/myapp.js';
This was a surprise to me so I ran to google but could not find this behavior documented in the specification for ES6 import or in Meteor's Docs.
So my questions are:
Can I rely on this behavior to build my apps?
Will my app will break when Meteor gets around to fix it -- if it's a bug --?
Notes
I am using Meteor v1.3, not sure if this works also with previous versions.
You can download the app to see this behavior from Github
After going through the implementation of the built files for my app
I found out why this works.
HTML
Files are read from the file system and their contents added to the global Template object, e.g.,
== myapp.html ==
<body>
<h1>Welcome to Meteor!</h1>
{{> hello}}
</body>
results in the following JS code:
Template.body.addContent((function () {
var view = this;
return [
HTML.Raw("<h1>Welcome to Meteor!</h1>\n\n "),
Spacebars.include(view.lookupTemplate("hello"))
];
}));
Which is wrapped in a function with the name of the file as it's key:
"myapp.html": function (require, exports, module) {
Template.body.addContent((function () {
var view = this;
return [
HTML.Raw("<h1>Welcome to Meteor!</h1>\n\n "),
Spacebars.include(view.lookupTemplate("hello"))];
}));
Meteor.startup(Template.body.renderToDocument);
Template.__checkName("hello");
Template["hello"] = new Template("Template.hello", (
function () {
var view = this;
return [
HTML.Raw("<button>Click Me</button>\n "),
HTML.P("You've pressed the button ",
Blaze.View("lookup:counter",
function () {
return Spacebars.mustache(view.lookup("counter"));
}), " times.")
];
}));
},
So all of our HTML is now pure JS code which will be included by using require like any other module.
CSS
The files are also read from the file system and their contents are embedded also in JS functions, e.g.
== myapp.css ==
/* CSS declarations go here */
body {
background-color: lightblue;
}
Gets transformed into:
"myapp.css": ["meteor/modules", function (require, exports, module) {
module.exports = require("meteor/modules").addStyles("/* CSS declarations go here */\n\nbody {\n background-color: lightblue;\n}\n");
}]
So all of our CSS is also now a JS module that's again imported later on by using require.
Conclusion
All files are in one way or another converted to JS modules that follow similar rules for inclusion as AMD/CommonJS modules.
They will be included/bundled if another module refers to them. And since all of them are transformed to JS code
there's no magic behind the deceitful syntax:
import '../imports/hello/myapp.html';
import '../imports/hello/myapp.css';
They both are transpiled to their equivalent forms with require once the assets have been transformed to JS modules.
Whereas the approach of placing static assets in the imports directory is not mentioned in the official documentation,
this way of importing static assets works.
This seems to be at the core of how Meteor works so I'd bet this functionality is going to be there for a long while.
I don't know if to call this a feature maybe a more appropriate description is unexpected consequence but that would
only be true from the user's perspective, I assume the people who wrote the code understood this would happen and perhaps even
designed it purposely this way.
One of the features in Meteor 1.3 is lazy-loading where you place your files in the /imports folder and will not be evaluated eagerly.
Quote from Meteor Guide:
To fully use the module system and ensure that our code only runs when
we ask it to, we recommend that all of your application code should be
placed inside the imports/ directory. This means that the Meteor build
system will only bundle and include that file if it is referenced from
another file using an import.
So you can lazy load your css files by importing them from the /imports folder. I would say it's a feature.
ES6 export and import functionally are available in Meteor 1.3. You should not be importing HTML and CSS files if you are using Blaze, the current default templating enginge. The import/export functionality is there, but you may be using the wrong approach for building your views.