Webpack imports-loader with messageformat and angular-translate - javascript

I'm having some difficulty making angular-translate-interpolation-messageformat play nice with the imports-loader for MessageFormat. I outline the issue in this issue.
Copied:
Even though the module is exposed using UMD (yay) it is actually using the global MessageFormat object here. This is forcing me to either expose MessageFormat onto window (which I would prefer not to do), or make a workaround with webpack (which is also cumbersome). The real solution is to use UMD correctly and not depend on globals but rather require things correctly.
Here's what the UMD format looks like now:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module unless amdModuleId is set
define([], function () {
return (factory());
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
factory();
}
}(this, function () {
// interpolation-messageformat code that uses the global MessageFormat variable
}));
Here's what it should look like:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module unless amdModuleId is set
define(['messageformat'], function (MessageFormat) { // <-- changed line
return (factory(MessageFormat)); // <-- changed line
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require('messageformat')); // <-- changed line
} else {
factory(root.MessageFormat); // <-- changed line
}
}(this, function (MessageFormat) { // <-- changed line
// interpolation-messageformat code that uses the global MessageFormat variable
}));
Thanks!
So, until that issue gets resolved, I need to do a workaround. I'd really prefer to avoid globals. Here's my current solution with the imports-loader:
require('imports?MessageFormat=messageformat!angular-translate/dist/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat');
With that, everything builds fine, however, when I open Chrome, the app breaks when running the angular-translate-interpolation-messageformat function that uses MessageFormat saying that MessageFormat is not defined
Here's where things get weird...
If I open the app in any other browser (other than Chrome) it works just fine. Also, if I open the app when it's deployed, it works fine (even in Chrome).
Here's where things get weirder...
If I open my Chrome DevTools and then open the local app in Chrome, everything works just fine. o_O
So, anyway, I'm wondering if maybe I'm using the imports-loader incorrectly or something. Any help appreciated!

Are you using by any chance devtool: 'eval'? I've seen that same weird behavior you describe, and it went away switching to devtool: 'source-map'.

Related

Implementing require in the absence of node

Modules generally start something like this
(function(root, factory)
{
/* globals define */
if (typeof define === 'function' && define.amd)
{
// AMD. Register as an anonymous module.
define([], factory);
}
else if (typeof module === 'object' && typeof exports !== 'undefined')
{
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
}
else
{
// Browser globals (root is window)
root.Papa = factory();
}
I'm trying to implement require to handle node style CommonJS-like modules.
Find the package folder, parse package.json to learn the entrypoint and dependencies, recursively descent with a shared cache to load dependencies... that stuff works.
But having loaded the script for a module, I'm having trouble executing it in such a way as to have it populate module.exports
This will all end up running on Jint (a JS interpreter) so node isn't supplying the usual furniture, I have to build it all myself. There's no step-debug with Jint so I'm using node from VS Code and faking Jint like this.
import * as fs from "fs";
var code = fs.readFileSync("node_modules/papaparse/papaparse.js").toString();
let x = 3;
console.log(eval("x*x"))
let result = eval(`
let module = { exports: "dunno" };
${code}
console.log(module.exports);
`);
This is in a file test.js and package.json nominates this file as main and specifies a type of module. It launches, reads the module code, creates module and runs the code, which promptly complains that it Cannot set properties of undefined (setting 'Papa').
Looking at the snippet above, that tells us it's executing the last else clause, which means it's not seeing module. I thought it might be some sort of scope thing for eval which is where this came from
let x = 3;
console.log(eval("x*x"))
but that duly writes 9 to the console so the question is why module isn't in scope.
This is one of those JavaScript closure weirdnesses; can anyone guide me on how to provide the module object so that the second clause will take effect populating module.exports with the result of factory()?
I know I said it's running in the absence of Node, but I'm debugging it using Node mostly because that's what you get when you launch a js file in VS Code. As already mentioned the production target is Jint. The debug environment is as close an approximation as I can manage, and I'm refraining from using facilities that won't be available in production.
In the debug environment I've created a package.json file that governs whether it's treated as CommonJS or ES6. Experiments show that the production environment behaves like more ES6 than commonjs. The trouble is that most packages expect that they will run either in a browser or in node. They don't consider the possibility of another headless environment. The code above decides it's browser hosted and tries to to install Papa in a DOM that isn't there.
The solution is to wrap the module like so (I'm constructing a string in C#).
string wrapped =
#"(function () {
var module = { exports: { } }; " +
jsSource + #"
var strays = Object.keys(this).filter(itemName => itemName !== 'module');
if (strays.length === 1)
module.exports = this[strays[0]]
else
strays.forEach(itemName => module.exports[itemName] = this[itemName]);
return module.exports;
}).apply({});";
Wrapping it in an anonymous function and using apply to set this for the call allows the "DOM write" to succeed, and a little follow-up code looks for these strays, gathering them up into module exports before returning to normal operation.

UMD modules in a Chrome Extension

The UMD module definition is approximately this:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'b'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('b'));
} else {
// Browser globals
factory((root.commonJsStrict = {}), root.b);
}
}(this, function (exports, b) {
//use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports.action = function () {};
}));
The issue is that Chrome Extensions don't support any of these methods of exporting the module:
define doesn't exist
exports doesn't exist
this isn't bound to window
For this reason, it seems that UMD modules fail in Chrome Extension environments. Is there any workaround to get a UMD module to correctly export into the window object in a Chrome Extension?
As #wOxxOm has correctly pointed out, the Chrome Extension environment is the same as the browser, and this is indeed bound to window, so UMD modules can and should work with extensions.
It turns out the actual problem is that babel was producing a bundle with this replaced by undefined, which is the problem outlined and resolved in this issue: How to stop babel from transpiling 'this' to 'undefined' (and inserting "use strict").

