• Stars
    star
    1,521
  • Rank 30,802 (Top 0.7 %)
  • Language
    JavaScript
  • Created about 10 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

📚 some best practices for JS modules

module best practices

This is a set of "best practices" I've found for writing new JavaScript modules. This guide deals specifically with front- and back-end Node/CommonJS modules hosted on npm, but the same concepts may apply elsewhere.

contents

module basics

A "module" is just a reusable chunk of code, abstracted into a more user-friendly API.

Modules should have a very specific purpose. Don't aim to build a large framework, instead; imagine you're building its underlying parts as separate pieces (which could, if desired, be composed together to mimic the scope of a framework).

This is the core of the Unix Philosophy: building small programs that do one thing, do it well, and compose easily with other programs.

naming conventions

Modules are lower case and usually dash-separated. If your module is a pure utility, you should generally favour clear and "boring" names for better discoverability and code clarity.

e.g. The following code is easy to read:

var collides = require('point-in-circle')
var data = require('get-image-pixels')(image)

This is not as clear:

var collides = require('collides')
var data = require('pixelmate')(image)

small focus

Writing modules with a single focus might be tricky if you've only ever worked with large frameworks (like jQuery, ThreeJS, etc).

One easy way to enforce this is to break your code into separate files. For example, a function that is not directly tied to the rest of the module can be split into its own file:

function random(start, end) {
    return start + Math.random() * (end - start)
}

//.. your module code ..

You could move random into its own file: random.js

var random = require('./random')

//.. your module code ..

This forces you to strip away code that doesn't belong in the module, keeping the entry point focused and narrow. It also makes it easy to move the separated functions into their own modules if you later feel the need.

Note: The random() one-liner is for demonstration; often you would be dealing with larger functions.

prefer dependencies

Although the above code is terse, it could be improved by depending on a module that already exists. For example: random-float or random-int.

There are some benefits to this approach:

  • The other module is already being used and depended on in the wild
  • The other module has (often) gone through revisions to fix edge cases
  • The other module has its own tests, versioning, documentation, issue tracking, etc
  • It reduces code duplication (e.g. in the case of browserify)

When you can't find a suitable dependency, or when the only dependencies are dangerous to depend on (i.e. no testing, unstable API, poorly written), this is where you could take it upon yourself to split the code into its own module.

It is also better to prefer small dependencies rather than broad "libraries." For example, if you need to shuffle an array or merge objects, it would be better to depend on array-shuffle or object-assign rather than all of underscore for those sole functions.

discoverability

You should make sure your module has these things:

  • a README.md file that describes the module, gives a short code example, and documents its public API
  • a repository field in package.json
  • common keywords listed in package.json
  • a clear description in package.json
  • a license field in package.json and LICENSE.md in the repository

This will improve the discoverability of your module through Google and npm search, and also give more confidence to people who may want to depend on your code. Better discoverability means less fragmentation overall, which means tighter and better tested application code.

The license is important for large companies to justify using your module to their legal teams.

For more tips on module creation workflow, see here.

API best practices

Keep your APIs short, simple, and easy to remember. If you've got a hundred functions in your module, you might want to rethink your design and split those into other modules. YAGNI (You Aren't Gonna Need It) is a good principle when building APIs for small modules.

You can use a default export to handle the most common use-case, for example: color-luminance provides different coefficients, but the default export is the most common case.

Classes and constructors can be a controversial topic, and it often comes down to preference. I've found the best approach is to avoid forcing the new operator on your end-user, and have parameters passed in an options object.

In many cases, closures can be a good choice for small modules.

module.exports = createFunkyParser
function createFunkyParser (opt) {
  // optional params
  opt = opt || {}
  
  // private data
  var foo = opt.foo || 'default'
  
  // API/data for end-user
  return {
    foo: foo,
    ...
  }
  
  // private functions
  function parse () {
    ...
  }
}

This can provide succinct APIs and proper information hiding. It also ensures your users won't rely on patterns like instanceof and class inheritance, which can be dangerous when composing many small modules.

Another common pattern is to use classes internally, but export a function that can be called without the new keyword. See here for examples.

With the above examples, your module can be required and instantiated inline, like so:

var parser = require('funky-parser')({ foo: 'bar' })
console.log(parser.foo)

avoid global state

Globals, static fields, and singletons are dangerous in module code, and should be avoided. For example:

var Parser = require('funky-parser')

//a "static" field
Parser.MAX_CHUNK = 250

var p = Parser()

Here, MAX_CHUNK is a global. If two modules both depend on funky-parser, and both of them mutate the global state, only one would succeed. In this case it's better as an instance member, so that both modules could modify it independently of the other.

var p = Parser({ maxChunk: 250 })

testing

This is a large topic that really deserves its own section.

In brief: add tests for your modules. tape is usually suitable for small modules. More info here. You can use nodemon during development to live-reload your tests.

For front-end modules, you may need to test in the browser. During development I often use budo, wzrd or prova to avoid redundant HTML and build step boilerplate. For command-line testing (i.e. PhantomJS) you can use smokestack or testling. You can use modules like faucet to pretty-print the output. For example, in your package.json:

  "scripts": {
    "test": "browserify test/*.js | testling | faucet"
  }

You can use modules like lorem-ipsum, baboon-image and baboon-image-uri for placeholder text and images.

For prototyping in WebGL/Canvas, you can use modules like game-shell or raf-loop to reduce boilerplate. Example here.

Dependencies used in tests and demos should be installed as devDependencies like so:

npm install domready testling faucet --save-dev

See here and here for a more detailed approach to unit testing and developing Node/Browser modules.

versioning

It's important to follow SemVer when you publish changes to your module. Others are expecting that they can safely update patch and minor versions of your module without the user-facing API breaking.

If you are adding new backward-compatible features, be sure to list them as a minor version. If you are changing or adding something that breaks the documented API, you should list it as a major (breaking) version. For small bug fixes and non-code updates, you can update the patch number.

Use the following npm command for updating — it will modify package.json and commit a new git tag:

npm version major|minor|patch

You should start modules with version 1.0.0. The exception to this is when you know your module will be going under a lot of major API changes before stabilizing (i.e. for experimental packages). In that case, you can start with 0.0.0 and only promote to 1.0.0 once the API is a little more stable.

environments

Your code should aim to work server-side and client-side where possible. For example; a color palette generator should not have any DOM dependencies; instead, those should be built separately, on top of your base module.

The closer you follow Node's standards and module patterns, the more likely your code will be useful in a variety of environments (like Ejecta/Cocoon, ExtendScript for AfterEffects, Browserify, etc).

You can use the browser field if you have a Node module which needs to be treated differently for the browser.

data types

It's best to assume generic types for vectors, quaternions, matrices, and so forth. For example:

var point = [25, 25]
var polyline2D = [ [25, 25], [50, 10], [10, 10] ]
var rgb = [0, 255, 0]
var rgba = [1.0, 1.0, 1.0, 0.5]

This makes it easier to compose with other modules, and avoids the problems of constantly "boxing and unboxing" objects across different modules with potentially conflicting versions.

A good example of this can be seen with simplicial complexes such as icosphere and cube-mesh. These are render-engine independent, and can be manipulated with modules like mesh-combine, simplicial-disjoint-union and normals.

npm ignores

For quicker installs, you should only publish the bare minimum to npm. You can ignore most files, like tests, example code, generated API docs, etc.

With the files entry in package.json, you can whitelist specific files to be published. This often leads to the tightest and smallest repos/packages. Alternatively, you can blacklist files from your module with an .npmignore file.

task running

If you have a build task (like UMD or a test runner) it is better to keep this small and light by just adding it to your npm scripts. For these simple tasks, you might find gulp/grunt to be overkill.

In package.json:

  "scripts": {
    "bundle": "browserify foo.js -s Foo -o build/foo.js",
    "uglify": "uglifyjs build/foo.js -cm > build/foo.min.js",
    "build": "npm run bundle && npm run uglify"
  }

These tools would be saved locally with --save-dev so that others cloning the repo can run the same versions. Then run the following to build:

npm run build

If you're writing small CommonJS modules, you typically won't need to have any tasks except a test runner. In this case you don't need to list browserify as a devDependency, since the source is assumed to work in any CommonJS bundler (webpack, DuoJS, browserify, etc).

Many npm scripts depend on Unix-only or bash-only features. If you want to make sure your scripts are platform-independent, keep these in mind:

  • Only use cross-shell operators: >, >>, <, |, && and ||. They work in POSIX-compliant shells (bash, sh, zsh) and Windows Command Prompt. You can use tools like concurrently to run commands sequentially or in parallel.
  • Avoid single quotes ('). Use escaped double quotes (\") instead.
  • Instead of platform-specific tools, use node modules with a CLI – for example mkdirp instead of mkdir, cpy instead of cp, mve instead of mv, or rimraf instead of rm

UMD builds

A UMD build is a JS bundle that works in multiple environments, like Node/CommonJS, AMD/RequireJS, and just a regular <script> tag. Instead of bloating your module code with the wrapper boilerplate, and potentially making errors in the process, you should let tools handle this. This also means you will be using the latest wrappers (they may change as new environments become popular). Example:

# with browserify
browserify index.js --standalone FunkyParser -o build/funky-parser.js

# with webpack
webpack --output-library FunkyParser \ 
    --output-library-target umd \
    --outfile build/funky-parser.js

Generally speaking, UMD builds are not very useful for small modules. Adding bundle files leads to heavier repos and another channel you need to support. If somebody wants to use your module, encourage them to depend on it via npm so they can receive patches, or build it themselves with their tool of choice.

entry points

Occasionally you'll see modules using unusual entry points. This is especially useful for modules targeting front-end code, to reduce the bundle size and only pull in methods as needed. This should be used sparingly; if you have a lot of different functions, you should consider whether they need to be in their own modules.

Examples:

  • gl-mat3 - splitting @toji's gl-matrix library into separate files for smaller bundle size
  • eases - Robert Penner's easing equations

more ... ?

Feel free to submit issues/PRs to this repo if you have suggestions or comments.

More Repositories

1

canvas-sketch

[beta] A framework for making generative artwork in JavaScript and the browser.
JavaScript
5,019
star
2

budo

🎬 a dev server for rapid prototyping
JavaScript
2,174
star
3

lwjgl-basics

🔧 LibGDX/LWJGL tutorials and examples
Java
1,841
star
4

graphics-resources

📝 a list of graphic programming resources
1,748
star
5

color-wander

🎨 Generative artwork in node/browser based on a seeded random
JavaScript
1,615
star
6

promise-cookbook

📙 a brief introduction to using Promises in JavaScript
1,603
star
7

workshop-generative-art

A workshop on creative coding & generative art
JavaScript
1,362
star
8

svg-mesh-3d

🚀 converts a SVG path to a 3D mesh
JavaScript
1,169
star
9

workshop-webgl-glsl

A workshop on WebGL and GLSL
JavaScript
1,032
star
10

webgl-wireframes

Stylized Wireframe Rendering in WebGL
JavaScript
713
star
11

workshop-p5-intro

Intro to Creative Coding workshop with p5.js and Tone.js
711
star
12

canvas-sketch-util

Utilities for sketching in Canvas, WebGL and generative art
JavaScript
661
star
13

threejs-app

Some opinionated structure for a complex/scalable ThreeJS app
JavaScript
444
star
14

bellwoods

JavaScript
395
star
15

webgl-lines

some interactive content for a blog post
JavaScript
385
star
16

eases

a grab-bag of modular easing equations
JavaScript
372
star
17

audiograph.xyz

A visual exploration of Pilotpriest's 2016 album, TRANS.
JavaScript
335
star
18

jsconfeu-generative-visuals

Code for the generative projection mapped animations during JSConf EU 2018 in Berlin.
JavaScript
334
star
19

load-asset

Loads a single or multiple assets and returns a promise.
JavaScript
311
star
20

glsl-fxaa

FXAA implementation for glslify in WebGL
GLSL
310
star
21

dictionary-of-colour-combinations

palettes from A Dictionary of Colour Combinations
Python
290
star
22

shader-reload

An interface for reloading GLSL shaders on the fly.
JavaScript
284
star
23

penplot

[DEPRECATED] see canvas-sketch
JavaScript
262
star
24

mp4-wasm

[proof-of-concept] fast MP4 mux / demux using WASM
C
258
star
25

gifenc

fast GIF encoding
JavaScript
246
star
26

codevember

codevember
JavaScript
242
star
27

impressionist

🎨 generative painting using perlin noise for motion
JavaScript
242
star
28

three-line-2d

lines expanded in a vertex shader
JavaScript
224
star
29

three-orbit-controls

orbit controls for ThreeJS
JavaScript
216
star
30

physical-text

🌂 simulating text in the physical world
JavaScript
216
star
31

mp4-h264

[project suspended] MP4 + H264 encoding for the browser with WASM
C
212
star
32

prot

highly opinionated dev environment [Proof of concept]
JavaScript
201
star
33

template-electron-installation

a template for media art installations using Electron in kiosk mode
JavaScript
199
star
34

workshop-web-audio

Web Audio workshop with Frontend Masters
JavaScript
189
star
35

yyz

JavaScript
187
star
36

parametric-curves

JavaScript
185
star
37

fontpath

Font to vector path tools
JavaScript
183
star
38

ghrepo

:octocat: create a new GitHub repo from your current folder
JavaScript
177
star
39

google-panorama-equirectangular

gets equirectangular images from Google StreetView
JavaScript
172
star
40

image-sdf

generate a signed distance field from an image
JavaScript
171
star
41

glsl-film-grain

natural looking film grain using noise functions
JavaScript
171
star
42

subscapes

generative artwork hosted on Ethereum
JavaScript
169
star
43

pack-spheres

Brute force circle/sphere packing in 2D or 3D
JavaScript
161
star
44

polartone

experimental audio visualizer
JavaScript
154
star
45

dom-css

fast dom CSS styling
JavaScript
153
star
46

tiny-artblocks

Toolkit for small ArtBlocks projects
JavaScript
152
star
47

adaptive-bezier-curve

adaptive and scalable 2D bezier curves
JavaScript
138
star
48

atcq

An implementation of Ant-Tree Color Quantization
JavaScript
136
star
49

workshop-data-artwork

material & notes for a workshop on data artwork & creative coding
JavaScript
125
star
50

kami-demos

🚧 Some demos for the Kami WebGL renderer
JavaScript
122
star
51

rust

experiments
JavaScript
122
star
52

kami

🚧 Rendering ecosystem using Node style packaging
JavaScript
120
star
53

looom-tools

Svelte
115
star
54

esmify

parse and handle import/export for browserify
JavaScript
112
star
55

polyline-normals

gets miter normals for a 2D polyline
JavaScript
112
star
56

three-vignette-background

a simple ThreeJS vignette background
JavaScript
111
star
57

simple-input-events

Unified mouse & touch events for desktop and mobile
JavaScript
105
star
58

tweenr

minimal tweening engine
JavaScript
105
star
59

text-modules

✏️ a list of text/font modules
104
star
60

spectrum

a small tool to visualize the frequencies of an audio file
JavaScript
104
star
61

three-shader-fxaa

optimized FXAA shader for ThreeJS
JavaScript
102
star
62

lerp

bare-bones linear interpolation function
JavaScript
101
star
63

canvas-sketch-cli

A CLI used alongside canvas-sketch
JavaScript
92
star
64

svg-path-contours

gets a discrete list of points from svg
JavaScript
90
star
65

pen-plotter-blog-post

JavaScript
90
star
66

simplify-path

simplify 2D polyline of arrays
JavaScript
83
star
67

garnish

🍸 prettifies ndjson from wzrd and similar tools
JavaScript
81
star
68

get-rgba-palette

gets a palette of prominent colors from an array of pixels
JavaScript
81
star
69

keytime

[EXPERIMENT] keyframe animation tools
JavaScript
79
star
70

three-glslify-example

a simple example of ThreeJS with glslify
GLSL
77
star
71

canvas-text

[experiment] better Canvas2D text rendering
JavaScript
77
star
72

raylight

Experimental WebGL Music Visualizer
JavaScript
76
star
73

verlet-system

2D and 3D verlet integration
JavaScript
75
star
74

word-wrapper

wraps words based on arbitrary 2D glyphs
JavaScript
71
star
75

mp4-wasm-encoder

JavaScript
70
star
76

gl-sprite-text

bitmap font rendering for stackgl
JavaScript
69
star
77

tendril-webtoy-blog-post

A blog post for an interactive Tendril web toy
68
star
78

paper-colors

A small set of pastel and off-white paper colors
JavaScript
68
star
79

threejs-tree-shake

Tree-shakes and optimizes ThreeJS apps
JavaScript
66
star
80

gh-readme-scrape

a CLI to bulk download URLs (images/pdfs/etc) from GitHub readmes
JavaScript
65
star
81

fika

A figma plugin generator
JavaScript
60
star
82

shadertoy-export

render ShaderToy demos to PNG
JavaScript
59
star
83

glsl-random

pseudo-random 2D noise for glslify
C
59
star
84

electron-canvas-to-buffer

in Electron, turns a Canvas into a Buffer
JavaScript
55
star
85

gl-vignette-background

a soft gradient background in WebGL
JavaScript
55
star
86

webpack-three-hmr-test

test of ThreeJS + Webpack + HMR
JavaScript
53
star
87

workshop-generative-color

a workshop on color science for generative art and creative coding
JavaScript
52
star
88

filmic-gl

filmic GLSL shaders in ThreeJS
JavaScript
51
star
89

riso-colors

A list of Risograph printer colors
51
star
90

gsx-pdf-optimize

Optimize PDFs with Ghostscript command
JavaScript
50
star
91

raf-loop

a minimal requestAnimationFrame render loop
JavaScript
49
star
92

gdx-swiper

An example of a "Fruit Ninja" style swipe in LibGDX
Java
47
star
93

gsap-promise

promise wrapper for gsap (TweenLite)
JavaScript
47
star
94

browserify-example

a bare-bones, no-bullshit example of using browserify to dev + build a static demo
JavaScript
45
star
95

extract-svg-path

extracts a string of subpaths from an svg file
JavaScript
45
star
96

figma-plugin-palette

"Image Palette" Plugin in Figma
JavaScript
42
star
97

adaptive-quadratic-curve

adaptive and scalable 2D quadratic curves
JavaScript
42
star
98

three-geometry-data

Get vertex and face data from THREE.Geometry
JavaScript
40
star
99

budo-chrome

an extension of budo dev server that supports live script injection
JavaScript
39
star
100

three-tube-wireframe

Builds a tube-based wireframe geometry in ThreeJS
JavaScript
39
star