platypus
platypus

Reputation: 1205

Node process object made available to browser client code

I'm trying to understand how webpack uses DefinePlugin. I have:

new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify('development'),
}),

and a function:

export const foo = () => {
  console.log(process)
  console.log(process.env.NODE_ENV)
}
window.foo = foo

when I print foo, I see the following in my browser console:

ƒ foo() {
  console.log(process);
  console.log("development");
}

It seems like the variable "development" was injected while webpack was compiling the input file. At the same time webpack also injected the process object into the JavaScript code, and the browser did print out the process object when foo was called:

{title: "browser", browser: true, env: {…}, argv: Array(0), nextTick: ƒ, …}

My question is, how can the process object, which is a Node concept, be made available to the browser?

In fact, if I do:

window.process = process

I can use process.nextTick right inside the browser console! I thought the nextTick function was a Node-specific implementation! Could anybody explain this?

Thank you!

Upvotes: 20

Views: 23980

Answers (5)

thu
thu

Reputation: 1

see https://webpack.js.org/configuration/node/#node

These options configure whether to polyfill or mock certain Node.js globals and modules. This allows code originally written for the Node.js environment to run in other environments like the browser.

This feature is provided by webpack's internal NodeStuffPlugin plugin. If the target is "web" (default) or "webworker", the NodeSourcePlugin plugin is also activated.

Upvotes: 0

dx_over_dt
dx_over_dt

Reputation: 14318

This doesn't directly answer this question, but it was the solution I needed. I was trying to access process in code that webpack compiled, intending the compiled code to be run in a NodeJS environment rather than in the browser. The process variable doesn't exist on window, but on global.

The solution was to set the target in the webpack config.

webpack.config.js

const config = {
    // ...
    target: 'node',
    // ...
};

module.exports = config;

This removes the window mock.

Upvotes: 2

Andrea Carraro
Andrea Carraro

Reputation: 10429

How webpack deals with Node globals and webpack.DefinePlugin are actually two different concerns.

Default node globals are globally injected, while constants defined in webpack.DefinePlugin are physically replaced one by one trough all the codebase.

eg:

// config
new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify('development'),
  'process.env.MY_VAR': {foo: JSON.stringify('bar')},
}),

// source
console.log('process.env.NODE_ENV', process.env.NODE_ENV);
console.log('process.env.MY_VAR', process.env.MY_VAR);

console.log('process.env', process.env);
console.log('process', process);

// compiled
console.log('process.env.NODE_ENV', "development");
console.log('process.env.MY_VAR', __webpack_require__.i({"foo":"bar"}));
console.log('process.env', process.env);
console.log('process', process);

Note that process.env.NODE_ENV and process.env.MY_VAR physically are replaced, while process.env and process keep their reference to the injected process mock.

But webpack.DefinePlugin is also able to override the mocked process object (or just part of it): a lot of power which implies the risk of getting unexpected behaviours.

From Webpack docs:

When defining values for process prefer 'process.env.NODE_ENV': JSON.stringify('production') over process: { env: { NODE_ENV: JSON.stringify('production') } }. Using the latter will overwrite the process object which can break compatibility with some modules that expect other values on the process object to be defined.

eg:

// config
new webpack.DefinePlugin({
  'process': JSON.stringify('override'),
  'process.env.NODE_ENV': JSON.stringify('development'),
}),

// source
console.log('process', process);
console.log('process.env', process.env);
console.log('process.env.NODE_ENV', process.env.NODE_ENV);


// compiled
console.log('process', "override");
console.log('process.env', "override".env);          // [boum!]
console.log('process.env.NODE_ENV', "development");  // [replaced by DefinePlugin!]

Upvotes: 6

simsom
simsom

Reputation: 2586

I am going to assume you are experimenting with this on your local webpack dev server and not in your production build. Webpack dev server utilizes websockets for real time communication between the underlying node processes and the front-end bundle. If you wanted to utilize this in a production environment you would need to set up a socket between your front end JS and your node instance to send commands. From a security perspective it sounds like a nightmare, but may have some valid use cases.

Upvotes: -1

Automatico
Automatico

Reputation: 12926

As mentioned here https://webpack.js.org/configuration/node/#node-process, the webpack can make polyfills for different node functions, but it appears as the node.process is a "mock";

"mock": Provide a mock that implements the expected interface but has little or no functionality.

Have you tested it to see if it actually works? It might just be an empty shell.

If it works I assume that the plugin actually uses something like node-process as shown in this blog-post: http://timnew.me/blog/2014/06/23/process-nexttick-implementation-in-browser/

Copied from that blogpost:

process.nextTick = (function () {
    var canSetImmediate = typeof window !== 'undefined'
    && window.setImmediate;
    var canPost = typeof window !== 'undefined'
    && window.postMessage && window.addEventListener;
    if (canSetImmediate) {
        return function (f) { return window.setImmediate(f) };
    }
    if (canPost) {
        var queue = [];
        window.addEventListener('message', function (ev) {
            var source = ev.source;
            if ((source === window || source === null) && ev.data === 'process-tick') {
                ev.stopPropagation();
                if (queue.length > 0) {
                    var fn = queue.shift();
                    fn();
                }
            }
        }, true);
        return function nextTick(fn) {
            queue.push(fn);
            window.postMessage('process-tick', '*');
        };
    }
    return function nextTick(fn) {
        setTimeout(fn, 0);
    };
})();

It is a bit hard to know for sure from the info you provided. If it truly works I think it is very likely you have Browserify enabled in your node app. Perhaps you find some of what you need here: https://webpack.js.org/loaders/transform-loader/#src/components/Sidebar/Sidebar.jsx

Hopefully you find this answer somewhat helpful.

The bottom line is that I believe it is a polyfill from somewhere.

Upvotes: 8

Related Questions