Jump to content
Search Community

ES6 Modules Proof of Concept

OSUblake test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Just something to think about...

 

The module issue is not going to go away until the source code is split up into actual modules. I've seen a lot of suggestions on GitHub, but I don't think any of them take into account the future. The JavaScript landscape changes too rapidly to depend on one type of technology. Just look at the decline of Grunt and Bower.

 

Instead of creating modules for a specific loader (AMD, CommonJS, UMD, global), just create ES6 modules and let the user decide on the loader. This is the best way to design for future since it's going to be part of the language itself.

 

To show how this would work, I split the TweenLite file up different modules including: Animation, SimpleTimeline, TweenPlugin, Easings, Ticker, and EventDispatcher. The only changes I really made were to the easings, to allow exporting eases individually because I didn't include the current GSAP way of defining classes. I also added an object called "gs" that get's passed around to the different modules as a work around to all the variables that get reused in the original file. So things like the ticker and rootTimeline instance are on that object.

 

This was a quick proof of concept, and I really didn't do any testing so I might have missed something, but I am able to request GSAP modules in the browser using an ES6 module loader polyfill. This will also work with NodeJS.

 

So now I can request stuff like this... (although I didn't actually do the CSSPlugin)

// Get individual components
import { TweenLite, CSSPlugin, Power4 } from "greensock";

TweenLite.to(myElement, 1, { x: 400, ease: Power4.easeInOut });

Of course this is only for users that want to use modules. For users that like using source files the traditional way, you could create a "dist" folder with these files already built, similar to current file structure of the src folder.

 

See the Pen by edit (@edit) on CodePen

  • Like 7
Link to comment
Share on other sites

Always pushing things forward, Blake. Thanks a bunch for starting this conversation and taking a crack at a proof of concept. Very helpful. I'll try to look into this further as soon as I have a chance. I'm curious what you'd say the down sides to this approach are. 

 

I love the idea of not being tied to a particular thing like AMD, CommonJS, RequireJS, Babel, etc. Wasn't sure that was even possible. I'd really hate to spend a bunch of time, for example, structuring things for requireJS and then in 6 months there's some hot new thing that everybody is moving to that isn't compatible and we've got to rework the guts again. Yuck. 

 

Also, what effect would this approach have on the compiled "bundled" (distribution) files like TweenMax.min.js and TweenLite.min.js? If there's a bunch of extra code due to the dependency injection, I wonder if that'd be a deal-breaker file-size-wise. Probably not, but just asking. In other words, if we split things apart like this and then had a task that re-combines them all into TweenMax.min.js, how would that file size compare with the TweenMax.min.js of today? 

  • Like 1
Link to comment
Share on other sites

I'm not sure what I would consider a downside to this approach. ES6 modules will be the end result sevaral years down the road, but right now the biggest thing is that ES6 modules have not been implement in any JavaScript engine, including Node. This means it needs to be converted to something usable somehow. So somewhere along the build process a transpiler like Babel, Traceur, or TypeScript has to be used. 

 

Another thing is that until ES6 modules are used everywhere, you might need to create a build process that compiles to everybody's flavor because I'm sure somebody will complain. However, this can all be automated, so you wouldn't have to change anything for each type of loader. Aurelia is a good example of how this can be done. In their dist folder they have versions for AMD, CommonJS, ES6, System (that's what I used on Plunker), and normal JS.

 

I don't think the file size would increase that much, if any. I took the dependency injection out because it really wasn't needed for the demo, and I wasn't sure if it would cause problems with the way I separated the code into different modules.

 

I was just looking at your ActionScript code, and it seems like you already have a good system in place of how to structure it. In fact, what I wrote as the imports for TweenLite is pretty much what you had in AS.

// JavaScript, including a bunch of "_" prefixed globals
import { gs, _tinyNum, _slice, _emptyFunc, _isArray, _defLookup } from "./gs";
import { Ticker, _reqAnimFrame, _cancelAnimFrame, _getTime } from "./ticker";
import { Animation } from "./animation";
import { SimpleTimeline } from "./timeline";
import { TweenPlugin } from "./tween-plugin";
import { Ease } from "./ease";

// ActionScript
import com.greensock.core.Animation;
import com.greensock.core.PropTween;
import com.greensock.core.SimpleTimeline;
import com.greensock.easing.Ease;

Here's a couple good reads on using ES6 modules...

https://hacks.mozilla.org/2015/08/es6-in-depth-modules/

http://developer.telerik.com/featured/choose-es6-modules-today/

 

  • Like 3
Link to comment
Share on other sites

  • 7 months later...

I was wondering if there has been any more info regarding GSAP and ES6 Modules?

I was an avid  Greensock  for many many  years,  both with Actionscript and Javascript.  

Recently I have been doing a lot of work with Vue.js and Laravel for web app development, and I really miss using the GSAP tools.

I use babal and browserify in my build process, and am looking for info on the best way to integrate GSAP into my workflow.

thanks drew

  • Like 1
Link to comment
Share on other sites

We're moving toward a more full ES6 implementation but that's a very large task. In the mean time, though, you should totally be able to use GSAP in your Browserify/Webpack/RequireJS project. It has hooks in it that should work for all of those. So you can import the classes directly (paths), or you can use the ES6-friendly import statements for anything that's included in TweenMax, like:

import { TweenLite, Elastic, CSSPlugin, TimelineLite } from "gsap";

And of course with NPM, you just "npm install gsap"

 

Does that help?

  • Like 3
Link to comment
Share on other sites

  • 3 weeks later...
  • 2 weeks later...

Has anyone had issues importing the ScrollToPlugin in React using WebPack ? I see the CSSPlugin gets loaded fine, but ScrollToPlugin is undefined.

 
Update: EasePack and TextPlugin too...probably others too ? Maybe CSSPlugin is just in by default already and that 's why it logs fine?
 
import React from 'react'
import { TweenMax, CSSPlugin, ScrollToPlugin, EasePack, TextPlugin } from 'gsap'

class ExampleComponent extends React.Component {
    componentDidMount() {
        console.log(TweenMax)
        // function (target, duration, vars) {
        //     Animation.call(this, duration, vars);
        //     this.render = TweenMax.prototype.render; //speed optimization (avoid prototype lookup on this "hot" method)
        //     if (…
        console.log(CSSPlugin)
        // function () {
        //     TweenPlugin.call(this, "css");
        //     this._overwriteProps.length = 0;
        //     this.setRatio = CSSPlugin.prototype.setRatio; //speed optimization (avoid prototype lookup on this "hot" metho…
        console.log(ScrollToPlugin)
        // undefined
        console.log(EasePack)
        // undefined
        console.log(TextPlugin)
        // undefined
    }

    render() {
        return (
            <div>Hello GSAP!</div>
        )
    }
}

export default ExampleComponent
Link to comment
Share on other sites

When you import from "gsap", that basically gives you anything that's inside TweenMax (because that's the default file for "gsap"). ScrollToPlugin is not included inside of TweenMax - that's why your code is failing. You should just import ScrollToPlugin directly from its file location, or set up some aliases in your config. See https://github.com/greensock/GreenSock-JS/issues/157 for WebPack and Browserify samples. 

 

Does that help?

Link to comment
Share on other sites

After initial attempts, still not having any luck using alias, but also new to react and webpack as well. Looking at webpack docs this seems to be a viable solution though so I'll keep at it and report back. So far none of the following has worked.

 

In my webpack config: (example of absolute and relative path in alias, not using both at once)

 

var webpack = require('webpack'),
path = require('path'),
extractText = require('extract-text-webpack-plugin');

module.exports = {
  entry: [
    'webpack-dev-server/client?http://localhost:5000',
    'webpack/hot/dev-server',
    './src/index'
  ],
  output: {
    path: __dirname,
    filename: 'init.js'
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
    root: path.resolve(path.join(__dirname, 'src')),
    alias: {
      ScrollToPlugin: './node_modules/gsap/uncompressed/plugins/ScrollToPlugin'
      ScrollToPlugin: path.resolve(path.join(__dirname, 'node_modules/gsap/uncompressed/plugins/ScrollToPlugin'))
    }
  },
  devtool: 'eval-source-map',
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin(),
    new extractText('style.css')
  ],
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        loaders: ['react-hot', 'babel'],
        include: path.join(__dirname, 'src')
      },
      {
        test: /\.scss$/,
        loader: extractText.extract('css!sass')
      }
    ]
  }
};

And then trying to import or require it:

import ScrollToPlugin from 'ScrollToPlugin'
const ScrollToPlugin = require('ScrollToPlugin')

