How to write a JavaScript library for client or server use

379 views
Skip to first unread message

Ryan Schmidt

unread,
Jul 14, 2013, 2:09:43 AM7/14/13
to nod...@googlegroups.com
Years before I knew about node, I wrote two client-side JavaScript libraries, one of which uses the other, each providing about half a dozen classes. Now I would like to modernize these libraries to make them most accessible to today's web developers.

What is the best way to write my modules so they can be included in any client-side or server-side JavaScript environment?

I would like to publish them to npm. To that end, I've started switching them to node-style require() and module.exports, but of course I can't use those files in the browser ("module is not defined") without a build step such as browserify, or adding boilerplate to each file, and I don't want to bloat my library too much.

I don't want to mandate that my users use an AMD loader, or a CommonJS loader, or RequireJS or curl.js or anything else; I want my libraries to be compatible with any script loading method of the user's choosing.

Avoiding the build step during development would be nice too. I'd like to be able to just include the dozen class files in script tags and use them.

Hage Yaapa

unread,
Jul 14, 2013, 5:40:47 AM7/14/13
to nod...@googlegroups.com


--
--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en

---
You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



Ryan Schmidt

unread,
Jul 14, 2013, 6:55:36 AM7/14/13
to nod...@googlegroups.com

On Jul 14, 2013, at 04:40, Hage Yaapa wrote:

> I'd recommend this - https://github.com/component/component

Thanks, that looks promising. I'll watch the screencast and look into it further.

It appear to require that I publish my source on Github?

Hage Yaapa

unread,
Jul 14, 2013, 1:21:42 PM7/14/13
to nod...@googlegroups.com
Not at all. Component can do many things apart from creating modules for the front-end. Here is a tutorial I wrote specific to for front-end components - http://www.hacksparrow.com/loading-javascript-modules-in-the-browser-with-component-js.html



Ryan Schmidt

unread,
Jul 14, 2013, 9:20:31 PM7/14/13
to nod...@googlegroups.com
On Jul 14, 2013, at 12:21, Hage Yaapa wrote:

> On Sun, Jul 14, 2013 at 4:25 PM, Ryan Schmidt wrote:
>
>> It appear to require that I publish my source on Github?
>
> Not at all. Component can do many things apart from creating modules for the front-end. Here is a tutorial I wrote specific to for front-end components - http://www.hacksparrow.com/loading-javascript-modules-in-the-browser-with-component-js.html

Thanks for that detailed tutorial; that's very helpful. But doesn't it confirm what I said?

The tutorial shows running "component install yields/capitalize". That's https://github.com/yields/capitalize.

How would I allow others to install my library by running "component install ryandesign/mylibrary" without having to host it at https://github.com/ryandesign/mylibrary?

I have a github account, I think github is pretty neat, but I am much more comfortable with subversion than git and am currently motivated to work on my library, and don't want that motivation to fade by first having to spend days splitting my single subversion repository into multiple git repositories and weeks learning how to use git properly.


Hage Yaapa

unread,
Jul 15, 2013, 1:26:17 AM7/15/13
to nod...@googlegroups.com
Oh, for others to install you will need to put in on a public repo. I haven't looked into using SVN. You can ping TJ Holowaychuk, the author, on Twitter, if he hasn't noticed this post already.


Nathan White

unread,
Jul 15, 2013, 1:39:14 AM7/15/13
to nod...@googlegroups.com, nod...@googlegroups.com


On Jul 14, 2013, at 11:26 PM, Hage Yaapa <cap...@hacksparrow.com> wrote:

Oh, for others to install you will need to put in on a public repo.

Not true. This only applies if you want to publish as a "component" in the component ecosystem.

If you want to publish standalone component provides a flag to do just that.

My suggestion is to use component. It plays so nicely with node. When your ready to publish to npm build your standalone first.

Hage Yaapa

unread,
Jul 15, 2013, 2:00:58 AM7/15/13
to nod...@googlegroups.com
He is talking about sharing with the general public.

Ryan Schmidt

