client-side packages

37
Client-Side Packages Or, how I learned to stop worrying and love @DOMENIC

Upload: domenic-denicola

Post on 21-Jan-2018

15.065 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Client-Side Packages

Client-Side PackagesOr, how I learned to stop worrying and love

@DOMENIC

Page 3: Client-Side Packages

Modules

@DOMENIC

Page 4: Client-Side Packages

Module systems express code dependencies

› 2000s state of the art:

– One big file with carefully ordered functions, or

– Many small files, and carefully ordered <script> tags

› Other languages solve this problem easily; in JS, until ES6, we have to solve it ourselves.

› We have two hacky solutions:

– CommonJS modules

– AMD modules

› CommonJS is better for code reuse.

@DOMENIC

Page 5: Client-Side Packages

CommonJS modules

› AMD:

define(['a', './b'], function (a, b) {return { c: 'd' };

});

› CommonJS:

var a = require('a');var b = require('./b');

module.exports = { c: 'd' };

@DOMENIC

Page 6: Client-Side Packages

The AMD configuration problem

› In require('a'), what does 'a' refer to?

– AMD module called 'a' (per baseUrl config)

– Some other AMD module (per map config)

– Non-AMD code (per shim config)

– AMD plugin (per plugin config)

– A package’s main module (per packages config)

› In a CommonJS + npm system, the answer is always the main module for package 'a'.

@DOMENIC

Page 7: Client-Side Packages

The AMD dependency problem

› If Ember depends on RSVP.js, how does it express this in AMD?– require('rsvp') doesn’t cut it.

– I know! We’ll use more config!

› Now your Ember-using project needs to know about Ember’s internal implementation details in order to make Ember’s require('rsvp') work, via configuration.

› This severely limits the extent to which library authors can bring in third-party code.

› Where did this 'rsvp' string come from anyway?

These are all problems to be solved by packages, but AMD tries—and fails—to solve them at the module level.

@DOMENIC

Page 8: Client-Side Packages

AMD is not an advantage

› AMD tries to solve too many problems that are way beyond the level of a module system.

› CommonJS can solve all AMD use cases except cross-domain single-module loading.

› Using AMD cuts you off from consuming most module code out there.

› Your code is more universal with CommonJS. More tools consume it, and it’s easier to test.

› AMD users can use r.js to convert from CommonJS.

@DOMENIC

Page 9: Client-Side Packages

Enter Packages

@DOMENIC

Page 10: Client-Side Packages

Packages

› Modules let us reuse our own code.

› Packages let us reuse other people’s code.

› Examples of packages:

– Backbone

– Underscore

– Sinon.JS

– jQuery

– jsdom

– Express

› Packages have dependencies

– E.g. Backbone depends on Underscore and jQuery

@DOMENIC

Page 11: Client-Side Packages

Packages as a unit of code reuse

› Packages are small, often one module.

› They can shuffle work off to their dependencies, allowing greater code reuse.

› If jQuery UI were composed of packages:

– jqueryui-position depends on nothing

– jqueryui-mouse depends on jqueryui-core, jqueryui-widget

– jqueryui-autocomplete depends on core, widget, menu, position

– etc.

› ―Download builders‖ are hacky and un-encapsulated ways of solving package management.

› Multi-thousand line modules belong in the 2000s.

@DOMENIC

Page 12: Client-Side Packages

Packages can be versioned independently

› When you separate functionality into small packages, you aren’t tied to monolithic, coordinated release cycles.

› By depending on small packages, you can get bug fixes and new features as soon as their authors publish them.

› Then users of your package get bug fixes, without you doing anything!

› Conventionally, we try to adhere to semantic versioning:

– 0.x.y releases: unstable; upgrade at your peril.

– 1.x.y and beyond:

› Increasing ―patch version‖ y means bug fixes.

› Increasing ―minor version‖ x means new features.

› Increasing ―major version‖ means new API, breaking backward-compat.

