Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
bjn

Webpack and SplitText

Recommended Posts

I just bought a "Shockingly Green" membership solely for SplitText. I have no interest, at least currently, in using the other Greensock features.

 

I'm trying to require SplitText with Webpack.

 

Compilation fails:

 

ERROR in ./resources/assets/javascript/vendor/SplitText.js
Module not found: Error: Cannot resolve module 'TweenLite' in /home/bjn/myproject/resources/assets/javascript/vendor
 @ ./resources/assets/javascript/vendor/SplitText.js 425:2-34

 

The docs at http://greensock.com/docs/#/HTML5/Text/SplitText/ say SplitText has no dependencies, so I don't know what business it has looking for TweenLite, or why TweenLite is even referenced.

 

Is this a bug, or am I doing something wrong?

Link to comment
Share on other sites

Oh and I've already solved it.

 

It's a bug with how the script is packaged, and I'm assuming the reference to TweenLite is simply a copy-and-paste error. I doubt the Greensock developers actually test this as an AMD module, because I suspect that would lead to a broken reference.

 

Anyway, the solution for me, since I'm not using AMD packaging, was just to remove AMD packaging, turning this code (which is at the end of the file):

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

into this:

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

Or even to this:

//export to CommonJS/Node
module.exports = (_gsScope.GreenSockGlobals || _gsScope)["SplitText"];

Greensock folks, please fix this issue. I want to be able to use your scripts unmodified with Webpack.

 

Speaking of, I also want to be able to download SplitText via NPM, which doesn't seem to currently be possible.

Link to comment
Share on other sites

Sorry about the hassle - I'm curious about why that amd stuff would cause any problems for you. The reason there's a "TweenLite" reference in there is simply because that's basically the core of everything GreenSock-related and it's what handles setting up packaging across all the GreenSock files. But you're right - SplitText doesn't technically need it. If you just remove that "TweenLite" string, does it work for you? Like define([], getGlobal);?

 

We can't really make SplitText available via NPM otherwise that'd make it super easy for anyone on the web to get it for free ;) 

Link to comment
Share on other sites

It's probably because Webpack provides a "define" function. Your code then thinks it's an an AMD environment, which it's not.

 

As for NPM, there's this https://www.npmjs.com/private-modules but I haven't really looked in to whether this would be suitable.

 

Or it could be available in a subscriber-only git repository, limited by public key, and you could provide the suitable string for package.json files to grab it from there.

Link to comment
Share on other sites

Ah, okay - so do you know of a suitable way to differentiate a Webpack environment from a "normal" AMD environment? What would be a better condition? 

 

As for the private modules or subscriber-only git repo, I don't think either of those could work because we'd need some way to have it managed per-user so that when each person's membership expires, their credentials don't work anymore. See what I mean? 

  • Like 1
Link to comment
Share on other sites

This looks to be a pretty good article. http://ifandelse.com/its-not-hard-making-your-library-support-amd-and-commonjs/ It looks for `define` and `define.amd` rather than just `define` to trigger the AMD export. Webpack would fail that test and so the CommonJS one, which is what we want, would apply.

 

As for subscriber-only git repo, it may be not as tricky as you think. Those users who want git access (for NPM reasons or to suggest patches, bisect for the source of bugs, etc) could provide a public key via this Greensock website. You then decide who is still allowed access and who isn't, and provide (git-only) SSH access only to those with allowed keys. Github may or may not have something in its API for this, but I know it's certainly possible on a self-hosted git repo.

Link to comment
Share on other sites

It looks in the version you just sent me that you switch the order of CommonJS and AMD tests as well as checking for `define.amd`. If you look at the bottom of the article I linked, you'll see that the guy recommends checking for AMD first since it's possible to get false positives for CommonJS sometimes. Just wanted to point that out.

Link to comment
Share on other sites

Yep, I can switch back to AMD-checking first. No biggie.

 

As for the subscriber-only git repo, I just can't fathom how it'd be done on a practical level. Are you saying that each subscriber would come to the GreenSock site and get their own unique access code that they'd use to authenticate with Github (or an NPMJS private repo) and on a daily basis our site would communicate with Github/NPM about which codes are now expired? Or are you saying users would come to greensock.com instead of github/npm and our site would somehow act as a proxy that first authenticates, then gets the requested data and shuttles it along to the user so that they're always going through greensock.com? 

Link to comment
Share on other sites

Well the way I see it, any developer who uses git and/or Github, and probably most users who want to use a package manager like NPM, already have a public/private keypair. They've probably already put the public key up on their Github profile so they can pull/push via SSH.

 

What I'm suggesting is that users who are so inclined could log in here to this Greensock site and add their public key to their profile, in the same way we do on Github. Then some process looks through the database for all users who have a public key and should be allowed access, and collects all the public keys. It then feeds them to something to allow access to the git repo.

 

Having had a brief look at the Github API and a few Google searches it doesn't look like there's any obvious way to post a list of public keys to grant access to.

 

But you could self-host a git repository pretty easily. There are some notes about the bare-metal way to do it here. https://git-scm.com/book/en/v2/Git-on-the-Server-Setting-Up-the-Server

 

In short:

 

1. Provision a server, give it a public DNS hostname and open port 22

2. Install SSH server on it

3. Install git

4. Make a system user called git, and lock its shell down to git-shell (tool provided by git), so that users can't actually get a proper shell on your server

5. Add your bare git repositories in the git user's home directory, under whatever paths make sense. Maybe utils/SplitText.git is the SplitText repo

6. (Not mentioned in the link above) lock down the repo to only allow reading, not writing. Can do this with plain filesystem permissions (for example, set the owner of all the files in the repo to your actual interactive user on the server so *you* can still push to it, and allow only read (and execute where appropriate) for other users including the git user), or pre-receive hooks, for example.

7. Write a script which fires via a cron job once a day or whatever which grabs the suitable public keys from your database and throws them all in ~git/.ssh/authorized_keys

8. Publish the DNS hostname and git repository name, and instructions, as an installation option for members

9. Users then add their public key to their profile, then add the dependencies to their package.json such as `"splittext": "git+ssh://git@git.greensock.com:utils/SplitText.git#stable"`, presuming that's where the server and repo are and that you have a branch called "stable" (can also lock down by tag, commit, etc if the user is so inclined)

 

There are also software packages which are supposed to make this kind of thing easier, such as gitosis.

 

I concede that if I'm the only person who's ever asked for git access/package manager for these utility scripts, it's probably not worth the effort. But I think it'd be nice.

  • Like 2
Link to comment
Share on other sites

I really appreciate the thoughtful response and detailed advice. We rarely get complaints about folks not being able to get the paid-for stuff via NPM or Github - they're typically very understanding of how dicey that'd be and are happy to snag things via the zip download but we'll certainly keep this in mind if we get a lot of requests and/or if it becomes more feasible with future tech. We just don't have the resources at this point to throw at an endeavor like this. Thanks again. 

Link to comment
Share on other sites

No problem.

Link to comment
Share on other sites

I should have tested rather than just assuming the check for require.amd would fix things.

 

It turns out it doesn't. Webpack *does* support AMD modules, and the environment *does* have a `require.amd`.

 

The solution you suggested earlier, of emptying those square brackets, *does* work. Presumably this is the list of dependencies for an AMD module, and so it's complaining that we don't already have TweenLite. Adding the check for `define.amd` was probably a good idea regardless.

 

Sorry about the confusion.

  • Like 1
Link to comment
Share on other sites

Excellent. Glad it's all working now (right?) Thanks for reporting back.

Link to comment
Share on other sites

I think you misread.

 

It's not working -- you still have `"TweenLite"` in the brackets, at least in the version of SplitText you're distributing (0.3.4). If you remove that it'll work.

 

Also looking forward to the version which fixes with the other issue (blank lines) being published.

Link to comment
Share on other sites

@Jack

 

That TweenLite dependency causes problems with every GSAP plugin/utility when using a module loader/bundler. Until GSAP gets a rewrite, it might be best to just remove it as a dependency in all the UMD definitions. Yes, it's technically correct, but it causes problems because most people are already loading the main GSAP file, TweenMax, which includes TweenLite. 

 

Link to comment
Share on other sites

I noticed this issue on GitHub... Lots of +1s.

https://github.com/greensock/GreenSock-JS/issues/114

 

Global pollution? Tree shaking? It looks like a lot people are trying to make this out to be a deal breaker... even switching to a different engine.

 

I don't feel like getting attacked on GitHub, so I'll just leave my two cents here...

 

ES6 Modules

Getting certain parts of GSAP to run as ES6 modules isn't necessarily that complicated. I created a proof of concept in less than an hour. It's a simple demonstration, so it's probably poorly written.

http://plnkr.co/edit/Yx5meXwVMO1HlrDDtpZ0?p=preview

 

However, I'm quite familiar with the GSAP codebase, and I can say with absolute certainty that converting the entire GSAP codebase over is going to require a substantial rewrite. Probably back to the way it was originally written in ActionScript.

 

One thing most people don't realize is that GSAP has it's own module loader that was written before people started using modules. Removing GSAP's dependency injector will make it easier for people to load it as separate modules, but on the flip-side is going to cause problems for people who load GSAP through normal script tags.

 

Tree Shaking (dead code elimination)

I doubt you're going to see any benefits using tree shaking with GSAP. First off, you're going to lose caching. Second, GSAP is not some helper library like jQuery or Lodash that includes a lot code you might not use. GSAP is a highly optimized engine that uses every line of code. The only savings you might see would happen in some of the plugins/utils, like the CSSPlugin, Draggable, or the ease pack, but those are already optional.

 

Global Scope

Yes, polluting the global scope can be a bad thing, but I've never had a problem with GSAP's globals. I know this is probably more of a personal preference, but I'm in no rush to start adding all these imports to my code. Everything that the TweenMax file currently includes will have to manually imported, including the CSSPlugin, BezierPlugin, AttrPlugin, RoundPropsPlugin, DirectionalRotationPlugin, EasePack, and TimelineLite/Max. The CSSPlugin could probably require some of those plugins, but it's still something that I don't think most people have considered.