unread,
Jul 18, 2013, 9:34:08 PM7/18/13
to nod...@googlegroups.com
On Jul 15, 2013, at 00:26, Hage Yaapa wrote:
> On Mon, Jul 15, 2013 at 6:50 AM, Ryan Schmidt wrote:
>> On Jul 14, 2013, at 12:21, Hage Yaapa wrote:
>>> On Sun, Jul 14, 2013 at 4:25 PM, Ryan Schmidt wrote:
>>>
>>>> It appear to require that I publish my source on Github?
>>>
>>> Not at all. Component can do many things apart from creating modules for the front-end. Here is a tutorial I wrote specific to for front-end components - http://www.hacksparrow.com/loading-javascript-modules-in-the-browser-with-component-js.html
>>
>> Thanks for that detailed tutorial; that's very helpful. But doesn't it confirm what I said?
>>
>> The tutorial shows running "component install yields/capitalize". That's https://github.com/yields/capitalize.
>>
>> How would I allow others to install my library by running "component install ryandesign/mylibrary" without having to host it at https://github.com/ryandesign/mylibrary?
>
> Oh, for others to install you will need to put in on a public repo. I haven't looked into using SVN. You can ping TJ Holowaychuk, the author, on Twitter, if he hasn't noticed this post already.

After a lengthy email discussion with TJ, he confirms that yes, my library's code must be in a git repository on github for component to be able to get its files. If I want to host my development repository elsewhere, as I currently do, that's fine, as long as I copy the code into a git repository on github to make it available to component. Which is a little like saying that I have to have Brussels sprouts for dinner, and if I don't like Brussels sprouts that's ok, I can eat whatever I want for dinner, so long as I have Brussels sprouts for dessert.

It's unfortunate that such a cool idea, whose goal is to be unopinionated about JavaScript DOM frameworks, is instead opinionated about where and how code should be hosted online.

Any other answers to my original question? Or is component the consensus, and I just have to deal with this?

greelgorke

unread,
Jul 19, 2013, 3:36:14 AM7/19/13
to nod...@googlegroups.com
component is way too young to be a consensus. component seams to have a build step too. there is nothing bad about a build step, most of build libs support a watch function.

to be interoperable you have 3 options: write idiomatic code with a bunch of boilerplate, use a loader lib and hope it's compatible with other loaders, or optimize your lib for different loaders and publish it redundandly in different registries. 

Peter Rust

unread,
Jul 19, 2013, 10:01:22 AM7/19/13
to nod...@googlegroups.com
> > Or is component the consensus, and I just have to deal with this?

> component is way too young to be a consensus

Ditto. Module loaders and patterns for the client-side are a bit like the wild west right now -- there are a lot of competing module patterns and module loaders. It's not like node, where almost everyone uses npm.

My recommendation for your situation is to follow the pattern that both Underscore.js and Backbone.js use in order to work on the server and the client without being opinionated about which client-side module loader is in use, if any. Yes, it requires a bit of boilerplate in each JS file, but really it's not much and it keeps things very simple. With it, your modules will work in the node/npm/require() ecosystem and they will also work on any browser without requiring people who want to use it on the client-side into any particular module loader or module pattern or pre-processor. I've been following this pattern for quite a while now and it's been working great for me.

You can find the pattern by looking at the source (with more details available on the annotated source) of those projects. In short, it involves wrapping your script in an Immediately Invoked Function Expression like this:

(function() {
  var root = this;
  // body of the module here

}).call(this);

