Converting a common js global namespace A-Frame project into es6 modules - javascript

I have an A-Frame project with a primary component street that also depends on other components and helper functions from the same repo.
Currently using the street component in an A-Frame scene requires importing 8 separate js files from the same repo each with their own <script> tag. (code / show page)
Instead, I would prefer a simpler structure to import just one file, but I'd rather not use a bundler such as webpack. I think there is an ES6 approach but I'm confused about how to go about leveraging ES6 imports while still allowing the components to access functions from other files. In other words, how to get the imported files into the same namespace.
This question helps but the comments clarify the imported functions are not added to the global namespace automatically:
ES6 import equivalent of require() without exports
Maybe the structure would be something like this?
// index.html
<script src="src/street.js" ></script>
<a-scene>
<a-entity street="dosomething"></a-entity>
</a-scene>
// helperFunctions1.js
function coolHelperFunction1a (variable) {
// useful code
}
// street.js
import 'helperFunctions1.js'
import 'helperFunctions2.js'
AFRAME.registerComponent('street', {
coolHelperFunction1a (variable);
})
That probably won't work, what's the right way to do it?

A-Frame and ES Modules don't play well together, and I don't think there's a solution without using a bundler like Webpack.
In short, if you want to have a single <script> element for your entire app while still organizing your code well, I'd recommend Webpack, with the generated bundle being loaded synchronously in the <head> of your HTML document. Webpack can consume ES Modules, so you're still free to use those with the build process.
More detail on why native module loading won't work:
Your code would need a few changes for native ES Module usage, but the first one presents a problem: the <script> elements loading your modules need the type="module" attribute. This has a side effect: the browser treats modules the same way it treats traditional script tags with the defer attribute, meaning the script is executed after the DOM has been parsed. This is by design, as it leads to better initial page performance. Unfortunately A-Frame expects to run before the DOM has been parsed, and further it expects that you've initialized all of your components, systems, etc. before it runs. That is why they recommend putting the a-frame script in the <head>, and you'll notice there is no defer attribute.
A-Frame is kind of going against modern web performance advice, but it has a good reason: it doesn't want the browser to render the HTML before it runs, since its using the HTML for its own purposes.
This all means using native ES modules directly in the browser isn't going to work for an A-Frame app.

The suggested structure above needs a few fixes.
First, add export to the helper functions. For example,
function helperFunction (variable) {
code (variable);
}
changes to
export function helperFunction (variable) {
code (variable);
}
(See How can I export all functions from a file in JS?)
In street.js we also need to make sure to reference the module object it's imported as (See MDN Creating a module object)
We also need to change the <script> tag to include type="module" to use import and export keywords.
Here is a revised structure:
// index.html
<script src="src/street.js" type="module"></script>
<a-scene>
<a-entity street="dosomething"></a-entity>
</a-scene>
// helperFunctions1.js
export function coolHelperFunction1a (variable) {
// useful code
}
// street.js
import * as helperFunctions1 from 'helperFunctions1.js'
import * as helperFunctions2 from 'helperFunctions2.js'
AFRAME.registerComponent('street', {
helperFunctions1.coolHelperFunction1a(variable);
})

Another option, don't use ES6 Modules and use webpack instead. This is a nice example of a webpack A-Frame component project:
https://github.com/supermedium/superframe/blob/master/components/state/

Related

How do you import jQuery plugins with Webpack?

