Webpack bundle dynamic client config - node.js

We have a node.js app bundled for production using Webpack.
Our problem is how to add dynamic configuration after you already have a bundle, without the need to re-bundle?
On the server-side, we can just use node env variables, but how can this be done for the client bundle? Specifically, we need to tell a browser module to which api server address to connect.
Having a js/json file with the configurations causes the configuration values to be injected into the bundle, and therefore can't be changed afterwards (in a comfortable manner, without open the bundle file and manually finding and replacing).
Using something like express-expose, isn't something we want, since it causes another network request to get the data, and our server address is dynamic.
node-config etc., don't work on client side

You can make creative use of the externals option:
externals: [
{ appConfig: 'var appConfig' },
],
If you add that to your configuration you can just let your web server add a script tag with var appConfig = {"config":"value"}; somewhere before the loading of your webpack bundle, and a simple require('appConfig') will pick it up.

Related

Detect whether an npm package can run on browser, node or both

I'm building a NextJs application which uses a set of abstractions to enable code from both react (Component logic etc.) and node (API logic) to utilize same types and classes. The motive here is to make the development process seem seamless across client side and server side.
For example. a call on User.create method of the User class will behave differently based on the runtime environment (ie. browser or node) - on the browser, it will call an api with a POST request and on server it will persist data to a database.
So far this pattern worked just fine, and I like how it all turned out in terms of the code structure. However, for this to work, I have to import modules responsible for working on the server and browser to a single module (which in this case is the User class) this leads to a critical error when trying to resolve dependencies in each module.
For example I'm using firebase-admin in the server to persist data to the Firebase Firestore. But it uses fs which cannot be resolved when the same code runs on the browser.
As a work around I set the resolve alias of firebase-admin to false inside the webpack configuration (given it runs on browser) see code below.
/** next.config.js **/
webpack: (config, { isServer }) => {
if (!isServer) {
// set alias of node specific modules to false
// eg: service dependencies
config.resolve.alias = {
...config.resolve.alias,
'firebase-admin': false,
}
} else {
// set alias of browser only modules to false.
config.resolve.alias = {
...config.resolve.alias,
}
}
While this does the trick, it won't be much long until the process gets really tedious to include all such dependencies within resolve aliases.
So, my approach to this is to write a script that runs prior to npm run dev (or manually) that will read all dependencies in package.json and SOMEHOW identify packages that will not run on a specific runtime environment and add them to the webpack config. In order to this, there should be a way to identify this nature of each dependency which I don't think is something that comes right out of the box from npm or the package itself.
Any suggestion on how this can be achieved is really appreciated. Thanks`

Meteor 1.3 and configuration

i have a simple question.
When you use node + webpack you can easily configure whatever you want.
For example i can write in config default path for my app modules.
Haw can i do it in Meteor 1.3? do they have some config file such Webpack?
Meteor applications can store configuration options like API keys or global settings. An easy way to provide this configuration is with a settings.json file in the root of your Meteor application. The key/value pairs are available only on the server, but you can provide public access to settings by using public:
settings.json
{
"privateKey": "privateValue",
"public": {
"publicKey": "publicValue"
}
}
These values are available in your app using Meteor.settings.
From the Full Meteor Docs:
Meteor.settings contains deployment-specific configuration options. You can initialize settings by passing the --settings option (which takes the name of a file containing JSON data) to meteor run or meteor deploy. When running your server directly (e.g. from a bundle), you instead specify settings by putting the JSON directly into the METEOR_SETTINGS environment variable. If the settings object contains a key named public, then Meteor.settings.public will be available on the client as well as the server. All other properties of Meteor.settings are only defined on the server. You can rely on Meteor.settings and Meteor.settings.public being defined objects (not undefined) on both client and server even if there are no settings specified. Changes to Meteor.settings.public at runtime will be picked up by new client connections.
A good write-up can also be found on TheMeteorChef's Blog

Browserifying react with addons to a standalone component, usable by plugins

I am experementing a bit with react and browserify and have these wishes:
I want to bundle all code written by me into a single file
I want to bundle all 3rd party dependencies (react, react-router, lodash etc) into separate files, one for each lib, to maximize caching possibilities
I have managed to do the things described above but I ran into this specific situation:
In some places of my code I want to use react with addons and as such require it like this: var React = require('react/addons). I don't do this in all parts of my code and it is not done in 3rd party dependencies such as react-router. This seems to create a conflict. Either the browserified bundle will only be available through var React = require('react/addons) which breaks 3rd party dependencies, or I will have to bundle react both with or without addons which menas that react is bundled and downloaded twice.
I tried to use aliasify and make react an alias for react/addons but I couldn't make it work. Should this be possible?
Another acceptable solution would be to bundle just the addons in a separate bundle and through that make both react and react/addons available through calls to require. Is any of this possible?
Addition
As a comment to the first comment by BrandonTilley, this is not just applicable to React and addons. Lodash also comes with a number of different distributions and I would like to be able to choose the version to use in my webapp in this case as well.
Notice that what you want to achieve is documented here: Browserify partitionning
I'm packaging my app in 2 parts: appLibs.js and app.js.
I've done this for caching too but I choose to put all the code that does not change often in a single bundle instead of splitting it like you want, but you can use the same trick.
Here's the code that might interest you:
var libs = [
"react",
"react/addons", // See why: https://github.com/substack/node-browserify/issues/1161
... other libs
];
gulp.task('browserify-libs', function () {
var b = browserify(...);
libs.forEach(function(lib) {
b.require(lib);
});
return b.bundle().......
});
gulp.task('browserify',['browserify-libs'],function () {
var b = browserify(...);
libs.forEach(function(lib) {
b.external(lib);
});
return b.bundle().......
});
This way, React is only bundled once in appLibs.js and can be required inside app.js using both react and react/addons
If you really want to bundle your libs in separate files, bundle then with b.require("myLib"), but in this case be sure to check that the libraries do not have direct dependencies. If a lib to bundle has a dependency in React, this means that lib will be packaged in the bundle, potentially leading to multiple bundles having React inside them (and making weird failures at runtime). The library should rather use peerDependencies so that b.require does not try to package these dependencies
Sounds like the perfect use case for factor-bundle.
From the browserify handbook:
factor-bundle splits browserify output into multiple bundle targets based on entry-point. For each entry-point, an entry-specific output file is built. Files that are needed by two or more of the entry files get factored out into a common bundle.
Thanks for all suggestions but the solution I have chosen is a "shim" if that is the correct term. Looks like this:
Browserify react/addons into it's own file
Create my own file (called shim) only containing this: module.exports = require('react/addons');
Browserify my shim and use the expose option, exposing it as react
Now, either if react or react/addons is required I get react/addons

Grunt task to optionally include an AMD module for different environment

I'm developing a web app using Require.js for AMD and amplify.request to abstract away my AJAX calls. The other advantage to amplify.request is that I've defined an alternative module containing mocked versions of my requests that I can use for testing purposes. Currently, I'm switching between the two versions of my request module by simply commenting/un-commenting the module reference in my main.js file.
What I'd love to do is use Grunt to create different builds of my app depending on which module I wanted included. I could also use it to do things like turn my debug mode on or off. I'm picturing something similar to usemin, only for references inside JavaScript, not HTML.
Anyone know of a plugin that does this, or have a suggestion about how I could do it with Grunt?
On our current project we have a few different environments. For each of them, we can specify different configuration settings for the requirejs build.
To distinguish between these different environments, I've used a parameter target.
You can simply pass this to grunt by appending it to your call like
grunt --target=debug
And you can access this parameter in the Gruntfile, by using grunt.option, like
var target = (grunt.option('target') || 'debug').toLowerCase();
The line above will default to debug. You could then make use of the paths configuration setting of requirejs to point the build to the correct module. Example code below.
requirejs: {
compile: {
options: {
paths: {
"your/path/to/amplify/request": target === "debug" ? "path/to/mock" : "path/to/real",
}
}
}
}

Change configuration in runtime by changing environment variables using the module node-config

I'm trying to use the node-config module to change some parameters of my configuration (basically logging level) during runtime.
In the official documentation says:
Environment variables can be used to override file configurations. Any environment variable that starts with $CONFIG_ is set into the CONFIG object.
I've checked that this is true when the server starts but it does not seem to work once it's up. (The handler of the watch function is never called when an environment variable is changed unlike a change in the runtime.json file or directly changing a config variable).
I'm currently watching the whole CONFIG object like this:
var CONFIG = require('config');
CONFIG.watch( CONFIG , null , function(object, propertyName, priorValue, newValue){
console.log("Configuration change detected");
});
Does anyone know if this is possible?
The environment is available during startup of a process.
If the process is running, you won't be able to change the environment anymore, the process is in.
The only option is to restart the process or use other mechanisms to communicate with it.
Say for example having a rest or tcp listener inside, where you can transfer your variable inside.
Best regards
Robert
As you must knowing, React is a single page application which is eventually when it is complied is a static page app that means all the files of the react application is complied into vanilla JS and CSS file bundle in a Tarball. Now that Tarball is eventually deployed on a web server. It could be Apache web server, nginx web server or anything which you are using it but an important point is the static app is running in someone else browser and someone access to website CSS and JS are downloaded in a browser and it is running in the browser runtime environment so technically you cannot have a runtime environment variable for someone else browser but may be there would be a way to access them during runtime.
SOLUTION
I have achieved this goal with the package called runtime-cra.
follow the steps on this official documentation: https://blog.risingstack.com/create-react-app-runtime-env-cra/

Resources