Webpack have trouble with scopes - javascript

There are several modules that are connected to app.js, for example the code that is inside:
var test = "TEST";
Here is my webpack.config:
module.exports = {
entry: './src/app.js',
output: {
filename: './dist/bundle.js'
}
};
The problem is that when I try to call my test variable in the developer console, I get an error:
Something about the scope, when I connect app.js directly - everything works, what's the problem and how to fix it?

Yes, this is a scope problem. There are two ways to fix this:
Instead of using var, use window.. (window.test = "TEST";)
Forget var (dosen't work in strict mode).test = "TEST";
Before the <script src="bundle.js"></script>, declare test (var test;) and then forget var.
Hope this is the anwser you're looking for.

The default functionality of webpack is to scope files that are passed in to its configuration, see documentation here: https://webpack.js.org/configuration/output/
This means that if you set a var in the file, then bundle it with webpack, it will become available only within its scope which, in this case, is the app.js file. If you open the file in your browser by itself, no scoping will take place hence why you don't have any issues when viewing directly.
If you need to access that test variable outside of the file, you'll have to turn it into a global variable, otherwise it will remain scoped within the bundle.js file created from webpack.

Related

npm global packages: Reference content files from package

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!

Browserify + AngularJS index.js IIFE params undefined

I have the following index.js for an angular app. Even though the var statements are executed first, and all variables are successfully logged to the console before the IIFE is executed, they are undefined once it steps into the IIFE scope, with the exception of angular. Presumably angular is defined, because angular is a global object. I've seen window and jquery passed in the same way. I'm just wondering if there is a way for me to pass these locally declared objects to my app's main IIFE script like this? Did I do it wrong? Please help - I'm stumped.
var angular = require('angular'),
config = {
params: require('./config/params').config,
routes: require('./config/routes').config
},
x = {};
(function (angular, config, x) {
// *****angular is the only one of these parameters that is defined here
console.info(angular) // works
console.info(config) // undefined
console.info(x) // works
})(angular, config, x);
Updated
As a commenter pointed out, this is an issue with browserify. If I pass an object I declare literally, it works fine. Does anyone know why this is happening or how to fix it?
Here's my browserify command:
browserify -e -d src/index.js -p [minifyify --map sourcemap.json --output sourcemap.json --uglify] > build/index.js
(I removed the parameter value and shortened the paths for brevity.)

Define global variable with webpack

Is it possible to define a global variable with webpack to result something like this:
var myvar = {};
All of the examples I saw were using external file require("imports?$=jquery!./file.js")
There are several way to approach globals:
1. Put your variables in a module.
Webpack evaluates modules only once, so your instance remains global and carries changes through from module to module. So if you create something like a globals.js and export an object of all your globals then you can import './globals' and read/write to these globals. You can import into one module, make changes to the object from a function and import into another module and read those changes in a function. Also remember the order things happen. Webpack will first take all the imports and load them up in order starting in your entry.js. Then it will execute entry.js. So where you read/write to globals is important. Is it from the root scope of a module or in a function called later?
config.js
export default {
FOO: 'bar'
}
somefile.js
import CONFIG from './config.js'
console.log(`FOO: ${CONFIG.FOO}`)
Note: If you want the instance to be new each time, then use an ES6 class. Traditionally in JS you would capitalize classes (as opposed to the lowercase for objects) like
import FooBar from './foo-bar' // <-- Usage: myFooBar = new FooBar()
2. Use Webpack's ProvidePlugin.
Here's how you can do it using Webpack's ProvidePlugin (which makes a module available as a variable in every module and only those modules where you actually use it). This is useful when you don't want to keep typing import Bar from 'foo' again and again. Or you can bring in a package like jQuery or lodash as global here (although you might take a look at Webpack's Externals).
Step 1. Create any module. For example, a global set of utilities would be handy:
utils.js
export function sayHello () {
console.log('hello')
}
Step 2. Alias the module and add to ProvidePlugin:
webpack.config.js
var webpack = require("webpack");
var path = require("path");
// ...
module.exports = {
// ...
resolve: {
extensions: ['', '.js'],
alias: {
'utils': path.resolve(__dirname, './utils') // <-- When you build or restart dev-server, you'll get an error if the path to your utils.js file is incorrect.
}
},
plugins: [
// ...
new webpack.ProvidePlugin({
'utils': 'utils'
})
]
}
Now just call utils.sayHello() in any js file and it should work. Make sure you restart your dev-server if you are using that with Webpack.
Note: Don't forget to tell your linter about the global, so it won't complain. For example, see my answer for ESLint here.
3. Use Webpack's DefinePlugin.
If you just want to use const with string values for your globals, then you can add this plugin to your list of Webpack plugins:
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object")
})
Use it like:
console.log("Running App version " + VERSION);
if(!BROWSER_SUPPORTS_HTML5) require("html5shiv");
4. Use the global window object (or Node's global).
window.foo = 'bar' // For SPA's, browser environment.
global.foo = 'bar' // Webpack will automatically convert this to window if your project is targeted for web (default), read more here: https://webpack.js.org/configuration/node/
You'll see this commonly used for polyfills, for example: window.Promise = Bluebird
5. Use a package like dotenv.
(For server side projects) The dotenv package will take a local configuration file (which you could add to your .gitignore if there are any keys/credentials) and adds your configuration variables to Node's process.env object.
// As early as possible in your application, require and configure dotenv.
require('dotenv').config()
Create a .env file in the root directory of your project. Add environment-specific variables on new lines in the form of NAME=VALUE. For example:
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
That's it.
process.env now has the keys and values you defined in your .env file.
var db = require('db')
db.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS
})
Notes
Regarding Webpack's Externals, use it if you want to exclude some modules from being included in your built bundle. Webpack will make the module globally available but won't put it in your bundle. This is handy for big libraries like jQuery (because tree shaking external packages doesn't work in Webpack) where you have these loaded on your page already in separate script tags (perhaps from a CDN).
I was about to ask the very same question. After searching a bit further and decyphering part of webpack's documentation I think that what you want is the output.library and output.libraryTarget in the webpack.config.js file.
For example:
js/index.js:
var foo = 3;
var bar = true;
webpack.config.js
module.exports = {
...
entry: './js/index.js',
output: {
path: './www/js/',
filename: 'index.js',
library: 'myLibrary',
libraryTarget: 'var'
...
}
Now if you link the generated www/js/index.js file in a html script tag you can access to myLibrary.foo from anywhere in your other scripts.
Use DefinePlugin.
The DefinePlugin allows you to create global constants which can be
configured at compile time.
new webpack.DefinePlugin(definitions)
Example:
plugins: [
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true)
})
//...
]
Usage:
console.log(`Environment is in production: ${PRODUCTION}`);
You can use define window.myvar = {}.
When you want to use it, you can use like window.myvar = 1
DefinePlugin doesn't actually define anything. What it does is replace variables that exist in your bundle code. If the variable doesn't exist in your code, it will do nothing. So it doesn't create global variables.
In order to create a global variable, write it in your code:
window.MyGlobal = MY_GLOBAL;
And use DefinePlugin to replace MY_GLOBAL with some code:
new webpack.DefinePlugin({
'MY_GLOBAL': `'foo'`,
// or
'MY_GLOBAL': `Math.random()`,
}),
Then your output JS will be like this:
window.MyGlobal = 'foo';
// or
window.MyGlobal = Math.random();
But MY_GLOBAL will never actually exist at runtime, because it is never defined. So that's why DefinePlugin has a misleading name.
I solved this issue by setting the global variables as a static properties on the classes to which they are most relevant. In ES5 it looks like this:
var Foo = function(){...};
Foo.globalVar = {};
You may hit this issue, when triing bundle < script > tag js files in some old project.
Do not use webpack for this, it may be even impossible if joining 50+ libraries like jquery and then figuring out all global variables or if they used nested require. I would advice to simply use uglify js instead , which drops all this problems in 2 commands.
npm install uglify-js -g
uglifyjs --compress --mangle --output bundle.js -- js/jquery.js js/silly.js

