Skip to content

Commit

Permalink
Merge branch 'feature/ssr-improved'
Browse files Browse the repository at this point in the history
# Conflicts:
#	README.md
  • Loading branch information
Jarzka committed May 30, 2020
2 parents 8847af0 + be5a942 commit 4afb236
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 150 deletions.
45 changes: 21 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

# Introduction

stylefy makes it possible to define CSS styles as Clojure data and attach them into HTML elements easily without writing selectors. Styles are converted to CSS on-demand and scoped locally. This makes writing style code easy and maintainable.
stylefy makes it possible to define CSS styles as Clojure data and attach them into HTML elements easily. Styles are converted to CSS on-demand and scoped locally. This makes writing style code easy and maintainable.

While being originally created with the frontend in mind, stylefy now runs on both Web browsers and servers. On the frontend, it is designed to be used along with [Reagent](https://github.com/reagent-project/reagent). stylefy uses [Garden](https://github.com/noprompt/garden) in the background for most of its CSS conversions.

Expand All @@ -33,7 +33,7 @@ While being originally created with the frontend in mind, stylefy now runs on bo
Add dependency:

```clj
[stylefy "2.0.0"]
[stylefy "2.1.0"]
```

# Setup
Expand Down Expand Up @@ -103,8 +103,6 @@ Full example:
(fn [] (html (index)))))
```

If you wish to do something else with the generated CSS, you can create your own execution context. Use the implementation of `query-with-styles` as an example.

# Usage

## Creating & using styles
Expand Down Expand Up @@ -134,7 +132,7 @@ To use it in a component, use the **use-style** function:
text])
```

On the frontend, **use-style** adds the style into the DOM as a CSS class and returns its class name (see FAQ for more details). On the server, it saves the generated CSS into the current execution context and returns a class name.
On the frontend, **use-style** adds the style into the DOM as a CSS class on-demand (see "How it works" for more details). On the server, it returns a class name pointing to the generated CSS code.

### Passing styles to components

Expand Down Expand Up @@ -379,9 +377,9 @@ Alternative syntax:
[bs-navbar-item 3 active-index "Four"]])))
```

## Font-face (frontend only)
## Font-face

Call **stylefy/font-face** and the given font-face is added into the DOM as CSS code asynchronously.
Call **stylefy/font-face** and the given font-face is added into the DOM as CSS code asynchronously (frontend) or into the current execution context (backend).

```clojure
(stylefy/font-face {:font-family "open_sans"
Expand All @@ -390,9 +388,9 @@ Call **stylefy/font-face** and the given font-face is added into the DOM as CSS
:font-style "normal"})
```

## Keyframes (frontend only)
## Keyframes

Call **stylefy/keyframes** and the given keyframes are added into the DOM as CSS code asynchronously.
Call **stylefy/keyframes** and the given keyframes are added into the DOM as CSS code asynchronously (frontend) or into the current execution context (backend).

```clojure
(stylefy/keyframes "simple-animation"
Expand All @@ -407,31 +405,30 @@ Call **stylefy/keyframes** and the given keyframes are added into the DOM as CSS
:animation-iteration-count "infinite"}))
```

## Custom tag styles (frontend only)
## Custom class names

Call **stylefy/keyframes** and the given tag style is added into the DOM as CSS code asynchronously.
As has been told, stylefy converts style definition to unique CSS classes automatically and there is no need to worry about class names. It can, however, be useful to be able to generate custom named classes for example when working with 3rd party libraries / frameworks. For this purpose, call **stylefy/class**:

```clojure
;; This generates a CSS tag selector and style for "body" element
(def body-style
{:background-color :lightyellow
:padding :5px})
;; This generates a CSS class with the name "background-transition" and adds it into the DOM asynchronously (frontend) or into the current execution context (backend).
(stylefy/class "background-transition"
{:transition "background-color 1s"})