– So we can express dependencies as 0.1.2 or 2.x or 2.3.x to get the benefits of independent versioning.

@DOMENIC

Page 13: Client-Side Packages

Packages can be app-specific

› Each ―widget‖ can be a package.

– Sidebar, header, currency table, news ticker, …

› Or you could have packages for business logic.

– Financial calculations, market predictions, discount rules, …

› Or you can isolate hard problems in packages.

– Talking to SMTP servers, traversing a RESTful API, rendering PDFs with JavaScript, sandboxing user input, …

› They can separate out cross-cutting concerns.

– Number formatting, CSS theming, injecting logging and error handling, …

› They can even help with domain-driven design.

– Encapsulating bounded contexts, exposing services, isolating a shared kernel, building an anticorruption layer, …

@DOMENIC

Page 14: Client-Side Packages

Packages with npm

@DOMENIC

Page 15: Client-Side Packages

What problems do we need to solve?

› Get code onto our computers

› Automatically install expressed dependencies

› Have a way for one package’s code to ―require‖ another package’s code.

› Make other peoples’ code available to you

@DOMENIC

Page 16: Client-Side Packages

npm stands for JavaScript package manager

› 26,000 packages and growing

› The emphasis on small packages means many work in the browser already.

› The npm registry is anarchic.

– This makes package discovery tricky, but in balance is a big win.

– Server code, browser code, even AMD code: it’s all welcome.

– A single namespace means no enforced prefixing.

› npm packages follow a set of conventions that make your life easy.

@DOMENIC

Page 17: Client-Side Packages

What’s in an npm package?

› lib/

– index.js

– AuxiliaryClass.js

– helpers.js

› test/

› LICENSE.txt

› README.md

› package.json

› .jshintrc, .gitignore, .npmignore, .travis.yml, …

@DOMENIC

Page 18: Client-Side Packages

package.json

{

"name": "sinon-chai",

"description": "Extends Chai with assertions for Sinon.JS.",

"keywords": ["sinon", "chai", "testing", "spies", "stubs", "mocks"],

"version": "2.3.1",

"author": "Domenic Denicola <[email protected]>",

"license": "WTFPL",

"repository": {

"type": "git",

"url": "git://github.com/domenic/sinon-chai.git"

},

"main": "./lib/sinon-chai.js",

@DOMENIC

Page 19: Client-Side Packages

package.json

"dependencies": {

"sinon": ">= 1.5 <2"

},

"peerDependencies": {

"chai": ">= 1.3.0 <2"

},

"devDependencies": {

"coffee-script": "~1.6",

"jshint": "~1.1",

"mocha": "~1.7",

},

"scripts": {

"test": "mocha",

"lint": "jshint ./lib"

}

} @DOMENIC

Page 20: Client-Side Packages

Packages are encapsulation boundaries

› Single point of entry API via the main module:

– require("backbone")

– require("underscore")

– require("sinon-chai")

– require("express")

› You don’t need to know about a package’s dependencies

– Unlike AMD!

› You don’t need to know about a package’s strategy

– Client-side MVC framework choice, templating engine, CSS precompiler, …

@DOMENIC

Page 21: Client-Side Packages

Packages are concern boundaries

› Their own license

› Their own readme

› Their own coding style and linting rules

› Their own compile-to-JS authoring language

› Their own tests and test style

› Their own author/owner/maintainer

@DOMENIC

Page 22: Client-Side Packages

Dependencies

@DOMENIC

Page 23: Client-Side Packages

npm dependency basics

› npm uses a global registry by default: https://npmjs.org

– A CouchDB server: http://isaacs.ic.ht/_utils/

› It uses package.json to install dependencies

@DOMENIC

Page 24: Client-Side Packages

Dependencies are hierarchical

› Unlike some other package managers, there is no global shared

―DLL hell‖ of packages on your system.

› They get installed in a hierarchy of node_modules folders:

