Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AMD #54

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

AMD #54

wants to merge 4 commits into from

Conversation

subtleGradient
Copy link
Member

This is not ready to be pulled yet, but I wanted to open up a discussion around this branch so we can discuss the implementation as it is done.

I didn't really get very far yet. I only just began learning the pragmatic details of how to use AMD. Most importantly though is how to build out a final minified build of the code.

It no longer makes sense to include all the oldIE implementations of everything right alongside the base implementation. It's far better to dynamically include that code only after a feature test proves that it is necessary. However, that complicates generating a build.

Ideally I'd like to implement environment-specific builds sooner than later. It's a very solvable problem and this is the time to make it happen. I've already gotten it kinda working, but I wanted to get some feedback before actually moving forward in that direction.

I'll post more in this thread later.

@subtleGradient
Copy link
Member Author

I got a kind of environment specific building done by using the requirejs optimizer, a require statement in and if and then passing all the stuff I wanted excluded from the build in excludeShallow attributes. e.g.

// main.js
require(['A'], function(){
    if (shouldLoadOldIECode) require('A-oldIE');
    ...
});
r.js -o name=main out=main-build.js baseUrl=. excludeShallow=A-oldIE
r.js -o name=main out=main-build-oldIE.js baseUrl=.
<script src=require.js></script>
<script>
if (inProductionMode) {
    require.config({paths:{ main:'main-build' }});
    if (shouldLoadOldIECode) require.config({paths:{ main:'main-build-oldIE' }});
}

require(['main']);
</script>

Is this as insane as it feels? Is there a better way to do stuff like this?

I guess ideally you'd send the client a tiny feature test script that'd figure out precisely what build you need for that environment. Then you could either pre-generate each of the possible builds ahead of time or lazily build them the first time they were requested. But until I have all that working, what's the best way to accomplish this on the small scale?

/cc @jrburke @unscriptable @rcgill

@arian
Copy link
Member

arian commented Sep 19, 2011

What I also think is quite interesting is has.js with require.js optimizer: http://requirejs.org/docs/optimization.html#hasjs

if (has('feature')){
    ...
}

which turns into

if (true){ ... }
// or
if (false){ ... }

UglifyJS or other minfiy tools can remove dead-code branches.

tbh I'm not sure how you can load or not load modules that way, but I think you could use a require (of course with the define(function(require, exports, module){…} stuff, I believe there are some differences between the global and the local require and only the local require is in the specification), like:

if (has('oldIE')){
    require('A-oldIE');
}

(Personally I'd love to use the has('feature') syntax in our new MooTools Milk projects.)

I think this would work for the optimizer, for the client-side it probably will load the 'A-oldIE' anyway if the loader supports the feature that it searches for require in the the toString() result of the factory function. This feature however is not mandatory of the specification. Anyway, in 99.9% of the cases you'll use the optimized version (in production), so that shouldn't be real problem..

@jrburke
Copy link

jrburke commented Sep 20, 2011

I would probably approach this with a loader plugin, a has loader plugin. I'm not sure if someone has made one yet, but it has been talked about in Dojo land. @rcgill may know of one used in Dojo. The basic idea though would be to do something like:

// main.js
require(['A', 'has!oldIE?A-oldIE'], function(){
});

and have the has plugin only load A-oldIE if the 'oldIE' test returns true. Then the has plugin during the build could try and use the same has: {} config in the build profile to know if it should include oldIE in the output.

I have been meaning to make a boilerplate has plugin, where you would insert your own tests in it, if you think that would be useful for you, I can take a crack at it.

@jrburke
Copy link

jrburke commented Sep 20, 2011

Or, even simpler, just do:
// main.js
require(['A', 'A-oldIE'], function(){
});

and do two different builds, one mapping A-oldIE to an empty file for the build that does not need the old IE stuff. There is shortcut for specifying an empty URL in the build profile:

paths: {
     'A-oldIE': 'empty:'
}

You can also pass it on the command line: paths.A-oldIE=empty:

@subtleGradient
Copy link
Member Author

Woah, lots of different options to explore. Very exciting.
Thanks very much!

@subtleGradient
Copy link
Member Author

All of the actual files in slick...

  • will use define with no boilerplate
  • will require a specific has feature test suite for the features that it needs
    • e.g. attr requires attr.has which provides has() but may not necessarily require has

There are still a few unsolved problems...

  • How do I remove requires for attr.has after using the RequireJS optimizer to remove all calls to has(), without removing the require when not all has() calls have been expanded?
  • How to I combine all the feature tests into a single build, deliver that as the first response to a browser, post back the results to my server to dynamically build an environment specific build for that user agent?
    • having all the feature tests in separate files is obviously very helpful

Node.js had its own implementation of define in earlier builds of Node 0.5, but the latest build have removed it to keep people from wasting their time bike shedding (or whatever). Currently I'm planning to provide an index.js file for node.js to require. e.g. require('slick').attr looks good enough to me.

@subtleGradient
Copy link
Member Author

I've moved the environment specific build issue to #55. That shouldn't block Slick from moving to AMD.

@subtleGradient
Copy link
Member Author

My latest thinking is...

  1. Provide AMD-compatible modules for normal people to use
    • Slick.js (the default, with everything for everyone)
    • slick-legacy-oldie.js (everything, optimized for oldIE only)
    • slick-legacy-nooldie.js
    • Include a *.min.js version of each of these files
  2. Split code into separate CommonJS modules
    • Each tiny sub-component of Slick will be split out into a separate tiny little js file
    • Will use the minimal CommonJS exports.foo = whatever syntax
      • everything supports this standard, even AMD loaders to some extent
  3. Platform integration
    • RequireJS
    • curl.js
    • Browserify
    • Ender
    • node.js
    • Strict CommonJS (Narwhal, RingoJS, etc…)
  4. Availability
    • npm
    • cpm
    • MooTools Forge
    • jQuery plugins
    • etc…?

@subtleGradient
Copy link
Member Author

Slick needs to provide stable require paths.

e.g.

  • require('slick/find')
  • require('slick/parse')
  • require('slick/attr')

I'm not sure I want to provide a single point of entry (i.e. require('slick')). I'd like to beat into peoples minds that slick is a package with multiple modules, not a single monolithic thing. Obviously with AMD/CommonJS when you require('slick/find') it'll require('slick/parse') and require('slick/attr') itself, so they'll be loaded and available either way. You'll just have to do a require('slick/parse') yourself if you want to actually interact with it.

@unscriptable
Copy link

cool to see you're targeting cpm. luv that.

Obviously with AMD/CommonJS when you require('slick/find') it'll require('slick/parse') and require('slick/attr') itself, so they'll be loaded and available either way. You'll just have to do a require('slick/parse') yourself if you want to actually interact with it.

I agree. The key is to provide examples that show users what to do. They'll understand it pretty quickly. :)

Fwiw, curl.js 0.6 will support CJS exports.foo = whatever syntax without a pre-compile step. Version 0.6 should be out in about a week.

Keep up the awesome work guys.

-- John

@arian
Copy link
Member

arian commented Sep 29, 2011

👍

@ghost ghost assigned subtleGradient Mar 12, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants