
Why I write plain JavaScript modules - bevacqua
https://ponyfoo.com/articles/why-i-write-plain-javascript-modules
======
clifanatic
My ongoing frustration with code reuse in general is that the code I'm looking
to reuse reuses other code that reuses other code that refuses other code that
reuses... it's turtles all the way down, and any of those turtles can throw a
completely opaque error message that required very deep knowledge of that
particular turtle to make any sense of. I applaud, thank, and appreciate the
author for going back to basics.

~~~
Roboprog
Preface: I'm not saying "coupling" is good.

Doesn't npm manage transitive dependencies, just like Maven in Java-land? I
sure thought it did. Are package maintainers just not getting their
dependencies right? E.g. - "everybody" uses jQuery, it will just be there, I'm
not going to list it.

I have only used Node/NPM a little bit at home. Most of my JS work has been
some things embedded within larger Java web apps, and my coworkers don't
really like JS (they are wanting to do static-OOP where dynamic-FP fits
better).

~~~
happyslobro
An example of a current problem in NPM land, is "peer dependencies". Suppose
30 of your direct and indirect dependencies require one popular, fast moving
library, say, React. For safety's sake, and to spare the effort of regression
testing, many of these modules specify a fairly conservative version range for
their own React dependency. Normally, NPM would just give you multiple
versions of React, and each library would get the React version that it
expects.

There is a small problem and a big problem with this. The small problem, is
that your build size will be unreasonably large. The big problem, is that this
React library has internal state - you can't just initialise multiple copies
of it, and expect them to share a single state.

Enter peer dependencies. Each library that needs React, demands that the root
module, your application, specify a normal depency on React, that satisfies
their version range requirements. Clearly, this quickly becomes impossible.
So, version mismatch is treated as a warning, not an error, and you (the
application developer) learn to basically ignore that warning.

I can think of lots of solutions to this problem, all of which have problems
of their own.

\- All libraries factor out their state, so that they are stateless. Now you
can load multiple instances of a module, but if they have different major
versions, then they are totally going to mess up that state that you are
holding for them.

\- Application developer: it is your responsibility to patch your
dependencies, and their dependencies, so that they all accept an overlapping
range of React versions. But who has time to test 30 modules for regressions?

\- Just build more, smaller, simpler applications. This is where I am right
now. I still get version warnings, but it is a little less risky to ignore
them.

How do you handle this issue in the Java community?

~~~
Roboprog
Luck, mostly. However, libraries do tend to fall into 2 categories: small, and
big. (yeah, I worked hard to come up with those)

* Small, sharp tools (sometimes) - things like Google Guava "collections" utilities, or the Ostermiller CSV file handling library, which have very small and stable scope.

* Big collections of things, such as Spring or Struts. Regardless of how you feel about these libraries, they do tend to come as a family of interrelated libraries (supplemental components) that get released in lock-step and play nice together.

I dislike the Java language, but I guess it is nice that most of the stuff on
Maven Central does tend to be stable.

~~~
Roboprog
Java-land has been going for about 20 years, and went through a bit of a
house-cleaning about 10 years ago around the time the Maven project started.

Javascript, a la Web 2.0, has really only been a serious development thing for
about 10 years. It's due for its own settling in period soon, I would imagine.

------
exogen
As someone who writes React every day, I'm not sure how the `react-dragula`
implementation even remotely constitutes React integration? AFAIK React never
made any guarantees about `data-reactid` nor what it means to remove it? It's
not a public API and is not even used in the latest React anymore except for
picking up SSR'd markup? I have serious doubts and wouldn't go anywhere near
this.

Secondly, if you tout "React integration" people are going to want a Draggable
component or mixin/HOC or whatever. Making people do things in
`componentDidMount` isn't really integration.

That said: yes, it's a good idea to base your framework-specific integration
on top of a generic battle-tested library. This however appears to be a bad
example of that.

------
tynpeddler
I like this approach, but one problem with plain javascript that I haven't
heard many people address is testability. A module I am using with angular has
lots of calls to setTimeout, but these timed out functions are sometimes not
invoked until after a test had cleaned up the DOM in preparation for the next
test. The timeout function fires and then throws an error because none of the
elements it expects are on the page.

Libraries that set a lot of native key handlers can also be problematic.
Native browser key events are a pain to mock, and the same mocking techniques
are not available in all browsers. If you want to unit test in all browsers,
you have to write some truly hideous tests.

The fact is that a lot of browser API's are terrible, and tools like jquery
can help smooth over browser differences and make it easier to test your code
that relies on a library.

~~~
spankalee
You don't need a whole framework to intermediate events and timers.

One approach is to have a library that you call instead of the native
addEventListener, setTimeout, etc. method.

Another is to use something like zones.js which does this for basically ever
async API in the browser, and then use test utilities that let you play with
time, like flushing all timers, or advancing time by specific amounts.

~~~
tynpeddler
But if you write your library to use those sorts of tools, then you now have a
dependency, which the article is arguing against doing.

~~~
spankalee
The article is not arguing against dependencies. It's arguing against tying
your library to a specific web framework.

------
minitech
Why is atoa() being used on arguments? You can pass an arguments object to
apply(). It even avoids deoptimization (at least on V8), IIRC. (No explanation
at [https://github.com/bevacqua/react-
dragula/commit/916c5fe4900...](https://github.com/bevacqua/react-
dragula/commit/916c5fe4900eca47c07e139d1e0783ca7c6ba814))

~~~
wanda
This is indeed true for V8 IIRC, but even if it weren't, both arguments.length
and arguments[ _x_ ] are safe to use without stopping optimisation, so even a
very cautious JavaScript dev can effectively clone the arguments object in a
roundabout sort of way and pass it to apply, like so:

    
    
        // given a context and function 'func'
    
        var args = [];
        for(var i = 0; i < arguments.length; i++){
          args[i] = arguments[i];
        }
        func.apply(context, args);
    

Source:
[https://github.com/petkaantonov/bluebird/wiki/Optimization-k...](https://github.com/petkaantonov/bluebird/wiki/Optimization-
killers)

~~~
minitech
Right, but passing it to atoa() (unless it’s a macro? I didn’t check) seems
redundant _and_ deoptimizes.

~~~
wanda
Oh yes, you're absolutely right there.

Even if it were okay, it's an absurd dependency.

------
mohsen1
I do exactly this now. I used to write "Angular Directives" but not everything
I want to share is in pure JavaScript.

Here are some of my successful pure JS modules:

[https://github.com/mohsen1/json-formatter-
js](https://github.com/mohsen1/json-formatter-js)
[https://github.com/mohsen1/json-schema-view-
js](https://github.com/mohsen1/json-schema-view-js)

If you want to use those modules in any framework you just wrap it in a tiny
thin wrapper and I don't have to keep up with framework changes and all
that...

~~~
clifanatic
> but not everything I want to share

Is that a typo? Did you mean "not" or "now"?

------
the_duke
I agree in general, but in the space of UI frameworks it's hard if not
impossible to write good cross-framework code.

Most frameworks have a specific way of rendering the DOM.

In React, manually altering the DOM is an anti-pattern. Even more so if you
are using a global store like Redux and/or functional components.

Integrating other "classic" UI code into React is almost always very hackish.

~~~
RodericDay
Coming from PyQt, I just cannot understand or agree with the idea that React
is "the future".

It's nice for some tasks, sure, but it also feels like a big hack.

~~~
hesarenu
Its very good for most tasks. Makes it simple to create and maintain complex
UIs.

~~~
RodericDay
rerender the entire thing "virtually", diff it, then apply the subset of
changes that matter? it doesn't seem optimal in any way to me.

it's great compared to the alternatives in the browser today, but it seems
extremely wasteful compared to an unbound universe of alternatives.

~~~
hesarenu
When in write react code i don't think in terms of diff but in terms of how my
ui looks based on current state. Underline implementation can change.

------
ghostly_s
Good god, this website design. 1980x1200 monitor and not even the entire
_first line_ of the headline is visible on the screen.

~~~
mike-cardwell
Ditto here on a 40 inch 16:9 monitor at 3840x2160.

------
rblatz
I've been advocating this for a while, business logic should not be tied to a
specific framework. It can be brought into any framework with a super thin,
easy to write/read wrapper.

But UI level stuff, probably should be done in your framework. So Business
logic drives state, which your framework translates into DOM.

------
Roboprog
It's hard (not impossible) to avoid this with some user interface code, due to
the amount of connected, stateful cruft (hopefully the state is pushed out to
the edges, but it has to be there somewhere).

I do appreciate little utility libraries like Ramdajs and Validatejs, though.

This article was a nice reminder to make the attempt to decouple small
plugins, though. I like the idea of the base module + alternate framework
adapters.

------
macawfish
Just yesterday I spent 4 hours looking for something that'd let me use DOM
queries with map and forEach. I settled on NodeList.js. I didn't want JQuery
or Zepto or anything larger than 1 KB, but I also didn't want to write
something to do this, something I'd have to copy and paste into the next
project.

~~~
coltonv
Not sure why you spent 4 hours on that, in addition to the other response
there's also this method, which lets you call map on it and also call all the
methods available to NodeList (you'd lose access to those if you converted it
to an array)

    
    
      var classNames = [].map.call(nodeList, (entry) => entry.className)
    

The 'call' function in java script allows you to specify the "this" in the
calling of the function. So we grab the map function from an empty array, and
call it with nodeList as "this". You can find out more here:
[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Function/call)

You could also use that to make a super simple method to allow you to call map
on any array like object, then you can import this into your other files:

    
    
      function map(arr, callback) {
        return Array.prototype.map.call(arr, callback);
      }
    

Then call it like

    
    
      var classNames = map(nodeList, (node) => node.className);

~~~
macawfish
Well, I spent four hours on it because I was also considering the other fancy
features of these libraries and playing with them. Which is why this post made
me think of yesterday's four hour fancy javascript helper library exploration
session.

------
spankalee
Yet another reason why moving to Web Components will make everyone's life
easier. No need for these framework-specific binding layers. It's just DOM.

~~~
andrewmcwatters
I believe anyone who's spent long enough time across multiple programming
backgrounds and looked at the JavaScript ecosystem would understand how solid
Web Components are. They're objects, and you fill methods on them. But that's
not "revolutionary" enough for this community. There's no flash or wow with
it, it's just solid programming.

Also, everyone loves to hate the DOM. It's so "low level."

Additionally, I feel Google tainted the idea of them by making Polymer the
face of the standard, rather than letting it stand by itself.

------
amelius
> The point in question is that developing a library that specifically targets
> a framework is a waste of your time, because when you eventually move on to
> the next framework ( this isJavaScript,youwill) you’ll kick yourself over
> tightly coupling the library to the framework.

How about not targeting a specific language either?

