I'm trying to interface ActionScript with JavaScript using ExternalInterface and webpack.
ExternalInterface can only provoked (call) functions found on the global object (window). How can I get a webpack module reference on window (global object)?
Allow me to elaborate some, I want to have a namespace for the company (window.companyName) with an interface for ExternalInterface:
window.companyName = { isReady: function() { ... },
driver1: function() { ... },
driver2: function() { ... } }
The ActionScript will drive my JavaScript. The more fundamental question is, how do I set globals using webpack so that ExternalInterface can see them (preferably as exports of a module)?
I've tried using expose-loader, exports-loader imports-loader with no luck. expose-loaderis ideally what I need, but doesn't seem to work. When I set window.companyName in my modules and try to verify it in my chrome console, it results in undefined.
Aren't you using webpack-dev-server?
Because when I try webpack command everything is working fine. I'm testing it by typing window.mySampleGlobalVariable in chrome developer tools.
BUT when I run webpack-dev-server command then my window variable is undefined.
I have this sample app:
app.js
window.mySampleGlobalVariable = 'TEST';
console.log('App is ready');
index.html
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Webpack test</title>
</head>
<body>
Webpack test
<script src="bundle.js"></script>
</body>
</html>
webpack.config.js
var path = require('path');
module.exports = {
entry: './app.js',
output: {
filename: './bundle.js'
},
resolve: {
extensions: ['', '.js']
}
};
You do have access to window object from your webpacked script. Webpack does not interfere with it since the wrapper function only injects module, exports and __webpack_require__ arguments.
Try it writing a script with a single line accessing window object and then check out the output script.
Your assignment should work, unless the execution never reaches it or some loader is altering the relevant code.
If you running webpack-dev-server with the iframe you cannot access the variable via the Chrome console.
Related
I'm developing a plugin for a tool. The plugin is just a zip file, and the tool only looks for and loads main.html file. Thefore, any JS must be inlined in the HTML file.
I'm trying to develop with separate Typescript files and then have webpack bundle all the .ts files into a bundle.js and inline it in the HTML.
I managed to this but there's one problem. How to call the logic in the bundle from the HTML?
Webpack creates __webpack_modules__ IIFE variables for the code in the bundle.
<head>
<script>bundled JS</script>
</head>
<body>
</body>
<script>
// How to call here an init() function in the bundle?
</script>
I tried to setup iife: false with no luck:
You'll need to explicitly export to the global object by assigning to window.
<script>
(() => {
function myFn() { console.log('myFn ran') }
window.myFn = myFn // assign to global object here.
})()
</script>
<script>
myFn()
</script>
Then if you want typescript type available for that, see: Create a global variable in TypeScript
It looks like you want to export your bundle as a library that we can use in a different script. If so, the option output#library comes to help so the only thing you need to do is to export it as library with umd format which works great for both node and browser envs.
{
output: {
// ...
library: {
name: 'yourLib', // you then can access it via window: `window.youLib`
type: 'umd',
umdNamedDefine: true,
},
}
I try to integrate instantclick to my rails 6 application. I added the instantclick.js file in my javascript/custom folder. Then, I import it in javascript/packs/application.js:
require("custom/instantclick")
I added to the end of the body tag of my layout the following line to initialize instantclick:
...
<script data-no-instant>InstantClick.init();</script>
</body>
</html>
Then, I get the following error in console:
(index):404 Uncaught ReferenceError: InstantClick is not defined
I'm new to using webpacker, maybe I did something wrong. However, the instantclick.js file is imported. Any idea what I'm doing wrong? Thanks.
Webpack works differently than Sprockets. One thing you're running into is that webpack does not expose anything to the global scope. That means calling functions from a script tag won't work as expected unless you instruct webpack to add those variables to the window object.
Your instantclick.js file is also not module-aware, i.e, it does not export anything, such as with module.exports = InstantClick.
With webpack though, you can configure it to do both. You'll need to both export and expose InstantClick:
yarn add expose-loader exports-loader
// config/webpack/environment.js
const { environment } = require('#rails/webpacker')
environment.loaders.append('InstantClick', {
test: /instantclick/,
use: [{
loader: 'expose-loader',
options: 'InstantClick'
},
{
loader: 'exports-loader',
options: 'InstantClick'
}]
})
module.exports = environment
There is more to learn and other ways you can do it of course. Here are some resources:
https://webpack.js.org/guides/shimming/
http://www.matthiassommer.it/programming/expose-global-variables-methods-modules-javascript/
https://rossta.net/blog/three-ways-webpack-surprises-rails-developers.html
The Scenario
I'm using Browserify (or NodeJS/CommonJS like imports) to require some JavaScript libraries. However, I'm having trouble getting them to play nice since apparently Browserify by default denies global scope access to all modules. For example, doing this doesn't work;
file1.js
require('moment');
file2.js
moment(new Date()); // throws moment is undefined
But this works by changing the contents of file1.js to the following;
window.moment = require('moment');
The Problem
This has worked well enough so far, however now I'm having trouble loading the moment timezone library (an extension of MomentJS). Moment Timezone's docs imply both scripts should run on the window global scope by adding them as script tags like so;
<script src="moment.js"></script>
<script src="moment-timezone-with-data.js"></script>
So they can be used like this;
moment().tz("America/Los_Angeles").format();
This however, seems very hard to achieve, since if I try the following;
window.moment = require('moment');
require('./../../node_modules/moment-timezone/builds/moment-timezone-with-data-2010-2020'); // the location of my moment-timezone library
I get a runtime error saying that moment.tz is undefined (meaning that the second library wasn't run with the global scope). And if I try to add the window.moment to the second line of code, I'll be rewriting the first instance of the full library.
So, in short;
Is there a way to allow certain Browserify imports to have global scope access, or to run with the window object as their scope?
I'm aware of the security implications this has, but selectively used, this would be immensely useful while requiring things like JavaScript libraries that need global access to set up themselves correctly. Any help would be greatly appreciated.
Put tiny libraries into vendor.js, include that on your page with <script>, make them known to browserify with browserify-shim and use them with require('libname') in your modules.
Larger libraries may be included from, say, vendor CDNs, and also be made known to browserify with browserify-shim.
index.html
<html>
<head>
<script src="vendor.js"></script>
<script src="bundle.js"></script>
</head>
<body>
Open devtools and inspect the output
</body>
</html>
package.json
{
"scripts": {
"build": "cat vendor1.js vendor2.js > dist/vendor.js && cp index.html dist/index.html && browserify index.js -o dist/bundle.js"
},
"browserify-shim" : {
"vendor1" : "global:vendor1",
"vendor2" : "global:vendor2"
},
"browserify" : {
"transform" : [ "browserify-shim" ]
},
"devDependencies": {
"browserify": "^14.1.0",
"browserify-shim": "^3.8.14"
},
"dependencies": {
"moment": "^2.18.1",
"moment-timezone": "^0.5.11"
}
}
A couple of things to note about the above package.json:
build: it builds 'application' in dist folder, creating the following structure:
dist
index.html
bundle.js
vendor.js
"browserify" entry: it just adds browserify-shim
"browserify-shim" entry: it tells browserify that vendor1 and vendor2 are available as properties on window (global) object. Browserify won't attempt to bundle them, and require() will just return those properties. You need to make sure they are actually available (so index.html above includes them with <script src="vendor.js">
index.js
var momentfromnpm = require('moment-timezone');
var vendor1 = require('vendor1');
var vendor2 = require('vendor2');
console.log("Hello");
console.log("Using module from npm", momentfromnpm().tz("Europe/London").format());
console.log("using a function from vendor1.js (bundled into vendor.js)", vendor1());
console.log("using a 'module' from vendor2.js (bundled to vendor.js)", vendor2.doWork());
vendor1.js
window.vendor1 = function() { // could also be just `function vendor1()...`
return "I'm a simple function, defined on a window";
};
vendor2.js
var vendor2 = { // could also be window.vendor2
doWork: function() {
console.log("vendor2 doing work");
}
};
Install the moment-timezone package as instructed on the website.
npm install moment-timezone --save
In file1.js, define window.moment as so:
window.moment = require('moment-timezone/builds/moment-timezone-with-data-2010-2020');
This should allow you to use moment().tz() in file2.js
I've been using RequireJS and it works perfectly. I use a lot of "window.document" to manipulate different DOM elements, but when I try to optimize it with r.js i get a ReferenceError: window is not defined which only happens with r.js.
Here is a minimal example of code that reproduces the issue:
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body >
<div id="commentbox">
</div>
<script data-main="code/main" src="code/require.js"></script>
</body>
</html>
main.js:
require(["roomManager"], function (roomManager){
return {
}
});
roomManager.js:
define(["commentManager"], function(commentManager){
var commentHand = new commentManager.commentHand();
commentHand.init();
return{
}
});
commentManager.js:
define([], function(){
function commManager(getDisplayIdVariable){
var messagebox = window.document.getElementById("commentbox");
this.init = function(){
messagebox.innerHTML = "hi!";
}
}
return{
commentHand : commManager
}
});
This version works correctly without r.js but when I try to compile it by running r.js main.js. I get this:
var messagebox = window.document.getElementById("commentbox);
ReferenceError: window is not defined
at new new commManager
You cannot just do r.js main.js.
For one thing, you have to specify -o so that r.js performs the optimization. (r.js can be used for other things.)
You also have to pass configuration to r.js, either in a file, or on the command line. One possibility for you would be:
r.js -o name=main out=built.js
I've tried this with the code you show in your question and I get no errors.
I strongly suggest going over this documentation for r.js.
if your code is optional you can use
if (typeof window !== 'undefined') {
// Inside browser
}
{
// outside browser
}
i want to use a config file in nodejs and in a web javascript.
config.js:
var conf = {};
conf.name = 'testname';
conf.pass = 'abc123';
conf.ip = '0.0.0.0';
conf.port = 100;
conf.delay = 5;
exports.config = conf;
use it in nodejs with:
var conf = require('config.js');
console.log(conf.config.name);
want to use this same file inside html but how? I was thinking by this way but i don't know how to use it in web. When i try to use it in web i get Reference error: exports is not defined.
config.html:
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<script src="./config.js"></script>
<script>
var cnf = conf;
function getCnf(){
alert(cnf.config.name);
}
</script>
</head>
<body>
<button onclick="getCnf();">test</button>
</body>
</html>
Anyone know how i must change config.js to use it in both systems nodejs and web?
PS: Webside is running on nodejs http npm module.
You can put a condition around that, like this
if (typeof module !== 'undefined' && module.exports) {
module.exports.config = conf;
}
This makes sure that you have module and exports are available before setting any value on exports.
Note: exports is just another variable referring module.exports. So, they both are one and the same unless you assign something else to either of them. In case, you assign something to either of them, whatever is there in module.exports will be exported in Node.js. You can read more about exports in this blog post
Thanks, that typeof was all i needed.
#Phoenix: I know that there is a way to do that but that's not necessary. The variable are only used for some ajax requests and deley timers later.
You can use browserify to bundle your CommonJS for the browser without resorting to environment switches.
Install browserify using npm i browserify -g
Bundle your config.js and export it for external use with the -r tag
browserify -r ./config.js -o bundle.js
Include the bundle in your code and use it:
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<script src="./bundle.js"></script>
<script>
var cnf = require("./config.js");
function getCnf(){
alert(cnf.config.name);
}
</script>
</head>
<body>
<button onclick="getCnf();">test</button>
</body>
</html>