Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rmacklin/sprockets-bumble_d
Sprockets plugin to transpile modern javascript using Babel, useful while migrating to ES6 modules
https://github.com/rmacklin/sprockets-bumble_d
asset-pipeline babel babel-plugin es2015 es2015-modules es6-modules incremental javascript-globals migration rails sprockets sprockets-plugin sprockets-transformer umd-modules
Last synced: 9 days ago
JSON representation
Sprockets plugin to transpile modern javascript using Babel, useful while migrating to ES6 modules
- Host: GitHub
- URL: https://github.com/rmacklin/sprockets-bumble_d
- Owner: rmacklin
- License: mit
- Created: 2016-09-10T01:53:46.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2022-02-16T20:44:40.000Z (over 2 years ago)
- Last Synced: 2024-10-14T15:41:53.625Z (22 days ago)
- Topics: asset-pipeline, babel, babel-plugin, es2015, es2015-modules, es6-modules, incremental, javascript-globals, migration, rails, sprockets, sprockets-plugin, sprockets-transformer, umd-modules
- Language: Ruby
- Homepage:
- Size: 149 KB
- Stars: 32
- Watchers: 3
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# Sprockets::BumbleD
This gem provides a plugin for [Sprockets] that enables you to transpile modern
javascript using [Babel].A primary use case for this gem is to incrementally migrate your
Sprockets-powered javascript codebase to ES6 modules. This works by
[transforming] them to [UMD] modules that preserve your existing global variable
references (hence the name: Babel + UMD = BumbleD). Once an entire subtree of
your javascript module tree is written in ES6 modules, this frees you up to
bundle that javascript using a more modern tool (e.g. [rollup] or [webpack]).That said, this gem can be used for general purpose babel transpilation within
the Sprockets pipeline.[Babel]: https://babeljs.io
[rollup]: https://rollupjs.org
[Sprockets]: https://github.com/rails/sprockets
[transforming]: https://en.wikipedia.org/wiki/Bumblebee_(Transformers)
[UMD]: https://github.com/umdjs/umd
[webpack]: https://webpack.js.org/## Background
ES6 modules are the new standard. The syntax is great: it's concise and
straightforward, and the static and explicit nature of `import` and `export`
statements make your code a complete spec of its dependencies and how to
resolve them. This means that moving to ES6 modules also makes moving away
from Sprockets `//= require` directives for javascript bundling (and Sprockets
in general) much easier.But when faced with a large legacy codebase, it's not feasible to convert
everything to ES6 modules at once. Thus, the goal is to be able to convert
module-by-module from explicitly exporting a global variable (and depending on
other modules' global variables) to following the ES6 module format, which we'll
then transpile to UMD that is compatible with non-converted code (e.g. existing
UMD modules and plain old global-dependent scripts).Sprockets::BumbleD accomplishes this goal by providing a Sprockets transformer
that acts on `.es6` files (this file extension is
[configurable](#customizing-the-file-extension)). These files are transpiled by
Babel and the [ES2015 -> UMD modules transform] plugin, preserving any globals
that you've [registered](#registering-globals).[ES2015 -> UMD modules transform]: https://github.com/babel/babel/tree/v7.3.4/packages/babel-plugin-transform-modules-umd
## Setup
### Installation
1. Add `gem 'sprockets-bumble_d'` to your `Gemfile` (or add a gemspec
dependency to an [inline engine](#inline-rails-engines) in your app) and
run `bundle install`.
2. Run `npm install --save @babel/core @babel/plugin-external-helpers @babel/plugin-transform-modules-umd @babel/preset-env`
to install the modules for the default babel config. If you want to
[customize the babel options](#customizing-your-babel-options), install any
additional plugins and presets you want.
3. Generate the [external helpers] and `//= require` them in at the beginning
of your application manifest or pull them in with a separate script tag. This
step is of course unnecessary if you won't be using the external-helpers
plugin, but it's highly recommended that you do (to avoid inlining them
everywhere, which unnecessarily bloats the bundle sent to the browser).[external helpers]: https://babeljs.io/docs/plugins/external-helpers
### Basic configuration
In `config/application.rb`:
```ruby
extend Sprockets::BumbleD::DSLconfigure_sprockets_bumble_d do |config|
config.babel_config_version = 1
end
```If you are not using Rails, you must also configure the `root_dir` (see below).
### Customizing the `root_dir`
Sprockets::BumbleD needs to know the directory from which node modules are to
be resolved (typically, wherever your `package.json` resides). If you're using
Rails, this defaults to `Rails.root.to_s`. If you are not using Rails, or if
your node_modules folder is not inside `Rails.root`, you **must** configure the
`root_dir` setting! For example, if you are configuring Sprockets::BumbleD in
the file `config/application.rb` and your `package.json` is located in the
parent directory, use:```ruby
configure_sprockets_bumble_d do |config|
config.root_dir = File.expand_path('..', __dir__)
config.babel_config_version = 1
end
```If it's in a specific subdirectory, specify that directory instead.
Sprockets::BumbleD doesn't care, as long as its node `require` statements will
resolve from that directory.### Customizing your babel options
By default you get [@babel/preset-env], [@babel/plugin-external-helpers], and
[@babel/plugin-transform-modules-umd]. If you want to customize this with
different plugins and presets, specify them in the
`configure_sprockets_bumble_d` block with the `babel_options` setting. Note
that (because it's central to the purpose of this gem)
@babel/plugin-transform-modules-umd is included for you (unless
you [set `transform_to_umd` to `false`](#do-i-have-to-transpile-to-umd-modules))
and configured to use the [registered globals](#registering-globals), so this
plugin does not need to be specified when you override the default plugins.[@babel/preset-env]: https://babeljs.io/docs/en/babel-preset-env
[@babel/plugin-external-helpers]: https://babeljs.io/docs/en/babel-plugin-external-helpers
[@babel/plugin-transform-modules-umd]: https://babeljs.io/docs/en/babel-plugin-transform-modules-umdFor example:
```ruby
configure_sprockets_bumble_d do |config|
config.babel_config_version = 2
config.babel_options = {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-external-helpers', 'custom-plugin']
}
end
```You can specify any options that are allowed in a `.babelrc` file.
### Customizing the file extension
By default the Sprockets transformer is registered to act on `.es6` files. This
is configurable:
```ruby
configure_sprockets_bumble_d do |config|
config.babel_config_version = 1
config.file_extension = '.babel'
end
```### The `babel_config_version` setting
What's this mysterious `babel_config_version` we're setting in the previous
examples? Good question. Essentially this is intended to be a value that
translates to the composite version of @babel/core and each babel preset
and plugin in your application. It's used to expire the cache for compiled
assets: since different versions of babel and its plugins can result in a
different transpiled output, we want to be able to invalidate the cache
whenever we change our babel configuration. So, when you upgrade @babel/core
or you add/remove/upgrade a babel plugin or preset, you'd increment this
version which will cause the Sprockets transformer's cache key to change.### Philosophy
You should own your babel setup. We want to be able to use the latest versions
of babel and its plugins as soon as they're available, so this gem doesn't
vendor any node modules - it's up to the application to provide those to the
gem. This is what the `root_dir` config is for. It's also why the
`babel_config_version` setting exists.### Registering globals
@babel/plugin-transform-modules-umd includes an `exactGlobals` option that lets
you specify exactly how to transpile any import statements into the global
reference it should resolve to. It also lets you specify what global should be
exported by an ES6 module in the resultant UMD output. (A complete description
is available in [babel PR #3534].)[babel PR #3534]: https://github.com/babel/babel/pull/3534
In `config/application.rb`, after `extend Sprockets::BumbleD::DSL`:
```ruby
register_umd_globals :my_app,
'my/great/thing' => 'MyGreatThing',
'her/cool/tool' => 'herCoolTool'
```Doing this will allow:
```js
import GreatThing from 'my/great/thing';
```
to be transpiled to:
```js
factory(/* ... */ global.MyGreatThing);
```
in the globals branch of the transpiled UMD output. Similarly, the above map
also specifies that the exports of the ES6 module `her/cool/tool` will be
assigned to the `herCoolTool` global.That is, registering these globals provides both:
- a way to depend on existing globals in ES6 modules
- a way to declare the global an ES6 module should export, to be used in
existing UMD modules or direct global referencesAs a corollary, if you are writing a new ES6 module that is only used by other
ES6 modules, you would not need to register a global for that module's export.Exported globals can also be nested objects and the transform will properly
handle creating the necessary prerequisite assignments. For example with this
registration:
```ruby
register_umd_globals :my_app,
'her/cool/tool' => 'Her.Cool.Tool'
```
the compiled `her/cool/tool` module will contain:
```js
global.Her = global.Her || {};
global.Her.Cool = global.Her.Cool || {};
global.Her.Cool.Tool = mod.exports;
```#### Inline Rails engines
If you have a large application, you may have split it into multiple inline
rails engines (as described in [this talk]). Inline engines with their own
assets should own the registration of globals for these assets. This is
supported in Sprockets::BumbleD:[this talk]: https://www.youtube.com/watch?v=-54SDanDC00
in `some_engine/engine.rb`:
```ruby
extend Sprockets::BumbleD::DSLregister_umd_globals :some_engine,
'some_namespace/first_module' => 'SomeNamespace.firstModule',
'some_namespace/second_module' => 'SomeNamespace.secondModule',
'another_thing/mod' => 'anotherModule'
```Since module globals should only be registered in the engine (or top level
application) where the module lives, `register_umd_globals` will raise
`Sprockets::BumbleD::ConflictingGlobalRegistrationError` if a module is
registered a second time. Of course, this still can't prevent you from
registering globals (that had not already been registered) in the wrong engine.### Reminder about Rails reloading
As with any `config` changes, updates to the globals registry are not
reloaded automatically; you must restart your server for the changes to take
effect.### Do I have to transpile to UMD modules?
No, you can transpile to other module formats (e.g. AMD). You'd just be using
less of this gem's API surface area 1. You can set `transform_to_umd` to
`false` in your `configure_sprockets_bumble_d` block, and
[override the default plugins](#customizing-your-babel-options) to use a
different module transform. For example if you're using an AMD loader like
[almond], you could configure modules to be transpiled to AMD like so:[almond]: https://github.com/requirejs/almond
```rb
configure_sprockets_bumble_d do |config|
config.root_dir = File.expand_path('..', __dir__)
config.babel_config_version = 1
config.transform_to_umd = false
config.babel_options = {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-external-helpers', '@babel/plugin-transform-modules-amd']
}
end
```You can reference the [5.0_amd test app](./test/test_apps/5.0_amd) which
demonstrates this in a full application.1 Of course if you're doing this, you wouldn't ever call
`register_umd_globals`## Similar projects
- [babel-schmooze-sprockets] - This takes a similar approach, but it requires
Sprockets 4 (which is still in beta), and it doesn't offer a way to register
globals within inline engines. Additionally, it diverges in
[philosophy](#philosophy) by vendoring some node_modules.
- [sprockets-es6] - This was the common solution for ES6 transpilation within
Sprockets for a while, but it takes a very different approach. Instead of
relying on node and the npm ecosystem, it uses [ruby-babel-transpiler], which
is stuck on babel 5. This means you cannot configure custom babel plugins
(which means you can't use `exactGlobals` to specify what it should transform
globals to in the UMD modules transform).
- [sprockets 4] - This takes the same approach as sprockets-es6 so it suffers
from the same limitations as sprockets-es6[babel-schmooze-sprockets]: https://github.com/fnando/babel-schmooze-sprockets
[ruby-babel-transpiler]: https://github.com/babel/ruby-babel-transpiler
[sprockets-es6]: https://github.com/TannerRogalsky/sprockets-es6
[sprockets 4]: https://github.com/rails/sprockets/tree/v4.0.0.beta4