'
+ end
+ end
+ else
+ ast.children.each_with_index do |child, index|
+ if ast.type != :str and child.is_a? String and child =~ /\A[!-~]+\z/
+ output << " :#{child}"
+ else
+ output << " #{Ruby2JS.nil == child ? 'nil' : child.inspect}"
+ end
+ output << ',' unless index == ast.children.length - 1
+ end
+ output << ")#{tail}#{',' unless last}"
+ output << ' ' if last
+ end
+
+ output << '
'
+
+ return output
+ end
+
+ # remove editor on disconnect
+ def teardown()
+ element.querySelector('.editor.ruby').remove()
+ end
+end
diff --git a/demo/editor.js b/demo/editor.js
index 50296832..407c3be2 100644
--- a/demo/editor.js
+++ b/demo/editor.js
@@ -1,12 +1,12 @@
-
-
-
import {EditorView} from "@codemirror/view"
import {StreamLanguage} from "@codemirror/stream-parser"
import {ruby} from "@codemirror/legacy-modes/mode/ruby"
+import {javascript} from "@codemirror/lang-javascript"
// following is from basicSetup, but it specifically EXCLUDES autocompletion
-// because, frankly, it is annoying.
+// because, frankly, it is annoying. It also excludes folding partly because
+// it is only available to non-legacy languages, and partly because it isn't
+// all that useful for this use case.
import {keymap, highlightSpecialChars, drawSelection, highlightActiveLine} from "@codemirror/view"
import {EditorState, Prec} from "@codemirror/state"
import {history, historyKeymap} from "@codemirror/history"
@@ -61,7 +61,7 @@ const setup = [
lineNumbers(),
highlightSpecialChars(),
history(),
- foldGutter(),
+ // foldGutter(),
drawSelection(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
@@ -77,49 +77,44 @@ const setup = [
...defaultKeymap,
...searchKeymap,
...historyKeymap,
- ...foldKeymap,
+ // ...foldKeymap,
...commentKeymap,
// ...completionKeymap,
...lintKeymap
])
]
-// create an editor below the textarea, then hide the textarea
-let textarea = document.querySelector('textarea.ruby');
-let editorDiv = document.createElement('div');
-editorDiv.classList.add('editor');
-textarea.parentNode.insertBefore(editorDiv, textarea.nextSibling);
-textarea.style.display = 'none';
-
-// create an editor below the textarea, then hide the textarea
-let editor = new EditorView({
- state: EditorState.create({
- extensions: [
- setup,
- StreamLanguage.define(ruby),
- EditorView.updateListener.of(update => {
- if (update.docChanged) {
- textarea.value = update.state.doc.toString();
- let event = new MouseEvent('click', { bubbles: true, cancelable: true, view: window });
- document.querySelector('input[type=submit]').dispatchEvent(event)
- }
- })
- ]
- }),
- parent: editorDiv
-});
-
-// focus on the editor
-editor.focus();
+globalThis.CodeMirror = class {
+ static rubyEditor(parent, notify=null) {
+ return new EditorView({
+ state: EditorState.create({
+ extensions: [
+ setup,
+ StreamLanguage.define(ruby),
+ EditorView.updateListener.of(update => {
+ if (notify && update.docChanged) {
+ notify(update.state.doc.toString())
+ }
+ })
+ ]
+ }),
+ parent
+ })
+ }
-// first submit may come from the livedemo itself; if that occurs
-// copy the textarea value into the editor
-let submit = document.querySelector('input[type=submit]');
-submit.addEventListener('click', event => {
- if (!textarea.value) return;
- if (editor.state.doc.length) return;
+ static jsEditor(parent) {
+ return new EditorView({
+ state: EditorState.create({
+ doc: 'content',
+ extensions: [
+ setup,
+ javascript(),
+ EditorView.editable.of(false)
+ ]
+ }),
+ parent
+ })
+ }
+}
- editor.dispatch({
- changes: {from: 0, to: editor.state.doc.length, insert: textarea.value}
- })
-}, {once: true});
+document.body.dispatchEvent(new CustomEvent('CodeMirror-ready'))
diff --git a/demo/litelement.js b/demo/litelement.js
new file mode 100644
index 00000000..9f3a14a9
--- /dev/null
+++ b/demo/litelement.js
@@ -0,0 +1,5 @@
+import { LitElement, html, css } from 'lit-element';
+
+globalThis.LitElement = LitElement;
+globalThis.html = html;
+globalThis.css = css;
diff --git a/demo/livedemo.js.rb b/demo/livedemo.js.rb
new file mode 100644
index 00000000..89bc93ae
--- /dev/null
+++ b/demo/livedemo.js.rb
@@ -0,0 +1,125 @@
+async {
+
+ # This superclass is intended for Stimulus controllers that not only
+ # connect to Stimulus, but pair with each other. Subclasses of
+ # DemoController don't define connect methods, instead they define
+ # setup methods. Subclasses that initiate pairing define source methods.
+ # Subclasses that expect to be targets define pair methods. A
+ # findController method is defined to help find sources.
+ #
+ # Examples: OptionsController sends options to RubyControllers.
+ # RubyControllers send scripts to JSControllers.
+ #
+ # codemirror_ready and ruby2js_ready methods can be used to wait for these
+ # scripts to load before proceeding.
+ #
+ class DemoController < Stimulus::Controller
+ attr_reader :source, :targets
+
+ # subclasses are expected to override this method
+ def setup()
+ end
+
+ # if subclasses override this, they need to call super. Most should
+ # just override setup instead.
+ async def connect()
+ @targets = Set.new()
+ await setup()
+ source.pair(self) if source
+
+ application.controllers.select do |controller|
+ if controller.source == self
+ controller.targets.add self
+ controller.pair(self)
+ end
+ end
+ end
+
+ # override this method in classes that initiate pairing
+ def source
+ @source = nil
+ end
+
+ # logic to be executed when the second half of the pair connects to
+ # Stimulus, independent of the order of the connection to Stimulus.
+ # if subclasses override this method, they need to call super.
+ def pair(component)
+ @targets.add component
+ end
+
+ # logic to be executed when the second half of the pair disconnects.
+ # Stimulus may reuse controller objects, so a controller needs to
+ # return to a state where they seek out new sources
+ def unpair(component)
+ @targets.delete component
+ @source = nil if @source == component
+ end
+
+ # subclasses can override this method
+ def teardown()
+ end
+
+ # unpair all partners (sources and targets)
+ # if subclasses override this method, they need to call super.
+ # Generally, it is best to override teardown instead.
+ def disconnect()
+ @source.unpair(self) if @source
+
+ application.controllers.select do |controller|
+ controller.unpair(self) if controller.targets.has(self)
+ end
+
+ teardown()
+ end
+
+ # utility method, primarily to be used by target attribute accessors.
+ # As the name indicates, it will find a controller that is either
+ # connected to a given element or of a given type, or both.
+ def findController(element: nil, type: nil)
+ return application.controllers.find do |controller|
+ (not element or controller.element == element) and
+ (not type or controller.is_a? type)
+ end
+ end
+
+ # wait for ruby2js.js to load and Ruby2JS to be defined.
+ def ruby2js_ready
+ Promise.new do |resolve, reject|
+ if defined? Ruby2JS
+ resolve()
+ else
+ document.body.addEventListener 'Ruby2JS-ready', resolve, once: true
+ end
+ end
+ end
+
+ # wait for codemirror.js to load and CodeMirror to be defined.
+ def codemirror_ready
+ Promise.new do |resolve, reject|
+ if defined? CodeMirror
+ resolve()
+ else
+ document.body.addEventListener 'CodeMirror-ready', resolve, once: true
+ end
+ end
+ end
+ end
+
+ #############################################################################
+
+ require_relative './controllers/options_controller'
+ require_relative './controllers/ruby_controller'
+ require_relative './controllers/js_controller'
+ require_relative './controllers/combo_controller'
+ require_relative './controllers/eval_controller'
+
+ application = Stimulus::Application.start()
+ application.register("options", OptionsController)
+ application.register("ruby", RubyController)
+ application.register("js", JSController)
+ application.register("combo", ComboController)
+ application.register("eval", EvalController)
+
+ globalThis.Stimulus = Stimulus
+
+}[]
diff --git a/demo/livedemo.opal b/demo/livedemo.opal
deleted file mode 100644
index 2ef35988..00000000
--- a/demo/livedemo.opal
+++ /dev/null
@@ -1,170 +0,0 @@
-require 'native'
-require 'ruby2js/demo'
-require 'patch.opal'
-require 'filters.opal'
-
-$document = $$.document
-jsdiv = $document.querySelector('div#js')
-jspre = jsdiv.querySelector('pre')
-convert = $document.querySelector('input.btn')
-ast = $document.getElementById('ast')
-parsed = $document.getElementById('parsed')
-filtered = $document.getElementById('filtered')
-
-# convert query into options
-def parse_options
- options = {filters: []}
- search = $document[:location].search
- return options if search == ''
- $load_error = nil
-
- search[1..-1].split('&').each do |parameter|
- name, value = parameter.split('=', 2)
- value = value ? $$.decodeURIComponent(value.gsub('+', ' ')) : true
-
- case name
- when :filter
- name = :filters
- value = value.split(',').map {|name| Filters[name]}.compact
- when :identity
- value = name
- name = :comparison
- when :nullish
- value = name
- name = :or
- when :autoimports
- value = Ruby2JS::Demo.parse_autoimports(value)
- when :defs
- value = Ruby2JS::Demo.parse_defs(value)
- when /^es\d+$/
- value = name[2..-1].to_i
- name = :eslevel
- end
-
- raise ArgumentError.new($load_error) if $load_error
-
- options[name] = value
- end
-
- options
-end
-
-# convert AST into displayable form
-def walk(ast, indent='', tail='', last=true)
- return [] unless ast
- output = ["
diff --git a/docs/src/_components/docs/note.liquid b/docs/src/_components/docs/note.liquid
index 000bbe6c..2f938fcf 100644
--- a/docs/src/_components/docs/note.liquid
+++ b/docs/src/_components/docs/note.liquid
@@ -1,12 +1,3 @@
----
-name: Documentation Note
-description: This is used to highlight certain tips or warnings within the documentation pages.
-variables:
- title?: [string, Title for the note]
- type?: [string, Specify `warning` for a red note]
- extra_margin?: boolean
- content: markdown
----
{%- if extra_margin -%}
{%- assign extra_margin_class = "my-10" -%}
{%- endif -%}
diff --git a/docs/src/_components/docs/note.preview.html b/docs/src/_components/docs/note.preview.html
deleted file mode 100644
index 018b8bad..00000000
--- a/docs/src/_components/docs/note.preview.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
Note without Title:
-
- {% rendercontent "docs/note" %}
- I am a note!
- {% endrendercontent %}
-
-
Note with Title:
-
- {% rendercontent "docs/note", title: "This is a test" %}
- I am a note!
- {% endrendercontent %}
-
-
Note with Title, Warning Type:
-
- {% rendercontent "docs/note", title: "This is another test", type: "warning" %}
- I am also a note! :)
- {% endrendercontent %}
-
-
Note with Markdown Title & Extra Margin:
-
- {% rendercontent "docs/note", extra_margin: true %}
- {% with title %}This is a test (_with_ ~~feeling~~ formatting){% endwith %}
-
- I am a note!
- {% endrendercontent %}
-
diff --git a/docs/src/_components/docs/toc.liquid b/docs/src/_components/docs/toc.liquid
index eac8e36b..0ba86d9e 100644
--- a/docs/src/_components/docs/toc.liquid
+++ b/docs/src/_components/docs/toc.liquid
@@ -1,10 +1,3 @@
----
-name: Table of Contents
-description: Shows in the sidebar of the Documentation layout
-variables:
- site: [object, Site liquid drop]
- page: [object, Page liquid drop]
----
- {% render "examples/toc", examples: site.examples, page: page %}
+ {% render "examples/toc", examples: collections.examples.resources, page: page %}
diff --git a/docs/src/_layouts/home.liquid b/docs/src/_layouts/home.liquid
index a03d7872..29abc702 100644
--- a/docs/src/_layouts/home.liquid
+++ b/docs/src/_layouts/home.liquid
@@ -3,11 +3,6 @@ layout: default
---
{% rendercontent "shared/page_layout" %}
- {% if bridgetown.version contains "beta" %}
-
- Youāre looking at the beta site for the latest pre-release of Bridgetown.Switch to the current production site.
-
- {% endif %}
{% rendercontent "shared/box" %}
{% svg images/ruby2js-square.svg %}
@@ -22,7 +17,7 @@ layout: default
News Updates
- {% for post in site.posts limit:4 %}
+ {% for post in collections.posts.resources limit:4 %}
{% render "content/news_item", post: post, authors: site.data.authors %}
{% endfor %}
diff --git a/docs/src/_layouts/post.liquid b/docs/src/_layouts/post.liquid
index 84f9f759..aa0b413b 100644
--- a/docs/src/_layouts/post.liquid
+++ b/docs/src/_layouts/post.liquid
@@ -21,7 +21,7 @@ layout: default
{% if page.previous %}
Previous
-
+ {{ page.previous.title }}
@@ -31,7 +31,7 @@ layout: default
{% if page.next %}
Next
-
+ {{ page.next.title }}
diff --git a/docs/src/_posts/2021-01-19-new-live-demo.md b/docs/src/_posts/2021-01-19-new-live-demo.md
index 7fd77220..efde107d 100644
--- a/docs/src/_posts/2021-01-19-new-live-demo.md
+++ b/docs/src/_posts/2021-01-19-new-live-demo.md
@@ -6,7 +6,7 @@ categories: updates
author: rubys
---
-Check it out [here](https://www.ruby2js.com/demo).
+Check it out [here](https://www.ruby2js.com/demo?preset=true).
Because the conversion from Ruby to JavaScript is fast and runs right in your
browser, this demo will update the converted output keystroke-by-keystroke as
diff --git a/docs/src/_posts/2021-02-24-new-stimulus-design-pattern.md b/docs/src/_posts/2021-02-24-new-stimulus-design-pattern.md
new file mode 100644
index 00000000..b79ce074
--- /dev/null
+++ b/docs/src/_posts/2021-02-24-new-stimulus-design-pattern.md
@@ -0,0 +1,98 @@
+---
+layout: post
+title: New Stimulus Design Pattern?
+subtitle: Editors, Options, and Results, oh my!
+categories: updates
+author: rubys
+---
+
+As far as I know, this is a new design pattern for Stimulus. At the very
+least, it isn't something I was able to readily find with Google searches.
+
+First, let's gets some standard stuff out of the way. The
+[Ruby2JS.com](https://ruby2js.com) is built using the
+[Bridgetown](https://www.bridgetownrb.com/) static site generator.
+[Opal](https://opalrb.com/) is used to generate the bulk of the scripts to be
+executed. [HTTP
+caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching) ensures
+that these scripts are only downloaded when they change. [Turbo](Turbo)
+ensures that these scripts are only loaded once per site visit.
+[Stimulus](Stimulus) associates HTML elements with controllers.
+
+All standard stuff so far.
+
+[Ruby2JS.com](https://ruby2js.com) has three controllers on the page. The
+first is a Ruby editor. The second is a read-only JavaScript view. And the
+third is invisible, but runs the JavaScript which outputs to the console log.
+Other pages have these same three controllers with different arrangements
+and/or different data, for example the [Stimulus
+introduction](https://www.ruby2js.com/examples/stimulus/) and the [React Simple
+Component](https://www.ruby2js.com/examples/stimulus/). The [Ruby2JS
+Demo](https://www.ruby2js.com/demo) has a Ruby editor and a JS ouput, but
+doesn't have a results controller and adds an options controller.
+
+The source to the controllers can be found in
+[GitHub](https://github.com/ruby2js/ruby2js/tree/master/demo/controllers).
+Unsurprisingly given that these controllers support the Ruby2JS site, they are
+written in Ruby.
+
+But that's not the unique design pattern part.
+
+Look at a Ruby editor on any of the pages mentioned. There isn't really any
+[Actions](https://stimulus.hotwire.dev/reference/actions),
+[Targets](https://stimulus.hotwire.dev/reference/targets),
+[Values](https://stimulus.hotwire.dev/reference/values), or [CSS
+Classes](https://stimulus.hotwire.dev/reference/css-classes) to speak of.
+
+Instead, updates made in the Ruby editor are sent to *other* controllers.
+A global overview of the design of these pages: the options controller on the
+demo page will update the Ruby controller. The Ruby controller will update
+both the JavaScript and evaluation results controllers. And there is even a
+case where the evaluation results controller will update the Ruby controller,
+but we will get to that in a minute.
+
+All of this is accomplished by subclassing a [common base
+class](https://github.com/ruby2js/ruby2js/blob/master/demo/livedemo.js.rb) and
+overridding the `source` method with calls to a `findController` method. The
+`findController` method unsurprisingly searches the `application.controllers`
+array. This base class also takes care of connecting sources with targets
+indpendent of the order in which the controllers connect.
+
+Once a source is paired with potentially multiple targets, messages pass via
+standard method calls and/or attribute accessors (getters and setters in
+JavaScript terms).
+
+As an example,
+[here](https://github.com/ruby2js/ruby2js/blob/master/demo/controllers/ruby_controller.js.rb#L96)
+are the lines of code where `Ruby2JS.convert` is called and the resulting JavaScript is sent to each target.
+
+The JSController's [implementation of the `contents=`
+method](https://github.com/ruby2js/ruby2js/blob/91f75c3b83026bb0027c6fb390dafdd15a6ab6a9/demo/controllers/js_controller.js.rb#L38)
+will dispatch the content to the jsEditor.
+
+The EvalController's [implementation of the `contents=`
+method](https://github.com/ruby2js/ruby2js/blob/master/demo/controllers/eval_controller.js.rb)
+will load the script into a `script` element and append it to the document.
+
+An interesting detail: if you bring up the [Stimulus
+Introduction](https://www.ruby2js.com/examples/stimulus/) page and click on the
+JavaScript tab you will see different results in Safari than you would in see
+in Chrome, Firefox, or Microsoft Edge. Safari doesn't yet support [static
+public fields](https://github.com/tc39/proposal-static-class-features), so an
+assignment statement after the class definition is used instead.
+
+The way this works is that the Ruby souce code is initially converted to
+JavaScript using the [ES2022
+option](https://www.ruby2js.com/docs/eslevels#es2022-support), and the results
+are sent to the evaluation controller. The evaluation controller captures the
+syntax error and given that this occurred on the very first update it will
+update the `options` in the Ruby Controller, triggering another conversion, the
+results of which are sent to both the JS and Eval controllers.
+
+While this usage is quite different than the traditional application of
+Stimulus, the end result is comparable: a site consisting entirely of static
+HTML augmented with a small number of `data-` attributes that cause the
+controllers to activate.
+
+I'm quite curious if others have seen this usage of Stimulus before, if they
+find it useful, or have any suggestions.
diff --git a/docs/src/_posts/2021-03-22-register.md b/docs/src/_posts/2021-03-22-register.md
new file mode 100644
index 00000000..e41bc5a1
--- /dev/null
+++ b/docs/src/_posts/2021-03-22-register.md
@@ -0,0 +1,59 @@
+---
+layout: post
+title: Register
+subtitle: Just like @babel/register, but for Ruby2JS!
+categories: updates
+author: rubys
+---
+
+Originally, CoffeeScript had a
+[coffeescript/register](https://coffeescript.org/#nodejs-usage) module, which
+would automatically compile CoffeeScript files on the fly.
+
+Originally, Babel later adopted it, producing the
+[@babel/register](https://babeljs.io/docs/en/babel-register/) module, which
+will automatically compile `.es6`, `.es`, `.jsx`, `.mjs`, and `.js` files the
+fly.
+
+Now, Ruby2JS has adopted the idea, and in fact uses the
+[same hook](https://github.com/ariporad/pirates#readme), and makes available
+the [@ruby2js/register](https://www.npmjs.com/package/@ruby2js/register)
+module, which will automatically compile `.rb` files on the fly.
+
+## Demonstration
+
+Let's start with the simple and somewhat canonical greet function in Ruby:
+
+```ruby
+# greet.rb
+def greet(name)
+ puts "Hello #{name}!"
+end
+```
+
+Now add a main program, in JavaScript, which registers and configures Ruby2JS,
+and then calls out to the above script:
+
+```javascript
+// main.js
+require('@ruby2js/register')({
+ options: {
+ eslevel: 2021,
+ autoexports: 'default',
+ filters: ['cjs', 'functions']
+ }
+})
+
+const greet = require('./greet')
+
+greet('World')
+```
+
+All that's left is to install the module and go:
+
+```sh
+yarn add @ruby2js/register
+node main.js
+```
+
+Enjoy!
diff --git a/docs/src/_posts/2023-02-20-future-of-ruby2js.md b/docs/src/_posts/2023-02-20-future-of-ruby2js.md
new file mode 100644
index 00000000..cdef755b
--- /dev/null
+++ b/docs/src/_posts/2023-02-20-future-of-ruby2js.md
@@ -0,0 +1,108 @@
+---
+layout: post
+title: Ruby2JS 5.1, esbuild, and a Peek at the Future
+subtitle: Improving the quality of our developer experience while ensuring future maintainability and health of the project.
+categories: updates
+author: jared
+---
+
+At long last, **Ruby2JS 5.1 is here!** It comes packed with several very welcome features:
+
+* A brand-new Ruby-based configuration DSL
+* A "preset" option for sane defaults
+* Magic comment support for sharing portable Ruby2JS code (based on the preset)
+
+In addition, the [esbuild plugin](https://github.com/ruby2js/ruby2js/tree/master/packages/esbuild-plugin) has reached 1.0 status and has been re-architected to use the Ruby version of the compiler rather than the Node (aka Opal) version. Why? Performance and modern language compatibility.
+
+Let's dig in a bit on all these new features! And keep reading as well for an update on the status of Ruby2JS and its open source governance.
+
+## Ruby-based Configuration
+
+Ruby2JS, despite being a language transpiler usually needing some degree of configuration, never had a canonical format for project-based configuration. Until today!
+
+You can add `config/ruby2js.rb` to your project and both the Ruby API and the CLI tool will automatically detect its presence and use it for configuration. In addition, you can specify the location of a configuration file manually if you prefer to use a different filename or folder.
+
+The configuration format is very simple and easy to work with, just like other configuration formats such as Puma. You can read all about it in [the documentation here](/docs/options). But that's not all!
+
+## A Preset Configuration for Sane Defaults
+
+We believe most Ruby2JS code would benefit greatly from transpiling to a modern ECMAScript version (namely ES2021), using a few common filters such as [Functions](/docs/filters/functions), [ESM](/docs/filters/esm), and [Return](/docs/filters/return), using identity-based comparison operators (aka `==` becomes `===`), and automatically underscoring instance variables (`@x` becomes `this._x`).
+
+So that's exactly what we built. By simply adding `preset` to a configuration file or passing `preset: true` or `--preset` to the Ruby API or CLI respectively, you can gain all the benefits of these common options. In addition, by writing your code to use the preset, you can ensure wider compatibility between projects and between tutorials/code samples and production workflows.
+
+Even more to that point, we've introduced the idea of a "magic comment". By adding `# ruby2js: preset` to the top of a `.js.rb` file, you instruct Ruby2JS to use the preset configuration for that file. You can even add additional filters right in the magic comment, change the ES level, or disable a filter that comes with the preset. [Read the documentation here.](/docs/options)
+
+We believe all of these features now mean that Ruby2JS code is easier to teach and easier to share. We took your feedback about these issues in the past to heart and are trying to make improvements for better DX.
+
+## esbuild is faster using Ruby?!
+
+[esbuild](https://esbuild.github.io) is a modern, fast, and easily-configured frontend bundling tool, and we want to support it as a "first-party" citizen in the Ruby2JS ecosystem.
+
+esbuild is fast because its core code is written in Go, not JavaScript. Along similar lines, we discovered something extraordinary when testing the beta of the esbuild bundling package. When we tried spawning a process to transpile a file using the Ruby version of Ruby2JS, rather than the Opal/Node-powered JavaScript version, we discovered that it was actually faster! And not just a little bit fasterā¦[almost 2x faster!](https://github.com/ruby2js/ruby2js/discussions/170)
+
+Transpiling using the Ruby version also has the added benefit that the syntax of the code you write on the frontend matches the version of Ruby your project uses overall. Before, you could be using Ruby 3.2 in your overall stack but the "version of Ruby" (in fact the version of Opal) might be older. In fact, there's actually an outstanding issue that the version of Opal used to generate the JavaScript version of Ruby2JS is locked to an older version of Opal due to bugs introduced when upgrading. More on that belowā¦
+
+So, all in all, it makes sense to standardize around Ruby, even when using esbuild. After all, I would be shocked if anyone had an interest in writing Ruby2JS frontend code and using esbuild as a bundling tool who _didn't_ actually have Ruby installed for use in a Ruby-based web project. So why rely on Opal/Node if we don't have to?
+
+## The Future of Ruby2JS
+
+Which brings us to a broader topic: the future of this project.
+
+[Sam Ruby](http://intertwingly.net/blog/), a well-known figure in the Ruby community and the brains behind Ruby2JS for many years, stepped down as an active maintainer in 2021. This effectively left me as the sole maintainer of Ruby2JSāand not only the sole maintainer, but by and large the _only_ active contributor to Ruby2JS.
+
+I had started contributing to the project in 2020, and through much trial-and-error and helpful mentorship from Sam, I eventually learned my way around the codebase enough to help usher in a few improvements to the feature set as well as set up this Bridgetown documentation site. It was an amazing experience, and I'd like to thank Sam publicly for his trust in (and patience with!) me.
+
+**Here's the deal:** I love this project and sincerely hope to continue to see it fill an important role in the niche of "Ruby frontend web developers" as [I like to consider myself to be](https://www.fullstackruby.dev).
+
+But the fact of the matter is I have my hands very full with the [Bridgetown project](https://www.bridgetownrb.com), and my ability to devote much attention to Ruby2JS is limited. In addition, what attention I _can_ devote to Ruby2JS is mostly relegated to the use cases for which Ruby2JS is personally useful to me. I'm not saying that's ideal. It just is what it is.
+
+So because I _primarily_ use Ruby2JS for writing web components (usually using [Lit](https://lit.dev)) and bundling using esbuild, that is the principal scope I intend to maintain going forward. I would also argue that it's a very ergonomic and obvious way to make the most of Ruby2JS as a web developer building projects (as I do) with Rails or Bridgetown.
+
+Thus I have decided to deprecate quite a number of features ("filters" and other integration points) which will be removed officially by the time Ruby2JS 6.0 is released. I don't have any immediate release date for that, but for the sake of discussion let's assume it will happen towards the end of this year.
+
+The list of deprecated features is as follows:
+
+* jQuery filter
+* JSX filter
+* matchAll filter (only necessary for ES < 2020)
+* minitest-jasmine filter
+* Preact filter
+* React filter
+* Require filter
+* Underscore filter
+* Vue filter
+* CGI server integration
+* ExecJS integration
+* Haml integration
+* Rails integration (outside of the new "jsbundling" esbuild pipeline)
+* Sinatra integration (as a view template type)
+* Sprockets integration
+* "use strict" option (all modern ESM code is considered strict anyway)
+* Webpack loader
+
+In addition, I am actively looking for a maintainer to own the [Rollup and Vite plugins](https://github.com/ruby2js/ruby2js/tree/master/packages) for transpiling Ruby2JS code via those bundlers. I don't myself use Rollup/Vite, but I understand they're quite popular as an alternative to using esbuild directly. They still need to be upgraded to use Ruby rather than Node for the transpilation (like esbuild).
+
+If any of these stand out to you as having a _serious impact_ on current production workflows, let's talk about possible strategiesāeither migrations to a better solution, or extracting features out to their own repo. For example, I simply have no interest in maintaining a React filter. I don't recommend people adopt React in new projects as a general rule, and if they do, then I recommend they use the Next.js framework because it just makes React much, much better. React + Ruby2JS is not a solution I can, in all good conscience, promote.
+
+**However**, if someone _really_ needs a React filter long into the future for their projects, I'd be happy to help extract this functionality out to a separate gem with fresh open source governance. Again, that holds true for any of the deprecated features listed above.
+
+The alternative to this approach, _and one I strongly considered_, would be for me to step down myself as a maintainer of Ruby2JS and seek someone else in the community to come onboard instead. I decided against this move for several reasons:
+
+* I still really enjoy writing Ruby2JS code and singing the praises of the project.
+* This is a pretty gnarly codebase to wrap your head around, and I had the benefit of being mentored by Sam Ruby himself. For someone to come in fresh and begin to make sizable contributions, that's a tall orderāespecially with the variety of JS packages now in the project as well. There's also a fair bit of technical debt that has no clear upgrade path at present. For example:
+* We're locked into an old version of Opal for compiling the JavaScript version of the Ruby2JS compiler. Over time, this will result in the Node version of Ruby2JS falling farther and farther behind relative to its native Ruby counterpart. It may mean that, at some future date, we sunset this other than for trivial use (such as the online interactive demos)āor we figure out why the transpiler is broken on newer version of Opal which will take a considerable amount of time (I've failed after two separate attempts).
+* And as mentioned above, nobody else _has_ been contributing with any frequency.
+
+So ultimately I gladly intend on continuing to act as lead maintainer for Ruby2JSāwhile significantly reducing the scope of the project down to what (in my opinion) it is best suited for and what I best understand. And beyond that, any additional features are quite welcome to be handled via Ruby2JS "plugins" by others in the community.
+
+## The Future of Ruby on the Frontend
+
+This brings me to my final and most general point regarding where we, the Ruby web developer community, are headed.
+
+I have become fairly convinced with the release of [Ruby 3.2 and its brand-new WebAssembly support](https://ruby.github.io/ruby.wasm/) that **the future of Ruby on the frontend is Wasm**. This means [I forsee a day](https://www.fullstackruby.dev/podcast/7/) when writing _actual Ruby code_ and directly executing it in the browser will be feasible for a considerable number of ambitious projects.
+
+In effect, this will render both Ruby2JS _and_ Opal obsolete. Why try to fiddle with various compile-to-JavaScript languages and syntaxes when you can simply write **Ruby** and run it?! That's obviously the ideal, even if today there are significant hurdles to overcome (most notably large Wasm payloads and simplistic Ruby<->JS APIs).
+
+So I look forward to that day, even if it's still a few years away. In the meantime, I'm thrilled I can continue to write frontend code in a Ruby-like way using Ruby2JS. And I hope you are as well.
+
+**Questions? Ideas? Suggestions?** Hop in our [GitHub Discussions](https://github.com/ruby2js/ruby2js/discussions) and let us know! And if you find an issue with Ruby2JS 5.1, please file an issue report so we can make Ruby2JS. Better yet, if you'd like to become a contributor yourself to Ruby2JS, we welcome your involvement and support!
diff --git a/docs/src/authors/jared.md b/docs/src/authors/jared.md
new file mode 100644
index 00000000..f63bc369
--- /dev/null
+++ b/docs/src/authors/jared.md
@@ -0,0 +1,20 @@
+---
+layout: default
+title: Jared White
+paginate:
+ collection: posts
+---
+
+{% rendercontent "shared/page_layout" %}
+