import { TweenLite, TweenMax, TimelineMax } from "gsap";
import { AttrPlugin, BezierPlugin, CSSPlugin, DirectionalRotationPlugin, MorphSVGPlugin, RoundPropsPlugin } from "gsap/plugins";
import { Draggable, SplitText } from "gsap/utils";
import { Back, Power4 } from "gsap/easing";
  • Like 2
Link to comment
Share on other sites

Thanks for the detailed info (as usual), Blake. You said that we probably don't need that "TweenLite" dependency in there, but wouldn't that cause problems if I removed it because then people couldn't just "require" something like CSSPlugin (or whatever)? It'd break because TweenLite wouldn't be there. I'm curious why that dependency is causing any problems at all, except for SplitText which doesn't really require it anyway. Is it because it assumes a particular directory structure? Are you saying you think it's wisest to just remove the dependencies altogether? 

Link to comment
Share on other sites

Yeah, removing it would cause issues if they only required the CSSPlugin, but I doubt anybody who uses a module loader would try that. They want to require everything individually.

 

I think all module loaders now use Node's method of module resolution...

https://nodejs.org/api/modules.html

 

...but I can't find anything about using a particular directory structure. However, I'm beginning to think that is the problem. There's just no other way to explain why TweenLite cannot be found. Using Draggable as an example, there are two requires...

require("../TweenLite.js");
require("../plugins/CSSPlugin.js");

Relative to Draggable, those paths are correct, but for some reason the CSSPlugin is the only file that can be resolved. So I'm thinking that you can't require files from same location that the main file is listed as in the package.json. I'm wondering if TweenLite was in another folder if the issue would go away.

 

I've never written modules like GSAP's current directory structure, with multiple files in the root (TweenLite/Max, TimelineLite/Max, and jquery.gsap). I only put 1 file in the root, and everything else is placed in folders. I also use index.js files, which are the main files for a folder, so that's the file it's going to be looking for first. Check out Pixi's source. In all the lower level folders there is an index.js file, which do nothing but import and export different things.

 

https://github.com/pixijs/pixi.js/tree/master/src

 

I think structuring GSAP like that might be a good first step, and would buy you some time to worry about the ES6 stuff later.

Link to comment
Share on other sites

  • 4 weeks later...

By the way, GSAP 1.18.5 was just released and it offers better support for things like this. 

import { TweenLite, TweenMax, TimelineMax, Elastic } from "gsap";

I got it to work okay in Webpack, Browserify, and RequireJS. 

  • Like 3
Link to comment
Share on other sites

Great. I downloaded the package again and updated my Splittext version. I confirm that it works with webpack.

  • Like 3
Link to comment
Share on other sites

  • 1 month later...

@bjn I can't get this to work. How did you include the util?

 

I'm doing like this:

  import { TweenLite, TimelineLite, Power3, CSSPlugin } from "gsap";
  import { Draggable } from "../vendor/gsap/utils/Draggable";

But I get the error you had about TweenLite still.

Link to comment
Share on other sites

Already addressed this in the github issue you opened: https://github.com/greensock/GreenSock-JS/issues/114

 

The problem is that you're using { Draggable } instead of just Draggable in your import statement.
//BAD:
import { Draggable } from "../vendor/gsap/utils/Draggable.js";
//GOOD: 
import Draggable from "../vendor/gsap/utils/Draggable.js";

And for anyone else reading this, you may need to tweak your Webpack config, kinda like: 

resolve: {
  root: path.resolve(__dirname),
  extensions: ['', '.js'],
  alias: {
    "TweenLite": "vendor/gsap/TweenLite",
    "CSSPlugin": "vendor/gsap/plugins/CSSPlugin"
  }
}
Link to comment
Share on other sites

I don't have anything special in my webpack configuration, but it looks like what Jack suggests above might be a good solution.

 

In my application code in plain old Javascript I have just

var SplitText = require('../javascript/vendor/SplitText.js');

where that path is relative from that particular JS file to where I'm storing the SplitText script.

  • Like 1
Link to comment
Share on other sites

  • 2 months later...

By the way, GSAP 1.18.5 was just released and it offers better support for things like this. 

import { TweenLite, TweenMax, TimelineMax, Elastic } from "gsap";

I got it to work okay in Webpack, Browserify, and RequireJS. 

 

Maybe I'm missing something, but I'm using v1.19.0 and everything is still using globals.

Even simply coding...

import 'gsap';

or...

import { tree } from 'gsap';

still allows you to use all globals anywhere in your code. TweenLite, TimelineMax etc etc.

 

What exactly is declaring the component name in the import supposed to do?

Link to comment
Share on other sites

Yep, that's normal. For maximum compatibility, it declares things globally by default in addition to regular exports for AMD/CommonJS/Node. You can define a GreenSockGlobals object in the global scope if you'd prefer to have GSAP put its stuff there (in that object rather than in the global scope). 

 

The import statement would scope those locally as well in your case (the way you wrote those import statements). Totally valid. 

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.
×