Firstly, I understand text/babel is not for use in production, but I found it quite useful for development as when I make a change to my .jsx file django's dev webserver will reload without me having to do anything (i.e. compile the JSX to JS after every change).
I am not in control of the build environment (e.g. django) as this is a small plugin for a larger system that I am not developing.
The problem is this:
<script type="text/babel" src="{% static "myapp/js/main.jsx" %}"></script>
<script>
$(function() {
console.log(mything);
}
</script>
Where mything is in main.jsx, something as simple as:
var mything = "hello";
If main.jsx is javascript (and the type of the script tags is changed accordingly) then this will work just fine. As text/babel though, it will not work because mything is not in scope.
Uncaught ReferenceError: mything is not defined
This makes sense to me as I wouldn't expect script tags of different types to share a scope, but I'm wondering if there is some clever way around this to aid development?
I previously had all the code in a single text/babel block, but as it grows, it would be nice to separate it out into several JSX files.
Without diving too deeply into the Babel source (looking at https://github.com/babel/babel/blob/master/packages/babel/src/api/browser.js), I'm going to guess that it reads your JSX source, performs transformation on the source, and then evals the source in some way to execute it. The scope is not shared because babel prepends 'use strict'; to the transformed code (standard in ES6).
If you really need to expose a variable, you can attach it to window (ie use window.mything in your JSX instead of just mything). Ideally, you should make use of modules as you split your code up into multiple files. You can make use of a build step to transform your code through Babel and use browserify/webpack to manage dependencies.
Related
While trying to get started with Reason, in one JavaScript project, I've got an extremely light file that tries to be a Reason-typed interface to the existing, heavy, library:
/* TheLibrary.re */
type engine
external addEngine : string -> engine -> unit = "" [##bs.val] [##bs.module "../"]
However, when I try to use that library in a ReasonReact project (having added #org/the-library to the bsconfig.json bs-dependencies),
/* AComponent.re */
[#bs.val] [#bs.module "#org/game-engine/dist/game-engine.js"]
external gameEngine : TheLibrary.engine = "default";
/* Further down, a React lifecycle method, */
TheLibrary.addEngine("Game", gameEngine);
I get errors about ../ being not found, relative to that React component:
./src/components/main-menu/AComponent.re
Module not found: Can't resolve '../' in '/Users/ec/Work/reason-reacty/src/components/main-menu'
I've also tried, instead of ../ in TheLibrary.re's external declaration:
#bs.module "./index.js" (the direct, ES6 entry-point for the untyped-JavaScript side of the package in question,)
#bs.module "#org/the-library", the entire name of said library (even though I'm typing inside that library???)
Please help! I'd love to be able to further adopt ML, but I'm having the hardest time wrapping my head around ReasonReact's dependency-resolution!
Additional context:
So, we're trying to build our first ReasonReact project, and we've successfully added baby's-first-opaque-types to one of our internal libraries and include that in the ReasonReact page with something like the following — which works, by the way:
/* Imports.re */
type engine;
[#bs.val] [#bs.module "#org/game-engine/dist/game-engine.js"]
external gameEngine : engine = "default";
[#bs.val] [#bs.module "#org/the-library"] [#bs.scope "default"]
external addEngine : (string, engine) => unit = "";
This yields, when we Imports.(addEngine("Game", gameEngine)), the global setup line we need: TheLibrary.addEngine("Game", GameEngine). I'm in the very first stages of trying to upstream that typing-information into the parent project, and publish that code to npm, so that all consuming projects can start to use Reason.
t sounds like you might be a bit confused about the different tools that make up your toolchain, so let's first go through them one by one to put them in their place:
ReasonReact is a library of opinionated, "thick" bindings to react.js, which despite the name isn't actually all that Reason-specific, except for its integration with Reason's JSX syntax. It would be more accurate to call it a BuckleScript library.
Reason is mostly just the syntax you use, but is often also used more broadly to refer to the ecosystem around it, and usually also imply that BuckleScript is being used.
OCaml is the underlying language. The "semantics" of Reason, if you will.
BuckleScript is the OCaml-to-JavaScript compiler. It compiles ONE source file, which is considered a module, into ONE JavaScript module, but also requires the type information of other OCaml modules as input.
Now, I suspect you already know most of that, but what you do not seem to know is that NONE of these actually do ANY dependency resolution. These next parts of your toolchain are what does that:
The BuckleScript Build System, or bsb, is what finds all the modules in your local project according to what you've specified in src and any BuckleScript libraries you've listed in bs-dependecies in bsconfig.json. It will figure out the dependency order of all these and feed them to the compiler in the correct order to produce one JavaScript module for each OCaml module (along with some other artefacts containing type information and such). But it will not resolve any JavaScript dependencies.
Lastly, webpack, or some other JavaScript bundler, is what you likely use to combine all the JavaScript modules into a single file, and which therefore needs to resolve any JavaScript dependencies. And this is likely where the error message comes from.
Using [#bs.module "some-module"] will make the BuckleScript compiler emit var ... = require('some-module') (or import ... from 'some-module' if es6 is used), but BuckleScript itself will not do anything more with it. The string you pass to #bs.module is the same string you would pass to require if it had been an ordinary CommonJS module (or whatever other module format you have configured).
Also note that the import is not emitted where the external is defined, but where it's used. You can work around, or "ground" it in a module by re-exporting it as an ordinary definition, ie. let addEngine = addEngine.
In order to precisely answer your question I would need to know which bundler you use, where you've configured BuckleScript to output its JavaScript artefacts, where the externals are used, not just defined, and where the external JavaScript module is located. But I hope all this underlying knowledge will make it easy for you and future readers to identify and resolve the problem yourself. If you're still a bit unsure, look at the compiled JavaScript artefacts and just treat them as ordinary JavaScript modules. At this point that's really all they are.
I'm currently working on a build process that uses webpack. One of the dependencies that I need to include in my bundle is an old third-party library that doesn't follow any module patterns. I wan't to just include it in it's raw form, but it's coded in such a way that the usual techniques don't work.
The offending library is coded a bit like this:
function OldLibrary() {
console.log('What are modules?');
};
var foo = window.OldLibrary;
// Proceeds to use foo without a care in the world
As you can see from the code above, the library assumes that it will always be in the global context, and so it doesn't bother to explicitly set window.OldLibrary.
At first I tried using script-loader, but this wraps the script inside function(module, exports) {}, which means that OldLibrary now belongs to the scope of that function instead of window's and this causes an exception. I also tried using imports loader like imports?this=>window, but this only sets this to window, so it doesn't have effect on functions that are supposed to be global.
Finally, I tried out raw-loader, but this just adds the script as a string, so it's not executed. Is there a way to tell Webpack to just concatenate the script as is without wrapping it inside another function?
Edit
It turns out that script-loader does actually handle this library as expected, I just wasn't matching it properly because it was the only file that didn't have a .min.js extension. I'm still interested in finding a way to concatenate scripts without wrapping them in functions, if possible.
I am new to javaScript and am unsure how to go about creating a new js library and referencing this in another js file.
If I have a standalone file Utilities.js
var Utilities=
{
show: function(input)
{
alert(input);
}
};
Am I missing something as to how a library should be defined standalone?
My second question is how to use that is sub-sequent js files. What I did so far is:
<script type="text/javascript" src="resources/Utilities.js"></script>
In my index.html. Is this enough to reference it as:
Utilities.show("Hello");
In any other java script file?
I tested it in this fashion and got and error "Utilities is not defined"
NOTE: This is just an example and not my full and practical purpose.
Yes, including that Javascript file with that global variable declared is enough to call your methods this way Utilities.show("Hello"); from another Javascript file loaded after Utilities.js or inside a <script></script> section of your html.
But you can actually improve it a little, following the module pattern and exposing only the functions you really need to the global scope (you'll likely write some functions that the users of your library should not call directly, this allows you to do it in a clean way):
var Utilities=Utilities||(function () {
//Accessible only here
var privateArray=[];
//Cannot be called from outside this function
var privateFunction=function(){
}
//Return only what must be publicly accessible, in this
//case only the show() method
return {
show: function(input){
privateFunction();
alert(input);
}
}
})();
That (function () { /* Code */})();, defining a new scope for your code will also avoid name clashes with other global javascript object/functions.
It is OK to use object literals, but you can define libraries using other patterns (module, revealing module, constructors, etc).
I recommend these links to understand primitives, scopes, closures, object literals, etc.
http://bonsaiden.github.io/JavaScript-Garden/
http://jsbooks.revolunet.com/
To call the method inside index.html you need to add a tag.
<script>
Utilities.show("Hello");
</script>
But this approach it's not recommended. Instead of it, you can create a new JS file to run your library code.
<script type="text/javascript" src="resources/Utilities.js"></script>
<script type="text/javascript" src="resources/main.js"></script>
In main.js
Utilities.show("Hello");
Hope it helps.
Given the fact that you gave, within yout question, zero context of what you're trying to achieve, the best answer to your original question is that it depends.
If you just need a bunch of files and you're done (like in your example, Utilities.js and a few more) then you're ok with the way you're heading to.
But of course, you'll allways want to scale your front end and thus you should adhere to some architectural pattern. So, if you're building a client side (browser-side) application, then you should really implement your libraries using the module pattern, and begin your project from a good project example / scaffold.
On the other hand, if you're rendering the html on server (e.g. you're using PHP to render the final html file that will be sent to browser) and you just need some thin functionality in the browser, the way you begun can be okay if you're careful. Also, you can still implement the module pattern here too, although I strongly suggest that you should make use of namespacing to have a clear separation of concerns.
In browser based javascript you can't just call functions from different files yet. In Es6 there are ways. But not yet. Which mean just because you have some variable or function etc then you cant reference it automatically in another file.
Unless both files are loaded into one html and are loaded in order.
Alternatively you could run task runner like grunt and 'merge' them upon each build.
Javascript doesnt have special concept of library, in es6 it's a little different, everything is an object.
What you are doing is just creating an object. and yes it will work.
Not really sure how to ask this, but I'm just looking for some insight/info.
If I'm including multiple JavaScript files in my app, how does the page/app see all the JS code. Does all the JS in all files become one-big JS file
If one JS file has a variable foo=true; and another JS file has foo=false, what is the scope of foo? Is it local to the script it is in or does it become seen in 'all' the js code?
Thanks for any insights.
Yes, for most purposes it's just like if there were only one file, built by concatenating all the files in the order of imports.
The differences :
"use strict"; (supposing it's present) is only valid per file
it's much slower for the browser to fetch 10 small files than a big one, which is why js developers building big applications usually concatenate (and often minify) all the files for production
The only thing the scripts share is the global scope; the separate parts don’t become one big file.
For example, you can’t do this:
<script type="text/javascript">
function hello() {
</script>
<script type="text/javascript">
}
</script>
And a 'use strict' in one <script> wouldn’t apply strict mode across all of them.
If you define in one file, without using var:
foo = true;
It will be a global variable, and it will be accessible by your second JS file when it does foo = false
All your scripts will share the same global namespace if they are defined in plainly on the page. To create a local scope for your variable, you'd have to wrap it in a function.
See the explanation provided in this question: What is the scope of variables in JavaScript?
To easily handle multiple JavaScript files and keeping the global scope minimal, while allowing the different scripts to share information, I recommend you try RequireJS (http://requirejs.org/), it is a JavaScript file and module loader. It will handle any file dependancies you may have and it'll load them asynchronously. It will also make it easy for you to pass variables amongst the different modules/files without having to expose them to the global scope.
There are other file/module loaders too. See: http://www.netmagazine.com/features/essential-javascript-top-five-script-loaders
I'm working with a very large project that uses:
Legacy JSP pages that includes javascript files with script tags
Backbone models and views that uses other javascript modules without RequireJS
We now want to start using RequireJS with jQuery, BackboneJS and UnderscoreJS for everything we develop from now on, but we don't have the resources to rewrite all the legacy JSP pages. We may have time to rewrite the Backbone models and views we have already developed.
The problem is that for our legacy code (both 1 and 2 above) we include all our javascript files in a huge file and ship to the browsers. This huge file must be able to co-exist with our new RequireJS templates, but how can I e.g. separate certain parts of the huge file, so I can use them with the templates using RequireJS? Without having to rewrite all pages that uses this part of the file, or having duplicate code.
Thanks!
I don't know if I fully grasp the problem at hand, but I think a the shim or map functions of RequireJS will help you out.
Extract the parts you want in a new module from your huge javascript file. Then tell RequireJS that your huge javascript file is a dependecy for this new module using shim. Something like:
requirejs.config({
shim: {
'new-module': {
deps: ['huge-javascript-file'],
exports: 'NewModule'
}
});
Shim documentation: http://requirejs.org/docs/api.html#config-shim
The map function might be useful when only portions of your new code have to use your old huge file. Check out this documentation: http://requirejs.org/docs/api.html#config-map
I don't think there is One True Way to achieve this, but I've approached a similar problem by defining module "facades" around the globally scoped code. Let's say your legacy scripts define a global variable called foo. You can define a AMD module and export that variable from it:
//foo.js
define(function() {
return window.foo;
});
//bar.js
define(['foo'], function(foo) {
//do something with foo
});
This way you only need to write a single-line facade every time you need to use a new piece of the existing, globally defined code, without breaking existing code which expects the same code to be globally defined. Over time you can move and refactor the actual implementation into the module without breaking consumer code.