stimulus-rails 0.2.4 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +107 -37
  4. data/app/assets/javascripts/{stimulus/loaders/autoloader.js → stimulus-autoloader.js} +5 -3
  5. data/app/assets/javascripts/stimulus-importmap-autoloader.js +27 -0
  6. data/app/assets/javascripts/stimulus-loading.js +85 -0
  7. data/app/assets/javascripts/stimulus.js +1948 -0
  8. data/app/assets/javascripts/stimulus.min.js +5 -0
  9. data/app/assets/javascripts/stimulus.min.js.map +1 -0
  10. data/lib/generators/stimulus/USAGE +15 -0
  11. data/lib/generators/stimulus/stimulus_generator.rb +20 -0
  12. data/lib/generators/stimulus/templates/controller.js.tt +7 -0
  13. data/lib/install/app/javascript/controllers/application.js +9 -0
  14. data/lib/install/app/{assets/javascripts → javascript}/controllers/hello_controller.js +1 -1
  15. data/lib/install/app/javascript/controllers/index_for_importmap.js +11 -0
  16. data/lib/install/app/javascript/controllers/index_for_node.js +8 -0
  17. data/lib/install/stimulus_with_importmap.rb +22 -0
  18. data/lib/install/stimulus_with_node.rb +18 -0
  19. data/lib/stimulus/engine.rb +1 -17
  20. data/lib/stimulus/manifest.rb +28 -0
  21. data/lib/stimulus/version.rb +1 -1
  22. data/lib/tasks/stimulus_tasks.rake +33 -9
  23. metadata +22 -20
  24. data/app/assets/javascripts/stimulus/libraries/es-module-shims.js +0 -1
  25. data/app/assets/javascripts/stimulus/libraries/es-module-shims@0.7.1.js +0 -475
  26. data/app/assets/javascripts/stimulus/libraries/stimulus.js +0 -1
  27. data/app/assets/javascripts/stimulus/libraries/stimulus@2.js +0 -1699
  28. data/app/assets/javascripts/stimulus/manifest.js +0 -2
  29. data/app/helpers/stimulus/stimulus_helper.rb +0 -10
  30. data/lib/install/app/assets/javascripts/controllers/index.js +0 -9
  31. data/lib/install/app/assets/javascripts/importmap.json.erb +0 -5
  32. data/lib/install/application.js +0 -1
  33. data/lib/install/stimulus_with_asset_pipeline.rb +0 -23
  34. data/lib/install/stimulus_with_webpacker.rb +0 -10
  35. data/lib/stimulus/importmap_helper.rb +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7b182fb2a48a9405b734b3d8180930f181cf115cb52b7d9061f3f92183d2f3e
4
- data.tar.gz: 663e24b437761d3ff60ad7e78e8dc9ac9191ad91b83935ae95a04100dd6c66c2
3
+ metadata.gz: 0ffd052c8f30205c21d994289dc5f4938f7926db658ac93a9e6264f1ff63810f
4
+ data.tar.gz: bafd756e667681009804b4387a04674d8af0b8a868873978f17bfea2759196de
5
5
  SHA512:
6
- metadata.gz: 0d84fd49c9ad55572e525b5239b289fbe9a01839f67a24453efa875357ab0a0ecb30f39bc43df447d565dd75cda282ab21c653e09ff7db36d1150136b53fcbb0
7
- data.tar.gz: 9e3bfa7a5be5a8457a3612863d7b136fd3c3e514f04367ceacce7327050f2403b69f18305c598cc07311a89cb3fa55c1f4aeb667c0f73e0c76cc15bdc8e684c7
6
+ metadata.gz: 9fb58d85d883bd881b1ad7a9d1478a98f57d26b41f9c844d27ffebe6020f0f7d578952e401e8057ab5555b69df63def79f8900f4b54b6115c83f25afb5080fa3
7
+ data.tar.gz: 386899154a67d5e79c9ca964757ba40d387367ce65309cbb704d1075d898d095f748478198eb30918af3783b9a7a6938e697b75f8bcf3f20bd014146d753f7d3
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2020 Basecamp
1
+ Copyright (c) 2021 Basecamp
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,70 +1,140 @@
1
1
  # Stimulus for Rails
