algolia / frontman

💎 A Ruby-based static website generator
https://rubygems.org/gems/frontman-ssg
MIT License
109 stars 4 forks source link

Add ability to monkey patch frontman / global_require #27

Open westonganger opened 4 years ago

westonganger commented 4 years ago

Its pretty difficult to monkey patch frontman. Currently I am using the following technique at the top of my config file.

# config.rb

::Frontman::App.class_eval do
  def import_config(path)
    eval File.read(path)
  end
end

It would be nice if this could be extracted to another file somehow. The problem here is that the context is within Frontman::App. Maybe we could have a method like global_require that requires files from the global context.

DevinCodes commented 4 years ago

Do you have some ideas on what this would look like? Some (code) examples would help in understanding how this would work exactly.

In the Algolia documentation, we currently monkey patch in seperate files when needed, and then require those files in our config.rb:

# config.rb

require './lib/overrides/sitemap'

I only think this wouldn't work for the Frontman::App class, because the config.rb file is executed in the context of this class. However, this means that you can define methods in config.rb that are then attached to Frontman::App. So instead of the code you're including, I believe you can use the following:

def import_config(path)
  eval File.read(path)
end
westonganger commented 4 years ago

It would appear that any require calls are also executed in the context of Front::App.

https://github.com/algolia/frontman/blob/f73d660e2cacc7b1eada5f4453a0b118b6a945ef/lib/frontman/bootstrapper.rb#L34

This will force us to prefix every single module/class in these files with :: to access the global namespace. This is going to make it difficult to include other code.

DevinCodes commented 4 years ago

This will force us to prefix every single module/class in these files with :: to access the global namespace. This is going to make it difficult to include other code.

I think this is only the case if there's a collision with class names in the Frontman namespace, but it would be a best practice in that case to always use :: to access the global namespace from your config.rb file to prevent naming collisions from happening.

I don't see a quick way to solve this, so I'll add it to the documentation for now. Feel free to dump ideas for solving this if you have any 🙂

westonganger commented 4 years ago

It would be cool if maybe there is a way to get the global/Kernel binding and evaluate the code within this binding. I would love to hear if anyone has an idea for this.

westonganger commented 3 years ago

I think we should be able to accomplish the global requires using:

module Frontman
  class App
    def global_scope(&block)
      Kernel.instance_exec(&block)
    end

    def global_require(file)
      Kernel.instance_exec do
        instance_eval(file)
      end
    end
  end
end

Then use like so:

global_scope do
  require 'foo'
end

# OR

global_require('foo')
DevinCodes commented 3 years ago

If that works indeed, it seems like an elegant solution to me 😄 Feel free to open a PR for this!