[email protected]

├─┬ [email protected]

│ ├── [email protected]

│ └─┬ [email protected]

│ └── [email protected]

└── [email protected]

request/node_modules/form-datarequest/node_modules/form-data/node_modules/asyncrequest/node_modules/form-data/node_modules/async/node_modules/combined-stream⋮

@DOMENIC

Page 25: Client-Side Packages

Git dependencies

› Dependencies can be Git URLs

› Useful for pointing to forked-and-patched versions of a library while you wait for your pull request to land.

› Useful for private Git servers.

› For GitHub, use "package": "user/repo#version" syntax.

› This allows an escape hatch from the central registry and its single namespace, if you’re into that.

@DOMENIC

Page 26: Client-Side Packages

Other important npm features

› npm dedupe: creates the minimal dependency tree that satisfies everyone’s versions

› npm link: symlinks, for local development

› The "scripts" field in package.json can give you arbitrary scripts which have access to your devDependencies.

– npm run <scriptName>

– npm test (shortcut for npm run test)

› Packages can have arbitrary metadata: it’s just JSON!

@DOMENIC

Page 27: Client-Side Packages

Private Code

@DOMENIC

Page 28: Client-Side Packages

How can we use this without publishing?

› We can’t put proprietary code in the npm registry.

› But we’d still like to use the package concept, and npm’sfeatures.

› There are hacks:

– npm link

– Using Git URLs entirely (this actually works pretty well).

› But, really we need our own registry!

@DOMENIC

Page 29: Client-Side Packages

Your own registry

› CouchDB is really good at replicating. So just do that!

› Then:

npm install lab49-ip –reg http://npm.lab49.org

› You can use the publishConfig settings in package.json to make a given package automatically publish to your internal registry, instead of the public one.

› We’re working on ―fallback registries,‖ to obviate the replication step.

@DOMENIC

Page 30: Client-Side Packages

Packages, Client-Side

@DOMENIC

Page 31: Client-Side Packages

A note on CSS

› Just include it in your package!

› Don’t reference it from your JS, e.g. automatically inject it into the page.

› It shouldn’t matter whether the CSS is in node_modules/mywidget or in app/widgets.

› The application’s build tool will consume it, if the application author needs it; otherwise it sits inert.

@DOMENIC

Page 32: Client-Side Packages

Browserify!

› The best solution for bringing CommonJS modules and npm packages to the browser.

› Shims common (and not-so-common) Node APIs to give you access to many npm packages.

– events, path, url, querystring, vm, http, crypto, zlib (!) and more…

› Understands npm’s node_modules structure.

› Has a flexible pipeline and vibrant ecosystem, for e.g. source map support or just-in-time bundle compilation when embedded in simple HTTP servers.

› Allows packages to declare source transforms and browser overrides in their package.json metadata.

@DOMENIC

Page 33: Client-Side Packages

Discovering client-side packages

› We trust packages most if they are validated by continuous integration, e.g. by Travis CI:

› We can trust client-side packages if they are validated by testling-ci:

@DOMENIC

Page 34: Client-Side Packages

Testling-ci setup

In package.json:

"testling": {

"browsers": [

"ie/7..10",

"firefox/10..13", "firefox/nightly",

"chrome/14..16", "chrome/canary",

"safari/5.0.5..latest",

"opera/10.6", ―opera/11.0", "opera/11.6",

"iphone/6",

"ipad/6",

"android/4.2"

},

"harness": "mocha",

"files": "test/tests.js―

}

@DOMENIC

Page 35: Client-Side Packages

browserify.org/search

@DOMENIC

Page 37: Client-Side Packages

WHAT’S NEXT› Make sure all your code is in

CommonJS module format.

› Then organize that code into packages.

› Consume other peoples packages.

› Start publishing to npm—or to a private registry.

› Use browserify in your build process (or in your server).

› Use testling-ci to encourage trust and discovery.

@DOMENIC