{"id":13455204,"url":"https://github.com/mattdesl/module-best-practices","last_synced_at":"2025-04-08T11:09:48.139Z","repository":{"id":21931239,"uuid":"25255619","full_name":"mattdesl/module-best-practices","owner":"mattdesl","description":":books: some best practices for JS modules","archived":false,"fork":false,"pushed_at":"2018-03-30T15:58:34.000Z","size":791,"stargazers_count":1551,"open_issues_count":7,"forks_count":86,"subscribers_count":47,"default_branch":"master","last_synced_at":"2025-04-01T08:44:26.794Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mattdesl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-10-15T13:25:28.000Z","updated_at":"2025-03-22T05:30:51.000Z","dependencies_parsed_at":"2022-08-17T23:10:10.395Z","dependency_job_id":null,"html_url":"https://github.com/mattdesl/module-best-practices","commit_stats":null,"previous_names":[],"tags_count":49,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattdesl%2Fmodule-best-practices","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattdesl%2Fmodule-best-practices/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattdesl%2Fmodule-best-practices/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattdesl%2Fmodule-best-practices/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mattdesl","download_url":"https://codeload.github.com/mattdesl/module-best-practices/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247829511,"owners_count":21002997,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T08:01:02.421Z","updated_at":"2025-04-08T11:09:48.096Z","avatar_url":"https://github.com/mattdesl.png","language":"JavaScript","readme":"# module best practices\n\nThis 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. \n\n### contents\n\n- [module basics](#module-basics)\n- [naming conventions](#naming-conventions)\n- [small focus](#small-focus)\n- [prefer dependencies](#prefer-dependencies)\n- [discoverability](#discoverability)\n- [API best practices](#api-best-practices)\n- [avoid global state](#avoid-global-state)\n- [testing](#testing)\n- [versioning](#versioning)\n- [environments](#environments)\n- [data types](#data-types)\n- [npm ignores](#npm-ignores)\n- [task running](#task-running)\n- [UMD builds](#umd-builds)\n- [entry points](#entry-points)\n\n## module basics\n\nA \"module\" is just a reusable chunk of code, abstracted into a more user-friendly API. \n\nModules 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).\n\nThis is the core of the Unix Philosophy: building small programs that do one thing, do it well, and compose easily with other programs.\n\n## naming conventions\n\nModules 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. \n\ne.g. The following code is easy to read:\n\n```js\nvar collides = require('point-in-circle')\nvar data = require('get-image-pixels')(image)\n```\n\nThis is not as clear:\n\n```js\nvar collides = require('collides')\nvar data = require('pixelmate')(image)\n```\n\n## small focus\n\nWriting modules with a *single* focus might be tricky if you've only ever worked with large frameworks (like jQuery, ThreeJS, etc). \n\nOne 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:  \n\n```js\nfunction random(start, end) {\n    return start + Math.random() * (end - start)\n}\n\n//.. your module code ..\n```\n\nYou could move `random` into its own file: `random.js`\n\n```js\nvar random = require('./random')\n\n//.. your module code ..\n```\n\nThis 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. \n\n\u003csup\u003e*Note:* The `random()` one-liner is for demonstration; often you would be dealing with larger functions.\u003c/sup\u003e\n\n## prefer dependencies\n\nAlthough the above code is terse, it could be improved by depending on a module that already exists. For example: [random-float](https://www.npmjs.org/package/random-float) or [random-int](https://www.npmjs.org/package/random-int).\n\nThere are some benefits to this approach:\n\n- The other module is already being used and depended on in the wild\n- The other module has (often) gone through revisions to fix edge cases\n- The other module has its own tests, versioning, documentation, issue tracking, etc\n- It reduces code duplication (e.g. in the case of browserify)\n\nWhen 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. \n\nIt 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](https://www.npmjs.com/package/array-shuffle) or [object-assign](https://www.npmjs.com/package/object-assign) rather than all of [underscore](http://underscorejs.org/#shuffle) for those sole functions. \n\n## discoverability\n\nYou should make sure your module has these things:\n\n- a `README.md` file that describes the module, gives a short code example, and documents its public API\n- a `repository` field in package.json \n- common `keywords` listed in package.json\n- a clear `description` in package.json\n- a `license` field in package.json and `LICENSE.md` in the repository\n\nThis 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.\n\nThe license is important for large companies to justify using your module to their legal teams.\n\nFor more tips on module creation workflow, [see here](http://mattdesl.svbtle.com/faster-and-cleaner-modules).\n\n## API best practices\n\nKeep 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](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it) (You Aren't Gonna Need It) is a good principle when building APIs for small modules.\n\nYou can use a default export to handle the most common use-case, for example: [color-luminance](https://github.com/mattdesl/color-luminance/blob/master/index.js) provides different coefficients, but the default export is the most common case.\n\nClasses 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. \n\nIn many cases, closures can be a good choice for small modules.\n\n```js\nmodule.exports = createFunkyParser\nfunction createFunkyParser (opt) {\n  // optional params\n  opt = opt || {}\n  \n  // private data\n  var foo = opt.foo || 'default'\n  \n  // API/data for end-user\n  return {\n    foo: foo,\n    ...\n  }\n  \n  // private functions\n  function parse () {\n    ...\n  }\n}\n```\n\nThis 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.\n\nAnother common pattern is to use classes internally, but export a function that can be called without the `new` keyword. See [here](https://gist.github.com/mattdesl/d074d956f07821f7d3bb) for examples.\n\nWith the above examples, your module can be required and instantiated inline, like so:\n\n```js\nvar parser = require('funky-parser')({ foo: 'bar' })\nconsole.log(parser.foo)\n```\n\n## avoid global state\n\nGlobals, static fields, and singletons are dangerous in module code, and should be avoided. For example:\n\n```js\nvar Parser = require('funky-parser')\n\n//a \"static\" field\nParser.MAX_CHUNK = 250\n\nvar p = Parser()\n```\n\nHere, `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.\n\n```js\nvar p = Parser({ maxChunk: 250 })\n```\n\n## testing\n\nThis is a large topic that really deserves its own section.\n\nIn brief: add tests for your modules. [tape](https://www.npmjs.org/package/tape) is usually suitable for small modules. More info [here](http://www.macwright.org/2014/03/11/tape-is-cool.html). You can use [nodemon](https://www.npmjs.org/package/nodemon) during development to live-reload your tests. \n\nFor front-end modules, you may need to test in the browser. During development I often use [budo](https://www.npmjs.org/package/budo), [wzrd](https://www.npmjs.com/package/wzrd) or [prova](https://www.npmjs.com/package/prova) to avoid redundant HTML and build step boilerplate. For command-line testing (i.e. PhantomJS) you can use [smokestack](https://www.npmjs.com/package/smokestack) or [testling](https://www.npmjs.com/package/testling). You can use modules like [faucet](https://www.npmjs.com/package/faucet) to pretty-print the output. For example, in your package.json:    \n\n```json\n  \"scripts\": {\n    \"test\": \"browserify test/*.js | testling | faucet\"\n  }\n```\n\nYou can use modules like [lorem-ipsum](https://www.npmjs.org/package/lorem-ipsum), [baboon-image](https://www.npmjs.org/package/baboon-image) and [baboon-image-uri](https://www.npmjs.org/package/baboon-image-uri) for placeholder text and images.\n\nFor prototyping in WebGL/Canvas, you can use modules like [game-shell](https://www.npmjs.org/package/game-shell) or [raf-loop](https://www.npmjs.org/package/raf-loop) to reduce boilerplate. Example [here](https://github.com/Jam3/touch-scroll-physics/blob/9459f4bf3a2b68cd0a5bfa74688f2b5ba663a13f/test.js). \n\nDependencies used in tests and demos should be installed as `devDependencies` like so:  \n\n```npm install domready testling faucet --save-dev```\n\nSee [here](https://github.com/Jam3/jam3-testing-tools) and [here](https://mattdesl.svbtle.com/rapid-prototyping) for a more detailed approach to unit testing and developing Node/Browser modules.\n\n## versioning\n\nIt'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. \n\nIf 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. \n\nUse the following npm command for updating — it will modify `package.json` and commit a new git tag:\n\n```npm version major|minor|patch```\n\nYou 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.\n\n## environments\n\nYour 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.\n\nThe 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).\n\nYou can use the [`browser` field](https://gist.github.com/defunctzombie/4339901) if you have a Node module which needs to be treated differently for the browser.\n\n## data types\n\nIt's best to assume generic types for vectors, quaternions, matrices, and so forth. For example:\n\n```js\nvar point = [25, 25]\nvar polyline2D = [ [25, 25], [50, 10], [10, 10] ]\nvar rgb = [0, 255, 0]\nvar rgba = [1.0, 1.0, 1.0, 0.5]\n```\n\nThis 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. \n\nA good example of this can be seen with *simplicial complexes* such as [icosphere](https://www.npmjs.org/package/icosphere) and [cube-mesh](https://www.npmjs.com/package/cube-mesh). These are render-engine independent, and can be manipulated with modules like [mesh-combine](https://www.npmjs.com/package/mesh-combine), [simplicial-disjoint-union](https://www.npmjs.com/package/simplicial-disjoint-union) and [normals](https://www.npmjs.com/package/normals). \n\n## npm ignores\n\nFor quicker installs, you should only publish the bare minimum to npm. You can ignore most files, like tests, example code, generated API docs, etc.\n\nWith the [`files`](https://www.npmjs.org/doc/files/package.json.html#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. \n\n## task running\n\nIf you have a build task (like [UMD](#umd-builds) 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.\n\nIn `package.json`:\n\n```js\n  \"scripts\": {\n    \"bundle\": \"browserify foo.js -s Foo -o build/foo.js\",\n    \"uglify\": \"uglifyjs build/foo.js -cm \u003e build/foo.min.js\",\n    \"build\": \"npm run bundle \u0026\u0026 npm run uglify\"\n  }\n```\n\nThese 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:  \n\n```npm run build```\n\nIf 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). \n\nMany 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:\n\n* Only use cross-shell operators: `\u003e`, `\u003e\u003e`, `\u003c`, `|`, `\u0026\u0026` and `||`. They work in POSIX-compliant shells (bash, sh, zsh) and Windows Command Prompt. You can use tools like [concurrently](https://www.npmjs.com/package/concurrently) to run commands sequentially or in parallel.\n* Avoid single quotes (`'`). Use escaped double quotes (`\\\"`) instead.\n* Instead of platform-specific tools, use node modules with a CLI – for example [`mkdirp`](https://www.npmjs.com/package/mkdirp) instead of `mkdir`, [`cpy`](https://www.npmjs.com/package/cpy) instead of `cp`, [`mve`](https://www.npmjs.com/package/mve) instead of `mv`, or [`rimraf`](https://www.npmjs.com/package/rimraf) instead of `rm`\n\n## UMD builds\n\nA [UMD](https://github.com/umdjs/umd) build is a JS bundle that works in multiple environments, like Node/CommonJS, AMD/RequireJS, and just a regular `\u003cscript\u003e` tag. Instead of bloating your module code with [the wrapper boilerplate](https://github.com/umdjs/umd), 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:\n\n```sh\n# with browserify\nbrowserify index.js --standalone FunkyParser -o build/funky-parser.js\n\n# with webpack\nwebpack --output-library FunkyParser \\ \n    --output-library-target umd \\\n    --outfile build/funky-parser.js\n```\n\nGenerally 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. \n\n## entry points\n\nOccasionally 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.\n\nExamples:  \n\n- [gl-mat3](https://www.npmjs.org/package/gl-mat3) - splitting @toji's gl-matrix library into separate files for smaller bundle size\n- [eases](https://www.npmjs.com/package/eases) - Robert Penner's easing equations\n\n## more ... ? \n\nFeel free to submit issues/PRs to this repo if you have suggestions or comments. \n","funding_links":[],"categories":["Resources","Articles","JavaScript","资源","Tutorials","Packages","文章"],"sub_categories":["Tutorials","Modules","教程"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattdesl%2Fmodule-best-practices","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmattdesl%2Fmodule-best-practices","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattdesl%2Fmodule-best-practices/lists"}