{"id":25722660,"url":"https://github.com/nolantait/staticky","last_synced_at":"2025-05-07T00:21:24.419Z","repository":{"id":252582732,"uuid":"828025415","full_name":"nolantait/staticky","owner":"nolantait","description":"Static site builder with first class support for Phlex","archived":false,"fork":false,"pushed_at":"2025-04-14T12:34:51.000Z","size":3786,"stargazers_count":19,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-14T13:30:31.869Z","etag":null,"topics":["phlex","ruby","static-site-generator"],"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/nolantait.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-07-12T23:17:09.000Z","updated_at":"2025-04-14T12:34:55.000Z","dependencies_parsed_at":"2024-08-15T09:12:27.306Z","dependency_job_id":"6c95f70a-59a7-49c1-90dd-8424fb70e5c0","html_url":"https://github.com/nolantait/staticky","commit_stats":null,"previous_names":["nolantait/staticky"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nolantait%2Fstaticky","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nolantait%2Fstaticky/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nolantait%2Fstaticky/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nolantait%2Fstaticky/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nolantait","download_url":"https://codeload.github.com/nolantait/staticky/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252789139,"owners_count":21804400,"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":["phlex","ruby","static-site-generator"],"created_at":"2025-02-25T19:39:31.297Z","updated_at":"2025-05-07T00:21:24.373Z","avatar_url":"https://github.com/nolantait.png","language":"Ruby","readme":"# Staticky\n\nStaticky is a static site builder for Ruby maximalists. I built this library\nbecause I wanted something more scriptable than Bridgetown and Jekyll that had\nfirst-class support for Phlex components.\n\n[Phlex](https://phlex.fun) makes building component based frontends fun and\nI wanted to extend the developer experience of something like Rails but focused\non static sites.\n\nI am currently using this to create https://taintedcoders.com\n\n- Hot reloading in development with Roda serving static files\n- Docker deployment with NGINX\n\nYou can find a working setup in `site_template` folder.\n\n## Installation\n\nInstall the gem and add to the application's Gemfile by executing:\n\n    $ bundle add staticky\n\nIf bundler is not being used to manage dependencies, install the gem by executing:\n\n    $ gem install staticky\n\n## Usage\n\nFirst you can use the CLI to generate a new template:\n\n```\nstaticky new my_blog --url \"https://example.com\"\n```\n\nThis will generate a new site at `./my_blog`, install your dependencies and run\n`rspec` just to make sure everything got set up correctly.\n\nYou can append `--help` to any commands to see info:\n\n```\nstaticky new --help\n```\n\nWhich outputs:\n\n```\nCommand:\n  staticky new\n\nUsage:\n  staticky new PATH\n\nDescription:\n  Create new site\n\nArguments:\n  PATH                              # REQUIRED Relative path where the site will be generated\n\nOptions:\n  --url=VALUE, -u VALUE             # Site URL, default: \"https://example.com\"\n  --title=VALUE, -t VALUE           # Site title, default: \"Example\"\n  --description=VALUE, -d VALUE     # Site description, default: \"Example site\"\n  --twitter=VALUE, -x VALUE         # Twitter handle, default: \"\"\n  --help, -h                        # Print this help\n```\n\n### Plugins\n\nThe router and resources use the plugin\n[pattern](https://janko.io/the-plugin-system-of-sequel-and-roda/)\nfound in [Sequel](https://github.com/jeremyevans/sequel) and\n[Roda](https://github.com/jeremyevans/roda).\n\nThis means you can easily extend each of them with plugins to fit the specific\ncontent of your site.\n\n```ruby\nmodule MyResourcePlugin\n  module InstanceMethods\n    def component=(component)\n      @component = component\n    end\n\n    def component\n      return @component if defined?(@component)\n\n      raise ArgumentError, \"component is required\"\n    end\n  end\nend\n```\n\nIn our own classes we can now reference our new plugin:\n\n```ruby\nclass SomeResource \u003c Staticky::Resource\n  plugin MyResourcePlugin\nend\n```\n\nOr, if we register the plugin with `register_plugin` we can just use our\nshorter symbol:\n\n```ruby\nStaticky::Resources::Plugins.register_plugin(:something, MyResourcePlugin)\n\nclass SomeResource \u003c Staticky::Resource\n  plugin :something\nend\n```\n\nThis system lets you define your own specific resources by subclassing and\nextending with your own plugins.\n\nHere is an example of hooking into the output of the component\n(a string of HTML):\n\n```ruby\nmodule MinifyHTML\n  module InstanceMethods\n    # Calling super works because the base class has no methods, everything is\n    # a plugin including the core behavior of a resource.\n    def build\n      SomehowMinifyTheHTML.call(super)\n    end\n  end\nend\n\nStaticky::Resources::Plugins.register_plugin(:minify_html, MinifyHTML)\n\nclass ApplicationResource \u003c Staticky::Resource\n  plugin :minify_html\nend\n```\n\nNow when an `ApplicationResource` gets rendered, its final output (a string of\nHTML) will be minified.\n\nEach plugin can define modules for:\n\n|Name|Description|\n|----|-----------|\n|InstanceMethods|Get added as instance methods of the Resource|\n|ClassMethods|Get added as the class methods of the Resource|\n\nIn addition you have methods you can define that let you hook into the resource\nthat adds your plugins:\n\n|Name|Description|\n|----|-----------|\n|load_dependencies(plugin, ...)|Hook to load any other plugins required by this one|\n|configure(plugin, ...)|Hook for additional setup required on the class|\n\n\n### Routing\n\nYour router is a plugin system that by default only has one plugin:\n\n```ruby\nplugin :prelude\n```\n\nThis gives you the `match` and `root` methods in your router. You can override\nor extend these methods yourself by redefining them (and optionally calling\n`super`) inside your own plugin or class that inherits from the router.\n\nOnce your site is generated you can use the router to define how your content\nmaps to routes in `config/routes.rb`:\n\n```ruby\nStaticky.router.define do\n  root to: Pages::Home\n\n  # We can pass in a phlex class\n  match \"404\", to: Errors::NotFound\n  # Or an instance\n  match \"500\", to: Errors::ServiceError.new\n\n  # We can specify the resource type\n  match \"about\",\n    to: Markdown.new(\"content/posts/about.md\"),\n    as: Resources::Markdown\n\n  # Write your own logic to parse your data into components\n  Site.posts.each_value do |model|\n    match model.relative_url, to: Posts::Show.new(model)\n  end\nend\n```\n\nEach route takes a Phlex component (or any object that outputs a string from\n`#call`). We can either pass the class for a default initialization (we just\ncall `.new`) or initialize it ourselves.\n\nThe resource will be initialized with a `component` and a `url`. It is used as\nthe view context for your phlex components.\n\n#### Match\n\nThis works in a similar way to your Rails routes. Match takes a path and\na component (either a class or an instance) that it will route to.\n\n```ruby\nmatch \"404\", to: Errors::NotFound, as: Resource\n```\n\n#### Root\n\nUsing `match` you can define a root path like:\n\n```ruby\nmatch \"/\", to: Pages::Home\n```\n\nFor convenience you can shorten this using `root`:\n\n```ruby\nroot to: Pages::Home\n```\n\n### Resources\n\nThey initialize the same way `ActiveModel` objects do. That is they take their\nkeywords and call the setter according to the keys:\n\n```ruby\ndef new(**env)\n  super().tap do |resource|\n    env.each do |key, value|\n      resource.send(:\"#{key}=\", value)\n    end\n  end\nend\n```\n\nThe base resource has two core plugins it includes by default:\n\n```ruby\nplugin :prelude\nplugin :phlex\n```\n\nRoutes define your resources, which in the end are just data objects that\ncontain all the information required to produce the static file that eventually\noutputs to your `Staticky.build_path`.\n\nLets say we had a router defined like:\n\n```ruby\nStaticky.router.define do\n  match \"foo\", to: Component\n  match \"bar\", to: Component\nend\n```\n\nThen we could view our resources:\n\n```\n(ruby) Staticky.resources\n[#\u003cStaticky::Resource:0x0000711525d82c18\n  @component=#\u003cComponent:0x0000711525d74848\u003e,\n  @destination=#\u003cPathname:/your-site-folder/build\u003e,\n  @uri=#\u003cURI::Generic /foo\u003e,\n  @url=\"foo\"\u003e,\n #\u003cStaticky::Resource:0x0000711525d82a88\n  @component=#\u003cComponent:0x0000711525d74208\u003e,\n  @destination=#\u003cPathname:/your-site-folder/build\u003e,\n  @uri=#\u003cURI::Generic /bar\u003e,\n  @url=\"bar\"\u003e]\n```\n\nThe `prelude` plugin provides the following methods:\n\n|Method|Description|\n|------|-----------|\n|`build_path`|`Pathname` of where the component's output will be written to|\n|`read`|Read the output of the resource from the file system|\n|`filepath`|The file path (e.g. `about/index.html`) for the resource|\n|`root?`|Whether or not the resource is the root path|\n\nWhile the `phlex` plugin provides:\n\n|Method|Description|\n|------|-----------|\n|`build`|Call the component and output its result as a string|\n\nThese resources are used by your site builder to output the files that end up in\nthe `Staticky.build_path`.\n\nEach resource needs to have a `#build` method that creates a file in your build\nfolder.\n\nThe `phlex` plugin will call your components with a `ViewContext` just like\n`ActionView` in Rails. But this context is tailored towards your static site.\n\nThis view context is a `SimpleDelegator` to your resource with a few extra\nmethods:\n\n|Method|Description|\n|------|-----------|\n|`root?`|Whether or not this resource is for the root page|\n|`current_path`|The path of the current resource being rendered|\n\nThese are useful for creating pages that hide or show content depending on which\npath of the site we are building.\n\n### Linking to your routes\n\nFirst you need to include the view helpers somewhere in your component\nhierarchy:\n\n```ruby\nclass Component \u003c Phlex::HTML\n  include Staticky::Phlex::ViewHelpers\nend\n```\n\nThis will add `link_to` to all your components which uses the router to resolve\nany URLs via their path.\n\nHere is an example of what the `Posts::Show` component might look like. We are\nusing a [protos](https://github.com/inhouse-work/protos) component, but you can\nuse plain old Phlex components if you like.\n\n```ruby\nmodule Posts\n  class Show \u003c ApplicationComponent\n    param :post, reader: false\n\n    def around_template(\u0026)\n      render Layouts::Post.new(class: css[:layout], \u0026)\n    end\n\n    def view_template\n      # Links can be resolved to component classes if they are unique:\n      link_to \"Home\", Pages::Home\n      # They can also resolve via their url:\n      link_to \"Posts\", \"/posts\"\n      # Absolute links are resolved as is:\n      link_to \"Email\", \"mailto:email@example.com\"\n\n      render Posts::Header.new(@post)\n      render Posts::Outline.new(@post, class: css[:outline])\n      render Posts::Markdown.new(@post, class: css[:post])\n      render Posts::Footer.new(@post)\n    end\n\n    private\n\n    def theme\n      {\n        layout: \"bg-background\",\n        outline: \"border\",\n        post: \"max-w-prose mx-auto\"\n      }\n    end\n  end\nend\n```\n\nThe advantage of using `link_to` over plain old `a` tags is that changes to your\nroutes will raise errors on invalidated links instead of silently\nlinking to invalid pages.\n\nIf your component is unique then you can link directly to them (if its not\nunique then it will link to the last defined `match`):\n\n```ruby\nlink_to(\"Some link\", Pages::Home)\n```\n\nOtherwise you can link to the path itself:\n\n```ruby\nlink_to(\"Some link\", \"/\")\n```\n\n### Building your site\n\nWhen you are developing your site you run `bin/dev` to start your development\nserver on [http://localhost:3000](http://localhost:3000).\nThis will automatically reload after a short period when you make changes.\n\nAssets are handled by Vite by default, but you can have whatever build process\nyou like just by tweaking `Procfile.dev` and your `Rakefile`. You will also need\nto create your own view helpers for linking your assets.\n\nBy default, to build your site you run the builder, usually inside a Rakefile:\n\n```ruby\nrequire \"vite_ruby\"\n\nViteRuby.install_tasks\n\ndesc \"Precompile assets\"\ntask :environment do\n  require \"./config/boot\"\nend\n\nnamespace :site do\n  desc \"Precompile assets\"\n  task build: :environment do\n    Rake::Task[\"vite:build\"].invoke\n    Staticky.builder.call\n  end\nend\n```\n\nThis will output your site to `./build` by default.\n\nDuring building, each definition in the router is compiled and handed a special\nview context which holds information about the resource being rendered such as\nthe `current_path`.\n\nThese are available in your Phlex components under `helpers` (if you are using\nthe site template). This matches what you might expect when using Phlex in\nRails with `phlex-rails`.\n\n## Live reloading\n\nThe development server has been hooked up with some live reloading using\nserver-side events.\n\nA javascript script is inserted into the `\u003chead\u003e` tag during development which\nwill poll the `_staticky/live_reloading` endpoint. If files have changed then\na reload is triggered with `Turbo` if available, and just plain\n`window.location.reload()` if not.\n\nYou can toggle this off by setting `live_reloading` to false inside the config.\n\n## Configuration\n\nWe can override the configuration according to the settings defined on the main\nmodule:\n\n```ruby\nStaticky.configure do |config|\n  config.env = :test\n  config.build_path = Pathname.new(\"dist\")\n  config.root_path = Pathname(__dir__)\n  config.logger = Logger.new($stdout)\n  config.server_logger = Logger.new($stdout)\n  config.live_reloading = false\nend\n```\n\n### Environment\n\nYou can define the environment of Staticky through its config.\n\n```ruby\nStaticky.configure do |config|\n  config.env = :test\nend\n```\n\nThis lets you write environment specific code:\n\n```ruby\nif Staticky.env.test?\n  # Do something test specific\nend\n```\n\n## Testing\n\nWe can setup a separate testing environment by putting the following\ninto your `spec/spec_helper.rb`:\n\n```ruby\nStaticky.configure do |config|\n  config.root_path = Pathname.new(__dir__).join(\"fixtures\")\n  config.build_path = Pathname.new(__dir__).join(\"fixtures/build\")\n  config.env = :test\nend\n```\n\nThis sets up our build path to something different than our development builds.\n\nStaticky uses `Dry::System` to manage its dependencies which means you can stub\nthem out if you want:\n\n```ruby\nrequire \"dry/system/stubs\"\n\nStaticky.application.enable_stubs!\n\nRSpec.configure do |config|\n  config.before do\n    Staticky.application.stub(:files, Staticky::Filesystem.test)\n  end\nend\n```\n\nThis lets you test your builds using `dry-files` (actually `staticky-files`, but\nthe interface is the same with additional capabilities for file folders).\n\nThe advantage of this is that we can perform our builds on a temporary in memory\nfile system rather than actually writing to our disk.\n\nThe plugins themselves can also be stubbed:\n\n```ruby\nrequire \"dry/system/stubs\"\n\nStaticky::Resources::Plugins.enable_stubs!\nStaticky::Routing::Plugins.enable_stubs!\n\nRSpec.configure do |config|\n  config.before do\n    Staticky::Resources::Plugins.stub(:prelude, MyOwnResourcePlugin)\n    Staticky::Routing::Plugins.stub(:prelude, MyOwnRoutingPlugin)\n  end\nend\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run\n`bin/rspec` to run the tests. You can also run `bin/console` for an interactive\nprompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To\nrelease a new version, update the version number in `version.rb`, and then run\n`bundle exec rake release`, which will create a git tag for the version, push\ngit commits and the created tag, and push the `.gem` file to\n[rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/nolantait/staticky.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnolantait%2Fstaticky","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnolantait%2Fstaticky","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnolantait%2Fstaticky/lists"}