How to import JavaScript functions across files in manner that is simultaneously compatible with Node.js and with browser JavaScript

In vanilla browser-based JavaScript, I can do this:
<html>
<head>
</head>
<body>
<script type="text/javascript" src="file1.js"></script>
<script type="text/javascript" src="file2.js"></script>
</body>
</html>
Then:
// file1.js
function abc(foo) {
console.log("abc received:", foo);
}
And:
// file2.js
abc(36);
...and things behave as expected. (I.e., "abc received 36" is printed to the console.)
How can I include functions from file1.js in file2.js in node.js, so as to preserve the functionality above, while also keeping the web page as-is? Specifically, I would like to keep the html as it appears above, while being able to run
node file2.js
at the command line, and getting abc received 36 at the terminal.
Edit
I found this question with a very similar motivation, by a seemingly more advanced user. But the main link in the answer to that question is dead, and I don't understand how the stated answer works, or what it's supposed to do. Maybe someone can provide me with a MWE tailored to my file1.js, file2.js?
In order for scripts to be functional as both Node.js modules and browser vanilla (non-modular) scripts, they should be defined as UMD modules.
UMD is basically a boilerplate factory function that detects modular environment and falls back to global variables if needed.
E.g. for CommonJS and globals (no AMD) the definition would be:
file1.js
(function (root, factory) {
if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// Node
module.exports = factory();
// Global
} else {
root.file1 = factory();
}
}(this, function () {
// module body
function abc(foo) {
console.log("abc received:", foo);
}
return { abc: abc };
}));
file2.js
(function (root, factory) {
if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// Node
module.exports = factory(require('./file1'));
// Global
} else {
root.file2 = factory(root.file1);
}
}(this, function (file1) {
// module body
file1.abc(36);
}));
Omitting this boilerplate is the task that bundling tools are supposed to solve, notably Browserify that is intended specifically to bundle CommonJS modules to browser scripts.

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!
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.
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!]
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.
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.
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.

How to use node modules with in UIAutomation

According to apple's documentation I can import one JS file into another with an import statement. And yes I am able to use JS functions and recursively call other JS functions.
But can I include node modules into my automation. Node/npm module seems to have a lot of tools that makes life easier and avoid code duplication.
And actually I was able to use one node module called moment.js through the following call in my code
#import "../node_modules/moment/moment.js"
But I am not have the same luck with other npm modules. I tried couple Faker.js, Charlatan.js and I getting the following error in Faker.js
Script threw an uncaught JavaScript error: Can't find variable: window
on line 618 of Faker.js
Looking at *.js files it looks like it has something to do with the way these modules are packaged. My JS knowledge isn't getting me anywhere.
The last few lines of moment js file
// CommonJS module is defined
if (hasModule) {
module.exports = moment;
}
/*global ender:false */
if (typeof ender === 'undefined') {
// here, `this` means `window` in the browser, or `global` on the server
// add `moment` as a global object via a string identifier,
// for Closure Compiler "advanced" mode
this['moment'] = moment;
}
/*global define:false */
if (typeof define === "function" && define.amd) {
define("moment", [], function () {
return moment;
});
}
Last few lines of Faker js file
if (typeof define == 'function'){
define(function(){
return Faker;
});
}
else if(typeof module !== 'undefined' && module.exports) {
module.exports = Faker;
}
else {
window.Faker = Faker;
}
I am perfectly able to play with these modules in node console, so nothing wrong with the modules, it just how to include/require them in my JS files.
Had to do two things for Faker to work for me
remove 'use strict'
Check if window is undefined
Add this statement
this['Faker'] = Faker;

Categories