The "root" variable now points to window on the browser and module.exports on the server (the Backbone comments / annotations have been corrected on this point, but the Underscore comments / annotations still incorrectly state that it's `global` on the server. I wrote a blog post on the subject (http://csnw.github.io/2013/06/23/this-in-node-modules-and-iifes.html), but still need to issue a pull request.

To export the main namespace object, you can follow the code in Backbone (the Underscore code that does the same thing is slightly more complicated because it is still backwards compatible with an old require() API, which IMO isn't necessary):

var Backbone;
  if (typeof exports !== 'undefined') {
    Backbone = exports;
  } else {
    Backbone = root.Backbone = {};
  }

You can see the blog post mentioned above for variations on this theme. 

Importing another module is also straightforward. I typically just check if it's on the root, for instance, "var _ = root._ || require('underscore');" Backbone is a bit more explicit and uses two lines to import underscore:

  var _ = root._;
  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');

Best of luck! Let me know if you have questions.

-- peter rust

Peter Rust

unread,
Jul 19, 2013, 10:28:57 AM7/19/13
to nod...@googlegroups.com
> but the Underscore comments / annotations still incorrectly state that it's `global` on the server
> ... I still need to issue a pull request

FWIW, this has been corrected now (https://github.com/jashkenas/underscore/pull/1211)

Angel Java Lopez

unread,
Jul 19, 2013, 12:28:29 PM7/19/13
to nod...@googlegroups.com
The simple strategy I'm using is at my code:

Example

Each require is launched by an if
if (typeof tokenizer === 'undefined')
    var tokenizer = require('./tokenizer');

The definition of module inside the execution of a function
var checker = (function() {
    var whitespace = 1;
    var newline = 2;
    var word = 3;
...

There is a check for exports. module
if (typeof module !== 'undefined' && module && module.exports)
    module.exports = checker;

I have a prologue and epilogue code, that it is used by build.cmd (no multiplatform yet)


But the resulting .js 

can be consumed from browser

Angel "Java" Lopez
@ajlopez


--

Jake Verbaten

unread,
Jul 19, 2013, 1:44:51 PM7/19/13
to nod...@googlegroups.com
Any other answers to my original question?

put it on npm. Have your users install it from npm and then use it with a commonJS compliant tool like browserify.

If you really want to support everyone I recommend you use `browserify --standalone` and put that code in `dist/my-lib.js` on github or your website. `--standalone` generates UMD and works with everything.


greelgorke

unread,
Jul 22, 2013, 2:34:28 AM7/22/13
to nod...@googlegroups.com
what he said

aeosynth

unread,
Jul 22, 2013, 9:12:10 AM7/22/13
to nod...@googlegroups.com
i'm not aware of anything that works without a build step or an async loader, but conceivably you could create one:

<!-- script which defines `require`, `module.exports` -->
<script src="definitions.js"></script>
<!-- your class files -->
<script src="Foo.js"></script>
<script src="Bar.js"></script>
...
<!-- your app's code -->
<script src="app.js"></script>

note that for this to work, your class files must not run any top level code which depends on another module

Peter Rust

unread,
Jul 22, 2013, 10:48:20 AM7/22/13
to nod...@googlegroups.com

> you could create one:

> <!-- script which defines `require`, `module.exports` -->

 

This is essentially what brequire is (client-side CommonJS) -- https://github.com/weepy/brequire. This works and is nicer than the typical browserify usage because it doesn’t bundle all dependencies into a large, single standalone file. Note that browserify now supports this use-case as well.

 

While this brings some advantages (a module system, dependency management, etc), it often isn’t what client-side developers want. The majority of client-side developers are not node developers. Even if you make all your node-module-handling stuff transparent to client-side users of your code, there is still a price-tag in weight and in complexity for client-side developers who want to read and understand the code that they’re bringing into their project.

 

If your modules have a ton of fine-grained dependencies or multiple layers of dependencies or a lot of node-specific dependencies, then using brequire or browserify or something similar can make your life easier. But if not – and especially if your goal is maximum adoption of your modules by client-side developers – you may want to consider following the lead of Underscore and Backbone and add a few lines of boilerplate to each file so client-side developers can use your code according to client-side idioms.

-- peter

--

--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en
 
---

You received this message because you are subscribed to a topic in the Google Groups "nodejs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nodejs/I9NoW70iQTo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nodejs+un...@googlegroups.com.

aeosynth

unread,
Jul 22, 2013, 12:14:38 PM7/22/13
to nod...@googlegroups.com
brequire still requires building. i created a proof of concept system where the commonjs wrapper is placed in the source, eliminating the need for a separate build step - http://plnkr.co/edit/z8LrjE9Jw5PHyv03mN56?p=preview. the same files can be used on client / server, although on the client, two additional files are needed

Ryan Schmidt

unread,
Jul 22, 2013, 12:39:44 PM7/22/13
to nod...@googlegroups.com

On Jul 19, 2013, at 12:44, Jake Verbaten wrote:

> put it on npm. Have your users install it from npm and then use it with a commonJS compliant tool like browserify.
>
> If you really want to support everyone I recommend you use `browserify --standalone` and put that code in `dist/my-lib.js` on github or your website. `--standalone` generates UMD and works with everything.

Thanks everyone for the informative discussion. Browserify is what I've gone with. I initially could not get standalone mode to work, but I'm not sure what I was doing wrong because it works fine now. It has a build step, which I was trying to avoid, but it won't be so bad once I have it triggered automatically when I change the source files. Jake 0.6 has this feature, and of course there are standalone tools for watching files and triggering commands in response.

I was having circular dependency problems for a few days. I had forgotten that my library has circular dependencies between some of the classes, and splitting the single monolithic file into separate files glued together with require() had exposed the apparently common problem where without warning you get an empty object instead of the function you were expecting to have. But I think I've managed to wrangle the order of the require()s to avoid this now.

Jake Verbaten

unread,
Jul 22, 2013, 3:56:00 PM7/22/13
to nod...@googlegroups.com
browserify has many tools to avoid the build step
 
 - beefy ( https://github.com/chrisdickinson/beefy ) will run a local server that does assets & browserify & live reload & watching
 - browserify-middleware ( https://github.com/ForbesLindesay/browserify-middleware ) serve browserify javascript as express middleware
 - serve-browserify ( https://github.com/Raynos/serve-browserify ) framework-agnostic route handler to browserify on demand.

Using any of these will avoid manual builds or manual watchers.


Arnout Kazemier

unread,
Jul 22, 2013, 3:56:54 PM7/22/13
to nod...@googlegroups.com
The best thing

3rdEden

unread,
Jul 22, 2013, 4:08:15 PM7/22/13
to nod...@googlegroups.com

Damn, premature send >_>, anyways:


The best thing about Node was always the promise of code sharing, and that definitely possible. The only problem here is that people think that you need to rewrite your code to node's module pattern in order for it to work in Node.js. This is definitely not the case. Node comes with a great file reading interface and ships a `vm` module that you can use to evaluate code. Armed with this knowledge you can leverage a "module system" that we've been working with for ages as front-end developers and that is globals. And there's nothing wrong with that.


I've always been annoyed by the fact that code that needed to be shared between environments needed to have this awful module bloat in order for it to work. You got node's module system, amd, ES6 imports and next week there's another hipster module system that you need to support. And for this reason i've written load:


https://github.com/3rd-Eden/load


Which basically reads your JavaScript file, loads it in the JavaScript virtual machine, read's out the exposed / added globals and exposes them. That it. No build system needed to make your code work. Just plain o'l JavaScript.


Op maandag 22 juli 2013 21:56:54 UTC+2 schreef 3rdEden het volgende:
The best thing

On Sunday, July 14, 2013 at 8:09 AM, Ryan Schmidt wrote:

Years before I knew about node, I wrote two client-side JavaScript libraries, one of which uses the other, each providing about half a dozen classes. Now I would like to modernize these libraries to make them most accessible to today's web developers.

What is the best way to write my modules so they can be included in any client-side or server-side JavaScript environment?

I would like to publish them to npm. To that end, I've started switching them to node-style require() and module.exports, but of course I can't use those files in the browser ("module is not defined") without a build step such as browserify, or adding boilerplate to each file, and I don't want to bloat my library too much.

I don't want to mandate that my users use an AMD loader, or a CommonJS loader, or RequireJS or curl.js or anything else; I want my libraries to be compatible with any script loading method of the user's choosing.

Avoiding the build step during development would be nice too. I'd like to be able to just include the dozen class files in script tags and use them.

--
--
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
For more options, visit this group at

---
You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+unsubscribe@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages