• Stars
    star
    284
  • Rank 140,669 (Top 3 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 8 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

CSS Modules for ambitious applications

ember-css-modules Actions Status Ember Observer Score

Ember-flavored support for CSS Modules. For an overview of some of the motivations for the CSS Modules concept, see this blog post.

If you have ideas or questions that aren't addressed here, try #topic-css on the Ember Discord Server.

Installation

ember install ember-css-modules

What and Why?

When you build a component, you drop a .js file and a .hbs file in your app directory, and your tooling takes care of the rest. Babel takes your fancy ES6 module and restructures it into nice browser-friendly code, while still giving you isolation and modularity guarantees. Meanwhile, the rest of the Ember CLI build pipeline picks up these new files and automatically incorporates them into your final JS artifact. And when it's time to come back and tweak your component, you (just like the Ember resolver) know exactly where those files live based just on the name of the component.

With ember-css-modules, your styling is a first-class citizen alongside your templates and JavaScript. You have one .css file per component (or route controller), in the same structure you're already using in the rest of your app. Every class you write is local to that file by default, with explicit mechanisms for opting into sharing, just like your JavaScript modules. And just like all your JS modules are automatically included in <app-name>.js, your CSS modules will automatically be included in <app-name>.css.

Usage

Simple Example

With ember-css-modules, you define styles on a per-component (or -controller) basis. You define these styles using the same file layout you use for templates; for example, in pod structure you'd put styles.css alongside template.hbs in the component's pod. The classes in that stylesheet are then automatically namespaced to the corresponding component or controller. In order to reference them, you use the local-class attribute rather than the standard class.

{{! app/components/my-component/template.hbs }}
<div local-class="hello-class">Hello, world!</div>
/* app/components/my-component/styles.css */
.hello-class {
  font-weight: bold;
}

Similarly, if you were styling e.g. your application controller, you would place your styles alongside controller.js in <podModulePrefix>/application/styles.css.

Component Colocation Example

In Octane apps, where component templates can be colocated with their backing class, your styles module for a component takes the same name as the backing class and template files:

{{! app/components/my-component.hbs }}
<div local-class="hello-class">Hello, world!</div>
/* app/components/my-component.css */
.hello-class {
  font-weight: bold;
}

Styling Reuse

In the example above, hello-class is rewritten internally to something like _hello-class_1dr4n4 to ensure it doesn't conflict with a hello-class defined in some other module.

For cases where class reuse is desired, there's the composes property. Suppose you have a title in your component that you'd like to inherit your app-wide "secondary header" styling, which itself uses generic styling shared by all headers:

/* app/styles/headers.css */
.header {
  font-weight: bold;
  text-decoration: underline;
}

.secondary-header {
  composes: header;
  color: #339;
}
/* app/components/my-component/styles.css */
.component-title {
  composes: secondary-header from 'my-app-name/styles/headers';
  background-color: #eee;
}

In the template for my-component, an element with local-class="component-title" will end up with an actual class string like _component-title_1dr4n4 _secondary-header_1658xu _header_1658xu, incorporating styles from all of the composing classes.

Note that you may also use relative paths to specify the source modules for composition.

Finally, you can compose local classes from global un-namespaced ones that are provided e.g. by a CSS framework by specifying global as the source of the class:

/* vendor/some-lib.css */
.super-important {
  color: orange;
}
/* app/components/my-component/styles.css */
.special-button {
  composes: super-important from global;
}

Programmatic Styles Access

Currently the local-class attribute is honored on HTML elements and component invocations, e.g. <div local-class="foo {{bar}}"> and {{input local-class="baz"}}. It is not (currently) supported in subexpressions like the (component) helper.

If you need to access a local class in a template in other scenarios (such as passing in a class name as a property or reusing a class from another module), there is also a local-class helper you can use. For example, the "secondary-header" example above can be written as:

{{! app/components/my-component/template.hbs }}
<div class="{{local-class 'secondary-header' from='my-app-name/styles/headers'}}">Hello, world!</div>

Note that the from parameter is optional; by default classes will come from the current module, as with the local-class attribute.

In a JavaScript context, the class mappings can also be imported directly from whatever path the corresponding CSS module occupies, e.g.

import styles from 'my-app-name/components/my-component/styles';
console.log(styles['hello-class']);
// => "_hello-class_1dr4n4"

Note: by default, the import path for a styles module does not include the .css (or equivalent) extension. However, if you set includeExtensionInModulePath: true, then you'd instead write:

import styles from 'my-app-name/components/my-component/styles.css';

Note that the extension is always included for styles modules that are part of an Octane "colocated" component, to avoid a conflict with the import path for the component itself.

Global Classes

Some libraries provide explicit class names as part of their public interface in order to allow customization of their look and feel. If, for example, you're wrapping such a library in a component, you need to be able to reference those unscoped class names in the context of your component styles. The :global pseudoselector allows for this:

.my-component :global(.some-library-class) {
  color: orange;
}

For more details on :local and :global exceptions, see the CSS Modules documentation.

Values

For exposing data other than class names across module boundaries, you can use @value.

/* app/styles/colors.css */
@value primary-color: #8af;
@value secondary-color: #fc0;
/* app/some-route-pod/styles.css */
@value primary-color, secondary-color from 'my-app-name/styles/colors';

.blurb {
  color: primary-color;
  background-color: secondary-color;
}

Note that values are also exposed on the styles object for a given module, so they are also accessible from JavaScript if you need to coordinate between the two. As a contrived example:

// app/some-route-pod/controller.js
import styles from 'app/some-route-pod/styles';

export default Ember.Controller.extend({
  logColor() {
    console.log('primary color is', styles['primary-color']);
  }
});

Usage in Addons

You can also use ember-css-modules in addons that expose components to their consuming application. To do this you'll need to move ember-css-modules out of devDependencies and into dependencies in your addon's package.json (see issue #8).

Note also that your addon must have an addon/styles directory in order to trigger CSS processing in Ember CLI. In order for the directory to be preserved when you publish your addon, you can create an empty .placeholder file (.gitkeep won't work; by default, the .npmignore for your addon will prevent files with that name from being published).

Plugins

Ember CSS Modules has a plugin ecosystem that allows for people to bundle up common configurations and extensions for easy reuse and distribution. For example, if your organization has a common set of PostCSS plugins you always use, you could package those as a plugin and then just drop that into any Ember project and have it automatically take effect.

For details on developing your own, see the plugins mini-guide. You can also look at the following examples of what plugin implementations can look like:

You can find a list of all publicly available plugins by browsing the npm ember-css-modules-plugin keyword.

Advanced Configuration

Details about specific advanced configuration options are broken out into smaller mini-guides that each focus on a single topic:

Where to Specify Options

For applications, custom configuration for ember-css-modules may be specified in ember-cli-build.js:

new EmberApp(defaults, {
  // ...
  cssModules: {
    // config
  }
});

For addons, configuration may be specified in your addon's index.js instead:

module.exports = {
  // ...
  options: {
    cssModules: {
      // config
    }
  }
};

Extensions in Module Paths

When importing a CSS module's values from JS, or referencing it via @value or composes:, by default you do not include the .css extension in the import path. The exception to this rule is for modules that are part of an Octane-style colocated component, as the extension is the only thing to differentiate the styles module from the component module itself.

If you wish to enable this behavior for all modules, you can set the includeExtensionInModulePath flag in your configuration:

new EmberApp(defaults, {
  cssModules: {
    includeExtensionInModulePath: true,
  },
});

Scoped Name Generation

By default, ember-css-modules produces a unique scoped name for each class in a module by combining the original class name with a hash of the path of the containing module. You can override this behavior by passing a generateScopedName function in the configuration.

new EmberApp(defaults, {
  cssModules: {
    generateScopedName(className, modulePath) {
      // Your logic here
    }
  }
});

Note that addons may specify their own generateScopedName function, but otherwise they will fall back to using the one (if any) specified by the host application.

Source Maps

Ember CLI allows you to specify source map settings for your entire build process, and ember-css-modules will honor that configuration. For instance, to enable source maps in all environments for both JS and CSS files, you could put the following in your ember-cli-build.js:

sourcemaps: {
  enabled: true,
  extensions: ['js', 'css']
}

Notes

  • You should specify the css extension in your source map configuration even if you're using a different extension for your modules themselves, since the final output file will be a .css file.
  • Currently CSS source maps (for any Ember CLI preprocessor) only work for applications, not for addons. Watch ember-cli/broccoli-concat#58 for progress on that front.
  • Enabling source maps for CSS can cause Ember CLI to output an invalid comment at the end of your vendor.css file. This is harmless in many situations, but can cause issues with tools that postprocess your css, like ember-cli-autoprefixer. ember-cli/broccoli-concat#58 is the root cause of this issue as well.

Ember Support

This addon is tested against and expected to work with Ember's active LTS releases as well as the current release, beta, and canary builds.

More Repositories

1

goldiloader

Just the right amount of Rails eager loading
Ruby
1,518
star
2

jsonstreamingparser

A JSON streaming parser implementation in PHP.
PHP
718
star
3

safer_rails_console

Make rails console less dangerous!
Ruby
141
star
4

avro-builder

Ruby DSL to create Avro schemas
Ruby
102
star
5

avro-schema-registry

Implementation of the Confluent Schema Registry API as a Rails application
Ruby
87
star
6

avromatic

Generate Ruby models from Avro schemas
Ruby
85
star
7

offline-sort

A Ruby gem to sort large amounts of data using a predictable amount of memory.
Ruby
84
star
8

ember-cli-dependency-lint

Lint your app's addon dependencies, making sure you only have one version of each.
JavaScript
83
star
9

action-detect-and-tag-new-version

A GitHub action to detect and tag new versions of a repo based on changes to its contents
TypeScript
57
star
10

ember-cli-pact

Contract testing with Ember.js and Pact
JavaScript
42
star
11

omniauth-multi-provider

OmniAuth support for multiple providers of an authentication strategy
Ruby
42
star
12

rails-multitenant

Ruby
37
star
13

ember-debug-logger

An Ember addon for attaching debug logging to container-managed objects
JavaScript
37
star
14

delayed_job_worker_pool

Worker process pooling for Delayed Job
Ruby
35
star
15

botanist

A JavaScript DSL for traversing and transforming data based on structural rules
TypeScript
26
star
16

delayed_job_groups_plugin

Job groups for delayed_job - http://www.salsify.com/blog/adding-job-groups-to-delayed-job-in-rails
Ruby
18
star
17

milestones

Tools for finding your way through async code
TypeScript
15
star
18

ember-exclaim

An addon allowing apps to expose declarative, JSON-configurable custom UIs backed by Ember components
JavaScript
14
star
19

arc-furnace

Need to melt, weave, and meld information together? Arc furnace will fuse anything you've got.
Ruby
14
star
20

omniauth-multi-provider-saml

An extension to omniauth-saml for handling multiple identity providers
Ruby
14
star
21

delayed_job_heartbeat_plugin

Delayed::Job plugin to unlock jobs from dead workers
Ruby
12
star
22

ember-cli-sticky

JavaScript
11
star
23

postgres-vacuum-monitor

Simple stats collector for postgres auto vacuumer and long running queries
Ruby
8
star
24

broccoli-css-modules

A broccoli plugin for compiling modular CSS
JavaScript
8
star
25

avro-patches

Patches to the official Apache Avro ruby implementation
Ruby
6
star
26

salsify_rubocop

Salsify shared RuboCop configuration and experimental cops
Ruby
5
star
27

broccoli-gzip

Broccoli plugin to apply gzip compression to trees
JavaScript
4
star
28

elasticsearch-proxy

Ruby
2
star
29

activerecord-forbid_implicit_connection_checkout

Optionally prevent threads from checking out out an ActiveRecord connection
Ruby
2
star
30

logstash-codec-avro-data-file

Logstash codec for parsing Avro Data Files
Ruby
2
star
31

salsify-to-4-tell

Example project showing how to run a service for free on Heroku that takes data published from Salsify and pushes it to another service, in this case 4-Tell.
PHP
2
star
32

salsify-gtin

Validates and converts GTIN variants to standardized GTIN-14 representation
Ruby
2
star
33

multipartuploader

Small PHP library to make sending multipart uploads a little less painful.
PHP
2
star
34

zzz-test-commissioner

A CircleCI test failure aggregator and analysis tool
Ruby
2
star
35

avro_schema_registry-client

Client for the the avro-schema-registry app
Ruby
1
star
36

heroku_rails_deploy

Simple script for deploying a Rails project to Heroku
Ruby
1
star
37

tree_reject

Remove deeply nested keys from hash.
Ruby
1
star
38

delayed_job_chainable_hooks

Implement DelayedJob lifecyle hook methods without overriding previous definitions
Ruby
1
star
39

alexa-app

JavaScript
1
star
40

thrifty_charlock_holmes

A charlock holmes decidedly trimmer, and lacking in history
Ruby
1
star
41

customer-success-interview

1
star
42

ruby-exclaim

Exclaim UI processor for Ruby
Ruby
1
star
43

html-lambda-cli

Command line interface for creating HTML lambda's
JavaScript
1
star