How to access a variable from another JS file in webpack - javascript

I'm trying to implement webpack in a project, but can't seem to find a clear answer to this issue in their docs. I need to be able to access a certain variable globally from a JS file, for example:
toProps.js:
var myProp = "test";
In my entry point I do the following:
entry.js:
require('./toProps.js');
console.log(myProp);
But the variable myProp is undefined. This is an extremely simplified example, not my actual use case, but the point is that I'm working with an existing code base where there are these types of global references all over the place, but I want to implement some module and lazy loading with webpack.
How can I access the myProp variable?

Inside toProps.js:
var myProp = "test";
export default myProp;
Inside entry.js:
import myProp from './toProps.js';
From what I see, you haven't exported the variable. Therefore, it can't be an 'import' inside entry.js.
Edit 1: Learn more about exporting here.
Edit 2:
Okay, to make it a global variable, here's what I did:
Move props.js into /dist and link it inside index.html, before bundle.js, or whatever you've named the output file.
Now it's a global variable, so you don't actually need to export or import it anywhere.
To test this, I made a file called test.js, imported it into entry.js. Inside test.js is:
const logTest = () => {
console.log(myProp)
}
export default logTest;
Now inside entry.js, I invoked the function. It worked as expected and 'test' showed up in the console.
As you mentioned, your example is simplified, so this may not be viable for you. Maybe, you could move all the global variables to one file inside /dist, inside an object, as it's best not to pollute the global object.
Edit 3: Go with the answer from Jonas W. Totally forgot you could do that.

If you really need a global variable (no you dont!), use window:
window.myProp = "test";
Otherwise just export it from your file and import it everywhere you need it. That maybe adds some overhead to your code, but actually you always know where the value comes from, which makes debugging super simple.

Using webpack 4
To update this post for anyone that can read it, acctually you can use a plugin in webpack.config.js to resolve names that used in another .js files.
Take a look in https://webpack.js.org/plugins/provide-plugin/ example:
Usage: jQuery
To automatically load jquery we can simply point both variables it exposes to the corresponding node module:
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
});
Then in any of our source code:
// in a module
$('#item'); // <= just works
jQuery('#item'); // <= just works
// $ is automatically set to the exports of module "jquery"

If the source file to import belongs to maintainer, it's best to export first and import latter. Just like the answer of #Ibrahim.
I also encounter a situation to import third party source file just not using export syntax. So I think below is a solution while using webpack.
First, install exports-loader for your project.
npm install exports-loader --save-dev
Then, in your JS source file, import and use like below:
const WEBGL = require("exports-loader?WEBGL!three/examples/js/WebGL.js");
WEBGL.isWebGL2Available()
In my case, WEBGL is the inner variable I wish to import, three/examples/js/WebGL.js is the third party source file.
Hoping this is useful.

Related

How to turn a .js file into ES6 module when both Webpack and Babel are used?

As title. I have a file that exposes a top-level/global var foo. But I got an error when I ran those production files on Webpack Dev Server, which said that the global variable foo is not defined. In the entry point(this is a term of Webpack) .tsx-file, I've added the following line to import this .js file: (I'm sure that the problem is not on the path alias, #, and indeed I can omit the .js-extension too)
import '#/external/path/to/non_es6_module.js'
Now I'm reading about this: https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs, i.e. I think babel is related to this problem. But the sample provided in the link is different than my case, i.e. in my case, there is no export in the .js file I want to import:
in
export default 42;
Out
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = 42;
Some of my reasoning:
One might say that:
Why not just modify the .js-file, i.e. by adding the export keyword, so you can use that Babel plugin to do the transform?
But the problem is that the .js-file that I want to import is a machine-generated file. And I think it's not ideal to manually modify it, since it will be upgraded in the (near) future. I don't want to do this every time!

Using a named alias of an imported module does not work for dot notation of exported names?