2
2
 
3
- [Stimulus](https://stimulus.hotwire.dev) is a JavaScript framework with modest ambitions. It doesn’t seek to take over your entire front-end in fact, it’s not concerned with rendering HTML at all. Instead, it’s designed to augment your HTML with just enough behavior to make it shine. Stimulus pairs beautifully with Turbo to provide a complete solution for fast, compelling applications with a minimal amount of effort.
3
+ [Stimulus](https://stimulus.hotwired.dev) is a JavaScript framework with modest ambitions. It doesn’t seek to take over your entire front-end in fact, it’s not concerned with rendering HTML at all. Instead, it’s designed to augment your HTML with just enough behavior to make it shine. Stimulus pairs beautifully with Turbo to provide a complete solution for fast, compelling applications with a minimal amount of effort. Together they form the core of [Hotwire](https://hotwired.dev).
4
4
 
5
- Stimulus for Rails makes it easy to use this modest framework with the asset pipeline and ES6/ESM in the browser. It uses the 7kb es-module-shim to provide [importmap](https://github.com/WICG/import-maps) support for all ES6-compatible browsers. This means you can develop and deploy without using any bundling or transpiling at all! Far less complexity, no waiting for compiling.
5
+ Stimulus for Rails makes it easy to use this modest framework with both import-mapped and JavaScript-bundled apps. It relies on either `importmap-rails` to make Stimulus available via ESM or a Node-capable Rails (like via `jsbundling-rails`) to include Stimulus in the bundle. Make sure to install one of these first!
6
6
 
7
- If you want to use Stimulus with a bundler, you should use [Webpacker](https://github.com/rails/webpacker) instead. This gem is purely intended for those who wish to use Stimulus with the asset pipeline using ESM in the browser.
8
7
 
9
8
  ## Installation
10
9
 
10
+ This gem is automatically configured for applications made with Rails 7+ (unless `--skip-hotwire` is passed to the generator). But if you're on Rails 6, you can install it with the installer or manually:
11
+
12
+ ### Installing with installer
13
+
11
14
  1. Add the `stimulus-rails` gem to your Gemfile: `gem 'stimulus-rails'`
12
15
  2. Run `./bin/bundle install`.
13
16
  3. Run `./bin/rails stimulus:install`
14
17
 
15
- If using the asset pipeline to manage JavaScript, the last command will:
18
+ The installer will automatically detect whether you're using an [import map](https://github.com/rails/importmap-rails) or [JavaScript bundler](https://github.com/rails/jsbundling-rails) to manage your application's JavaScript. If you're using an import map, the Stimulus dependencies will be pinned to the versions of the library included with this gem. If you're using Node, yarn will add the dependencies to your `package.json` file.
16
19
 
17
- 1. Create an example controller in `app/assets/javascripts/controllers/hello_controller.js`.
18
- 2. Append the include tags to the `<head>` of your `app/views/layouts/application.html.erb`.
19
- 3. Initialize your `importmap.json` in `app/assets/javascripts/importmap.json.erb`.
20
- 4. Ensure JavaScript is included in your `app/assets/config/manifest.js` file for compilation.
20
+ The installer amends your JavaScript entry point at `app/javascript/application.js` to import the `app/javascript/controllers/index.js` file, which is responsible for setting up your Stimulus application and registering your controllers.
21
21
 
22
- If using Webpacker to manage JavaScript, the last command will:
22
+ ### Installing manually
23
23
 
24
- 1. Import the controllers directory in the application pack.
25
- 2. Create a controllers directory at `app/javascripts/controllers`.
26
- 3. Create an example controller in `app/javascripts/controllers/hello_controller.js`.
27
- 4. Install the Stimulus NPM package.
24
+ Note that we recommend running the installer as described above. But the following is helpful if you encounter errors while running the installer (e.g., if there are conflicts with existing files) or if you just like doing stuff manually. Follow the instructions for import map *or* JavaScript bundler, depending on your setup.
28
25
 
29
- ## Usage
26
+ 1. Add the `stimulus-rails` gem to your Gemfile: `gem 'stimulus-rails'`
27
+ 2. Run `./bin/bundle install`.
30
28
 
31
- With the Stimulus include tags added, you'll automatically have activated Stimulus through the controller autoloader. You can now easily add new Stimulus controllers that'll be loaded via ESM dynamic imports.
29
+ #### With import map
32
30
 
33
- For example, a more advanced `hello_controller` could look like this:
31
+ 3. Create `app/javascript/controllers/index.js` and load your controllers like this:
32
+ ```javascript
33
+ import { application } from "controllers/application"
34
+
35
+ // Eager load all controllers defined in the import map under controllers/**/*_controller
36
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
37
+ eagerLoadControllersFrom("controllers", application)
38
+ ```
34
39
 
40
+ 4. Create `app/javascript/controllers/application.js` with the following content:
35
41
  ```javascript
36
- // app/assets/javascripts/controllers/hello_controller.js
37
- import { Controller } from "stimulus"
42
+ import { Application } from "@hotwired/stimulus"
38
43
 
39
- export default class extends Controller {
40
- static targets = [ "name", "output" ]
44
+ const application = Application.start()
41
45
 
42
- greet() {
43
- this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
44
- }
45
- }
46
+ // Configure Stimulus development experience
47
+ application.debug = false
48
+ window.Stimulus = application
49
+
50
+ export { application }
51
+ ```
52
+
53
+ 5. Add the following line to `app/javascript/application.js` to import all your controllers:
54
+ ```javascript
55
+ import "controllers"
56
+ ```
57
+
58
+ 6. Finally, Pin Stimulus and controllers in `config/importmap.rb` by adding:
59
+ ```ruby
60
+ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
61
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
62
+ pin_all_from "app/javascript/controllers", under: "controllers"
63
+
64
+ ```
65
+
66
+ #### With JavaScript bundler
67
+
68
+ 3. Create `app/javascript/controllers/index.js` and load your controllers.
69
+
70
+ Make sure to change `HelloController` to an actual controller and repeat for every controller you have or use the command mentioned in the comments below:
71
+ ```javascript
72
+ // This file is auto-generated by ./bin/rails stimulus:manifest:update
73
+ // Run that command whenever you add a new controller or create them with
74
+ // ./bin/rails generate stimulus controllerName
75
+
76
+ import { application } from "./application"
77
+
78
+ import HelloController from "./hello_controller"
79
+ application.register("hello", HelloController)
46
80
  ```
47
81
 
48
- And it'll be activated and registered automatically when encountering the data-controller attribute in your DOM:
82
+ 4. Create `app/javascript/controllers/application.js` with the following content:
83
+ ```javascript
84
+ import { Application } from "@hotwired/stimulus"
85
+
86
+ const application = Application.start()
49
87
 
50
- ```html
51
- <div data-controller="hello">
52
- <input data-hello-target="name" type="text">
88
+ // Configure Stimulus development experience
89
+ application.debug = false
90
+ window.Stimulus = application
53
91
 
54
- <button data-action="click->hello#greet">
55
- Greet
56
- </button>
92
+ export { application }
93
+ ```
94
+
95
+ 5. Add the following line to `app/javascript/application.js` to import all your controllers:
96
+ ```javascript
97
+ import "controllers"
98
+ ```
57
99
 
58
- <span data-hello-target="output">
59
- </span>
60
- </div>
100
+ 6. Finally, add the Stimulus package to yarn:
101
+ ```bash
102
+ yarn add @hotwired/stimulus
61
103
  ```
62
104
 
63
- That's it!
64
105
 
65
- You can add additional libraries needed by your controllers in `app/assets/javascripts/libraries` using the `library@1.0.0.js` naming convention. These libraries will be added to the dynamically generated [importmap](https://github.com/WICG/import-maps) (a shim is included with the `stimulus_include_tags`), so you can reference `cookies@0.5.6.js` as `import Cookie from "cookies"`.
106
+ ## Usage with import map
107
+
108
+ With an import-mapped application, controllers are automatically pinned and registered based on the file structure. The installer will amend your `config/importmap.rb` to configure this such that all controllers in `app/javascript/controllers` are pinned.
109
+
110
+ By default, your application will be setup to eager load all the controllers mentioned in your import map under "controllers". This works well together with preloading in the import map when you have a modest number of controllers.
111
+
112
+ If you have a lot of controllers, you may well want to lazy load them instead. This can be done by changing from `eagerLoadControllersFrom` to `lazyLoadControllersFrom` in your `app/javascript/controllers/index.js` file.
113
+
114
+ When lazy loading, controllers are not loaded until their data-controller identifier is encountered in the DOM.
115
+
116
+
117
+ ## Usage with JavaScript bundler
118
+
119
+ With an application using a JavaScript bundler, controllers need to be imported and registered directly in the index.js file. But this can be done automatically using either the Stimulus generator (`./bin/rails generate stimulus [controller]`) or the dedicated `stimulus:manifest:update` task. Either will overwrite the `controllers/index.js` file.
66
120
 
67
- The libraries must be made for ESM. See https://skypack.dev where you can either directly reference libraries or download them and use them with the ESM conversion.
121
+ You're encouraged to use the generator to add new controllers like so:
122
+
123
+ ```javascript
124
+ // Run "./bin/rails g stimulus hello" to create the file and update the index, then amend:
125
+
126
+ // app/assets/javascripts/controllers/hello_controller.js
127
+ import { Controller } from "@hotwired/stimulus"
128
+
129
+ // Connects with data-controller="hello"
130
+ export default class extends Controller {
131
+ static targets = [ "name", "output" ]
132
+
133
+ greet() {
134
+ this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
135
+ }
136
+ }
137
+ ```
68
138
 
69
139
 
70
140
  ## License
@@ -1,4 +1,4 @@
1
- import { Application } from "stimulus"
1
+ import { Application } from "@hotwired/stimulus"
2
2
 
3
3
  const application = Application.start()
4
4
  const { controllerAttribute } = application.schema
@@ -19,11 +19,11 @@ function extractControllerNamesFrom(element) {
19
19
  function loadController(name) {
20
20
  import(controllerFilename(name))
21
21
  .then(module => registerController(name, module))
22
- .catch(error => console.log(`Failed to autoload controller: ${name}`, error))
22
+ .catch(error => console.error(`Failed to autoload controller: ${name}`, error))
23
23
  }
24
24
 
25
25
  function controllerFilename(name) {
26
- return `${name.replace(/--/g, "/").replace(/-/g, "_")}_controller`
26
+ return `controllers/${name.replace(/--/g, "/").replace(/-/g, "_")}_controller`
27
27
  }
28
28
 
29
29
  function registerController(name, module) {
@@ -50,3 +50,5 @@ new MutationObserver((mutationsList) => {
50
50
  }).observe(document, { attributeFilter: [controllerAttribute], subtree: true, childList: true })
51
51
 
52
52
  autoloadControllersWithin(document)
53
+
54
+ console.warn("stimulus-autoload.js has been deprecated in favor of stimulus-loading.js")
@@ -0,0 +1,27 @@
1
+ // FIXME: es-module-shim won't shim the dynamic import without this explicit import
2
+ import "@hotwired/stimulus"
3
+
4
+ export function registerControllersFrom(under, application) {
5
+ const paths = Object.keys(parseImportmapJson())
6
+ .filter(path => path.match(new RegExp(`^${under}/.*_controller$`)))
7
+
8
+ paths.forEach(path => registerControllerFromPath(path, under, application))
9
+ }
10
+
11
+ export function parseImportmapJson() {
12
+ return JSON.parse(document.querySelector("script[type=importmap]").text).imports
13
+ }
14
+
15
+ function registerControllerFromPath(path, under, application) {
16
+ const name = path
17
+ .replace(new RegExp(`^${under}/`), "")
18
+ .replace("_controller", "")
19
+ .replace(/\//g, "--")
20
+ .replace(/_/g, "-")
21
+
22
+ import(path)
23
+ .then(module => application.register(name, module.default))
24
+ .catch(error => console.error(`Failed to register controller: ${name} (${path})`, error))
25
+ }
26
+
27
+ console.warn("stimulus-importmap-autoload.js has been deprecated in favor of stimulus-loading.js")
@@ -0,0 +1,85 @@
1
+ // FIXME: es-module-shim won't shim the dynamic import without this explicit import
2
+ import "@hotwired/stimulus"
3
+
4
+ const controllerAttribute = "data-controller"
5
+ const registeredControllers = {}
6
+
7
+ // Eager load all controllers registered beneath the `under` path in the import map to the passed application instance.
8
+ export function eagerLoadControllersFrom(under, application) {
9
+ const paths = Object.keys(parseImportmapJson()).filter(path => path.match(new RegExp(`^${under}/.*_controller$`)))
10
+ paths.forEach(path => registerControllerFromPath(path, under, application))
11
+ }
12
+
13
+ function parseImportmapJson() {
14
+ return JSON.parse(document.querySelector("script[type=importmap]").text).imports
15
+ }
16
+
17
+ function registerControllerFromPath(path, under, application) {
18
+ const name = path
19
+ .replace(new RegExp(`^${under}/`), "")
20
+ .replace("_controller", "")
21
+ .replace(/\//g, "--")
22
+ .replace(/_/g, "-")
23
+
24
+ if (!(name in registeredControllers)) {
25
+ import(path)
26
+ .then(module => registerController(name, module, application))
27
+ .catch(error => console.error(`Failed to register controller: ${name} (${path})`, error))
28
+ }
29
+ }
30
+
31
+
32
+ // Lazy load controllers registered beneath the `under` path in the import map to the passed application instance.
33
+ export function lazyLoadControllersFrom(under, application, element = document) {
34
+ lazyLoadExistingControllers(under, application, element)
35
+ lazyLoadNewControllers(under, application, element)
36
+ }
37
+
38
+ function lazyLoadExistingControllers(under, application, element) {
39
+ queryControllerNamesWithin(element).forEach(controllerName => loadController(controllerName, under, application))
40
+ }
41
+
42
+ function lazyLoadNewControllers(under, application, element) {
43
+ new MutationObserver((mutationsList) => {
44
+ for (const { attributeName, target, type } of mutationsList) {
45
+ switch (type) {
46
+ case "attributes": {
47
+ if (attributeName == controllerAttribute && target.getAttribute(controllerAttribute)) {
48
+ extractControllerNamesFrom(target).forEach(controllerName => loadController(controllerName, under, application))
49
+ }
50
+ }
51
+
52
+ case "childList": {
53
+ lazyLoadExistingControllers(under, application, target)
54
+ }
55
+ }
56
+ }
57
+ }).observe(element, { attributeFilter: [controllerAttribute], subtree: true, childList: true })
58
+ }
59
+
60
+ function queryControllerNamesWithin(element) {
61
+ return Array.from(element.querySelectorAll(`[${controllerAttribute}]`)).map(extractControllerNamesFrom).flat()
62
+ }
63
+
64
+ function extractControllerNamesFrom(element) {
65
+ return element.getAttribute(controllerAttribute).split(/\s+/).filter(content => content.length)
66
+ }
67
+
68
+ function loadController(name, under, application) {
69
+ if (!(name in registeredControllers)) {
70
+ import(controllerFilename(name, under))
71
+ .then(module => registerController(name, module, application))
72
+ .catch(error => console.error(`Failed to autoload controller: ${name}`, error))
73
+ }
74
+ }
75
+
76
+ function controllerFilename(name, under) {
77
+ return `${under}/${name.replace(/--/g, "/").replace(/-/g, "_")}_controller`
78
+ }
79
+
80
+ function registerController(name, module, application) {
81
+ if (!(name in registeredControllers)) {
82
+ application.register(name, module.default)
83
+ registeredControllers[name] = true
84
+ }
85
+ }