I browserified a module that takes a value and returns a new one.
The original .js file was:
module.exports = function (term) {
return term + ' blabla';
}
If I want to call it from Node.Js I'd simply include it as in
var foo = require('./my-file.js');
foo('no'); // returns 'no blabla'
But how do I call this same function from a browser if I include the browserify-generated file in <script src="/javascripts/new-file.js"></script>?
Thank you!
You want to use the --standalone flag for browserify. From the documentation:
Generate a UMD bundle for the supplied export name. This bundle works with other module systems and sets the name given as a window global if no module system is found.
So if you use the --standalone flag,
browserify --standalone my_global_name my-file.js > new-file.js
you will be able to use the window.my_global_name property to access your function.
You need to compile with the -r flag, to set it up as exposing for requiring.
browserify -r my-file.js > new-file.js
Then, in your script, you should be able to do:
var foo = require('./my-file.js');
foo('no'); // returns 'no blabla'
For more information, you can read the documentation.
Related
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');
. . .
}
I'm in the process of building an npm package which will be installed globally. Is it possible to have non-code files installed alongside code files that can be referenced from code files?
For example, if my package includes someTextFile.txt and a module.js file (and my package.json includes "bin": {"someCommand":"./module.js"}) can I read the contents of someTextFile.txt into memory in module.js? How would I do that?
The following is an example of a module that loads the contents of a file (string) into the global scope.
core.js : the main module file (entry point of package.json)
//:Understanding: module.exports
module.exports = {
reload:(cb)=>{ console.log("[>] Magick reloading to memory"); ReadSpellBook(cb)}
}
//:Understanding: global object
//the following function is only accesible by the magick module
const ReadSpellBook=(cb)=>{
require('fs').readFile(__dirname+"/spellBook.txt","utf8",(e,theSpells)=>{
if(e){ console.log("[!] The Spell Book is MISSING!\n"); cb(e)}
else{
console.log("[*] Reading Spell Book")
//since we want to make the contents of .txt accesible :
global.SpellBook = theSpells // global.SpellBook is now shared accross all the code (global scope)
cb()//callBack
}
})
}
//·: Initialize :.
console.log("[+] Time for some Magick!")
ReadSpellBook((e)=>e?console.log(e):console.log(SpellBook))
spellBook.txt
ᚠ ᚡ ᚢ ᚣ ᚤ ᚥ ᚦ ᚧ ᚨ ᚩ ᚪ ᚫ ᚬ ᚭ ᚮ ᚯ
ᚰ ᚱ ᚲ ᚳ ᚴ ᚵ ᚶ ᚷ ᚸ ᚹ ᚺ ᚻ ᚼ ᚽ ᚾ ᚿ
ᛀ ᛁ ᛂ ᛃ ᛄ ᛅ ᛆ ᛇ ᛈ ᛉ ᛊ ᛋ ᛌ ᛍ ᛎ ᛏ
ᛐ ᛑ ᛒ ᛓ ᛔ ᛕ ᛖ ᛗ ᛘ ᛙ ᛚ ᛛ ᛜ ᛝ ᛞ ᛟ
ᛠ ᛡ ᛢ ᛣ ᛤ ᛥ ᛦ ᛧ ᛨ ᛩ ᛪ ᛫ ᛬ ᛭ ᛮ ᛯ
If you require it from another piece of code, you will see how it prints to the console and initializes by itself.
If you want to achieve a manual initalization, simply remove the 3 last lines (·: Initialize :.) and use reload() :
const magick = require("core.js")
magick.reload((error)=>{ if(error){throw error}else{
//now you know the SpellBook is loaded
console.log(SpellBook.length)
})
I have built some CLIs which were distributed privately, so I believe I can illuminate a bit here.
Let's say your global modules are installed at a directory called $PATH. When your package will be installed on any machine, it will essentially be extracted at that directory.
When you'll fire up someCommand from any terminal, the module.js will be invoked which was kept at $PATH. If you initially kept the template file in the same directory as your package, then it will be present at that location which is local to module.js.
Assuming you edit the template as a string and then want to write it locally to where the user wished / pwd, you just have to use process.cwd() to get the path to that directory. This totally depends on how you code it out.
In case you want to explicitly include the files only in the npm package, then use files attribute of package.json.
As to particularly answer "how can my code file in the npm package locate the path to the globally installed npm folder in which it is located in a way that is guaranteed to work across OSes and is future proof?", that is very very different from the template thingy you were trying to achieve. Anyway, what you're simply asking here is the global path of npm modules. As a fail safe option, use the path returned by require.main.filename within your code to keep that as a reference.
When you npm publish, it packages everything in the folder, excluding things noted in .npmignore. (If you don't have an .npmignore file, it'll dig into .gitignore. See https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package) So in short, yes, you can package the text file into your module. Installing the module (locally or globally) will get the text file into place in a way you expect.
How do you find the text file once it's installed? __dirname gives you the path of the current file ... if you ask early enough. See https://nodejs.org/docs/latest/api/globals.html#globals_dirname (If you use __dirname inside a closure, it may be the path of the enclosing function.) For the near-term of "future", this doesn't look like it'll change, and will work as expected in all conditions -- whether the module is installed locally or globally, and whether others depend on the module or it's a direct install.
So let's assume the text file is in the same directory as the currently running script:
var fs = require('fs');
var path = require('path');
var dir = __dirname;
function runIt(cb) {
var fullPath = path.combine(__dirname, 'myfile.txt');
fs.readFile(fullPath, 'utf8' , function (e,content) {
if (e) {
return cb(e);
}
// content now has the contents of the file
cb(content);
}
}
module.exports = runIt;
Sweet!
I'm trying to use Browserify so that I can use an npm package in the browser. The package I'm trying to use is this
I have a fcs.js file:
// Use a Node.js core library
var FCS = require('fcs');
// What our module will return when require'd
module.exports = function(FCS) {
return FCS;
};
And an index.js file:
var FCS = require('./fcs.js');
console.log('FCS IS ');
console.log(FCS);
I then ran:
browserify index.js > bundle.js
And created an index.html file:
<html>
<script src="bundle.js"></script>
<script>
var fcs = new FCS();
</script>
</html>
But I end up with the error:
Uncaught ReferenceError: FCS is not defined
Maybe I'm not grasping the concept correctly. How can i use this package in the browser? Thanks.
Don't do this: require('./fcs.js');
Do this: require('./fcs');
When you require something, the extension is implicitly .js. Also make sure that your module, FCS, has an entry point (the default is index.js, but you can change that in the main entry in the package.json).
Also, in your index.html document, you're expecting that FCS is a global object. I haven't seen the internal code of FCS, but it will only be globally available if it's attached to the window object.
When you require something, it only makes it available to the rest of your code where it's required. If you want to make it globally available, you have to attach it to the window object, just like anything else.
In other words, the internals of your FCS module might look something like:
// node_modules -> fcs -> index.js
var FCS = function() {};
window.FCS = FCS; // this makes it globally available
module.exports = FCS; // this lets you require it
#JoshBeam's answer is correct - in order to expose assets inside the browserify bundle, you need to attach something to the window object. But instead of exposing specific assets, I wanted something more general.
Here's what I did:
in my app.js
require('this')
require('that')
require('./modules/mycode')
...
window.req = require
And in my <script> tag in my HTML:
something = req('./modules/mycode')
Notice that, instead of assigning the require function directly to window.require, I gave it a different name. The reason for this is simple: if you call it window.require, you're overwriting the original require and you'll find yourself in an infinite loop of recursion (at least until the browser runs out of stack space).
The problem is that your inline script (in index.html) is expecting a global variable called FCS to exist (when you do new FCS()). This is not the case because in index.js, your FCS variable is scoped to that file.
You should write all your scripts in separate files and bundle them all using browserify, avoiding the inline script or make FCS global, by attaching it to window.
when I run a test in node.js with mocha, how I can set temporal environment variables?
in a module, I have a variable depending of a environment variable
var myVariable = proccess.env.ENV_VAR;
now I use the rewire module,
var rewire = require('rewire');
var myModule = rewire('../myModule');
myModule.__set__('myVariable', 'someValue');
exist a more simple way? without the rewire module?
In your myModule.js file, export a function that takes the variable as an argument eg:
module.exports = function (var) {
// return what you were exporting before
};
Then when you require it, require it like so:
var myModule = require('../myModule')(process.env.ENV_VAR);
My first instinct was to simply set the env var at the top of the test.js before any require statements. However, this may not work for you if you have a module that depends on a env var, and it is required multiple times in the same test run. say you have an env dependent module called mode.js:
module.exports = {
MODE : process.env.ENV_VAR
};
If you add a single test file called bTest.js with
process.env.ENV_VAR= "UNIT_TEST_MODE"
const mode = require('./mode.js')
// describe some tests scenarios that use mode.MODE
...
you will be OK. but if you add a second test file
const mode = require('./mode.js')
// describe some more tests scenarios that use mode.MODE
...
and name it aTest.js, the new file will run first in your suite and mode.MODE will be undefined for all subsequent test js files. The require command won't actually reload the same module multiple times.
Let's assume you aren't able to use the dotenv package in your tests. If so, you can set values on the process.env programmatically in the mocha config file. By default, this is found in .mocharc.json or .mocha.yml, but this can easily be translated to .mocharc.js . Referring to the sample js file here: https://github.com/mochajs/mocha/blob/master/example/config/.mocharc.js
So your .mocharc.js could be
"use strict";
process.env.ENV_VAR = "UNIT_TEST_MODE";
// end of .mocharc.js
and ENV_VAR will be set before mocha requires or runs any of your modules.
Even if you are using dotenv , you can choose to flip set other dotenv option from inside your mochajs config that you might not want to set on your local dev server's .env file. That way, your .env.mocha vars will be available to individual modules that don't require dotenv.
"use strict";
require('dotenv').config({ debug: process.env.DEBUG, { path: '/full/custom/path/to/.env.mocha' } })`.
// end of .mocharc.js
Although in the second case, you may be better off just setting the dotenv env path as part of the test command in your package.json:
node -r dotenv/config /node_modules/mocha/bin/_mocha dotenv_config_path=/full/custom/path/to/.env.mocha
I have a very simple module that I am bundling with Browserify. I want to use that bundle both in the browser as well as in node. In node, it works just fine if I require the non-bundled module; however, if I require the browserified bundle, require returns an empty object. Here's a reproduction:
Simple module
function Foo(bar) {
this.bar = bar;
}
module.exports = Foo;
Test script
var Foo = require("./foo"); // not bundled with Browserify
var Foob = require("./foob"); // bundled with Browserify
console.log("Foo =", Foo);
console.log("Foob =", Foob);
Executed thusly
browserify foo.js -o foob.js
node foo-test.js
Output
Foo = function Foo(bar) {
this.bar = bar;
}
Foob = {}
You can see that Foo (the non-bundled version) is the expected function but Foob (the bundled version) is a sad and empty object.
So the question is why isn't the browserified module working in node?
Clarification: I'm using browserify to bundle my webapp and I use its paths options to simplify paths in my app's require statements and avoid relative path hell. However, I'm trying to use tap to do unit testing, but it doesn't seem to have a similar configuration feature. Because of this, trying to require non-bundled files when using tap causes everything to break.
I found a way around this. The solution is to use browserify's --standalone option when bundling. This will add the necessary module.exports statement in the bundled output.
You want to nest browserify bundles. In this case, ensure that your nested bundles actually have module.exports defined.
For instance in the main file of your foob.js bundle, be sure to return a function you can use externally (using module.exports)