├── .gitignore ├── CHANGES.md ├── README.md ├── circle.yml ├── project.clj ├── resources ├── om-i.css ├── om-i.less └── om-i.min.css └── src └── om_i ├── core.cljs ├── hacks.cljs └── keyboard.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /out/ 6 | /target/ 7 | .lein-deps-sum 8 | .lein-repl-history 9 | .lein-plugins/ 10 | /script/ 11 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 0.1.8 2 | ### Changes 3 | * Stop depending on _rootNodeId so that we can support React 0.13. 4 | 5 | ## 0.1.7 6 | ### Changes 7 | * Fix ordering of render-ms and mount-ms 8 | 9 | ## 0.1.6 10 | ### Changes 11 | * fix borked release to Clojars of 0.1.5 12 | 13 | ## 0.1.5 14 | ### Changes 15 | * Note: this released was messed up somehow, use 0.1.6 instead 16 | * fix un-required goog.string.format. Thanks [@cldwalker](https://github.com/cldwalker)! 17 | 18 | ## 0.1.4 19 | ### Changes 20 | * fixes compilation error from requiring `goog` 21 | 22 | ## 0.1.3 23 | ### Changes 24 | * remove unused cljs-time requires 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Om-instrumentation 2 | 3 | Instrumentation helper for Om applications. 4 | 5 | [![Circle CI](https://circleci.com/gh/PrecursorApp/om-i.svg?style=svg)](https://circleci.com/gh/PrecursorApp/om-i) 6 | 7 | ## Overview 8 | 9 | Om-i (pronounced "Oh, my!") helps you identify the components in your [Om](https://github.com/omcljs/om) application that are being passed too much of your app state and rendering unnecessarily. It provides useful statistics about render times and frequencies for all of your components. 10 | 11 | You can see it live on [Precursor](https://precursorapp.com), a collaborative drawing application. Use Ctrl+Alt+Shift+j to toggle it. 12 | 13 | 14 | 15 | 16 | 17 | ## Setup 18 | 19 | ### Dependencies 20 | Add Om-i to your project's dependencies 21 | 22 | ``` 23 | [precursor/om-i "0.1.8"] 24 | ``` 25 | 26 | ### Enable the instrumentation 27 | 28 | Use Om-i's custom descriptor so that it can gather render times for your components. To enable it globally, use the `:instrument` opt in `om/root` 29 | 30 | ```clojure 31 | (om/root 32 | app-component 33 | app-state 34 | {:target container 35 | :instrument (fn [f cursor m] 36 | (om/build* f cursor 37 | (assoc m 38 | :descriptor om-i.core/instrumentation-methods)))}) 39 | ``` 40 | 41 | ### Mount the component 42 | 43 | Add the following somewhere in your setup code. If you're using figwheel, place it somewhere that won't get reloaded. 44 | 45 | ```clojure 46 | (om-i.core/setup-component-stats!) 47 | ``` 48 | 49 | Om-i renders its statistics in a separate root so that it doesn't interact with your application. 50 | 51 | It will create a `div` in the body with classname "om-instrumentation" by default and assign three keyboard shortcuts: Ctrl+Alt+Shift+j to bring down the statistics menu, Ctrl+Alt+Shift+k to clear the statistics, and Ctrl+Alt+Shift+s to switch the sort order. 52 | 53 | 54 | You can override the defaults with: 55 | 56 | ```clojure 57 | (om-i.core/setup-component-stats! {:class "om-instrumentation" 58 | :clear-shortcut #{"ctrl" "alt" "shift" "k"} 59 | :toggle-shortcut #{"ctrl" "alt" "shift" "j"} 60 | :sort-shorcut #{"ctrl" "alt" "shift" "s"}}) 61 | ``` 62 | 63 | ### Styles 64 | 65 | You need to set up css styles to handle displaying the instrumentation when it's opened. There are sample less and css files in the resources directory. 66 | 67 | If you want to try out Om-i, or just use it in development, we've provided a helper that will embed a style tag with the syles from resources/om-i.min.css. 68 | 69 | ```clojure 70 | (om-i.hacks/insert-styles) 71 | ``` 72 | 73 | It's not recommended to use this in production. 74 | 75 | ### Wrapping a pre-existing descriptor 76 | 77 | If you're already using a custom descriptor, you can still use Om-i. Here's an example wrapping Om's `no-local-descriptor`. 78 | 79 | ```clojure 80 | (let [methods (om-i.core/instrument-methods om/no-local-state-methods) 81 | descriptor (om/no-local-descriptor methods)] 82 | (om/root 83 | app-component 84 | app-state 85 | {:target container 86 | :instrument (fn [f cursor m] 87 | (om/build* f cursor (assoc m :descriptor descriptor)))})) 88 | ``` 89 | 90 | ## Acknowledgements 91 | 92 | Thanks to [@sgrove](https://github.com/sgrove) for his keyboard handling code. Om-i uses a minimal version of the code he wrote for Precursor. There is an older, [public version of the code in Omchaya](https://github.com/sgrove/omchaya/blob/master/src/omchaya/components/key_queue.cljs). 93 | 94 | Thanks to [@brandonbloom](https://github.com/brandonbloom) for demonstrating how to use descriptors in Om. [Related blog post](http://blog.circleci.com/local-state-global-concerns/). 95 | 96 | Thanks to [@swannodette](https://github.com/swannodette) for releasing Om. 97 | 98 | 99 | ## License 100 | 101 | Copyright © 2015 PrecursorApp 102 | 103 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 104 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | post: 3 | - lein cljsbuild once 4 | 5 | test: 6 | override: 7 | - echo no tests 8 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject precursor/om-i "0.1.8" 2 | :description "Instrumentation helpers for Om applications" 3 | :url "https://github.com/PrecursorApp/om-i" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | [org.clojure/clojurescript "0.0-2755" :scope "provided"] 8 | [org.omcljs/om "0.8.8" :scope "provided"]] 9 | 10 | :plugins [[lein-cljsbuild "1.0.4"]] 11 | :cljsbuild {:builds [{:id "test" 12 | :source-paths ["src"] 13 | :compiler {:output-to "script/tests.simple.js" 14 | :output-dir "script/out" 15 | :source-map "script/tests.simple.js.map" 16 | :output-wrapper false 17 | :optimizations :simple}}]} 18 | 19 | :source-paths ["src"]) 20 | -------------------------------------------------------------------------------- /resources/om-i.css: -------------------------------------------------------------------------------- 1 | @keyframes in-fade-top-soft { 2 | 0% { 3 | opacity: 0; 4 | transform: translate3d(0, -4rem, 0); 5 | } 6 | 100% { 7 | opacity: 1; 8 | transform: none; 9 | } 10 | } 11 | @-webkit-keyframes in-fade-top-soft { 12 | 0% { 13 | opacity: 0; 14 | -webkit-transform: translate3d(0, -4rem, 0); 15 | } 16 | 100% { 17 | opacity: 1; 18 | -webkit-transform: none; 19 | } 20 | } 21 | .om-instrumentation { 22 | user-select: none; 23 | -moz-user-select: none; 24 | -webkit-user-select: none; 25 | pointer-events: none; 26 | color: #888888; 27 | position: fixed; 28 | z-index: 1000; 29 | top: 0; 30 | left: 0; 31 | width: 100%; 32 | } 33 | .instrumentation-table { 34 | -webkit-animation: in-fade-top-soft 500ms; 35 | animation: in-fade-top-soft 500ms; 36 | background-color: rgba(0, 0, 0, 0.6); 37 | font-family: Monaco, monospace; 38 | font-size: .75rem; 39 | line-height: 2; 40 | width: 100%; 41 | } 42 | .instrumentation-table th { 43 | color: #ffffff; 44 | line-height: 1rem; 45 | padding: 1.5rem 0; 46 | text-transform: uppercase; 47 | text-align: left; 48 | } 49 | .instrumentation-table th:not(:first-child) { 50 | text-align: center; 51 | } 52 | .instrumentation-table th.left { 53 | text-align: left; 54 | } 55 | .instrumentation-table th.right { 56 | text-align: right; 57 | } 58 | .instrumentation-table th, 59 | .instrumentation-table td { 60 | white-space: pre-wrap; 61 | } 62 | .instrumentation-table th:first-child, 63 | .instrumentation-table td:first-child { 64 | padding-left: 1.5rem; 65 | } 66 | .instrumentation-table tbody tr:nth-child(odd) { 67 | background-color: rgba(0, 0, 0, 0.4); 68 | } 69 | .instrumentation-table tbody td:nth-child(even) { 70 | text-align: right; 71 | border-right: 1px dashed; 72 | padding-right: .5em; 73 | } 74 | .instrumentation-table tbody td:nth-child(odd) { 75 | text-align: left; 76 | padding-left: .5em; 77 | } 78 | .instrumentation-table tbody td:first-child { 79 | padding-left: 1.5rem; 80 | } 81 | .instrumentation-table tfoot td { 82 | line-height: 1rem; 83 | text-align: center; 84 | padding: 1.5rem 0; 85 | } 86 | .instrumentation-table small { 87 | font-size: 1em; 88 | opacity: .5; 89 | } 90 | -------------------------------------------------------------------------------- /resources/om-i.less: -------------------------------------------------------------------------------- 1 | @black: #000; 2 | @white: #fff; 3 | @gray: #888; 4 | 5 | @font_mono: Monaco, monospace; 6 | @zindex-instrumentation: 1000; 7 | 8 | @tile: (1rem * 4); 9 | @menu_padding: 1.5rem; 10 | 11 | @keyframes in-fade-top-soft { 12 | 0% { opacity: 0; transform: translate3d(0, -@tile, 0);} 13 | 100% { opacity: 1; transform: none;} 14 | } 15 | @-webkit-keyframes in-fade-top-soft { 16 | 0% { opacity: 0; -webkit-transform: translate3d(0, -@tile, 0);} 17 | 100% { opacity: 1; -webkit-transform: none;} 18 | } 19 | .om-instrumentation { 20 | user-select: none; 21 | -moz-user-select: none; 22 | -webkit-user-select: none; 23 | pointer-events: none; 24 | color: @gray; 25 | position: fixed; 26 | z-index: @zindex-instrumentation; 27 | top: 0; 28 | left: 0; 29 | width: 100%; 30 | } 31 | .instrumentation-table { 32 | -webkit-animation: in-fade-top-soft 500ms; 33 | animation: in-fade-top-soft 500ms; 34 | background-color: fade(@black, 60); 35 | font-family: @font_mono; 36 | font-size: .75rem; 37 | line-height: 2; 38 | width: 100%; 39 | th { 40 | color: @white; 41 | line-height: 1rem; 42 | padding: @menu_padding 0; 43 | text-transform: uppercase; 44 | text-align: left; 45 | &:not(:first-child) { 46 | text-align: center; 47 | } 48 | &.left { 49 | text-align: left; 50 | } 51 | &.right { 52 | text-align: right; 53 | } 54 | } 55 | th, 56 | td { 57 | white-space: pre-wrap; 58 | &:first-child { 59 | padding-left: @menu_padding; 60 | } 61 | } 62 | tbody { 63 | tr { 64 | &:nth-child(odd) { 65 | background-color: fade(@black, 40); 66 | } 67 | } 68 | td { 69 | &:nth-child(even) { 70 | text-align: right; 71 | border-right: 1px dashed; 72 | padding-right: .5em; 73 | } 74 | &:nth-child(odd) { 75 | text-align: left; 76 | padding-left: .5em; 77 | } 78 | &:first-child { 79 | padding-left: @menu_padding; 80 | } 81 | } 82 | } 83 | tfoot { 84 | td { 85 | line-height: 1rem; 86 | text-align: center; 87 | padding: @menu_padding 0; 88 | } 89 | } 90 | small { 91 | font-size: 1em; 92 | opacity: .5; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /resources/om-i.min.css: -------------------------------------------------------------------------------- 1 | @keyframes in-fade-top-soft{0%{opacity:0;transform:translate3d(0, -4rem, 0)}100%{opacity:1;transform:none}}@-webkit-keyframes in-fade-top-soft{0%{opacity:0;-webkit-transform:translate3d(0, -4rem, 0)}100%{opacity:1;-webkit-transform:none}}.om-instrumentation{user-select:none;-moz-user-select:none;-webkit-user-select:none;pointer-events:none;color:#888;position:fixed;z-index:1000;top:0;left:0;width:100%}.instrumentation-table{-webkit-animation:in-fade-top-soft 500ms;animation:in-fade-top-soft 500ms;background-color:rgba(0,0,0,0.6);font-family:Monaco,monospace;font-size:.75rem;line-height:2;width:100%}.instrumentation-table th{color:#fff;line-height:1rem;padding:1.5rem 0;text-transform:uppercase;text-align:left}.instrumentation-table th:not(:first-child){text-align:center}.instrumentation-table th.left{text-align:left}.instrumentation-table th.right{text-align:right}.instrumentation-table th,.instrumentation-table td{white-space:pre-wrap}.instrumentation-table th:first-child,.instrumentation-table td:first-child{padding-left:1.5rem}.instrumentation-table tbody tr:nth-child(odd){background-color:rgba(0,0,0,0.4)}.instrumentation-table tbody td:nth-child(even){text-align:right;border-right:1px dashed;padding-right:.5em}.instrumentation-table tbody td:nth-child(odd){text-align:left;padding-left:.5em}.instrumentation-table tbody td:first-child{padding-left:1.5rem}.instrumentation-table tfoot td{line-height:1rem;text-align:center;padding:1.5rem 0}.instrumentation-table small{font-size:1em;opacity:.5} -------------------------------------------------------------------------------- /src/om_i/core.cljs: -------------------------------------------------------------------------------- 1 | (ns om-i.core 2 | (:require [clojure.string :as str] 3 | [goog.dom] 4 | [goog.string :as gstring] 5 | [goog.string.format] 6 | [om.core :as om :include-macros true] 7 | [om.dom :as dom :include-macros true] 8 | [om-i.keyboard :as keyboard])) 9 | 10 | ;; map of display name to component render stats, e.g. 11 | ;; {"App" {:last-will-update