Inside a jest test, I need to import a javascript module that declare some global functions. This javascript module is autogenerated from django (ex jsi18n) and it's an auto invoking function
(function(globals) {
var django = globals.django || (globals.django = {});
...
}(this));
This is helpful for use the translation inside the react component. For example including a translation string in our .jsx file using a global defined function gettext()
<p>{ gettext('got it') }</p>
I've tried to import the module using the standard form
import './djangojs';
but jest report the error
TypeError: Cannot read property 'django' of undefined
because this it's undefined (strict mode). So I've tried to manual edit the module and adding at the end }(this || window)); and works correctly.
But the module is autogenerated every time. So how I can bind this to window for using the global object without manually editing the file?
Solution:
I have imported the django javascript modules directly using the setupFiles as #dfsq told
"setupFiles": [
"<rootDir>/path/to/jsi18n/djangojs.js",
"<rootDir>/path/to/js/reverse.js"
],
The problem is specific to Babel. Babel is commonly used in Jest testing rig, and it is likely used here.
Babel transform-es2015-modules-commonjs enables strict mode for both ES module imports and require, so this is undefined in imported module.
In order to make a piece of code executed in loose mode while its context is in strict mode, it should be evaluated in global context:
const fs = require('fs');
const script = fs.readFileSync(require.resolve('./djangojs')).toString();
new Function(script)(); // or (0, eval)(script);
instead of import './djangojs' or require('./djangojs').
It's expected that script doesn't have to be transpiled (it will be evaluated as is) and doesn't rely on the context (doesn't use require, because it's not available in global context). I.e. eval will work for regular script like ./djangojs but not for CommonJS module.
This may be useful if a module shouldn't be available globally or may vary between tests.
If it should be available for all tests then setupFiles should be used, as another answer suggests.
Related
I have a Node server with a utils file that looks like this:
update = () => {
//
};
module.exports = { update };
This works, but when I use the same file in a React app, I get the error
"'update' is not defined no-undef"
To fix this I must add const keyword. This makes no sense to me. Why is declaring the function needed only in React?
This is because the React you're using has come bundled with a linter which warns you of potential code quality problems. create-react-app is one of the React bundles which comes integrated with ESLint.
If you don't want to see the warning and don't want to use ESLint, you can build your project manually from scratch, rather than using a boilerplate setup like create-react-app.
If you installed ESLint for your Node project, you'd see the no-undef warning for Node code as well.
Note that what you're seeing is a linter warning, not a runtime error. If you ignore the warning and bundle and serve the project anyway, it will be still be runnable if it doesn't run in strict mode. (In strict mode, the use of an undefined variable without declaring it with const / let / var first will throw an error)
The linting rule being violated in the code is described at length here:
https://eslint.org/docs/rules/no-undef
For further reference on what's going on when you don't declare a variable (and why it probably isn't a good idea), see
What is the purpose of the var keyword and when should I use it (or omit it)?
If you're using modules, you should pretty much always be using explicit imports and exports only - don't implicitly create global variables (like you're currently doing with update = () => {), since that defeats the point of using modules.
I would like to add an axternal javascript library I created. How can I do it?
Let's say I have a file, converter.js
that file looks like that:
module.export = {
myFunc: a=>a
}
and I copy it in ./lib folder
Then in .angular-cli.json I add this entry into app:
"scripts": ["../lib/converter.js"],
I can see in the source of html file the javascript file containing my file. Good.
But I don't understand how to use myFunc for example in app.component.ts...
Like doc said
Once you import a library via the scripts array, you should not import
it via a import statement in your TypeScript code (e.g. import * as $
from 'jquery';). If you do that, you'll end up with two different
copies of the library: one imported as a global library, and one
imported as a module.
...
If the global library you need to use does not have global typings,
you can also declare them manually in src/typings.d.ts as any:
declare var libraryName: any;
And I did: declare var myFunc: any in src/typings.d.ts
Nut nothing: trying to call myFunc (as global) but it was undefined.
Thank you
I think your declaration is correct, although I would prefer declare var myFunc: (any) => any;. I see that you use module.export in your library, may be for using it server-side in node.js. That's the syntax used by the SystemJS module loader. It was used in old Angular tutorials. Since Angular CLI switched to Webpack as a module loader, we don't need it anymore.
Try to write your function as following:
var myFunc = function (a) {
return a;
}
You have chosen to add your script to the "scripts" array in .angular-cli.json. That apparently works. But I don't like declaring functions on the global scope. There probably must be a better way of importing a library. Try to use the import way instead as R. Richards suggested.
I'm trying to use import and export to create modules and it's not working.
I added https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.min.js to the index.html header and tried to import a js file and get an error message saying SyntaxError: import declarations may only appear at top level of a module. What can I possibly be doing wrong?
I know I can use require.js but rather use import and export.
HTML
script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.min.js"></script
JS File
import Mymodule from './modules/mymodule';
Babel cannot perform client-side transpiling of modules, or rather it is not universally supported by browsers. In fact, unless you use a plugin, Babel will transform import into require().
If I run the following code:
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.js"></script>
<script defer type="text/babel" data-presets="es2015">
import Mymod from './modules/module';
Mymod();
</script>
</head>
I get the following error:
Uncaught ReferenceError: require is not defined
From Babel Docs:
Compiling in the browser has a fairly limited use case, so if you are working on a production site you should be precompiling your scripts server-side. See setup build systems for more information.
Most people choose a pre-compiled module bundler like Webpack or Rollup.
If you really want to perform this client-side, use RequireJS with Babel run via a plugin, though you may need to use AMD syntax.
Native browser support for ES6 modules is still in early stages. But to my knowledge there isn't a preset/plugin available yet for Babel to tell it not to transform import/export statements.
The scripts that babel-standalone translates execute by default in global scope, so any symbols defined by them are automatically available to every other module. From that perspective, you don't need import/export statements in your modules.
However, you might be trying to maintain source files that can be used both by babel-standalone (e.g. for quick test environments, feature demonstrations, etc) and via bundlers such as webpack. In that case, you need to keep the import and export statements there for compatibility.
One way to make it work is to add extra symbols into the global scope that cause the import and export code that babel generates to have no effect (rather than causing an error as usually occurs). For example, export statements are compiled into code that looks like this:
Object.defineProperty (exports, "__esModule", {
value: true
});
exports.default = MyDefaultExportedClass;
This fails if there is no existing object called "exports". So give it one: I just give it a copy of the window object so anything interesting that gets defined is still accessible:
<script>
// this must run before any babel-compiled modules, so should probably
// be the first script in your page
window.exports = window;
import statements are translated to calls to require(). The result (or properties extracted from it) is assigned to the variable used as the identifier in the import statement. There's a little bit of complication around default imports, which are different depending on whether or not the result of require() contains the property __esModule. If it doesn't, things are easier (but then you can't support having both default and named exports in the same module ... if you need to do this, look at the code babel produces and figure out how to make it work).
So, we need a working version of require(). We can provide one by giving a static translation of module name to exported symbol/symbols. For example, in a demo page for a React component, I have the following implementation:
function require (module) {
if (module === "react") return React;
if (module === "react-dom") return ReactDOM;
}
For a module returning multiple symbols, you'd just return an object containing the symbols as properties.
This way, a statement like
`import React from "react";`
translates to code that is effectively:
`React = React;`
which is roughly what we want.
I'm trying to use the exif-js in a webpack+babeljs project. This library (exif-js) creates a global variable "EXIF", but I can't access it in Chrome devtools neither in my js script.
I tried to use webpack provide-plugin to make "EXIF" visible in all pages, but it is not working.
plugins: [
new webpack.ProvidePlugin({
EXIF: 'exif-js/exif.js'
})
]
What is the best way to use this library in a webpack project?
Thanks!
It looks like in CommonJS, it exports the EXIF var instead of attaching it to the global scope.
Which means with webpack you can just import it like any other module:
var exif = require('exif-js');
To demonstrate, see this webpackbin
If you actually do need it in global scope, you can manually attach it to the window object after importing it:
var exif = require('exif-js');
window.EXIF = exif;
To answer the actual title of the question regarding using scripts that set global variables, you can usually either use ProvidePlugin as you demonstrated, or you can use externals:
The externals configuration option provides a way of excluding dependencies from the output bundles. Instead, the created bundle relies on that dependency to be present in the consumer's environment. This feature is typically most useful to library developers, however there are a variety of applications for it.
For example (in your webpack.config.js):
externals: {
jquery: 'jQuery'
}
However, this works differently than ProvidePlugin. Whereas ProvidePlugin resolves undeclared variables that are assumed to be global, externals resolves specific module names to global variables that are assumed to exist (eg: require('jquery') would resolve to window.$).
I am learning export feature of ES2015. I tried understanding it online but my doubts are still not resolved
When I declare export inside a anonymous function, jshint shows following error (at least inside Intellij plugin):
E053 Export declaration must be in global scope.
On the contrary, JSHint always asks to wrap up whole code inside Anonymous function. If I write code in following way:
export const MY_CONSTANT = 1000;
(function(){
'use strict';
//Complete code goes here
}();
We have to write a lot of code in top and bottom of the page. Some code will jump from between the file to the beginning (or end) of page.
The best way I can explain it is the javascript IIFE was way of creating encapsulation. You would place the code of your module inside one and return and object of some kind. If you needed to import code into it you would do so with the argument. The new module syntax lets you do the same in a different fashion. Think of the the imports as arguments to the IIFE and the exports as the return. Here is the full explanation for import export syntax from Mozilla https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Further more if you want to explore more I have created some boilerplate that uses babel, gulp, browserify, and jasmine so I can write all of my code going forward as es2015. https://github.com/jamesrhaley/es2015-babel-gulp-jasmine.git
If you are using ES2015 module syntax you probably don't need to wrap your code in an anonymous function since the module loader handles what code is exported.
I'm not sure about every module loader but when using TypeScript + browserify, each file gets wrapped to prevent variables cluttering the global namespace. See Why must export import declarations be on top level in es2015 for more on how to work with the module syntax and why variables can be declared globally.