css_modules 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 431374aa2a26649b2dac684cf81efe66e419c84c
4
+ data.tar.gz: 2dffe66a072a52b0c60074940be0774a6513e7a1
5
+ SHA512:
6
+ metadata.gz: b89042953316ab3a5b1b17c7c46eb5d2882e529415f5fb7a1c5247d3628ca1cd11aa796b09c9d0318c89578fe384a896f014d124f3f5616d07047b073fcc4c5f
7
+ data.tar.gz: 9c6387bfb0e5b68e7f5879c2999e4cd994c1b3ddfa03449ac18b5213f276ca7bff7f6c4abe9227732f8430134f68a59d2db39673594b9128fc2f992d3656dc5e
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Robert Mosolgo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # CSSModules
2
+
3
+ An alternative to "magic string" classnames in Sass or SCSS. Currently supports Sprockets 2 only 😖.
4
+
5
+ Thanks to [Fatih Kadir Akın](https://twitter.com/fkadev) for his post, ["How I Implemented CSS Modules in Ruby on Rails, Easily"](https://medium.com/@fkadev/how-i-implemented-css-modules-to-ruby-on-rails-easily-abb324ce22d), which led the way on this idea!
6
+
7
+ ## Usage
8
+
9
+ ### Add modules to stylesheets
10
+
11
+ Your `.sass` or `.scss` stylesheets can define modules with `:module(module_name)`:
12
+
13
+ ```scss
14
+ :module(events) {
15
+ .header {
16
+ font-style: bold;
17
+ }
18
+
19
+ .link:visited {
20
+ color: purple;
21
+ }
22
+
23
+ #footer {
24
+ font-size: 0.8em;
25
+ }
26
+ }
27
+ ```
28
+
29
+ Sass requires an extra `\`:
30
+
31
+ ```sass
32
+ \:module(events)
33
+ .header
34
+ font-style: bold
35
+ ```
36
+
37
+ ### Put modulized names into HTML
38
+
39
+ To access the contents of a module in a view, you must include the helpers in your controller:
40
+
41
+ ```ruby
42
+ class EventsController < ApplicationController
43
+ helper CSSModules::ViewHelper
44
+ end
45
+ ```
46
+
47
+ (To use the view helper _everywhere_, include it in `ApplicationController`.)
48
+
49
+ Then, in your view, you can access the module & its contents by name:
50
+
51
+ ```erb
52
+ <!-- access by module + identifier -->
53
+ <h1 id="<%= css_module("events", "main_header") %>">
54
+ Events
55
+ </h1>
56
+
57
+ <!-- block helper -->
58
+ <% css_module("events") do |events_module| %>
59
+ <div id="<%= events_module.selector("footer") %>">
60
+ <%= link_to "Home", "/", class: events_module.selector("link") %>
61
+ © My company
62
+ </div>
63
+ <% end %>
64
+ ```
65
+
66
+ ### Use modulized names in JavaScript
67
+
68
+ In JavaScript, you can include a helper to access module styles:
69
+
70
+ ```jsx
71
+ //= require css_module
72
+
73
+ // Module + identifier
74
+ var headerClass = CSSModule("events", "header")
75
+ $("." + headerClass).text() // => "Events"
76
+
77
+ // Or module helper function
78
+ var eventsModule = CSSModule("events")
79
+
80
+ function header() {
81
+ var headerClass = eventsModule("header")
82
+ return (
83
+ <h1 className={headerClass}>Events</h1>
84
+ )
85
+ }
86
+ ```
87
+
88
+ `CSSModule` requires the global JS function `btoa`. To include a polyfill from this gem, add:
89
+
90
+ ```js
91
+ //= require base64
92
+ ```
93
+
94
+ ## Installation
95
+
96
+ Add this line to your application's Gemfile:
97
+
98
+ ```ruby
99
+ gem 'css_modules'
100
+ ```
101
+
102
+ And then execute:
103
+ ```bash
104
+ $ bundle
105
+ ```
106
+
107
+ Or install it yourself as:
108
+ ```bash
109
+ $ gem install css_modules
110
+ ```
111
+
112
+ ## TODO
113
+
114
+ - Support minified identifiers for Production Env
115
+ - Dead code warning for Development env:
116
+ - Warn when not all styles are used?
117
+ - Sprockets require CSS to JS? `require_styles` ?
118
+ - Support plain `.css`
119
+ - Use Sass's built-in parser?
120
+ - Support Sprockets 3+
121
+
122
+ ## License
123
+
124
+ [MIT](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'bundler/gem_tasks'
8
+
9
+ require 'rake/testtask'
10
+
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ t.warning = false
17
+ end
18
+
19
+
20
+ task default: :test
@@ -0,0 +1,136 @@
1
+ // btoa polyfill, from https://jsfiddle.net/gabrieleromanato/qaght/
2
+ var Base64 = {
3
+
4
+
5
+ _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
6
+
7
+
8
+ encode: function(input) {
9
+ var output = "";
10
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
11
+ var i = 0;
12
+
13
+ input = Base64._utf8_encode(input);
14
+
15
+ while (i < input.length) {
16
+
17
+ chr1 = input.charCodeAt(i++);
18
+ chr2 = input.charCodeAt(i++);
19
+ chr3 = input.charCodeAt(i++);
20
+
21
+ enc1 = chr1 >> 2;
22
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
23
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
24
+ enc4 = chr3 & 63;
25
+
26
+ if (isNaN(chr2)) {
27
+ enc3 = enc4 = 64;
28
+ } else if (isNaN(chr3)) {
29
+ enc4 = 64;
30
+ }
31
+
32
+ output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
33
+
34
+ }
35
+
36
+ return output;
37
+ },
38
+
39
+
40
+ decode: function(input) {
41
+ var output = "";
42
+ var chr1, chr2, chr3;
43
+ var enc1, enc2, enc3, enc4;
44
+ var i = 0;
45
+
46
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
47
+
48
+ while (i < input.length) {
49
+
50
+ enc1 = this._keyStr.indexOf(input.charAt(i++));
51
+ enc2 = this._keyStr.indexOf(input.charAt(i++));
52
+ enc3 = this._keyStr.indexOf(input.charAt(i++));
53
+ enc4 = this._keyStr.indexOf(input.charAt(i++));
54
+
55
+ chr1 = (enc1 << 2) | (enc2 >> 4);
56
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
57
+ chr3 = ((enc3 & 3) << 6) | enc4;
58
+
59
+ output = output + String.fromCharCode(chr1);
60
+
61
+ if (enc3 != 64) {
62
+ output = output + String.fromCharCode(chr2);
63
+ }
64
+ if (enc4 != 64) {
65
+ output = output + String.fromCharCode(chr3);
66
+ }
67
+
68
+ }
69
+
70
+ output = Base64._utf8_decode(output);
71
+
72
+ return output;
73
+
74
+ },
75
+
76
+ _utf8_encode: function(string) {
77
+ string = string.replace(/\r\n/g, "\n");
78
+ var utftext = "";
79
+
80
+ for (var n = 0; n < string.length; n++) {
81
+
82
+ var c = string.charCodeAt(n);
83
+
84
+ if (c < 128) {
85
+ utftext += String.fromCharCode(c);
86
+ }
87
+ else if ((c > 127) && (c < 2048)) {
88
+ utftext += String.fromCharCode((c >> 6) | 192);
89
+ utftext += String.fromCharCode((c & 63) | 128);
90
+ }
91
+ else {
92
+ utftext += String.fromCharCode((c >> 12) | 224);
93
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
94
+ utftext += String.fromCharCode((c & 63) | 128);
95
+ }
96
+
97
+ }
98
+
99
+ return utftext;
100
+ },
101
+
102
+ _utf8_decode: function(utftext) {
103
+ var string = "";
104
+ var i = 0;
105
+ var c = c1 = c2 = 0;
106
+
107
+ while (i < utftext.length) {
108
+
109
+ c = utftext.charCodeAt(i);
110
+
111
+ if (c < 128) {
112
+ string += String.fromCharCode(c);
113
+ i++;
114
+ }
115
+ else if ((c > 191) && (c < 224)) {
116
+ c2 = utftext.charCodeAt(i + 1);
117
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
118
+ i += 2;
119
+ }
120
+ else {
121
+ c2 = utftext.charCodeAt(i + 1);
122
+ c3 = utftext.charCodeAt(i + 2);
123
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
124
+ i += 3;
125
+ }
126
+
127
+ }
128
+
129
+ return string;
130
+ }
131
+
132
+ }
133
+
134
+ if (!btoa) {
135
+ var btoa = function(str) { return Base64.encode(str) }
136
+ }
@@ -0,0 +1,31 @@
1
+ // Usage:
2
+ //
3
+ // - Call with a module name + selector name to get a transformed (opaque) selector
4
+ //
5
+ // ```js
6
+ // CSSModule("events_index", "header")
7
+ // // => "..." (some opaque string that matches the stylesheet)
8
+ // ```
9
+ //
10
+ // - Call with a module name to get a function for modulizing selectors
11
+ //
12
+ // ```js
13
+ // var eventsModule = CSSModule("events_index")
14
+ // var headerSelector = eventsModule("header")
15
+ // var footerSelector = eventsModule("footer")
16
+ // ```
17
+ //
18
+ // This behavior has to match `CSSModules::Rewrite` in Ruby
19
+ // so that generated selectors match.
20
+ function CSSModule(moduleName, selectorName) {
21
+ // This matches `Rewrite`:
22
+ var opaqueString = btoa(moduleName).replace(/[^a-zA-Z0-9]/g, "")
23
+ var transformedModuleName = opaqueString + "_" + moduleName;
24
+ if (selectorName) {
25
+ return transformedModuleName + "_" + selectorName;
26
+ } else {
27
+ return function(selectorName) {
28
+ return transformedModuleName + "_" + selectorName;
29
+ };
30
+ }
31
+ };
@@ -0,0 +1,17 @@
1
+ module CSSModules
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace CSSModules
4
+
5
+ initializer "css_modules.register_preprocessor" do |app|
6
+ # Make css_module.js accessible to Sprockets
7
+ app.config.assets.paths << File.expand_path("../assets", __FILE__)
8
+
9
+ # This is Sprockets 2 only :S
10
+ app.config.assets.configure do |env|
11
+ env.register_postprocessor('text/css', :css_modules) do |context, data|
12
+ CSSModules::Rewrite.rewrite_css(data)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,75 @@
1
+ require "css_parser"
2
+ require "base64"
3
+
4
+ module CSSModules
5
+ module Rewrite
6
+ # Module Scopes
7
+ # :module(login) { .button {color: red;} }
8
+ RE_MODULE = /^\:module\((?<module_name>.*?)\)\s+(?<declarations>.*)/
9
+
10
+ module_function
11
+ # Take css module code as input, and rewrite it as
12
+ # browser-friendly CSS code. Apply opaque transformations
13
+ # so that selectors can only be accessed programatically,
14
+ # not by class name literals.
15
+ def rewrite_css(css_module_code)
16
+ parser = CssParser::Parser.new
17
+ parser.load_string!(css_module_code)
18
+
19
+ rules = ""
20
+
21
+ parser.each_selector do |selector, declarations, specificity|
22
+ prettified_declarations = declarations.gsub(/;\s+/, ";\n ")
23
+ modulized_selector = parse_selector(selector)
24
+ rules << "#{modulized_selector} {\n #{prettified_declarations}\n}\n"
25
+ end
26
+
27
+ rules.strip!
28
+ rules
29
+ end
30
+
31
+ # Combine `module_name` and `selector`, but don't prepend a `.` or `#`
32
+ # because this value will be inserted into the HTML page as `class=` or `id=`
33
+ def modulize_selector(module_name, selector)
34
+ transformed_name = transform_name(module_name)
35
+ "#{transformed_name}_#{selector}"
36
+ end
37
+
38
+ private
39
+ module_function
40
+
41
+ # This parses the selector for `:module` definitions or does nothing.
42
+ def parse_selector(selector)
43
+ matches = RE_MODULE.match(selector)
44
+ if matches.nil?
45
+ selector
46
+ else
47
+ module_name = transform_name(matches[:module_name])
48
+ declaration_parts = matches[:declarations].split(" ")
49
+ declaration_parts
50
+ .map { |declaration_or_operator| rebuild_selector(module_name, declaration_or_operator) }
51
+ .join(" ")
52
+ end
53
+ end
54
+
55
+ # If `selector` is a class or ID, scope it to this module.
56
+ # If it is a bare element, leave it unscoped.
57
+ def rebuild_selector(module_ident, selector)
58
+ case selector[0]
59
+ when "#"
60
+ "##{module_ident}_#{selector[1..-1]}"
61
+ when "."
62
+ ".#{module_ident}_#{selector[1..-1]}"
63
+ else
64
+ selector
65
+ end
66
+ end
67
+
68
+ def transform_name(css_module_name)
69
+ # Some base64 characters aren't valid for CSS (eg, `=`)
70
+ opaque_string = Base64.encode64(css_module_name).gsub(/[^a-zA-Z0-9]/, "")
71
+ # p [css_module_name, opaque_string]
72
+ "#{opaque_string}_#{css_module_name}"
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ module CSSModules
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,56 @@
1
+ module CSSModules
2
+ # Provides helpers to the view layer.
3
+ # Add it to a controller with Rails' `helper` method.
4
+ #
5
+ # @example including ViewHelper in ApplicationController (and therefore all its descendants)
6
+ # class ApplicationController < ActionController::Base
7
+ # helper CSSModules::ViewHelper
8
+ # end
9
+ module ViewHelper
10
+ # @overload css_module(module_name, selector_name)
11
+ # Apply the styles from `module_name` for `selector_name`
12
+ #
13
+ # @example Getting a selector within a module
14
+ # css_module("events_index", "header")
15
+ # # => "..." (opaque string which matches the stylesheet)
16
+ #
17
+ # @param module_name
18
+ # @param selector_name DOM id or class name
19
+ # @return [String] modulized selector name for `class=` or `id=` in a view
20
+ #
21
+ # @overload css_module(module_name, &block)
22
+ # Modulize selectors within a block using the yielded helper.
23
+ #
24
+ # @example modulizing a few selectors
25
+ # <% css_module("events_index") do |events_module| %>
26
+ # <h1 class="<%= events_module.selector("heading") %>">All events</h1>
27
+ # <p id="<%= events_module.selector("description") %>"> ... </p>
28
+ # <% end %>
29
+ #
30
+ # @param module_name
31
+ # @yieldparam [ModuleLookup] a helper for modulizing selectors within `module_name`
32
+ # @return [void]
33
+ def css_module(module_name, selector_name = nil, &block)
34
+ if selector_name.nil? && block_given?
35
+ lookup = ModuleLookup.new(module_name)
36
+ yield(lookup)
37
+ nil
38
+ elsif selector_name.present?
39
+ CSSModules::Rewrite.modulize_selector(module_name.to_s, selector_name.to_s)
40
+ else
41
+ raise("css_module must be called with a module_name and either a selector_name or a block")
42
+ end
43
+ end
44
+
45
+ # Shorthand for getting several classnames from one module
46
+ class ModuleLookup
47
+ def initialize(module_name)
48
+ @module_name = module_name.to_s
49
+ end
50
+
51
+ def selector(selector_name)
52
+ CSSModules::Rewrite.modulize_selector(@module_name, selector_name.to_s)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,4 @@
1
+ require "css_modules/engine"
2
+ require "css_modules/version"
3
+ require "css_modules/rewrite"
4
+ require "css_modules/view_helper"
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: css_modules
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Mosolgo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: css_parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: execjs
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Provides a css-module-like experience to Sass/SCSS, Rails views and JavaScript
70
+ email:
71
+ - rdmosolgo@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - MIT-LICENSE
77
+ - README.md
78
+ - Rakefile
79
+ - lib/css_modules.rb
80
+ - lib/css_modules/assets/base64.js
81
+ - lib/css_modules/assets/css_module.js
82
+ - lib/css_modules/engine.rb
83
+ - lib/css_modules/rewrite.rb
84
+ - lib/css_modules/version.rb
85
+ - lib/css_modules/view_helper.rb
86
+ homepage: https://github.com/rmosolgo/css_modules
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.5.1
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Prevent naming conflicts in Sass stylesheets
110
+ test_files: []