How to use a compiled ES6 to ES5 file in browser? - javascript

I have two modules in ES6:
//mycalss.js
class MyClass {
}
export {MyClass};
//main.js
import {MyClass} from './../src/myclass';
And I am using Laravel-mix to handle the compilation and bundle process.
In the end I end up with a main.js.
I link this file in my html.
<script type="text/javascript" src="main.js"></script>
And try to make a reference to the class MyClass:
<script type="text/javascript">
var object = new MyClass();
</script>
But a Uncaught ReferenceError: MyClass is not defined is thrown.
So, how do I use a classe compiled from es6 to es5 in my html?

ES6 modules have module scope. This prevents them from leaking variables to global scope. This is exactly what modularity is for.
In order to use items from ES6 modules (the same applies to CommonJS modules or IIFEs that implement module pattern) in global scope, they have to be defined as globals. In the case of client-side script this means that they should be defined as window properties inside bundled application:
window.MyClass = MyClass;
This indicates potential XY problem. A better option is to reconsider the reasons why it should be available in global scope. If there is bundled ES6 application already, it is supposed to run code from inline <script type="text/javascript">...</script> too.

You can use Babel to compile JavaScript from ES6 to ES5 in browser.
Often, you have to use webpack to do that automatically for you and insert the script inside HTML.

Related

Can not include JQuery without npm in Vue JS project

I'm trying to import JQuery without using npm to my Vue JS project. Here's what I'm trying:
***jquery-functions.js***
import '../../public/js/jquery.min.js'
export function bar(){ $(".foo"){...} }
/$ is not defined as function/
var hello is evaluated in module scope, this prevents variables from leaking to global scope.
In order for a global to be defined, it should be assigned explicitly as such:
window.hello = function (x){x+5};
As for jQuery, it's specific to how the module works. It's UMD module and it isn't exposed as a global when jquery.min.js is imported as a module.
It should be assigned explicitly as a global either:
import jQuery from '../../public/js/jquery.min.js';
window.$ = window.jQuery = jQuery;
Or this can be done by means of Webpack that is used by Vue CLI internally, as the answer in related question suggests.
I tried everything above but somehow I couldn't get it working. Either eslint was giving function not defined error for $() or bootstrap couldn't see the included jQuery. I don't know if it's ok to include jQuery globally but I found the solution by including jQuery to my index.html file with <script> tags. Then in my jquery-functions.js file I disabled eslint then everything was working just fine on run time.
***jquery-functions.js***
/* eslint-disable */
export function bar(){ $(".foo"){...} }
I did it like that so that I can separate jQuery methods from from Vue JS methods. It might be better to use npm to avoid including jQuery globally so that you can include it only in components that you need. For me I was concerned with compatibility of all other (6 of them) jQuery dependent libraries included in the template that I downloaded from internet.

Can I access global ES5 variable inside ES6 module?

I'm trying to gradually modularize and rewrite in ES6 a project written in ES5.
With this I have common scripts with classes and variables as well as modules using the type = "module" attribute when loading ES6 modules.
Example:
<script src="constants.js"></script>
<script src="main.js"></script>
<script src="banners.js" type="module"></script>
My problem is the following, I want to access inside the module "banners.js" the constants of the file "constants.js"
At first I thought this was possible, as linux chrome and firefox accept calling a global variable within the banners.js module, but unfortunately the iPad / iOS11 chrome does not accept and is accusing error: ReferenceError: Can't find variable
Unfortunately I can not convert the constants.js file in a module to use the export / import syntax because these constants are used in ES5 parts of the code that can not yet be converted to ES6 modules and could not read the constants if I convert the constants into an ES6 module.
Is there a way to access the global scope from within an ES6 module? Or some other way to get the variables from the file "constants.js"?
Maybe this is a chrome bug for iOS and the Linux version shows the correct behavior?
This difference in behavior between operating systems really left me confused as to the scope of the modules and their ability to access the global scope.

Multiple TypeScripts Files Configuration