(stylefy/tag "body" body-style)
;; Use the generated class in a component like any other class
[:div.background-transition]
```

## Custom tag styles (frontend only)

## Custom class names (frontend only)

As has been told, stylefy converts style maps to unique CSS classes automatically and there is no need to create classes manually. It can, however, be useful to be able to generate custom named classes for example when working with 3rd party libraries / frameworks. For this purpose, call **stylefy/class**, which generates a new CSS class and adds it into the DOM asynchronously.
You can generate styles for HTML tags by calling **stylefy/tag**:

```clojure
;; This generates a CSS class with the name "background-transition" and adds it into the DOM.
(stylefy/class "background-transition"
{:transition "background-color 1s"})
;; This generates a CSS tag selector and style for "body" element and adds it into the DOM asynchronously (frontend) or into the current execution context (backend).
(def body-style
{:background-color :lightyellow
:padding :5px})

;; Use the generated class in a component like any other class
[:div.background-transition]
(stylefy/tag "body" body-style)
```

## Manual mode
Expand Down
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject stylefy "2.0.0"
(defproject stylefy "2.1.0"
:description "Library for styling UI components"
:url "https://github.com/Jarzka/stylefy"
:dependencies [[org.clojure/clojure "1.9.0"]
Expand Down
163 changes: 92 additions & 71 deletions src/cljc/stylefy/core.cljc
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
(ns stylefy.core
(:require [clojure.string :as str]
[stylefy.impl.hashing :as hashing]
[stylefy.impl.styles :as impl-styles]
[garden.core :refer [css]]
[garden.stylesheet :refer [at-media at-keyframes at-font-face]]
[stylefy.impl.conversion :as conversion]
#?(:cljs [stylefy.impl.dom :as dom])
[stylefy.impl.hashing :as hashing]
[stylefy.impl.log :as log]
[stylefy.impl.state :as state]
[stylefy.impl.log :as log]))
[stylefy.impl.styles :as impl-styles]))

(def ^:dynamic css-in-context (atom nil))
#?(:clj (def ^:dynamic css-in-context (atom nil)))

(defn use-style
"Defines a style for a component by converting the given style map in to an unique CSS class,
Expand Down Expand Up @@ -64,7 +67,7 @@
(assert (or (map? options) (nil? options)) (str "Options should be a map or nil, got: " (pr-str options)))
#?(:cljs (impl-styles/use-style! style options dom/save-style!)
:clj (impl-styles/use-style! style options (fn [{:keys [hash css]}]
(swap! css-in-context assoc hash css))))))
(swap! css-in-context assoc-in [:stylefy-classes hash] css))))))


(defn use-sub-style
Expand All @@ -82,7 +85,7 @@
(str "Options should be a map or nil, got: " (pr-str options)))
#?(:cljs (impl-styles/use-sub-style! style sub-style options dom/save-style!)
:clj (impl-styles/use-sub-style! style sub-style options (fn [{:keys [hash css]}]
(swap! css-in-context assoc hash css))))))
(swap! css-in-context assoc-in [:stylefy-classes hash] css))))))

(defn sub-style
"Returns sub-style for a given style."
Expand Down Expand Up @@ -131,6 +134,82 @@
(reset! state/stylefy-initialised? true)
#?(:cljs (dom/update-dom))))

(defn keyframes
"Frontend: Adds the given keyframe definition into the DOM asynchronously.
Backend: Adds the given keyframe definition into the current context.
Identifier is the name of the keyframes.
Frames are given in the same form as Garden accepts them.
Example:
(stylefy/keyframes \"simple-animation\"
[:from
{:opacity 0}]
[:to
{:opacity 1}])"
[identifier & frames]
(assert (string? identifier) (str "Identifier should be string, got: " (pr-str identifier)))
(let [garden-syntax (apply at-keyframes identifier frames)]
#?(:cljs (dom/add-keyframes identifier garden-syntax)
:clj (swap! css-in-context assoc-in [:keyframes identifier] (css garden-syntax)))))

(defn font-face
"Frontend: Adds the given font-face definition into the DOM asynchronously.
Backend: Adds the given font-face definition into the current context.
Properties are given in the same form as Garden accepts them.
Example:
(stylefy/font-face {:font-family \"open_sans\"
:src \"url('../fonts/OpenSans-Regular-webfont.woff') format('woff')\"
:font-weight \"normal\"
:font-style \"normal\"})"
[properties]
(assert (map? properties) (str "Properties should be a map, got: " (pr-str properties)))

(let [garden-syntax (at-font-face properties)]
#?(:cljs (dom/add-font-face garden-syntax)
:clj (swap! css-in-context assoc :font-faces
(conj (:font-faces @css-in-context) (css garden-syntax))))))

(defn tag
"Frontend: Creates a CSS selector for the given tag and properties and adds it into the DOM asynchronously.
Backend: Creates a CSS selector for the given tag and properties and adds it into the current context.
Normally you should let stylefy convert your style maps to unique CSS classes by calling
use-style, instead of creating tag selectors. However, custom tag styles
can be useful for setting styles on base elements, like html or body.
Example:
(stylefy/tag \"code\"
{:background-color \"lightyellow\"})"
[name properties]
(assert (string? name) (str "Tag name should be a string, got: " (pr-str name)))
(assert (map? properties) (str "Properties should be a map, got: " (pr-str properties)))

(let [tag-as-css (conversion/style->css {:props properties :custom-selector name})]
#?(:cljs (dom/add-tag tag-as-css)
:clj (swap! css-in-context assoc-in [:tags name] tag-as-css))))

(defn class
"Frontend: Creates a CSS class with the given name and properties and adds it into the DOM asynchronously.
Backend: Creates a CSS class with the given name and properties and adds it into the the current context.
Normally you should let stylefy convert your style maps to unique CSS classes by calling
use-style. Thus, there is usually no need to create customly named classes when using stylefy,
unless you work with some 3rd party framework.
Example:
(stylefy/class \"enter-transition\"
{:transition \"background-color 2s\"})"
[name properties]
(assert (string? name) (str "Name should be a string, got: " (pr-str name)))
(assert (map? properties) (str "Properties should be a map, got: " (pr-str properties)))

(let [class-as-css (conversion/style->css {:props properties :custom-selector (conversion/class-selector name)})]
#?(:cljs (dom/add-class class-as-css)
:clj (swap! css-in-context assoc-in [:classes name] class-as-css))))

;
; Backend only
;
Expand All @@ -144,76 +223,18 @@
[query]
(binding [stylefy.core/css-in-context (atom nil)]
(let [result (query)
result-with-styles-attached (str/replace
result
#"_stylefy-server-styles-content_"
(apply str (vals @css-in-context)))]
css (str
(apply str (:font-faces @css-in-context))
(apply str (vals (:keyframes @css-in-context)))
(apply str (vals (:tags @css-in-context)))
(apply str (vals (:classes @css-in-context)))
(apply str (vals (:stylefy-classes @css-in-context))))
result-with-styles-attached (str/replace result #"_stylefy-server-styles-content_" css)]
result-with-styles-attached))))

;
; Frontend only
;
#?(:cljs
(defn keyframes
"Adds the given keyframe definition into the DOM asynchronously.
Identifier is the name of the keyframes.
Frames are given in the same form as Garden accepts them.
Example:
(stylefy/keyframes \"simple-animation\"
[:from
{:opacity 0}]
[:to
{:opacity 1}])"
[identifier & frames]
(assert (string? identifier) (str "Identifier should be string, got: " (pr-str identifier)))
(apply dom/add-keyframes identifier frames)))

#?(:cljs
(defn font-face
"Adds the given font-face definition into the DOM asynchronously.
Properties are given in the same form as Garden accepts them.
Example:
(stylefy/font-face {:font-family \"open_sans\"
:src \"url('../fonts/OpenSans-Regular-webfont.woff') format('woff')\"
:font-weight \"normal\"
:font-style \"normal\"})"
[properties]
(assert (map? properties) (str "Properties should be a map, got: " (pr-str properties)))
(dom/add-font-face properties)))

#?(:cljs
(defn tag
"Creates a CSS selector for the given tag and properties and adds it into the DOM asynchronously.
Normally you should let stylefy convert your style maps to unique CSS classes by calling
use-style, instead of creating tag selectors. However, custom tag styles
can be useful for setting styles on base elements, like html or body.
Example:
(stylefy/tag \"code\"
{:background-color \"lightyellow\"})"
[name properties]
(assert (string? name) (str "Tag name should be a string, got: " (pr-str name)))
(assert (map? properties) (str "Properties should be a map, got: " (pr-str properties)))
(dom/add-tag name properties)))

#?(:cljs
(defn class
"Creates a CSS class with the given name and properties and adds it into the DOM asynchronously.
Normally you should let stylefy convert your style maps to unique CSS classes by calling
use-style. Thus, there is usually no need to create customly named classes when using stylefy,
unless you work with some 3rd party framework.
Example:
(stylefy/class \"enter-transition\"
{:transition \"background-color 2s\"})"
[name properties]
(assert (string? name) (str "Name should be a string, got: " (pr-str name)))
(assert (map? properties) (str "Properties should be a map, got: " (pr-str properties)))
(dom/add-class name properties)))

#?(:cljs
(defn prepare-styles
Expand Down
49 changes: 21 additions & 28 deletions src/cljs/stylefy/impl/dom.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@
(when-not @dom-update-requested?
(reset! dom-update-requested? true)
(go
(update-dom)))))
(update-dom))
nil)))

(defn init-multi-instance [{:keys [multi-instance] :as options}]
(let [base-node (:base-node multi-instance)
Expand Down Expand Up @@ -130,30 +131,22 @@
;; itself if the "CSS in DOM" state of this specific style hash is changed.
(boolean @(get @styles-in-dom style-hash)))

(defn add-keyframes [identifier & frames]
(let [garden-definition (apply at-keyframes identifier frames)]
(swap! keyframes-in-use assoc identifier (css garden-definition))
(request-asynchronous-dom-update)
garden-definition))

(defn add-font-face [properties]
(let [garden-definition (at-font-face properties)]
(swap! font-faces-in-use conj {::css (css garden-definition)})
(request-asynchronous-dom-update)
garden-definition))

(defn add-tag [name properties]
(let [custom-tag-definition {::tag-name name ::tag-properties properties}]
(swap! custom-tags-in-use conj {::css (conversion/style->css
{:props (::tag-properties custom-tag-definition)
:custom-selector (::tag-name custom-tag-definition)})})
(request-asynchronous-dom-update)
custom-tag-definition))

(defn add-class [name properties]
(let [custom-class-definition {::class-name name ::class-properties properties}]
(swap! custom-classes-in-use conj {::css (conversion/style->css
{:props (::class-properties custom-class-definition)
:custom-selector (conversion/class-selector (::class-name custom-class-definition))})})
(request-asynchronous-dom-update)
custom-class-definition))
(defn add-keyframes [identifier garden-syntax]
(swap! keyframes-in-use assoc identifier (css garden-syntax))
(request-asynchronous-dom-update)
nil)

(defn add-font-face [garden-syntax]
(swap! font-faces-in-use conj {::css (css garden-syntax)})
(request-asynchronous-dom-update)
nil)

(defn add-tag [tag-css]
(swap! custom-tags-in-use conj {::css tag-css})
(request-asynchronous-dom-update)
nil)

(defn add-class [class-as-css]
(swap! custom-classes-in-use conj {::css class-as-css})
(request-asynchronous-dom-update)
nil)
Loading

0 comments on commit 4afb236

Please sign in to comment.