We use Webpack to automate build tasks related to both CSS and JS and NodeJS v6.9.2 (we recommend to use NVM).
Install dependencies using a normal npm install as such:
npm install
Then you can run the project with:
npm start
That enables CSS and JS watchers for rebuilding bundles automatically upon changes.
Note! Make sure config/app_config.yml
doesn't contain the app_assets
configuration, i.e.:
# Make sure the following lines are removed, or commented like this:
# app_assets:
# asset_host: '//cartodb-libs.global.ssl.fastly.net/cartodbui'
Don't forget to restart Rails after you have modified config/app_config.yml
.
This is a list of available tasks to run:
Task | Description |
---|---|
npm start |
Compiles carto-node , the static pages and watches Builder and Dashboard |
npm run dev |
Runs webpack for Builder and Dashboard |
npm run dev:static |
Runs webpack for static pages |
npm run dev:editor |
Runs Editor for development |
npm run dev:do-catalog |
Runs Data Observatory catalog for development |
npm run build |
Create production builds for Builder and Dashboard |
npm run build:static |
Create production builds for static pages |
npm run build:do-catalog |
Create production builds for Data Observatory catalog |
npm run carto-node |
Create production builds for carto-node |
npm run test |
Run all test suites |
npm run test:builder |
Run and watches builder test suites |
npm run test:dashboard |
Run and watches dashboard test suites |
npm run test:editor |
Run and watches editor test suites |
npm run lint |
Runs the Javascript linter |
npm run lint:fix |
Runs the Javascript linter with the --fix flag |
npm run lint:css |
Runs the CSS linter |
npm run bump |
Creates a patch version |
npm run bump:major |
Creates a major version |
npm run bump:minor |
Creates a minor version |
npm run update-internal-deps |
Update the package-lock file |
npm run ci |
Runs the CSS lint and tests |
There are several rules you should follow when creating a new pull request:
- Title has to be descriptive. If you are fixing a bug don't use the ticket title or number.
- Explain what you have achieved in the description. If you change something related with the UI of the application add an image or an animation (LiceCap is awesome) about the feature you have just implemented. Or show the change against what it is already done.
- Add acceptance instructions, they're really useful for the person who tests it.
- Update
NEWS.md
file with a description about the task, and the issue number. - CSS and JS linters must pass.
- Every new feature (as well as bug fixes) must come with a test case.
- All tests must pass (see Testing).
- You can see an example of a pull request here.
CARTO is built on top of CARTO.js, which in turns depends on some common libraries, in particular worth mentioning:
Source code is located at lib/assets/javascripts
, dependencies at vendor/assets/javascripts
.
We use semistandard style guide for syntax consistency, it's checked as part of test run.
It's recommended to use it in your IDE, you can either use a Semistandard plugin or a ESLint plugin.
We use dangling underscores to mark a method as "private", so we know it's only used there. For example _showTooltip
.
To keep the code organised we initialise all events in the _initBinds
function, which is usually called in the constructor
.
When using on
or bind
methods, we have to use add_related_model
so when the view is removed, the binding is removed too. We won't need to do it if we're using listenTo
or the model is this.model
.
// Example with listenTo
_initBinds: function () {
this.listenTo(this.model, 'change:show', this._onShowChange);
this.listenTo(this.model, 'destroy', this._onDestroy);
},
// Example with on and add_related_model
_initBinds: function () {
this._stateModel.on('change:status', this.render, this);
this.add_related_model(this._stateModel);
},
If you want to create a custom event, remember to create it in camelCase
and using the action in past simple, example:
this.model.trigger('sendMessage', 'Hello there!');
this.listenTo(this.model, 'sendMessage', message => console.log(message));
The render method should be the first in the view after the initialize.
The test should always have the toHaveNoLeaks
test.
You can add a label to help you relate where the view belongs in the code. Use the attribute module
in any view and you'll see a data attribute in the HTML element when in development node. more info
If we have to initialise multiple views inside another view, we use the _initViews
function as same as we do with events, to keep the code organised.
_initViews: function () {
// Create view
this._firstView = new FirstView({
model: this.model,
});
// Render it
this.$('.js-first').append(this._firstView.render().el);
// Add as subview to the current view
this.addView(this._firstView);
// ... more views here
},
We use SASS, with .scss format, which are located at assets/stylesheets
. Webpack is used to compile the files into .css
files.
Also CARTO makes use of a linter machine for checking possible errors in those stylesheets. Rules are specified in the scss-style.yml file.
We use Stylelint with the standard config and the property sort ordering based on the SMACSS methodology.
- All new elements added in this CartoAssets repository should have included a CDB- namespace.
- Don't create default styles for common elements (e.g.
input { padding: 10px 0 }
). It will make more difficult edit styles for the future custom elements and the !important use will grow. - Avoid creating new classes with only one attribute (e.g.
.marginRight { margin-right: 10px }
). It is impossible to manage the amount of cases we would like to cover.
The component's name must be written in camel case:
.MyComponent {}
A component modifier is a class that modifies the presentation of the base component in some form.
- Modifier names must be written in “camelCase” and be separated from the component name by two hyphens.
- The class should be included in the HTML in addition to the base component class.
.Button {}
.Button--small {}
A component descendent is a class that is attached to a descendent node of a component. It's responsible for applying presentation directly to the descendent on behalf of a particular component. Descendent names must be written in camel case.
.Card {}
.Card-header {}
.Card-footer {}
.Card-fullWidth {}
Use is-stateName
to reflect changes to a component's state, the state name must be camel case.
Never style these classes directly, they should always be used as an adjoining class, this means that the same state names can be used in multiple contexts, but every component must define its own styles for the state (as they are scoped to the component).
.Dropdown {}
.Dropdown.is-open {}
.Dropdown.is-disabled {}
JavaScript-specific classes reduce the risk that changing the structure or theme of components will inadvertently affect any required JavaScript behaviour and complex functionality. It is not necessary to use them in every case, just think of them as a tool in your utility belt. If you are creating a class, which you dont intend to use for styling, but instead only as a selector in JavaScript, you should probably be adding the js-
prefix. In practice this looks like this:
<button class="Button Button--primary js-delete">Delete</button>
JavaScript-specific classes should not, under any circumstances, be styled.
We use Jasmine 2.5.2 as test framework.
You can find the spec files in:
lib/assets/test/spec/(dashboard|builder|carto-node|deep-insights)
To start specs development type the next command:
# Builder specs
npm run test:builder
# Dashboard specs
npm run test:dashboard
You can optionally provide an argument to grunt to filter what specs will be generated, like this:
npm run test:builder -- --match=dropdown
After building the whole suite for the first time, a web server will be started on port 8088 and the spec runner webpage will show up. If you need to use a different port, change the port & URL values on the connect task
The process will watch changes in the codebase and will regenerate the specs as needed. Just refresh the Jasmine page to pass the tests again.
If you only want to run a subset of tests the easiest and fastest way is to use focused specs, but you can also append ?spec=str-matching-a-describe
to test URL, or use --filter flag if running tests in a terminal.
There are some views that can be served from a static file in public/static/
directory and must be built beforehand. For that purpose run the following command:
npm run build:static
Follow these steps to update to get latest changes:
- go to
lib/assets/javascripts/cdb/
git checkout v3 && git pull
- go back to root and run
grunt cdb
- commit both the new revision of the submodule and the generated file
vendor/assets/javascripts/cartodb.uncompressed.js
In order to develop tests for the codebase outside Builder (that is, old Editor and dashboard pages) we advise to run:
npm run test:editor
After the building process finish, a webpage will show up with a link to the Jasmine page with all the specs. The URL of this page is http://localhost:8089/_SpecRunner.html
Then, the process will watch changes in the codebase and will regenerate the specs as needed. Just refresh the Jasmine page to pass the tests again.
Run specs and regular codebase simultaneously
If you want to run simultaneously the application and the specs generation follow these steps:
-
Open a terminal with Node v6.9.2 (use nvm) and run
grunt editor
. This will build the application assets and will watch for changes. -
Open a second terminal and run
grunt affected_editor_specs
. -
That's it. When you change any Builder Javascript file
grunt editor
will build the application bundle andgrunt affected_editor_specs
will build the specs.