These guidelines reflect our shared consensus on protocol and etiquette from what we've built so far. Every single item that is presented here is the result of lots of experimentation. However, that doesn't mean that there isn't a better way to do things. What we have below is simply what we've found to work best for us. In this document you will find notes about:
- Project structure.
- Code style.
- Continuous integration.
- Tests.
- Tasks (asset pipeline, transpiling, releasing, etc).
- Dependency management.
Our toolkit for each of these is not set in stone, and we don't plan to halt our constant search for better tools. Get in touch if you've got ideas. These are guidelines rather than rules.
For the majority of our JavaScript projects, our goals are to:
- Ensure browser compatibility, with the possible exceptions being:
- Access to the file system.
- Native bindings.
- Network transports (uTP, udt, curveCP, etc) that are not available in the browser.
- Don't break CommonJS's
require
. This means that if someone requires a JavaScript module from the IPFS ecosystem, they should be able to require it and use browserify, webpack or other bundlers without having to worry about adding special shims for module internals. - Encourage contribution.
- Have great UX for everyone involved.
Please follow the conventions described in this document.
When reporting a bug, if possible, provide a way for us to reproduce it (or even better, write a test that fails with your case).
Always run tests before pushing and PR'ing your code.
The IPFS JavaScript projects work with the Current and Active LTS versions of Node.js and respective npm version that gets installed with Node.js. Please consult nodejs.org for LTS timeline.
IPFS JavaScript projects default to standard code style. It is a clean codestyle, and its adoption is increasing significantly, making the code that we write familiar to the majority of the developers.
However, we've added an extra linting rule: Enforce the use of strict mode. This avoids issues we had when using ES2015 features outside of strict mode. We enforce this rule by using eslint and extending standard module with the eslint-config-standard.
Using aegir-lint will help you do this easily; it automatically lints your code.
When introducing a new error code that may be useful outside of the current scope, make sure it is exported as a new Error
type:
class NotFoundError extends Error {
constructor (message) {
super(message || 'Resource was not found')
this.name = 'NotFoundError'
this.code = NotFoundError.code
}
}
NotFoundError.code = 'ERR_NOT_FOUND'
exports.NotFoundError = NotFoundError
This enables others to reuse those definitions and decreases the number of hardcoded values across our codebases. For example:
const { NotFoundError } = require('some-module')
// throw predefined errors types
if (!value) {
throw new NotFoundError()
}
// compare against code from imported type
if (err.code === NotFoundError.code) {
// handle
}
Our rule is: Use ~ for everything below 1.0.0 and ^ for everything above 1.0.0. If you find a package.json that is not following this rule, please submit a PR.
The only exception to this is if a third party library accidentally releases a breaking change, in which case temporarily pin the dependency to a single version (e.g. "my-dep": "1.0.0"
).
Using aegir-lint will show you if any of your dependency versions need changing to comply with this.
Since js-ipfs
is meant to be both a Node.js and Browser app, we strongly recommend having tests that run in both platforms, always. For most cases, we use mocha to run write the tests and karma to automate the test execution in the browser. This solution has been extremely convenient.
Each time a new release happens, these are the steps we follow to make sure nothing gets left out:
- Run linting
- Run all tests
- Build all three different versions described on the build
- Bump the version in
package.json
- Commit the version bump
- Create a git tag
- Push to GitHub
- Publish to npm
For releasing a js-ipfs, see RELEASE_ISSUE_TEMPLATE
Documentation will be generated automatically by the JSDoc based TS types in the codebase.
Type definitions and type imports should be created on the top of any JS file (below eventual requires needed). For tooling instructions and best practices, see Documentation for JSDoc based TS types.
We have guidelines for how our git commit messages should be formatted. This leads to more readable messages that are easy to follow when looking through the project history. But also, we use the git commit messages to generate the change log.
The commit message formatting can be added using a typical git workflow or through the use of a CLI wizard (Commitizen).
- Type - Must be one of the following:
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests
- chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
- Scope - The scope could be anything specifying the place of the commit change. For example
api
,cli
, etc... - Breaking Changes - Should be identified at the end of commit message. Start with the words
BREAKING CHANGE:
on a new line followed by a space or two new lines. The rest of the commit message is then used to describe in detail what was broken and the migration path (if there is one).
Examples:
feat(pencil): add 'graphiteWidth' option
fix(graphite): stop graphite breaking when width < 0.1
Closes #28
perf(pencil): remove graphiteWidth option
BREAKING CHANGE: The graphiteWidth option has been removed. The default graphite width of 10mm is always used for performance reason.
revert: feat(pencil): add 'graphiteWidth' option
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
There should be no dependencies that rely on commits. Instead, there should be WIP PR and each PR that depends on other WIP PR should list what it depends on. Yes, everyone will have to do the extra work of npm link
ing everything, but this helps us have a cleaner workflow.
We've created a module to help us achieve all of the above with minimal effort. Feel free to also use it for your projects. Feedback is appreciated!
There are a couple of binaries that aegir
provides for you to use
> aegir lint
> aegir test
> aegir test -t browser
> aegir test -t node
> aegir test -t webworker
> aegir build
> aegir release
> aegir release --type minor
> aegir release --type major
If you prefer using npm scripts, you can set them up in your package.json:
{
"scripts": {
"lint": "aegir lint",
"build": "aegir build",
"test": "aegir test",
"test:node": "aegir test -t node",
"test:browser": "aegir test -t browser",
"release": "aegir release",
"coverage": "aegir coverage",
"coverage-publish": "aegir coverage publish"
}
}
You also need to add it your devDependencies
by running:
$ npm install --save-dev aegir
You can find samples for Travis and circle in the examples folder.
We also use coveralls.io to automatically publish coverage reports. This is done from travis using this:
script:
- npm run coverage
after_success:
- npm run coverage publish --providers coveralls
To avoid checking in unwanted files, the .gitignore
file should follow the example. This is if you are using aegir
- smaller projects can use smaller .gitignore
files.
NPM uses the .gitignore
by default, so we have to add a .npmignore
file to ensure we actually ship lib
and dist
files. You can use this example to get started.
We suggest either of these:
- david-dm
- greenkeeper to keep your dependencies up to date.
Every module below 1.0.0 should use ~
instead of ^
.
You can get a bundled version by running npm run build
, an npm script we add to the package.json
. You can find the generated bundle in the /dist
folder. This is available for every project that uses aegir
.
For use in the browser through script tags, there are regular and minified versions in the npm release.
You can use unpkg to include those:
<script src="https://unpkg.com/ipfs-api/dist/index.js"></script>
<script src="https://unpkg.com/ipfs-api/dist/index.min.js"></script>
If you install the module through npm, you can require it using:
const API = require('ipfs-api')
There are two possibilities: either it didn’t work out for us, or we don’t know about it. If you think we might have missed it please tell us, but please believe us if we say we tried and it didn’t work for us.
Gulp is not a hard dependency. It’s just a simple way to structure our tasks at the moment. Usually projects only depend on the aegir binaries completely hiding the fact that we are using gulp under the hood. So we are free if we want to switch it out without any issues. We all enjoy npm scripts, and are using them to call the aegir binaries, but there is no nice way of sharing them yet.
Our linting rules are compatible with standard, which has many examples on documentation on this. Please go there and read it if you're still curious.
We want to see the web move forward, and some of us enjoy writing their JavaScript with things like const
and arrow functions.
No.
No. But other people might ask you to at some point, so it may be better to be prepared.
Because it saves us hours every single day. This tooling is the result of a lot of effort, thought, and hard learning. Its goal is to minimize process road bumps and provide a unified low-friction workflow for contributors.
Any IPFS JavaScript project follows the same Code of Conduct applied to the whole IPFS ecosystem.
- Comparison between WebPack, browserify, requirejs, jspm and rollup - https://github.com/webpack/docs/wiki/comparison
- The cost of transpiling ES2015 in 2016
- standardjs.com
This project would not be possible without the hard work of many many people. So a big shout out to all contributors to these projects: