How to detect whether my code is executed as a Webpack bundle - javascript

I'm currently experimenting with the following strategy to dynamically load a json file from a path relative to my module :
If my code is bundled as a Webpack bundle, use import(filename.json)
In any other case, fall back to an AJAX call, with an absolute path
The following seems to work fine :
function parse (fileName, callback) {
var path = "./relative/path/to/" + fileName + ".json";
var cb = process.bind(this, fileName, callback);
if (typeof webpackJsonp !== "undefined") { // <-- Test if run as Webpack bundle
// Do dynamic import
} else {
// Do Ajax call
}
}
However, I can't find any documentation on webpackJsonp, however, so I assume this is not part of Webpack's public API.
I also noticed that webpackJsonp is a function in 3.12 and an Object (inheriting from Array) in 4.28, indicating how fragile it is to rely on the presence, value or type of webpackJsonp.
Is there a (future-proof) reliable way test whether my code is being run as a Webpack bundle, using public API?
Basically, what should I replace typeof webpackJsonp !== "undefined" with, to achieve the same effect, but using public API?
Additionally, I'm also having some problems with getting the actual import to work in Webpack 4.28. See import() breaks in Angular 7.2.3 + Webpack 4.28 for that.

Related

detect whether ES Module is run from command line in Node

When using CommonJS modules in Node, you can detect whether a script is being run from the command line using require.main === module.
What is an equivalent way to detect whether a script is being run from the command line when using ES Modules in Node (with the --experimental-modules flag)?
Use
if (import.meta.url === `file://${process.argv[1]}`) {
// module was not imported but called directly
}
See the MDN docs on import.meta for details.
Update Sep 27, 2021
Perhaps more robust, but involving an extra import (via Rich Harris)
import {pathToFileURL} from 'url'
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
// module was not imported but called directly
}
There is none - yet (it's still experimental!). Although the prevailing opinion is that such a check is a bad practice anyway and you should just provide separate scripts for the library and the executable, there is an idea to provide a boolean import.meta.main property for this purpose.
The other answers get close, but will miss the mark for a pretty typical usecase - cli scripts exposed by the bin property in package.json files.
These scripts will be symlinked in the node_modules/.bin folder. These can be invoked through npx or as scripts defined in the scripts-object in package.json. process.argv[1] will in that case be the symlink and not the actual file referenced by import.meta.url
Furthermore, we need to convert the file path to an actual file://-url otherwise it will not work correctly on different platforms.
import { realpathSync } from "fs";
import { pathToFileURL } from "url";
function wasCalledAsScript() {
// We use realpathSync to resolve symlinks, as cli scripts will often
// be executed from symlinks in the `node_modules/.bin`-folder
const realPath = realpathSync(process.argv[1]);
// Convert the file-path to a file-url before comparing it
const realPathAsUrl = pathToFileURL(realPath).href;
return import.meta.url === realPathAsUrl;
}
if (wasCalledAsScript()) {
// module was executed and not imported by another file.
}
I would have posted this as a comment on the accepted answer, but apparently I'm not allowed to comment with a fresh account.
The module global variable will be defined in CommonJS, but won’t exist at
all in an ES module. Yes, there is an inconsistency there, that ES modules are
the things that don’t have module variables.
You can check for an undefined variable by seeing if typeof v is the string
(not value!) 'undefined'.
That turns into:
const inCommonJs = typeof module !== 'undefined';
console.log(`inCommonJs = ${inCommonJs}`);
If we put that exact code into both .cjs and .mjs files, we get the correct answers:
$ node foo.mjs
inCommonJs = false
$ cp foo.mjs foo.cjs
$ node foo.cjs
inCommonJs = true
I like import.meta.url === `file://${process.argv[1]}` , but it does not work in Windows inside bash shell. This is the alternative that is only checking the basename:
const runningAsScript = import.meta.url.endsWith(path.basename(process.argv[1]));
It looks like there is a documented way to do this now:
if (require.main === module) {
console.log('executed directly');
. . .
}

RequireJS: apply plugin to module id dynamically

I'm using the requirejs-babel plugin which requires prepending 'es6!' to all module ids that need babel transpilation.
define(['es6!some-es6-module'], function(module) {
// ...
});
Is there an API in RequireJS that would allow me to inspect a module id and prepend the plugin id as-needed? For example, if I wanted to apply 'es6!' to all module ids in a specific directory?
Ultimately I need to be able to write defines like this define(['some-es6-module'], ...) and automatically add the es6! prefix depending on what the module id is.
Not looking for information on SystemJS or gulp tasks that do the transpilation ahead of time, etc.
The exact module ids are not known at configuration time- I just know in certain locations/directories, modules will need es6!.
Needs to work in the browser, at runtime
I am not 100% sure on your overall objective (do you want the es6 addition to module ID saved permanently or always auto-added?), but you may be able to use RequireJS mapping to substitute module ID's for defined modules. For example: -
requirejs.config({
map: {
// * - for all modules that require these, do this
'*': {
'some-es6-module': 'es6!some-es6-module'
}
}
});
However, considering your use-case you may need something more complicated than this, as mapping assumes you have actual different versions of files and is generally used for this purpose.
A more complicated solution I assume you are looking to avoid could be to dynamically loop your files before optimising them in r.js and loading/editing them via Node. It would get a little messy!
var config = requirejs.s.contexts._.config;
var needBabel = ['some-es6-module', 'another-module-name', 'another'];
for (var property in config.paths) {
if (config.paths.hasOwnProperty(property) && needBabel.indexOf(property) > -1) {
// load the module in node
// fs.readFileSync(__dirname + config.paths[property] + '.js');
// dynamically modify this file with text replacement
// save this file via Node again
}
}
// run Require JS optimiser
// undo everything you've just done when optimisation is complete
I ended up overriding the load method. The override uses the standard load for modules with mapped paths, otherwise it uses the es6 (requirejs-babel) plugin to load the module.
require.standardLoad = require.load;
require.load = function(context, moduleName, url) {
var config = requirejs.s.contexts._.config;
if (moduleName in config.paths) {
return require.standardLoad(context, moduleName, url);
}
require(['es6'], function(es6) {
es6.load(
moduleName,
require,
{
fromText: function(text) {
require.exec(text);
context.completeLoad(moduleName);
}
},
{});
});
};
Here it is in action: https://gist.run/?id=7542e061bc940cde506b

Webpack: unable to require automatically resolved dependencies using variables

While working on a Web app using Webpack to manage JavaScript dependencies, I stumbled upon the problem i'm going to describe.
Loading dependencies passing strings to require() works beautifully:
// main.js
var jQuery = require('jquery');
Here, jquery is installed with Bower, and Webpack is correctly configured to automatically resolve Bower modules.
Now, I'm working on the problem of conditionally loading modules, with particular regard to the situation where modules have to be downloaded from a CDN, or from the local server if the CDN fails. I use scriptjs to asynchronously load from the CDN, by the way. The code I'm writing is something like this:
var jQuery = undefined;
try {
jQuery = require('jquery-cdn');
} catch (e) {
console.log('Unable to load jQuery from CDN. Loading local version...');
require('script!jquery');
jQuery = window.jQuery;
}
// jQuery available here
and this code works beautifully as well.
Now, since I obviously have a lot of dependencies (Handlebars, Ember, etc.) that I want to try to load from a CDN first, this code starts to get a little redundant, so the most logical thing I try to do is to refactor it out into a function:
function loadModule(module, object) {
var lib = undefined;
try {
lib = require(module + '-cdn');
} catch (e) {
console.log('Cannot load ' + object + ' from CDN. Loading local version...');
require('script!' + module);
lib = window[object];
}
return lib;
}
var jQuery = loadModule('jquery', 'jQuery');
var Handlebars = loadModule('handlebars', 'Handlebars');
// etc...
The problem is that Webpack has a particular behaviour when dealing with expressions inside require statements, that hinders my attempts to load modules in the way described above. In particular, when using an expression inside require it
tries to include all files that are possible with your expression
The net effect is a huge pile of error messages when I try to run Webpack with the above code.
Though the linked resources suggest to explicitly declare the path of the JavaScript files to include, what I fail to get is how to do the same thing when I cannot, or don't want to, pass a precise path to require, but rather use the automatically resolved modules, as shown.
Thanks all
EDIT:
I still don't known how to use expressions to load those scripts, however, I designed a workaround. Basically, the idea is to explicitly write the require('script') inside a callback function, and then dinamically call that function when it's time. More precisely, I prepared a configuration file like this:
// config.js
'use strict';
module.exports = {
'lib': {
'jquery': {
'object': 'jQuery',
'dev': function() { require('script!jquery'); },
'dist': function() { return require('jquery-cdn'); },
'cdn': '//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'
},
'handlebars': {
// ...
}
}
};
Inside my main code I, then, define an array of resources to load, like:
var config = require('./config.js');
var resources = [ config.lib.jquery, config.lib.handlebars, ... ];
And then when I have to load the development version, or the distribution version, I dinamically call:
// Inside some kind of cycle
// resource = resources[index]
try {
window[resource.object] = resource.dist();
} catch (e) {
console.log('Cannot load ' + resource.object + ' from CDN. Loading local version...');
resource.dev();
}
Here there's a more complete example of this in action.

Unable to load module that uses a named define call

I am using RequireJS & its working fine for some modules. Recently, I added two test modules (code for the modules seems to be fine). I am importing them in my main (javascript) file and I can see that the file get imported in the browser. However, I see the following error message and I cannot troubleshoot properly what might be causing this issue. Any suggestions to fix or troubleshoot this will be greatly appreciated.
Error Message in browser
Error: No define call for urlCore/urlTest http://requirejs.org/docs/errors.html#nodefine
...{c=c[b]});return c}function C(b,c,d,g){c=Error(c+"\nhttp://requirejs.org/docs/er...
module to be imported:
define("urlTest", [], function() { //no dependencies on other modules
'use strict';
var urlTest = function() {
this.getTestURL = function(url, urlChk) {
if (typeof url === 'undefined' || typeof urlChk === 'undefined' ||
url === '' || urlChk === '') { //we can check length instead as well || removing typeof does not solve the issue
throw new Error("urlCommon - url or urlChk values are invalid:" + url + "|" + urlChk);
}
if (url !== null && url.length > 0 && urlChk === "Y") {
return url;
}
}; //end of getTestURL
urlTest.version = '0.0.1';
};
return urlTest;
});
I tried this but it does not work either:
/*
define("urlTest", [], function() { //no dependencies on other modules
'use strict';
return {
getTestURL : function(url, urlChk) {
if (url !== null && url.length > 0 && urlChk === "Y") {
return url;
}
}
} //end return
}); //end test module
*/
Main.js paths:
paths: {
//"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
'validateTest': 'validation/validateTest', //works
'normalizedXHR': 'xhr/normalizedXHR', //works
'urlTest': 'urlCore/urlTest', //does not work
'jsonTest': 'json/jsonTest' //does not work
}
Update:
I am using the urlTest from this module:
define('testResponse', ['./urlCore/urlTest', './json/jsonTest'], function(urlTest, jsonTest) {
'use strict';
Another Update
When I set enforceDefine to true with waitSeconds to a value greater than 0, I receive the following error (even though I see the JS file being loaded in the browser):
Error: Load timeout for modules: urlCore/urlTest
This urlTest is a dependency in a module (which sits in another directory). When I try the following, the dependencies do not load up in the browser:
define('testResponse', ['../urlCore/urlTest'], function(urlTest) {
When I try the following, the file appears to load in the browser window but I get the requirejs error stating a load timeout for module error.
define('testResponse', ['./urlCore/urlTest'], function(urlTest) {
File structure:
javascripts
javascripts/main.js
javascripts/abc/testResponse.js
javascripts/urlCore/urlTest.js
Another Update
I am able to call other modules residing in a separate directory. The issue comes forward when one of the modules in those directories e.g. abc/testResponse has a dependency on urlCore/urlTest. This appears to cause the issue for me. Should I be specifying the config part in this module as well in addition to the main.js (I'm not doing that right now).
define('testResponse', ['./urlCore/urlTest'], function(urlTest) {
When I try the ../urlCore/urlTest, the file does not event load up.
Judging by what you are showing in the question, urlTest is loaded with the path urlCore/urlTest and at this path there is a file which contains this:
define("urlTest", [], function() {
This works, so long as what RequireJS is loading a module named urlTest. No problem there. However, if you require any other module name that ultimately resolves to the path urlCore/urlTest, then you are in trouble, because the module name will not correspond to the name that you have in your define call. Or to put it differently, for your module to load when required under the name urlCore/urlTest, the define call in the module's file would have to say:
define("urlCore/urlTest", [], function() {
The easy fix for this is to not assign names to modules in your define calls. Just put the dependencies and the callback and leave the name out.
Even if you use define without specifying a module name, you still have to be careful not to end up loading the same module under two different names, because if you do, you will get a timeout when you try loading with the 2nd name. In the code you show in the question, I'd recommend using urlTest everywhere or using relative paths everywhere to load this module but don't mix the two.

How to wait until a file is available in a Jake build (Node.js)?

Is there a way in a Node.js Jake build to wait until a certain file has been copied, and advance to do some operation only after the destination file can be found? I think this question pretty much comes down to "is there a way to copy files synchronously in Node.js/Jake?" (Perhaps something else than writing something from scratch, using the combination of fs.readSync and fs.writeSync.)
Background:
I'm developing a web app that is run on Node.js (with Express) during development, but will be deployed on a Java server in production. (We use Jade and Stylus in the client and Express enables us to run the app without generating all the HTML files etc. and deploying it after every change.)
I use Jake for making the build, i.e. generating HTML files from Jade files and CSS from Stylus files etc. Now I'm also trying to concatenate all of the app's JavaScript files into one minimized file and change all the HTML files to use that instead of all the separate JS files that are used in "raw" form during development.
However, I now have a problem with that last step. My idea was to copy all of my Jade files into a temporary directory for the deployment build and replace the reference (in a Jade file used as a header on all HTML pages) to a list of all separate JS files to the one that has just been generated by concatenating and minimizing the whole bunch. But as I first copy all of the Jade files to another location (which happens asynchronously) and try to edit one of the files, opening the file always fails since the copy operation hasn't really finished yet.
This is what I have now (in a simplified form) in my jakefile:
var fs = require('fs');
var fse = require('fs-extra');
var path = require('path');
var glob = require('glob');
var Snockets = require('snockets');
var snockets = new Snockets();
// generating the minimized JS file
snockets.getConcatenation(baseDir + '/scripts/all.js', { minify: true }, function(err, allJs) {
if (err) {
throw err;
}
fs.writeFileSync(generatedJsFileName, allJs);
});
// copying all the Jade files to a temp dir
glob.sync('**/*.*', {
cwd : srcDir
}).forEach(function(file) {
var loadPath = srcDir + '/' + file;
var savePath = targetDir + '/' + file;
fse.mkdirsSync(path.dirname(savePath));
fse.copy(loadPath, savePath);
});
// trying to read one of the copied files (which fails, since the file cannot be found yet)
fs.readFile(targetDir + '/views/includes/head.jade', 'utf8', function(err, data) {
...
});
This might be a stupid question, and a stupid way to try to solve the problem in the first place. So, also suggestions for a better approach are very welcome.
Update:
I also tried using Parseq, putting each operation (creating the JS file, copying the Jade files, reading one file) in its own function, but even that gives me the same error. If I run the script several times without deleting the target directory of the copy operation in between, the file can be found. So e.g. the path is correct and the problem really seems to be about timing.
I didn't really find an answer to the main question so I don't know if this helps anyone else facing the same problem. But I did find a way to get around the problem.
I ended using the same original Jade files for the two different conversions, but in the second conversion I use a custom js function to change the script tag reference to point to the minified file.
I.e.
var data = jade.compile(str, { filename: file, pretty: true })({
css: function(path) {
return '<link rel="stylesheet" href="/styles/' + path + '.css" />';
},
js: function(path) {
var name = '<script src="/scripts/';
if (path == 'all') {
name += generatedJsFileName;
}
else {
name += path + '.js';
}
name += '"></script>';
return name;
}
});
It might not be the prettiest workaround but it works.

Categories