I'm using Webpack v4, and I have jQuery plugins which I currently load into our app with the webpack-merge-and-include-globally webpack plugin (and then manually load these into the html file with a <script> tag) but I would like to be able to move this into our main app code, so that webpack can be aware of them. There's been issues where some dependancies/classes are loaded twice, once in the merge-plugin mentioned above, and again in the Webpack dynamic imports.
So far its been hit and miss trying to get jQuery plugins to load and properly attached to the jQuery object.
Is there a recommended way to import jQuery plugins, as its not like a normal JavaScript ES6 class which you can just prefix with export class ClassName or export default class ClassName, because the plugin is wrapped in an IIFE (Immediately Invoked Function Expression).
A few potential solutions:
Option 1 - jQuery plugin that does not need its own global
import 'myapp/jquery-plugins/MyjQueryPlugin';
This one seemed potentially too good to be true / too easy, as it did not require new loaders.
Add the import statement without a name, just the script path. This should self execute the script, similar to how it would on a load from HTML.
Note that this would not work with a script that needs to be Global. For that, use the expose-loader in option 2 to specify what globals to expose.
As I found that each plugin/library/class I had to work with required a different way of handling it, I will also mention some other methods I found useful, and may be useful to others:
Option 2 - globals, such as jQuery
import 'expose-loader?exposes[]=$&exposes[]=jQuery!jquery';
This would import and execute it as it went, so that the next import line could use the global variable straight away.
Option 3 - alternative for globals
import MyClass from './views/MyClass';
window.MyClass = MyClass;
You can import, and then set on the window yourself, but if you have several import statements, and the latter depend on the first one already being available, then this wont work, as its not yet defined. See option 2. This is my least favorite, but worth mentioning as a fallback.
Option 4
Use the [webpack-merge-and-include-globally][1] plugin, but this is the one I was trying to avoid/reduce the use of, as its outside of "webpack's awareness."
Others
There's also other loaders like raw-loader, which you can use as normal with a !! in the import, or with a .then promise to control execution after load.
import('raw-loader!someScript.js').then(rawModule => eval.call(null, rawModule.default))
This is the example given by the docs in script-loader as an alternative, as script-loader is deprecated.

How to build a JS module that can be used on the page directly?

I am the author of vue-sequence. This library is built with vue-cli and it export a library in umd format. To use this library, I have to use webpack to make it work on a web page, something like:
import { SeqDiagram, Store } from "vue-sequence";
...
Vue.use(vuex);
Vue.component("seq-diagram", SeqDiagram);
let store = new vuex.Store(Store);
A complete example of using it can be found at here: https://codesandbox.io/s/5v4v78w1wk.
I would like to be able to use it directly on a page (but still support using it as a node module), like:
<script src="vue-sequence.js" />
<script>
vueSequence.processAll()
</script>
I understand I would need to define this processAll() method somewhere. My question is about how to package this library.
I tried a few things. I think I know how umd works better. If we directly include the umd library on a web page, the library will create a field on the global window variable (in this case vue-sequence and referable via window['vue-sequence']).
With the following html configuration
<title>vue-sequence demo</title>
<script src="./vue-sequence.umd.js"></script>
<script>
console.log(window['vue-sequence'])
</script>
It will print out the exported module. Knowing about this is enough for me to continue working on a solution to add the processAll() fuction.

Is it correct in terms of principle, to use `import` statement to import file types other than JS

Does using the JavaScript import statement for images, css and others defeat the purpose of import statement which was designed to import only the JS Modules ?
Of course, for now it gets transpiled to ES5 require using webpack. But that same question comes up again. Is it incorrect to use import statement to import images or css or files ?
EDIT:
I like the idea of controlling imports that we can control the assets on build time in such a clean way - The idea that I use the image path to import the image, and
on different environments the image path would contain different values - url or path
this image can be compressed on build time
the JS module importing this image can contain the image dimensions through a custom loader
assets dependency tree is maintained at one place and un-imported items gets chucked away automatically
rebuild time is fast - DX(developer experience) would be good
I guess, this is much better than using any templating, using placeholders in the JS files to inject URLs or paths based on environment during pre-build (webpack).
But using the import statement feels not right to do so in terms of principle or semantics.
This is from Mozilla Developer Network:
The import statement is used to import functions that have been exported from an external module, another script, etc.
From everything I've read on MDN and other sources its purpose is to make a module/script methods and properties available within the current scope it's being imported into.

Import existing library with JavaScript ES6 Modules

