Wednesday, July 25, 2012

On client components for web apps

This is a response to a blog post by TJ Holowaychuk about browser-based components for web applications, and Isaac's notes on TJ's post.

I am going to try to make this brief because I get tired of people in the Node community wanting to apply the same patterns from Node in the browser. I feel like I say these things on a periodic basis, but human communication is hard, and I certainly could do better. But I also want to get back to just making things. So this will be terser than I normally would like.

Web components

I suggest TJ look at volo, my attempt in this space. It does lots of what he describes already, and it can even be used as a module in another command line tool. We use volo for some things in Mozilla already.

volo uses GitHub as the component registry. It does so without the downsides that TJ mentions.

Specifically, volo uses the GitHub HTTP API to get version tags, do registry searches. I grabs .zip snapshots for a given version/github commit/branch, so the command line tool (the consumer) does not need use git. Git is not necessary on the client side.

This means the downloaded code is smaller -- no need to get a full repo and all of its commits.

volo also understands dependencies via the shorter "owner/repo/tag" IDs instead of the full github URLs.

It has a "shim" repo that means it can support installing components without needing the author of the component to conform to some new publishing system. Since it allows {version} replacement in URLs, the registry setup just needs to be done once. From then on, normal best-practice versioning via git tags is enough.

Some other notes in this post.

Base module format

Node bros, the AMD trolling is getting tiresome. Node's module system is woefully under-specified for web-based loading. While you can limp along with browserify, there are still these issues:

* For builds you need a wrapped format. For CDN deployment you need a wrapped format. browserify uses a wrapped format. AMD anyone? For that reason alone, AMD will never go away. Get used to it already.
* Web code needs a callback-style require for on-demand loading.
* Browserify's uses of file paths for IDs is awful for mixed local and CDN-based loading. Module IDs need to stay in ID format, not translated to a specific file path.
* Loader plugins reduce the need for callback-style APIs, and callback pyramid of doom, or inside-out callback hell, or the need for promise based programs. This more than makes up for the extra level of indent in AMD.

Loader plugins solve the translation issues TJ talks about, and they can participate in optimization builds, meaning templating engines can inline the JS function form of the template. Ditto for language transpilers like CoffeeScript.

By doing this:

define(function (require) {
    //node module code in here.

    //Return module value instead
    //of needing `module.exports`

you have an AMD module.

Quit dismissing AMD for surface issues. AMD avoids mandating translation layers that lead to more things for the developer to understand and fix, and more process for the user to go through to deploy code. It is a net win when the source file works when deployed anywhere, without requiring specialized builds/converters.

Even if you want to personally use Node style and always do builds before loading in the browser, AMD is a great target for the built, wrapped format. You can even use the requirejs optimizer to do this, with the cjsTranslate option.

The universal module boilerplate gets simpler when Node supports AMD's define along with Node's existing module format. If you want to help improve the ugliness, start there.

AMD comes from real world exprience in Dojo with trying to deploy an unwrapped module format that depended on XHR+eval in dev and a wrapped format for builds. Yes, you can something to to work but the second order translation and support costs are not worth it. Some environments disallow eval. CORS configuration is awkward, and potentially hazardous if your API is on the same domain and CORS is done incorrectly.

The simplicity of the complete module lifecycle is worth the function wrapping. Quit looking just at what you type once, and consider the complete code lifecycle, and how much time could be wasted there.

npm's registry as the component registry

The implied rules with npm and node's module behavior are not good fits for front end web development:
  • Forcing a directory structure is complicating project layout and loading for web-based projects. It should be possible to publish and install single JS libraries as single files. volo can do this.
  • Related: the "index.js" convention is awful for web development and debugging. Debugging 'jquery.js' instead of trying to find 'jquery/index.js' in the web tools? No thank you.
  • npm's registry namespace is already polluted. Check searches for 'jquery'. Maybe that just means having a separate npm registry-based registry for client code. But if there needs to be a separate repo, might as well use one that can adapt better to front end development. Like single JS/CSS file installs without extra Java-esque directory structures and metadata debris on the file system.
Ender is not a success story for using npm. Ask the Ender folks how well that worked out. It only works because it is small scale.

By using GitHub, it comes with user auth handled, private repos, and robust social tools that will not be matched by a something like npm because the financial incentives are not there. Plus developers already use it. For simple open source sharing, make it easy without introducing more things in the middle.

Github as the registry is not perfect, and we still need some standalone servers that can be run inside corporations/for mirrors, but I would model those standalone servers on the github API. At least the default case of a public repo can be bootstrapped very quickly.

Monday, July 09, 2012

RequireJS 2.0.4 released

 RequireJS 2.0.4 is available.
And hot on the heels of 2.0.3, there is a 2.0.4 release! Unfortunately a bug with a fix that was in 2.0.3 caused a different bug, and it was not found until after the 2.0.3 release. It made the optimizer unusable in certain situations, so the change was rolled back and 2.0.4 is out, with the only change over 2.0.3 being the rollback of that previous change.

I apologize for the extra noise.

RequireJS 2.0.3 released

 RequireJS 2.0.3 is available.

Just a maintenance bug fix release. Most notable changes are probably:
  • optimizer now does not fully resolve "paths" until all config sources (mainConfigFile, build profile and comand line args) have been merged.
  • a fix for data-main resolution for require.js.
Full list of fixed bugs:

Saturday, July 07, 2012

volo 0.2.2 released

volo 0.2.2, a JS package manager and project automator, has been released. To install/update:

npm install -g volo

Here is a list of changes. Probably the most notable one:

volo create will run npm if there is a package.json in the downloaded project template with a dependencies property, and if there is not an existing node_modules directory.

This makes it easier to share volo commands between projects and their volofiles.

Example: the create-responsive-template now uses these separate volo commands in its volofile:
  • appcache: generates an appcache manifest for a project.
  • ghdeploy: deploys a directory of code to github pages.