{"id":13747385,"url":"https://github.com/antulik/pagelet_rails","last_synced_at":"2025-05-09T08:32:39.211Z","repository":{"id":56887246,"uuid":"66075184","full_name":"antulik/pagelet_rails","owner":"antulik","description":"Improve perceived performance of your rails application with minimum effort","archived":false,"fork":false,"pushed_at":"2017-10-24T08:25:14.000Z","size":114,"stargazers_count":247,"open_issues_count":1,"forks_count":4,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-05-12T07:01:52.155Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/antulik.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-08-19T10:40:08.000Z","updated_at":"2024-02-24T12:31:12.000Z","dependencies_parsed_at":"2022-08-20T14:31:24.467Z","dependency_job_id":null,"html_url":"https://github.com/antulik/pagelet_rails","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antulik%2Fpagelet_rails","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antulik%2Fpagelet_rails/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antulik%2Fpagelet_rails/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antulik%2Fpagelet_rails/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/antulik","download_url":"https://codeload.github.com/antulik/pagelet_rails/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224842693,"owners_count":17379015,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T06:01:27.068Z","updated_at":"2024-11-15T20:31:40.139Z","avatar_url":"https://github.com/antulik.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# PageletRails\n\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/antulik/pagelet_rails/master/LICENSE)\n[![Gem Version](https://badge.fury.io/rb/pagelet_rails.svg)](https://badge.fury.io/rb/pagelet_rails)\n[![Build Status](https://travis-ci.org/antulik/pagelet_rails.svg?branch=master)](https://travis-ci.org/antulik/pagelet_rails)\n[![Code Climate](https://codeclimate.com/github/antulik/pagelet_rails/badges/gpa.svg)](https://codeclimate.com/github/antulik/pagelet_rails)\n[![Test Coverage](https://codeclimate.com/github/antulik/pagelet_rails/badges/coverage.svg)](https://codeclimate.com/github/antulik/pagelet_rails/coverage)\n\nPageletRails is the addon for Rails which allows you to build components. Achieve amazing performance and reusability by slicing your webpages into components. PageletRails is built on top of Rails and uses it as much as possible. The main philosophy: **Do not reinvent the wheel, build on shoulders of giants.**\n\n## Why?\n\n* Do you have pages with a lot of information?\n* The pages where you need to get data from 5 or 10 different sources?\n* What if one of them is slow?\n* Does this mean your users have to wait?\n\nDon't make your users wait for page to load.\n\n[View Demo Project](http://polar-river-18908.herokuapp.com)\n\n## Example\n\n![](https://camo.githubusercontent.com/50f4078cc4015e3df89afc753a5ff79828ac0e8e/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f662e636c2e6c792f6974656d732f303031323133314d324b3147335831483276314f2f313433303033383036373738372e6a7067)\n\nFor example let's take facebook user home page. It has A LOT of data, but it loads very quickly. How? The answer is [perceived performance](https://en.wikipedia.org/wiki/Perceived_performance). It's not about in how many milliseconds you can serve request, but how fast it **feels** to the user.\n\nThe page body is served instantly and all the data is loaded after. Even for facebook it takes multiple seconds to fully load the page. But it feels instant, that it's all about.\n\n## Who is doing that?\n\nOriginally I saw such solution implemented at Facebook and Linkedin. Each page consists of small blocks, where each is responsible for it's own functionality and does not depend on the page where it's included. You can read more on that below.\n\n* [BigPipe: Pipelining web pages for high performance](https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919/)\n* [Engineering the New LinkedIn Profile](https://engineering.linkedin.com/profile/engineering-new-linkedin-profile)\n* [Play Framework \u0026 SF Scala: Jim Brikman, Composable Applications in Play, 7/23/14](https://www.youtube.com/watch?v=4b1XLka0UIw)\n\n## What is Pagelet?\n\nYou can break a web page into number of sections, where each one is responsible for its own functionality. Pagelet is the name for each section. It is a part of the page which has it's own route, controller and view.\n\nThe closest alternative in ruby is [cells gem](https://github.com/apotonick/cells). After using it for long time I've faced many limitations of its approach. Cells has a custom Rails-like syntax but not quite. That is frustrating as you have to learn and remember those differences. The second issue, and the biggest, cells are internal only and not designed to be routable. This stops many great possibilities for improving perceived performance, as request has to wait for all cells to render.\n\n# Usage\n\n\n## Installation\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'pagelet_rails'\n```\n\nOr install it yourself as:\n```bash\n$ gem install pagelet_rails\n```\n\n## Setup\n\nInclude small javascript extension `pagelet_rails`:\n\n```js\n// file app/assets/javascripts/application.js\n\n//= require jquery\n//= require jquery_ujs\n// ...\n//= require pagelet_rails\n\n````\n\n## Structure\n\n```\napp\n├── pagelets\n│   ├── current_time\n│   │   ├── current_time_controller.rb\n│   │   ├── views\n│   │   │   ├── show.erb\n```\n\n## Example Usage\n\n```ruby\n# app/pagelets/current_time/current_time_controller.rb\nclass CurrentTime::CurrentTimeController \u003c ApplicationController\n  include PageletRails::Concerns::Controller\n\n  # add pagelets_current_time_path route\n  # which gives \"/pagelets/current_time\" url route\n  pagelet_resource only: [:show]\n\n  def show\n  end\nend\n```\n\n```erb\n\u003c!-- Please note view path --\u003e\n\u003c!-- app/pagelets/current_time/views/show.erb --\u003e\n\u003cdiv class=\"panel-heading\"\u003eCurrent time\u003c/div\u003e\n\n\u003cdiv class=\"panel-body\"\u003e\n  \u003cp\u003e\u003c%= Time.now %\u003e\u003c/p\u003e\n  \u003cp\u003e\n    \u003c%= link_to 'Refresh', pagelets_current_time_path, remote: true %\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n```\n\nAnd now use it anywhere in your view\n\n```erb\n\u003c!-- app/views/dashboard/show.erb --\u003e\n\u003c%= pagelet :pagelets_current_time %\u003e\n```\n\n## Documentation\n\n- [Pagelet view helper](#pagelet-view-helper)\n- [Pagelet options](#pagelet-options)\n- [Inline routes](#inline-routes)\n- [Pagelet cache](#pagelet-cache)\n- [Advanced functionality](#advanced-functionality)\n  - [Partial update](#partial-update)\n  - [Streaming](#streaming)\n  - [Super smart caching](#super-smart-caching)\n  - [Ajax Batching](#ajax-batching)\n\n## Pagelet view helper\n\n`pagelet` helper allows you to render pagelets in views. Name of pagelet is its path.\n\nFor example pagelet with route `pagelets_current_time_path` will have `pagelets_current_time` name.\n\n### remote\n\nExample\n```erb\n\u003c%= pagelet :pagelets_current_time, remote: true %\u003e\n```\n\nOptions for `remote`:\n* `true`, `:ajax` - always render pagelet through ajax\n* `:turbolinks`  - same as `:ajax` but inline for turbolinks page visit\n* `false` or missing - render inline\n* `:stream` - (aka BigPipe) render placeholder and render full version at the end of html. See streaming for more info.\n* `:ssi` - render through [server side includes](https://en.wikipedia.org/wiki/Server_Side_Includes)\n\n### params\n\nExample\n```erb\n\u003c%= pagelet :pagelets_current_time, params: { id: 123 } %\u003e\n```\n\n`params` are the parameters to pass to pagelet path. Same as `pagelets_current_time_path(id: 123)`\n\n### html\n\n```erb\n\u003c%= pagelet :pagelets_current_time, html: { class: 'panel' } %\u003e\n```\n\nYou can specify html attributes to pagelet with `html` option\n\n### placeholder\n\nConfiguration for placeholder before pagelet is loaded.\n\n```erb\n\u003c%= pagelet :pagelets_current_time, placeholder: { text: 'Loading...', height: 300 } %\u003e\n```\n\nor use your own placeholder template\n\n```erb\n \u003c%= pagelet :pagelets_current_time, placeholder: { view: 'path_to_template' } %\u003e\n```\n\n### other\n\nYou can pass any other data and it will be available in `pagelet_options`\n\n```erb\n\u003c%= pagelet :pagelets_current_time, title: 'Hello' %\u003e\n```\n\n```ruby\n# ...\n  def show\n    @title = pagelet_options.title\n  end\n#...\n```\n\n\n## Pagelet options\n\n`pagelet_options` is similar to `params` object, but for private data and config. Options can be global for all actions or specific actions only.\n\n```ruby\nclass CurrentTime::CurrentTimeController \u003c ::ApplicationController\n  include PageletRails::Concerns::Controller\n\n  # Set default option for all actions\n  pagelet_options remote: true\n\n  # set option for :show and :edit actions only\n  pagelet_options :show, :edit, remote: :turbolinks\n\n  def show\n  end\n\n  def new\n  end\n\n  def edit\n  end\n\nend\n```\n\n```erb\n\u003c%= pagelet :new_pagelets_current_time %\u003e\u003c!-- defaults to remote: true --\u003e\n\u003c%= pagelet :pagelets_current_time %\u003e \u003c!-- defaults to remote: turbolinks --\u003e\n\n\u003c%= pagelet :pagelets_current_time, remote: false %\u003e \u003c!-- force remote: false --\u003e\n```\n\n## Inline routes\n\nBecause pagelets are small you will have many of them. In order to keep them under control pagelet_rails provides helpers.\n\nYou can inline routes inside you controller.\n\n```ruby\nclass CurrentTime::CurrentTimeController \u003c ::ApplicationController\n  include PageletRails::Concerns::Controller\n\n  pagelet_resource only: [:show]\n  # same as in config/routes.rb:\n  #\n  # resource :current_time, only: [:show]\n  #\n\n  pagelet_resources\n  # same as in config/routes.rb:\n  #\n  # resources :current_time\n  #\n\n  pagelet_routes do\n    # this is the same context as in config/routes.rb:\n    get 'show_me_time' =\u003e 'current_time/current_time#show'\n  end\n\nend\n```\n\n## Pagelet cache\n\nCache of pagelet rails is built on top of [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching).\n\nSimple example\n\n```ruby\n# app/pagelets/current_time/current_time_controller.rb\nclass CurrentTime::CurrentTimeController \u003c ::ApplicationController\n  include PageletRails::Concerns::Controller\n\n  pagelet_options expires_in: 10.minutes\n\n  def show\n  end\n\nend\n```\n\n### cache_path\n\nIs a hash of additional parameters for cache key.\n\n* `Hash` - static hash\n* `Proc` - dynamic params, it must return hash. Eg. `Proc.new { params.permit(:sort_by) }`\n* `Lambda` - same as `Proc` but accepts `controller` as first argument\n* `String` - any custom identifier\n\n### expires_in\n\nSet the cache expiry. For example `expires_in: 1.hour`.\n\nWarning: if `expires_in` is missing, it will be cached indefinitely.\n\n### cache\n\nThis is toggle to enable caching without specifying options. `cache_defaults` options will be used (see below).\n\nIf any of `cache_path`, `expires_in` and `cache` is present then cache will be enabled.\n\n\n### cache_defaults\n\nYou can set default options for caching.\n\n```ruby\nclass PageletController \u003c ::ApplicationController\n  include PageletRails::Concerns::Controller\n\n  pagelet_options cache_defaults: {\n    expires_in: 5.minutes,\n    cache_path: Proc.new {\n      { user_id: current_user.id }\n    }\n  }\nend\n```\n\nIn the example above cache will be scoped per `user_id` and for 5 minutes unless it is overwritten in pagelet itself.\n\n\n## Advanced functionality\n\n### Partial update\n\n```erb\n\u003c!-- app/pagelets/current_time/views/show.erb --\u003e\n\u003cdiv class=\"panel-heading\"\u003eCurrent time\u003c/div\u003e\n\n\u003cdiv class=\"panel-body\"\u003e\n  \u003cp\u003e\u003c%= Time.now %\u003e\u003c/p\u003e\n  \u003cp\u003e\n    \u003c%= link_to 'Refresh', pagelets_current_time_path, remote: true %\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n```\nPlease note `remote: true` option for `link_to`.\n\nThis is default Rails functionality with small addition. If that link is inside pagelet, than controller response will be replaced in that pagelet.\n\n```ruby\n# app/pagelets/current_time/current_time_controller.rb\nclass CurrentTime::CurrentTimeController \u003c ::ApplicationController\n  include PageletRails::Concerns::Controller\n\n  pagelet_resource only: [:show]\n\n  def show\n  end\n\nend\n```\n\nThis will partially update the page and replace only that pagelet.\n\n\n### Streaming\n\nThis is the most efficient way to deliver data with minimum delays. The placeholder will be rendered first and the full version will be delivered at the end of page and replaced with Javascript code. Everything is delivered in the same request.\n\nThis mode requires rendering of templates with [streaming mode](http://api.rubyonrails.org/classes/ActionController/Streaming.html) enabled.\n\nWarning: Session and Cookies are currently not supported in streaming mode.\n\n```ruby\n  #...\n  def show\n    render :show, stream: true\n  end\n  #...\n```\n\nIn you layout add `pagelet_stream` right before `\u003c/body\u003e` tag.\n\n```erb\n\u003c!-- app/views/layouts/application.erb --\u003e\n\n\u003cbody\u003e\n\u003c%= yield %\u003e\n\n\u003c% pagelet_stream %\u003e\n\u003c/body\u003e\n```\n\nUsage:\n\n```erb\n\u003c%= pagelet :pagelets_current_time, remote: :stream %\u003e\n```\n\n**Warning!!!** You also should have webserver compatible for streaming like puma, passenger or unicorn (requires special config).\n\n**Warning!!!** you need to have multiple threads/processes configured in the web server. This is required so the page could fetch assets while content is streaming.\n\nFinally if everything is done right you should see significant rendering speed improvements especially on old browsers, slow network or with cold cache.\n\n\n### Super smart caching\n\nProbably one of the coolest functionality of pagelet_rails is \"super smart caching\". It allows you to render pagelets through ajax and cache them, but if page is reloaded the pagelet is rendered instantly from cache.\n\n So on the first page load user sees \"Loading...\" blocks, but after the content is instant.\n\nThe best thing, it's enabled by default if pagelet has caching enabled and is rendering through ajax request.\n\n### Ajax Batching\n\nOnly relevant for `remote: true` and `remote: :turbolinks` when request is loaded through ajax. Loading each pagelet with a separate request is inefficient if you need to do make many requests. That's why you need to group multiple requests into one. By default all ajax requests are grouped into single request. But you can have full control of it. You can specify how to group requests with `ajax_group` option.\n\n```erb\n\u003c%= pagelet :pagelets_current_time, remote: true, ajax_group: 'main' %\u003e\n\u003c%= pagelet :pagelets_current_time, remote: true, ajax_group: 'leftpanel' %\u003e\n```\n\nThere will be one request per group. Missing value is considered a separate group as well.\n\n## Todo\n\n* assets support with webpacker in rails 5.1\n* session (and CSRF) support in streaming mode\n* delay load of not visible pagelets (aka. below the fold)\n  * do not load pagelets which are not visible to the user until user scrolls down. For example like Youtube comments.\n* high test coverage\n* update actionpack-action_caching gem to support rails 5\n\n## Links\n\n- [Parallel rendering in Rails](http://antulik.com/posts/2016-10-02-parallel-rendering-in-rails.html)\n\n## License\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantulik%2Fpagelet_rails","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantulik%2Fpagelet_rails","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantulik%2Fpagelet_rails/lists"}