How can an existing library be loaded and run using JavaScript's ES6 Modules?
For example, suppose I need to load an existing polyfill:
import {poly} from "thirdParty/poly";
How can I run the imported poly script and load its properties into the current namespace without changing the source?
Here are two practical problems to help clarify the problem I'm trying to solve:
I have a script called rafPolyfill.js which is a polyfill for window.requestAnimationFrame. I need to import it into the global scope and run it immediately as soon as it loads. This is easy to do with a <script> tag:
It runs and loads itself into the global scope. How is can this be done using ES6 modules?
I have another script called Font.js which is a pre-loader for fonts. It let's you create new font object like this:
var font = new Font();
I used Font.js by loading it with a script tag, like this:
<script src="Font.js"><script>
Without accessing, changing, or understanding the source code of this script, how is it possible to use ES6 modules to load and use the in the same way that I would with a <script> tag? I just need these scripts to run when they're loaded and take care of themselves.
A possible solution might be using the module Loader API:
http://wiki.ecmascript.org/doku.php?id=harmony:module_loaders
This document describes global binding of the System loader, but I'm afraid I don't have the vocabulary to fully understand what it's trying to explain. Any help in decoding this document would be greatly appreciated!
This answer is: "No, based on the ES6 spec it's not possible to load and run a global script the same way you can with a script tag."
But there is a solution: SystemJS
https://github.com/systemjs/systemjs
It's a universal module loader for ES6 modules, AMD modules, and global scripts (anything you load with the <script> tag)
Does this or something close to this work for you?
var stuffFromPoly = import "thirdParty/poly"
Then you would call methods off of the object stored in stuffFromPoly.
If that's not quite it, could you expand your question a bit, I'm trying to guess at exactly what you mean and I may be a bit off.
Quick note post-'your update':
Are you opposed to using https://www.npmjs.org/package/es6-module-loader ? Or does that not quite solve the problem?
From the readme:
The new ES6 module specification defines a module system in JavaScript using import and export syntax. For dynamically loading modules, a dynamic module loader factory is also included in the specification (new Loader).
A separate browser specification defines a dynamic ES6 module loader loader for the browser, window.System, as well as a tag for using modules.
This polyfill implements the Loader and Module globals, exactly as specified in the 2013-12-02 ES6 Module Specification Draft and the System browser loader exactly as suggested in the sample implementation.

Using require.js for client side dependancies in Adobe CQ5

I was wondering if anyone had experience using require.js with the Adobe CQ5 platform. I'm writing a Chaplin.js(backbone-based) single page app that will be integrated into the rest of CQ5-based site we're working on. Chaplin requires the use of a module system like AMD/Common.js and I want to make sure my compiled javascript file will usable within CQ5's clientlibs. Is it as simple as adding require.js as a dependency in clientlibs prior to loading in my javascript modules? Someone's insight who has experience in doing this would be greatly appreciated.
I've implemented this as a solution of organize in a better way all the modules such as:
//public/js/modules/myModule.js
define('myModule',[ /* dependencies */] ,function( /* stuff here */ ))
and in the components such:
<% //components/component.jsp %>
<div>
<!-- stuff here -->
</div>
componentJS:
//components/component/clientlibs/js/component.js
require(['modules/myModule']);
and finally I've configured require (config.js) and I've stored the JSs modules in a new different design folder. Located the compiled JS after close </body> to guarantee the JS is always located at the bottom after the HTML.
<!-- page/body.jsp -->
...
<cq:includeClientLib js="specialclientlibs.footer"/>
</body>
solving with this the issue of have "ready" all the content before the JS is executed. I've had some problems to resolve with this async stuff managed for the clienlibs compilation tool, in production the problem was different, however, in development, the order in what CQ compiles the JS has produced me some lacks in terms of order of the JS. The problem really was a little bit more complex than the explanation because the number of JS was really big and the team too, but in simple terms it was the best way I've discovered so far..
The Idea
I think you can compile your Chaplin.js with one of the AMDShims to make it self contained, wraps every dependencies it needs inside a function scope, expose an entry point as global variable (which is a bad practise, but CQ do it all the time...) and then use the compiled.js inside a normal clientlib:
AMD Shims
https://github.com/jrburke/requirejs/wiki/AMD-API-Shims
Example
Here is an example of how we make the one of our libs self-contained:
https://github.com/normanzb/chuanr/blob/master/gruntfile.js
Basically, in source code level the lib "require"s the other modules just as usual. However after compiled, the generated chuanr.js file contains everything its needs, even wrapped a piece of lightweight AMD compatible implementation.
check out compiled file here: https://github.com/normanzb/chuanr/blob/master/Chuanr.js
and the source code: https://github.com/normanzb/chuanr/tree/master/src
Alternative
Alternatively rather than compile every lib you are using to be independent/self-contained, what we do in our project is simply use below amdshim instead of the real require.js. so on cq component level you can call into require() function as usual:
require(['foo/bar'], function(){});
The amd shim will not send the http request to the module immediately, instead it will wait until someone else loads the module.
and then at the bottom of the page, we collect all the dependencies, send the requirements to server side handler (scriptmananger) for dynamic merging (by internally calling into r.js):
var paths = [];
for (var path in amdShim.waiting){
paths.push(path);
}
document.write('/scriptmananger?' + paths.join(','));
The amdShim we are using: https://github.com/normanzb/amdshim/tree/exp/defer

Categories

Resources