Here comes silly and simple question:
I am new to this whole webpack tools.
I have been trying to build a simple web-app using typescript and webpack. In the old days, I created typescript and compile them without bundling them.
With webpack, I already installed necessary loaders, typescript, and jQuery.
The problem is, I have 3 typescript files:
main.ts -> imports all assets (images, css) and other typescripts
functions.ts -> consist all of my custom functions/modules
ui-controller.ts
in functions.ts I always created namespaces such as:
module Functions{
export module StringTools{
export function IsEmpty(): boolean{
//some code
}
}
}
in the browser, I knew that the snippet code above will be called, but it is not recognized in the main.ts (in the run time) even thou I already import it.
This is how I import it in main.ts:
import '.src/functions'
Any suggestion how I can resolve this?
Typescript module keyword is confusing, and in version 1.5 it was indeed changed to namespace to better reflect it's meaning. Look here.
Namespaces are also called internal modules. They are meant to be used when your files are evaluated at the global scope. You can use typescript playground to see how namespaces are transpiled. The point is - namespaces are not modules.
Webpack however, does not evaluate files in the global scope, it evaluates them inside a function in order to provide real module behavior.
So what does make your typescript file into a module? the keywords export and import (but not inside a namespace like in your example).
Typescript will see those keywords, and will transpile them into commonjs\AMD\es6 require or define statements, according to your configuration in tsconfig.json. Now you have "real" modules. Then it's Webpack's job to do something (lots of info about that online) with those modules so that they will work in the browser where you don't have modules, but that part is not related to typescript.
TL;DR -
So how would you do this in Webpack?
/* functions.ts */
export function IsEmpty(): boolean{
//some code
}
and
/* main.ts */
import {isEmpty} from './functions';
if you want better code organisation as suggested by your use of module StringTools, just split into different files. You can read more about es6 import syntax for more info.
The first part of this answer is that your main module (which has been replaced with the namespace keyword) is not exported... so there are no exported members for your functions file.
export namespace Functions { //...
Part two of the answer is that if you are using modules, you don't need to use namespaces as the module is enclosed within its own scope.
Working version of the file as you designed it, I'd say drop the wrapping namespace:
functions.ts
export namespace StringTools{
export function IsEmpty(): boolean{
//some code
}
}

IIFE across multiple files

Is there anyway to have javascript code defined in 2 or more separate files to run in the same IIFE? I'm open to using build tools like gulp to accomplish this.
It just seems like such a mundane problem. I want my code to be organized and separated into their own files (distinct knockout view models, by the way). But I want them all to run in the same function and not pollute global.
The modern way to do this is to use modules rather than try to put everything into an IIFE. Right now, using modules means using a module bundler like RequireJS, SystemJS, Webpack, Browserify, etc. In the medium-term future, you'll be able to use ES2015+ modules directly in the browser if you like, or again use bundlers to bundle them into a single file. (You can use ES2015+ module syntax today with transpilers like Babel, but you still need a bundler.)
You've mentioned you're using RequireJS at the moment but not using its define functionality. Just for the purposes of illustration, here's roughly how you'd define a module (I'm not a fan of Require's syntax, but you can use Babel to transpile ES2015 syntax to it):
Say I have a module that defines a KO component:
define("my-component", ["ko"], function(ko) {
// ...define the component...
// Return it
return MyComponent;
});
That:
Says the module is called my-component (module names are optional, so for instance the top-level module of an app needn't have a name)
Says it depends on the module ko (which provides Knockout); note how that dependency is then provided as an argument to the callback that you use to define your module
Returns MyComponent as the top-level thing defined by the module
Then in my app:
define(["my-component", "another-component"], function(MyComponent, AnotherComponent) {
// use MyComponent and AnotherComponent here
});
You can also have modules that group together other modules that are commonly used in groups, to simplify things.
In ES2015+ syntax instead:
my-component.js:
import ko from "./ko";
// ...define MyComponent...
export default MyComponent;
app.js:
import MyComponent from "./my-component";
import AnotherComponent from "./another-component";
// ...use them...
Obviously, both examples are very simplified and there's a lot more you can do.
This is two separate issues.
Can an IIFE span multiple files - NO!
Can classes/functions/etc in multiple files NOT polute global scope - YES!
file1
var myNS = {};
file2
myNS.MyViewModel = function(){ ... }
file3
myNS.OtherViewModel = function() { ... }
As a very simplistic example, of which there are 101 ways to achieve the same.

ES2015 modules vs ES5 globals determined by 'export' keyword?

I'm reading about ES2015 modules and trying to make sure I understand this new feature.
Since there's nothing like "use strict", how does the browser determine that a .js file is an ES2015 module v.s. an ES5 file with a bunch of globals? Is it just by the presence of at least one "export" statement?
// This file is interpreted as ES5 with globals
function fun1() {...}
function fun1() {...}
// This file is interpreted as ES2015 module
function fun1() {...}
function fun1() {...}
export default function(){...}
Since there's nothing like "use strict", how does the browser determine that a .js file is an ES2015 module v.s. an ES5 file with a bunch of globals? Is it just by the presence of at least one "export" statement?
When you asked this question, it hadn't been decided, but it was a couple of years later: The type attribute is used:
<script type="module" src="./mod.js"></script>
You also need to include a path (not just src="mod.js"), unless you use a import map (which is relatively new as of this writing in July 2019, and I don't think any browser supports them natively yet).
If you use import or export in something that isn't a module, you'll get a syntax error.
In Node.js, which has its own CommonJS-like (CJS) module system, ECMAScript Modules (ESM) are signified in one of two ways:
By having "type": "module" in the nearest package.json, or
By giving the script the extension .mjs instead of .js.
(If the nearest package.json has "type": "module", you can still have a script that's a CJS module by giving it the extension .cjs.) Details here.
According to this page, modules can't go inside script tags, they must go inside module tags. So code loaded by script tags can't be treated as modules, and code loaded by module tags must be a module.

Categories

Resources