Using NPM package in the browser with Browserify

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.

In Node.js, how can I load my modules once and then use them throughout the app?

I want to create one global module file, and then have all my files require that global module file. Inside that file, I would load all the modules once and export a dictionary of loaded modules.
How can I do that?
I actually tried creating this file...and every time I require('global_modules'), all the modules kept reloading. It's O(n).
I want the file to be something like this (but it doesn't work):
//global_modules.js - only load these one time
var modules = {
account_controller: '/account/controller.js',
account_middleware: '/account/middleware.js',
products_controller: '/products/controller.js',
...
}
exports.modules = modules;
1. Using a magic variable (declared without var)
Use magic global variables, without var.
Example:
fs = require("fs");
instead of
var fs = require("fs");
If you don't put var when declaring the variable, the variable will be a magic global one.
I do not recommend this. Especially, if you are in the strict mode ("use strict") that's not going to work at all.
2. Using global.yourVariable = ...
Fields attached to global object become global variables that can be accessed from anywhere in your application.
So, you can do:
global.fs = require("fs");
This is not that bad like 1., but still avoid it when possible.
For your example:
Let's say you have two files: server.js (the main file) and the global_modules.js file.
In server.js you will do this:
require("./global_modules");
and in global_modules.js you will have:
_modules = {
account_controller: require('/account/controller.js'),
account_middleware: require('/account/middleware.js'),
products_controller: require('/products/controller.js'),
...
}
or
global._modules = {...}
In server.js you will be able to do:
_modules.account_controller // returns require('/account/controller.js'),
require already does it. Try loading a module, modify it and then load it another time in another place or file:
var fs = require('fs'):
console.log(fs.hey);
fs.hey = 'HEY TIMEX, WHATS UP?';
//another place of the same process
var fs = require('fs');
console.log(fs.hey);

Categories