When webpack builds I always get Module not found as if the alias doesn't work. The import/require still seems to be relative to the components path.

ERROR in ./src/components/example.js
Module not found: Error: Cannot resolve 'file' or 'directory' /Users/anthony/repos/example/node_modules/gsap/uncompressed/plugins/ScrollToPlugin in /Users/anthony/repos/example/src/components

 

Link to comment
Share on other sites

I am totally *not* a Webpack guru. I wish I had more info to offer. I can say, however, that you've got a typo in your code above - there are two aliases to ScrollToPlugin one-after-the-other (that in itself is invalid) and there's no comma between. Gotta choose one or the other. 

Link to comment
Share on other sites

No worries and sorry, mentioned it was just example, but would've been better to comment in the code snippet:

 

 

In my webpack config: (example of absolute and relative path in alias, not using both at once)

 

I believe I made some progress though. Still not quite there yet, but will update if I find a resolve. Thanks again Jack.

  • Like 1
Link to comment
Share on other sites

Turns out I wasn't really having path/alias issues after all. If I do either of the following, I can import/require files in any component in my react app without issue using relative pathing:

import EasePack from '../../node_modules/gsap/src/uncompressed/easing/EasePack'
require('../../node_modules/gsap/src/uncompressed/easing/EasePack')

Since I can't think of a time I'd actually need access to the plugin object, just requiring it (and not storing it in a const like in my previous post) is all I'm doing and so far everything is working ok. The only concern now, is the initialization of all plugins makes webpack have a fit when a plugin tries to define TweenLite:

Module not found: Error: Cannot resolve module 'TweenLite' in /Users/anthony/repos/exampleProject/node_modules/gsap/src/uncompressed/easing
resolve module TweenLite in /Users/anthony/repos/exampleProject/node_modules/gsap/src/uncompressed/easing
  looking for modules in /Users/anthony/repos/exampleProject/src
    /Users/anthony/repos/exampleProject/src/TweenLite doesn't exist (module as directory)
    resolve 'file' TweenLite in /Users/anthony/repos/exampleProject/src
      resolve file
        /Users/anthony/repos/exampleProject/src/TweenLite doesn't exist
        /Users/anthony/repos/exampleProject/src/TweenLite.js doesn't exist
        /Users/anthony/repos/exampleProject/src/TweenLite.jsx doesn't exist
  looking for modules in /Users/anthony/repos/exampleProject/node_modules
    /Users/anthony/repos/exampleProject/node_modules/TweenLite doesn't exist (module as directory)
    resolve 'file' TweenLite in /Users/anthony/repos/exampleProject/node_modules
      resolve file
        /Users/anthony/repos/exampleProject/node_modules/TweenLite doesn't exist
        /Users/anthony/repos/exampleProject/node_modules/TweenLite.js doesn't exist
        /Users/anthony/repos/exampleProject/node_modules/TweenLite.jsx doesn't exist
  looking for modules in /Users/anthony/repos/node_modules
    /Users/anthony/repos/node_modules/TweenLite doesn't exist (module as directory)
    resolve 'file' TweenLite in /Users/anthony/repos/node_modules
      resolve file
        /Users/anthony/repos/node_modules/TweenLite doesn't exist
        /Users/anthony/repos/node_modules/TweenLite.js doesn't exist
        /Users/anthony/repos/node_modules/TweenLite.jsx doesn't exist
[/Users/anthony/repos/exampleProject/src/TweenLite]
[/Users/anthony/repos/exampleProject/src/TweenLite]
[/Users/anthony/repos/exampleProject/src/TweenLite.js]
[/Users/anthony/repos/exampleProject/src/TweenLite.jsx]
[/Users/anthony/repos/exampleProject/node_modules/TweenLite]
[/Users/anthony/repos/exampleProject/node_modules/TweenLite]
[/Users/anthony/repos/exampleProject/node_modules/TweenLite.js]
[/Users/anthony/repos/exampleProject/node_modules/TweenLite.jsx]
[/Users/anthony/repos/node_modules/TweenLite]
[/Users/anthony/repos/node_modules/TweenLite]
[/Users/anthony/repos/node_modules/TweenLite.js]
[/Users/anthony/repos/node_modules/TweenLite.jsx]
 @ ./~/gsap/src/uncompressed/easing/EasePack.js