In a React app I have a file with imports like this:
import * as cArrayList from './ClassArrayList'
import * as mCalc1 from './moduleCalc1'
Then in the same file, I have exports like this:
export const cArrayList.ArrayList
export const mCalc1.doPreCalculationSetup
export const mCalc1.doTheCalculations
But I get a parsing error: Identifier 'cArrayList' has already been declared.
The MDN docs say in this statement:
import * as myModule from '/modules/my-module.js'
the "myModule" alias of the module object can be used as a kind of namespace when referring to the imports, to be used as so:
myModule.doAllTheAmazingThings().
So why my error?
The only items in this file are 61 import lines and 394 export lines. The whole purpose of this file is to do all the necessary imports just once, creating a namespace holding all the exports available in the project. And then make the individual exports avaiable by reference for all the other modules using them, instead of each module doing another import. This is an experment to avoid a cyclic reference (I think) that causes the program to just loop in the startup phase.
UPDATED
While going through the steps to implement this hack idea of a nameSpace file, I learned some things that made me think that this whole exercise was unnecessary. What I thought was a looping app was really a stalled splash screen with a gif. And even thought the finished hack worked, the many small changes to code that had worked fine before raised questions. And my late idea to try doing a build and run that was key. That build failed, but identified the failing code for correction. Then there was a series of small code changes to finish up.
So thanks to GIT, I was able to jump back to the point in time where I first saw this problem: a clean compile but a stalled splash screen. Now I ran a build and got this:
Failed to compile.
Failed to minify the code from this file:
./src/calculations/moduleMoneyPlanEntry.js:53:4
The code at that location was simple:
function moneyPlanEntry() {
try {
process.env.MONEYPLAN = 'development'
process.env.NODE_ENV = 'development' (A)
The left over line from previous work marked (A) was the problem. Delete that line and all is well. The build runs, and the corrected code runs with the "yarn start" command. So now I will discard my nameSpace hack and continue normally. The key lesson? Run the build once in awhile to check for lurking errors.
#CertainPerformance already said everything about what's wrong with the syntax.
The whole purpose of this file is to do all the necessary imports just once, creating a namespace holding all the exports available in the project. And then make the individual exports avaiable by reference for all the other modules using them, instead of each module doing another import.
For that, you'll want to use export * from '…'; syntax. Or export { name as alias } from '…'; in case of conflicts.
But it sounds like a really bad idea, going contrary to modularisation by putting everything in a single global namespace. You will no longer have clear dependencies between the parts of your package, but just one big mess.
This is an experiment to avoid a cyclic reference (I think) that causes the program to just loop in the startup phase.
Circular dependencies usually cause TDZ exceptions, not infinite loops.
Using const declarations in your global module will actually make these worse, you really need to use re-exports that actually alias the bindings.
To solve circular dependency issues (and don't want to refactor but keep the dependency structure), you generally need to decide which module should be the entry point to your circle, and in which order the modules should be executed, and then always strictly follow that order in any module import list.
export const <someIdentifier> will do two things:
Declare a local variable in the module scope named someIdentifier (as a const)
Export that as a named export
The error message you're getting is a bit misleading. Whether the variable is declared beforehand or not,
export const someObject.someProperty
is invalid syntax, because someObject.someProperty is not a valid identifier. For similar reasons, you can't do
const someObject.someProperty
For what you're trying to do, I'd destructure the properties from the imported object when exporting, like so:
export const {
doPreCalculationSetup,
doTheCalculations
// you can add more properties here
} = mCalc1;
Demo:
<script type="module">
const obj = { foo: 'foo' };
export const { foo } = obj;
console.log(foo);
</script>

Add an external script to React and create new instance

I am trying to implement this github project to my React web app. It is an external script to put a fancy canvas in the background.
I have tried to load it:
import {WarpSpeed} from './warpspeed.js'
import WarpSpeed from './warpspeed.js'
And then create a new instance:
let x = new WarpSpeed("canvasID")
But it throws an error:
TypeError: __WEBPACK_IMPORTED_MODULE_4__helpers_warpspeed___default.a is not a constructor
I also tried to use react-load-script, but it does not make sense I cannot call new WarpSpeed after, because it is undefined.
The module you are trying to use will not work with commonjs importing. You will need to wrap the whole thing inside of a universal module definition.
Please use the one here:
https://gist.githubusercontent.com/tameemsafi/0d909a4060640b948f37ec59460f20d4/raw/c7f4e9020ccb1fb0a9dcf54221c67249030640eb/warpspeed-umd.js
I have wrapped it in a UMD IFFE which will allow you to use ES6 import. I also changed the window.requestAnimationFrame polyfill to a better version.
You can place the code inside of a file warpspeed.js.
CommonJS:
const WarpSpeed = require('./warpspeed.js');
ES6 (Requires transpiling to commonjs):
import WarpSpeed from './warpspeed.js'
According to this:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Import_a_module_for_its_side_effects_only
other info I've found and after checking source code of WarpSpeed.js, what you want seems impossible.
You can also confirm it here:
ES6 import equivalent of require() without exports
You should probably add proper export to WarpSpeed.js file.
Maybe fork the project, modify it so it is ES5+ compatibile and create pull request. Probably lib creator will be greateful ;)

Defining an imported external module with WebPack + TypeScript

I'm trying to get WebPack set up with TypeScript and some external libraries, but having some trouble.
I'm importing the src version of jQuery in order to let WebPack choose the required modules (this was needed to get the Boostrap-SASS loader working), so am importing the library like this in my TS:
import jquery = require( 'jquery/src/jquery.js' );
When compiling, this throws the error cannot find external module 'jquery/src/jquery'. If I create a manual .d.ts file to define the library, like so:
declare module "jquery/src/jquery"{}
then the compiler works, but warns:
cannot invoke an expression whose type lacks a call signature
Alternatively, if I change the import line to
var jquery = require( 'jquery/src/jquery' );
(which I think is a CommonJS style import?) then it all compiles (and runs) fine. So, questions:
Is there a better way of requiring/including the source files using WebPack?
What's the correct way to define the module so it doesn't lack a call sig?
Should I be worried about using var instead of import or just roll with it if it's working?
I've only just picked up WebPack so it's entirely possible I'm doing something stupid. Please correct me if so!
Edit:
As I was thinking about how to phrase the question this came to me:
declare module "jquery/src/jquery" { export = $; }
which seems to let me use "import..." - but is that a good way to handle this?
Edit 2:
In reply to #basarat's answer below (my comment got a bit long to be read without line breaks):
I'm already using the jquery def from DefinitelyTyped, but it doesn't work by itself because the module I'm requiring is "jquery/src/jquery" and not just "jquery". If I use "jquery" then the compiled dist version of jquery is loaded, which doesn't work with the Bootstrap loader.
So the declare module "jquery/src/jquery" { export = $; } is, I think, extending the existing definition but using the path that I need to load it from.
import jquery = require( 'jquery/src/jquery.js' );
Use the definitely typed definition for jquery instead : https://github.com/borisyankov/DefinitelyTyped/blob/master/jquery/jquery.d.ts#L3158-L3160 which already allows you to do import jquery = require('jquery').
To answer my own question, this is what I ended up doing.
NB I don't know if this is the "correct" way of achieving this (I suspect not), so I'm open to anyone with a better answer!
In my TS definitions file tsd.d.ts I have the Definitely Typed jQuery definition:
/// <reference path="jquery/jquery.d.ts" />
but on its own this wasn't enough for the compiler to pick up the jQuery source files from within the node_modules folder.
So further down tsd.d.ts I've added:
declare module "jquery/src/jquery"
{
export = $;
}
The compiler can now locate the jQuery source files.
I also had to edit one of the jQuery source files for the WebPack compiler to play nicely with it: selector.js doesn't have a factory function included by default, and WebPack requires one to be present.
There's a loader that says it fixes this but I couldn't get it to work, so I just changed the code in selector.js to:
define([ "./selector-sizzle" ], function(){});
This all feels a little bit hacky... but it got me over this particular hurdle.

Use Browserify with JavaScript libraries such as Backbone or Underscore?

I know I can install underscore using npm but that's not what I can do in my work environment. I need to be able to download the Underscore.js library and then make it "browserify-compatible".
So let's assume Underscore.js looks something like this:
(function() {
var root = this;
// Rest of the code
}.call(this));
I downloaded that file on my hard drive and saved it as under.js.
My file that requires underscore looks like this:
var underscore = require("./under");
console.log(underscore);
And then I run browserify from the cli.
I have an HTML page called test.html and basically all it does is load the generated bundle.js.
However, the console.log(underscore) line fails - says that underscore is undefined.
What have I tried?
Obviously I added module.exports to the first line - right before the function definition in under.js, and that's how I got the error mentioned above. I also tried the method from this answer , still got the same error.
So, how would I use Browserify to load libraries such as Underscore.js or Backbone without using npm-installed modules?
That's because browserify does not add variables to the global scope. The version you download is identical to the version that you install via NPM.
You need to explicitly attach it to the window to export it to the top level scope.
If you create a file called "expose_underscore.js" and put this in it:
var _ = require('./under');
window._ = _;
Will do it, followed by: browserify expose_underscore.js > bundle.js and then add bundle.js as a <script> tag you will be able to do the following in your console:
HOWEVER, you shouldn't do this if you're using browserify. The point behind it (and Node's version of commonJS) is that you explicitly require it everywhere you need it. So every file you have that needs underscore should import it to a local variable.
Don't worry -- you will still only have one copy loaded.
I typically add my vendor libs like Underscore as script tags. Underscore will attach itself to the global scope, so then you don't need to require it anywhere to use it.
If you do want to use it in a Browserified fashion, verify that you have the correct path in your require statement (browserify requires are relative paths) and move the module.exports statement to the end of the file.

Categories

Resources