Nonstop Progressive Marxism

    rails.macro
    TypeScript icon, indicating that this package has built-in type declarations

    0.2.0 • Public • Published

    rails.macro

    A babel macro to let JavaScript code access Ruby on Rails named routes

    Installation

    Install babel-plugin-macros (along with rails.macro) and add it to your babel config:

    npm install --save-dev babel-plugin-macros rails.macro

    .babelrc:

    {
      "plugins": ["macros"]
    }

    Routes

    Similar to Rails url helpers, rails.macro provides methods for each of your defined routes i.e. <routeName>_path and <routeName>_url. You can call them from the exported Routes object.

    Basic Usage

    Given the Rails route my_cool_thing

    config/routes.rb

    Rails.application.routes.draw do
      ...
      get 'my_cool_thing/:thing_id', as: :my_cool_thing
      ...
    end

    Access the path within your JavaScript code like:

    import { Routes } from 'rails.macro';
    
    const myPath = Routes.my_cool_thing_path({ thing_id: 'abc123', some: 'query' });
    // myPath === '/my_cool_thing/abc123?some=query'

    Absolute Urls

    There are a few ways to define the url host when using the <routeName>_url methods. They are listed in priority order below:

    1. Provide a host param to the url method. e.g.
      Routes.my_cool_thing_url({
        thing_id: 'abc123',
        some: 'query',
        host: 'http://example.com',
      });
      // 'http://example.com/my_cool_thing/abc123?some=query'
    2. Provide a global host option in the railsMacro global config.
    3. As a fallback, window.location.origin is used if window is defined when the url method is called at runtime.

    If none of these options are available, an error is thrown when the url method is called at runtime.

    More Examples

    • anchor is a reserved parameter that appends a url hash
      // Given Rails route definition: `get '/foo', as: :foo`
      Rails.foo_path({ anchor: 'buzz', wow: 123 });
      // Creates the string: /foo?wow=123#buzz
    • You can also pass a string or number as the first argument if the route has an :id param
      // Given Rails route definition: `get '/bar/:id', as: :bar`
      Rails.bar_path('buzz');
      // Creates the string: /bar/buzz

    See Jest tests for in-depth examples

    How it works

    At build-time, your config/routes.rb file is parsed with bundle exec rails runner get_routes.rb, and the route methods called in your JavaScript source code are transformed from:

    import { Routes } from 'rails.macro';
    Routes.my_thing_path({ ...stuff });
    Routes.my_thing_url({ ...stuff });

    to something like:

    import RailsMacroRoutes from 'rails.macro/routes';
    RailsMacroRoutes.registerRoutes({
      my_thing: {
        /* AST used to construct this route */
      },
    });
    RailsMacroRoutes.getPath('my_thing', { ...stuff });
    RailsMacroRoutes.getUrl('my_thing', { ...stuff });

    As you can see, only the route definitions needed in that specific file are provided in the resulting code. However, this means that if you use the same route in multiple files, that route definition code will be duplicated in each file.

    The qs lib is used to encode queries, and tries to replicate Rails' to_query as closely as possible.

    Config

    You can configure rails.macro by providing options to the babel-plugin-macros config, under the namespace railsMacro:

    .babelrc

    {
      "plugins": [
        ["macros", {
          "railsMacro": {
            ...
          }
        }]
      ]
    }

    Config Options

    Name Default Value Description
    host undefined Default url host used by Routes.<routeName>_url methods e.g. https://example.com
    railsDir process.cwd() Path to the rails project directory you want to read routes from
    cache true Cache the parsed routes.rb results in the filesystem to speed up consecutive builds
    watchFiles ['config/routes.rb'] These files' modification times will be used to determine if we need to regenerate the routes when we start babel
    preparsedRoutes undefined Path to a preparsed routes json file. More info

    Pre-parsing Rails routes

    If you're using thread-loader with babel-loader, rails.macro will try to parse and cache config/routes.rb in each thread, which might lead to undesirable results.

    To avoid this, run npx rails.macro preparse_routes > parsed_routes.json before running babel/webpack, and set the config option preparsedRoutes: './parsed_routes.json'.

    Re-evaluating on change

    For now, you must restart your dev-server if you change the Rails routes in config/routes.rb. A watch option may be considered in the future.

    Disclaimer

    This library doesn't implement all Rails features:

    • Route constraints. Unfortunately, this lib can't respect constraints since they allow procs
    • Rails treats empty strings as valid values for named params, so they are not considered "missing" if the named param is required. Instead, this lib does treat empty strings as "missing" and may throw an error if the named param is required
    • Rails routes do special encoding on Hash/Array named params e.g.
      # Route definition:
      get 'foo/:bar', as: :foo
      # Call this route with a Hash/Array for `bar`
      foo_path(bar: ['bar', { foo: 'buzz', wow: 123 }])
      # => /foo/bar%2Ffoo=buzz&wow=123
      This lib will instead ignored named params with Hash/Array values.

    This library has only been tested with Rails 6.0.3 and uses internal Rails APIs that are subject to change.

    This library allows you to expose Rails route definitions to frontend code, which may create potential security concerns if you rely on route secrecy.

    ⚠️ Use in production at your own risk! ⚠️

    License

    MIT

    Install

    npm i rails.macro

    DownloadsWeekly Downloads

    381

    Version

    0.2.0

    License

    MIT

    Unpacked Size

    22.2 kB

    Total Files

    8

    Last publish

    Collaborators

    • wyattades