Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] How to migrate to ViewComponent #170

Open
Spone opened this issue Jan 29, 2021 · 0 comments
Open

[Draft] How to migrate to ViewComponent #170

Spone opened this issue Jan 29, 2021 · 0 comments

Comments

@Spone
Copy link
Collaborator

Spone commented Jan 29, 2021

This is an evolving draft of the steps needed to migrate your project and components from Komponent to ViewComponent. Please add your notes and comments below.

Goals

List the steps and gotchas when migrating. This is preliminary work to hopefully provide tools to ease the transition.

Preparation

In my case, I use frontend/ as my root folder, and I want to use ViewComponent's sidecar assets.

  • Install ViewComponent
  • If you're using a non-standard root folder (in my case frontend/) :
    • move frontend/components/ to app/components/
    • move frontend/ to app/javascript/
  • In config/application.rb, replace:
- config.i18n.load_path += Dir[config.root.join('frontend/components/**/*.yml')]
+ config.i18n.load_path += Dir[config.root.join('app/components/**/*.yml')]
- config.autoload_paths << config.root.join('frontend/components')
+ config.komponent.root = Rails.root.join('app')
  • In config/webpacker.yml, replace:
- source_path: frontend
+ source_path: app/javascript

(I also had to add extract_css: true, not sure why yet)

  • In app/javascript/packs/application.js, replace:
- import "../components";
+ import "../../components";
  • In app/components/index.js, replace the import path for each component:
- import "components/button/button";
+ import "./button/button";

If you have namespaced components, find their index.js and replace the import paths as well.

  • At the end of app/components/index.js, add this in order to get sidecar assets:
function importAll(r) {
  r.keys().forEach(r);
}

importAll(require.context("../components", true, /_component.js$/));

Migrating a component

Let's say you have a component named example.

  • make a list of the params taken by your component (you can look for instance variables in _example.html.slim and example_component.rb)
  • run bin/rails generate component Example title content (replace title and content with the params you listed)
  • move app/components/example/_example.html.slim to app/components/example_component/example_component.html.slim
  • move app/components/example/example.js to app/components/example_component/example_component.js
  • in app/components/example_component/example_component.js, replace:
- import "./example.css";
+ import "./example_component.css";
  • move app/components/example/example.css to app/components/example_component/example_component.css
  • now open side-by-side app/components/example/example_component.rb and app/components/example_component.rb
    • if you have property with required: true, nothing to do: the params are required by default. For instance property :foo, required: true becomes def initialize(foo:)
    • if you have property with default values, move these values to the initialize keyword arguments. For instance property :foo, default: "bar" becomes def initialize(foo: "bar")
    • take all the ExampleComponent module methods and move them to the ExampleComponent class
  • if your component uses view helpers, prefix them with helpers. (ie. current_user becomes helpers.current_user)
  • in frontend/components/index.js, remove:
- import "./example/example";

Now take a deep breath, because it will get tedious.

  • find all the occurrences of c "example" or c("example" or component "example" or component("example" in your project. You can use this regex if your editor allows you to search by regex: /c(omponent)?[ (]{1}"example"/
  • replace them with the ViewComponent syntax to render components: render(ExampleComponent.new(foo: "bar"))

If your component has a Stimulus controller

  • if you haven't done so already, change this in app/javascript/stimulus_application.js:
import { Application } from "stimulus";
+ import { definitionsFromContext } from "stimulus/webpack-helpers";

const application = Application.start();
+ const context = require.context("./controllers", true, /\.js$/);
+ const componentsContext = require.context("../components", false, /_controller\.js$/);
+ application.load(
+   definitionsFromContext(context).concat(
+     definitionsFromContext(componentsContext)
+   )
+ );

export default application;
  • move app/components/example/example_controller.js to app/components/example_component/example_controller.js
  • in app/components/example_component/example_component.js, remove :
-import application from "stimulus_application";
 import { definitionsFromContext } from "stimulus/webpack-helpers";
+import application from "../../javascript/stimulus_application";
import "./example_component.css";

const context = require.context("./", true, /_controller\.js$/);
application.load(definitionsFromContext(context));

This trick (calling one application.load for each component) prevents you from having the -- in the controller name when placing the file in a sidecar directory, as described here.

Tools

Here is a first try at automating the last part (replacing all occurrences of rendering a given component);

Warning: this is a work in progress, not ready for use. Help needed!

namespace :komponent do
  namespace :migrate do
    desc "Migrate component renders to view_component syntax"
    task :render, [:component_name] => :environment do |_t, args|
      component_name = args.component_name
      puts "Migrating component #{component_name}"

      # This regexp only finds the beginning of the component rendering call.
      # It does not handle the parameters, so you will have a missing `))` at the end.
      # FIXME: how can we improve this?
      search_regexp = /c(omponent)?[ (]{1}"#{Regexp.quote(component_name)}",?\s+/

      component_klass = component_name.classify
      replacement = "render(#{component_klass}Component.new("

      # FIXME: Replace `slim` with your templating language of choice here.
      file_names = Dir.glob(Rails.root.join("**/*.slim"))

      file_names.each do |file_name|
        text = File.read(file_name)
        new_contents = text.gsub(search_regexp, replacement)

        File.open(file_name, "w") {|file| file.puts new_contents }
      end
    end
  end
end

That's all for now. I will update this issue when I find edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

1 participant