A humble but ambitious clone of Slack, the chat app, using React, Flux, and Firebase.
git clone https://github.com/rorysaur/quack.git
npm install
Some sort of Sass mixin library we may use more of in the future.
Sass grid framework.
We use Firebase's generic web client; we don't use ReactFire because it would not help us learn Flux, but also because it would make our codebase less backend-agnostic.
Unidirectional data flow. We use Facebook's notorious library only for the Dispatcher.
An polyfill/ponyfill for ES6's Object.assign.
I guess it's like Underscore's extend
. We'll probably remove this library when we move to ES6.
A JavaScript library for building user interfaces.
Lets you load dependencies with npm's require()
and module.exports
system, but on the client side.
Concatenates everything into js/bundle.js
.
Lets you use browserify to require libraries that aren't CommonJS-compatible (in other words, they attach themselves to the window
object as a global variable, rather than yielding an exports
object).
A unit testing framework we figure will be friendly with Flux and React, seeing as it's also made by Facebook. It's a simple wrapper for Jasmine which changes the require
function to auto-mock everything. This forces you to specify exactly what you're testing with jest.dontMock()
and is great for enforcing modular code and one-way data flow. It will be super obvious your God-object is deeply intertwined with the entire app because you'll have 10 lines of boilerplate telling jest to not mock things.
A browserify transform (a step that can be added to the browserify concatenation process) that transpiles JSX to JavaScript.
Compiles all your SCSS/Sass and lets you require
and browserify it as JavaScript.
We've turned on the auto-inject feature so when you require a scss file sassify will append a link to the compiled stylesheet to document head. Like so.
Watch mode for browserify: it runs browserify and rebuilds bundle.js
every time you save changes to a file in the watched project.
Firebase is a real-time backend platform thing that opens a WebSockets connection with each client. It stores data as JSON objects and its client library provides methods to write, read, and listen for changes to the data.
Firebase has a dashboard/UI for looking at and managing the app's data. Collaborators will be given access to the Quack account.
- A
ref
is a reference to a node in the tree of the app's Firebase data. The highest-level ref you can point to (the root node) is denoted by the project name, "quack", in the Firebase dashboard. Setting a key on this ref adds a child to it. In our codebase, we get a ref for the root level, then derive any needed child refs from the root. A value found atquack --> messages --> bestcohort
would be referred to thus:var messagesRef = FirebaseRef.child('messages/bestcohort')
, where/
indicates nesting. - A
snapshot
is the form in which Firebase sends data. It has its own little API, but we mainly useval()
, to get the POJO.
- set(): Sets a key/value pair.
- push(): Adds an item to a list. Note that Firebase does not use true lists or arrays, because indices are not reliable when you have multiple clients manipulating data simultaneously. Instead, think of
push
asset
but, instead of providing a key, you let Firebase generate one that is unique and based on the current time. We use atoArray
utility function that translates Firebase objects to JavaScript arrays when retrieving data.
- on(): Listens for a Firebase event (something changed, added, removed, etc.), for a given ref and its children, and fires a callback.
- off(): Stops listening for a Firebase event.
- once(): Fetches a snapshot for a given ref, and fires a callback, just once and doesn't listen for further changes.
Below is some example Firebase data to show our planned schema (which is a work in progress). It uses ideas and recommendations from Firebase docs.
The gibberish keys are unique ids, which Firebase automatically generates if you use push
instead of set
(i.e., if you don't provide your own key).
channels-messages are associated by the messages being categorized/bucketed by channel. users-channels is the two-way relationship discussed in the link above. users-messages are associated by each message having a reference to the user id.
users: {
rorysaur: {
name: "rory", // this the display name and can be changed with /nick
channels: {
bestcohort: true // this is an "index" as recommended by firebase docs
}
},
jacknoble: {
name: "jack",
channels: {
bestcohort: true
}
}
},
channels: {
bestcohort: {
users: {
wkejfak: "rorysaur", // rory's unique id
iasudsd: "jacknoble", // jack's unique id
}
}
},
messages: {
bestcohort: {
qeouqdfue: {
timestamp: 123456789,
text: "hi",
user: "rorysaur"
}
}
}
In one terminal pane, navigate to the project root and run npm start
. This runs the start
script specified in the package.json
, which watches for any changes in the js/
directory, transpiles JSX to JavaScript, and concatenates everything into js/bundle.js
.
In another terminal pane, navigate to the project root and boot up a server for index.html
. I use python -m SimpleHTTPServer
, then visit localhost:8000
in my browser.
You should be able to save changes to the js/
directory, then refresh in the browser to see the changes.
Run tests with npm test
.
Tests go in a __tests__
directory in the folder of the module being tested and are suffixed with _test
.
See here for a good explanation of testing Flux with Jest.
- React dev tools for Chrome.
- Firebase dev tools for Chome (called Vulcan).
If you are a collaborator, branch from master
and create a pull request for each feature. Merge master
when necessary.
We'll use the Issues section for task tracking. Assign an issue to yourself as soon as you start working on it, so that others know to not work on the same feature.
If you are not a collaborator, fork this repo, create a feature branch, and submit a pull request from your fork.