Since TweenMax is loaded already, I've resolved this by simply commenting out the define in any plugin that gets used:

//export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date)
(function() {
    "use strict";
    var getGlobal = function() {
        return (_gsScope.GreenSockGlobals || _gsScope);
    };
    if (typeof(define) === "function" && define.amd) { //AMD
        // define(["TweenLite"], getGlobal);
    } else if (typeof(module) !== "undefined" && module.exports) { //node
        require("../TweenLite.js");
        module.exports = getGlobal();
    }
}());

I haven't settled on the best way to do this using webpack yet, but there are a plethora of webpack plugins to copy, extract text, etc. to use until "later date". :D

 

Link to comment
Share on other sites

Wouldn't it solve things to just define TweenLite as an alias in your config, as described earlier? 

 

FYI, plugins never define TweenLite. The code you referenced is just referencing "TweenLite" as a dependency for defining the plugin itself. So it sounds like the problem is that in your setup, Webpack is having a tough time resolving "TweenLite", so defining it in the config would likely solve it. 

  • Like 2
Link to comment
Share on other sites

Thanks Jack. I must've tried this ten different ways, changing all sorts of configs, and never got there. After fresh eyes, I just got it all working properly. Then, out of curiosity, I grepped my command line history to see the previous webpack errors and realized every time I was trying to alias TweenLite, I used a lower-case instead of upper-case L. So yeah...copy/paste fail throughout all of the testing... :shock: Considering remapping the damn paste key combo in Sublime so this kind of human error is avoided. For any interested and so there's no confusion with my previous examples. Import/requiring the files using relative pathing as I have in my last post, creating an alias in webpack is definitely the way to handle this:

var webpack = require('webpack'),
path = require('path'),
extractText = require('extract-text-webpack-plugin');

module.exports = {
  entry: [
    'webpack-dev-server/client?http://localhost:5000',
    'webpack/hot/dev-server',
    './src/index'
  ],
  output: {
    path: __dirname,
    filename: 'init.js'
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
    root: path.resolve(path.join(__dirname, 'src')),
    alias: {
      TweenLite: path.resolve(path.join(__dirname, 'node_modules/gsap/src/uncompressed/TweenLite'))
    }
  },
  devtool: 'eval-source-map',
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin(),
    new extractText('style.css')
  ],
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        loaders: ['react-hot', 'babel'],
        include: path.join(__dirname, 'src')
      },
      {
        test: /\.scss$/,
        loader: extractText.extract('css!sass')
      }
    ]
  }
};
  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

ftw: one suggestion is TypeScript. It's used by Angular, Away3d and much like ActionScript - I feel at home.

And first step is easy and works: ren *.js *.ts.

Because it is supper set, all .js works and then you can start using classes & features you guys are talking about and you can tell it to target es6 or 5.

 

And it looks nice.

Link to comment
Share on other sites

I dunno. Unless Greensock wants to support multiple codebases, I would be hesitant to be tied to a technology (ES6) not universally supported by all the browsers in the wild.  It will be a long time before everyone in corporate-land gets over their love for IE.

  • Like 1
Link to comment
Share on other sites

I dunno. Unless Greensock wants to support multiple codebases, I would be hesitant to be tied to a technology (ES6) not universally supported by all the browsers in the wild.  It will be a long time before everyone in corporate-land gets over their love for IE.

 

Yep! It looks like that is what Aurelia is doing. Check out these folders for their framework. They have a build for every different flavor. Granted, all this is automated, but it's still results in more code to maintain.

https://github.com/aurelia/framework/tree/b30bba1ddb1559c2e67dd62c9d88fc47670beff7/dist

Link to comment
Share on other sites

TypeScript is an amazing tool, and can even help out with reducing bloated ES6 code, but emitting modules is not the issue. GSAP is a published library that must be able to work with stuff like Bower, NPM, JSPM, SystemJS, Webpack, Rollup, tree-shaking, Grunt, Gulp, CommonJS, AMD, UMD, TypeScript, Babel, Traceur, import, export, require, Node.js, Electron, etc. And the list gets longer everyday.

 

Lots of externalities are being forced upon GSAP, taking away from development time that could be better spent on animation related features. It's going to take a lot of work. Creating that proof of concept took some time to figure out, and that was just the TweenLite file. I'm sure Jack wishes there was a flag he could just set, and have all these problems go away.

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...