css_modules 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []