superglue 2.0.0.rc.3 → 2.0.0.rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/superglue/install/install_generator.rb +191 -67
  3. data/lib/generators/superglue/install/templates/bun/bun.config.typia.js +2 -2
  4. data/lib/generators/superglue/install/templates/bun/page_to_page_mapping.js +1 -1
  5. data/lib/generators/superglue/install/templates/bun/page_to_page_mapping.ts +1 -1
  6. data/lib/generators/superglue/install/templates/js/application.jsx +5 -7
  7. data/lib/generators/superglue/install/templates/js/jsconfig.json +2 -3
  8. data/lib/generators/superglue/install/templates/js/layout.jsx +2 -2
  9. data/lib/generators/superglue/install/templates/rollup/rollup.config.deepkit.js +8 -11
  10. data/lib/generators/superglue/install/templates/rollup/rollup.config.js +2 -6
  11. data/lib/generators/superglue/install/templates/rollup/rollup.config.ts.js +2 -9
  12. data/lib/generators/superglue/install/templates/rollup/rollup.config.typia.js +5 -10
  13. data/lib/generators/superglue/install/templates/ssr/rollup.build_ssr.config.js +6 -7
  14. data/lib/generators/superglue/install/templates/ssr/server_rendering.jsx +6 -3
  15. data/lib/generators/superglue/install/templates/ssr/server_rendering.tsx +5 -2
  16. data/lib/generators/superglue/install/templates/ts/application.tsx +5 -7
  17. data/lib/generators/superglue/install/templates/ts/flash.ts +3 -0
  18. data/lib/generators/superglue/install/templates/ts/layout.tsx +3 -2
  19. data/lib/generators/superglue/install/templates/ts/tsconfig.json +2 -3
  20. data/lib/generators/superglue/scaffold_controller/scaffold_controller_generator.rb +12 -12
  21. data/lib/generators/superglue/view_collection/templates/js/edit.html.jsx +2 -3
  22. data/lib/generators/superglue/view_collection/templates/js/index.html.jsx +3 -3
  23. data/lib/generators/superglue/view_collection/templates/js/new.html.jsx +2 -3
  24. data/lib/generators/superglue/view_collection/templates/js/show.html.jsx +2 -3
  25. data/lib/generators/superglue/view_collection/templates/ts/edit.html.tsx +2 -3
  26. data/lib/generators/superglue/view_collection/templates/ts/index.html.tsx +3 -3
  27. data/lib/generators/superglue/view_collection/templates/ts/new.html.tsx +2 -3
  28. data/lib/generators/superglue/view_collection/templates/ts/show.html.tsx +3 -4
  29. data/lib/generators/superglue/view_collection/view_collection_generator.rb +0 -34
  30. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d17162adf65950ea5d4ee63e82835d8893c7f532607206b8a9e5bce609547297
4
- data.tar.gz: e18b77ee95528a871782d9cc5a296601fa3763f03ef162354b5b2b165a7455b6
3
+ metadata.gz: aff506e09c15008bbc37ae8c5e14ce6c7ab0520956fd0f31653a3deb90a6e674
4
+ data.tar.gz: 9a3f7534e42d41ae13e08f496debbc3d692d085f99dc36875f6b047956a1eda4
5
5
  SHA512:
6
- metadata.gz: 1a23f6f9263af4619a667d9eb6fc6722c04547821676499417eb4533353a038efe76d80cebde261f0d085e4bb160709b4198672f89fc32ad83e507ea7cd0b80e
7
- data.tar.gz: 86a94adb38233d8f67aad33f79dd6be31c12fdc0249e2431c2af9bef852831c3f022fc3f5f356e054275749c200d8a58736b6861441035d6048b9a16af7180a2
6
+ metadata.gz: 2a1b558cad7439918dde128f6cf45ba12b6d028d63d4cca79d22d125d8add1f81ee486f9319d5a5d9fb1e43cccdb86d33d42a5100e603ffb22371a63d6978272
7
+ data.tar.gz: e285f3872cbc1f9235971fc27171cc277121ca4f89a5aa91a8c775b040f8599253001382f7aee66f799c6a93ff54349df95f5a498ce115d6d0660f0f42577572
@@ -1,4 +1,5 @@
1
1
  require "json"
2
+ require "shellwords"
2
3
  require "rails/generators/named_base"
3
4
  require "rails/generators/resource_helpers"
4
5
 
@@ -9,31 +10,33 @@ module Superglue
9
10
 
10
11
  class_option :typescript,
11
12
  type: :boolean,
12
- required: false,
13
- desc: "Use TypeScript. Skips interactive prompt."
13
+ default: true,
14
+ desc: "Use TypeScript. Pass --no-typescript for JavaScript."
14
15
 
15
16
  class_option :bundler,
16
17
  type: :string,
17
18
  required: false,
18
- desc: "JavaScript bundler to use (esbuild, bun, rollup, webpack). Skips interactive prompt."
19
+ desc: "JavaScript bundler to use (esbuild, bun, rollup, webpack). Defaults to the detected bundler."
19
20
 
21
+ # A string default, rather than nil, lets `--no-validator` (which Thor
22
+ # parses as nil) be told apart from leaving the option out.
20
23
  class_option :validator,
21
24
  type: :string,
22
- required: false,
23
- desc: "Runtime type validator to use (deepkit, typia, none). Skips interactive prompt."
25
+ default: "auto",
26
+ desc: "Runtime type validator to use (deepkit, typia). Pass --no-validator to skip it. " \
27
+ "auto picks typia on TypeScript 7 or later (or when TypeScript is not installed yet) " \
28
+ "and deepkit on TypeScript 6 or earlier."
24
29
 
25
30
  class_option :svgr,
26
31
  type: :boolean,
27
- required: false,
28
- desc: "Enable SVGR to import SVGs as React components. Skips interactive prompt."
32
+ default: true,
33
+ desc: "Enable SVGR to import SVGs as React components. Pass --no-svgr to disable."
29
34
 
30
35
  def create_files
31
36
  remove_file "#{app_js_path}/application.js"
32
37
 
33
- @bundler = ask_bundler
34
- @use_typescript = ask_typescript
35
- @validator = @use_typescript ? ask_validator : "none"
36
- @use_svgr = ask_svgr
38
+ resolve_choices
39
+ say_choices
37
40
 
38
41
  if @use_typescript
39
42
  copy_ts_files
@@ -59,6 +62,9 @@ module Superglue
59
62
  say "Copying stream.json.props"
60
63
  copy_file "#{__dir__}/templates/stream.json.props", "app/views/layouts/stream.json.props"
61
64
 
65
+ say "Adding initial page state and #app container to application.html.erb"
66
+ update_application_layout
67
+
62
68
  say "Adding required member methods to ApplicationRecord"
63
69
  add_member_methods
64
70
 
@@ -101,20 +107,33 @@ module Superglue
101
107
  false
102
108
  end
103
109
 
104
- def ask_bundler
105
- if options["bundler"]
106
- bundler = options["bundler"]
107
- unless BUNDLERS.include?(bundler)
108
- raise Thor::Error, "Unknown bundler '#{bundler}'. Must be one of: #{BUNDLERS.join(", ")}"
109
- end
110
- say "Using bundler: #{bundler}", :green
111
- return bundler
112
- end
110
+ # typia's ttsc toolchain needs TypeScript 7's native compiler, while
111
+ # deepkit's type compiler needs the JavaScript compiler API that
112
+ # TypeScript 7 removed.
113
+ TYPIA_MIN_TYPESCRIPT = 7
114
+ DEEPKIT_MAX_TYPESCRIPT = 6
115
+ AUTO_VALIDATOR = "auto"
116
+
117
+ def resolve_choices
118
+ @bundler = resolve_bundler
119
+ @use_typescript = options["typescript"]
120
+ @typescript_major = detect_typescript_major
121
+ @validator = resolve_validator
122
+ @use_svgr = options["svgr"]
123
+ end
113
124
 
114
- detected = detect_bundler
125
+ def resolve_bundler
126
+ requested_bundler = options["bundler"]
127
+ detected_bundler = detect_bundler
115
128
 
116
- if detected
117
- say "Detected #{detected} in your project.", :green
129
+ if requested_bundler
130
+ unless BUNDLERS.include?(requested_bundler)
131
+ raise Thor::Error, "Unknown bundler '#{requested_bundler}'. Must be one of: #{BUNDLERS.join(", ")}"
132
+ end
133
+
134
+ requested_bundler
135
+ elsif detected_bundler
136
+ detected_bundler
118
137
  else
119
138
  say "No JavaScript bundler detected.", :red
120
139
  say "Install one first via jsbundling-rails:"
@@ -122,48 +141,141 @@ module Superglue
122
141
  say "See: https://github.com/rails/jsbundling-rails"
123
142
  raise Thor::Error, "No bundler found. Install one via jsbundling-rails and re-run this generator."
124
143
  end
144
+ end
145
+
146
+ # The major version of the project's TypeScript: the installed package
147
+ # first, then the range declared in package.json. nil when TypeScript is
148
+ # not part of the project yet, or its range has no version (e.g. "latest").
149
+ def detect_typescript_major
150
+ installed_version = installed_typescript_version
151
+ declared_range = declared_typescript_range
152
+ version = installed_version || declared_range
125
153
 
126
- ask("Which bundler are you using?", limited_to: BUNDLERS, default: detected)
154
+ version.to_s[/\d+/]&.to_i
127
155
  end
128
156
 
129
- def ask_typescript
130
- unless options["typescript"].nil?
131
- say "TypeScript: #{options["typescript"] ? "enabled" : "disabled"}", :green
132
- return options["typescript"]
157
+ def installed_typescript_version
158
+ manifest_path = "node_modules/typescript/package.json"
159
+
160
+ if File.exist?(manifest_path)
161
+ JSON.parse(File.read(manifest_path))["version"]
133
162
  end
163
+ rescue JSON::ParserError
164
+ nil
165
+ end
134
166
 
135
- say ""
136
- yes?("Would you like to use TypeScript? [y/N]")
167
+ def declared_typescript_range
168
+ if File.exist?("package.json")
169
+ package_json = JSON.parse(File.read("package.json"))
170
+ dependencies = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
171
+ dependencies["typescript"]
172
+ end
173
+ rescue JSON::ParserError
174
+ nil
137
175
  end
138
176
 
139
- def ask_validator
140
- if options["validator"]
141
- validator = options["validator"]
142
- unless VALIDATORS.include?(validator)
143
- raise Thor::Error, "Unknown validator '#{validator}'. Must be one of: #{VALIDATORS.join(", ")}"
144
- end
145
- say "Runtime type validator: #{validator}", :green
146
- return validator
177
+ # --no-validator and --skip-validator arrive as nil; --validator=none
178
+ # still works too.
179
+ def resolve_validator
180
+ requested_validator = options["validator"]
181
+ validator_disabled = requested_validator.nil? || requested_validator == "none"
182
+ validator_automatic = requested_validator == AUTO_VALIDATOR
183
+
184
+ if validator_disabled
185
+ "none"
186
+ elsif validator_automatic
187
+ @use_typescript ? default_validator : "none"
188
+ elsif !@use_typescript
189
+ raise Thor::Error, "--validator=#{requested_validator} requires TypeScript. Remove --no-typescript."
190
+ else
191
+ ensure_validator_supported(requested_validator)
192
+ requested_validator
147
193
  end
194
+ end
148
195
 
149
- say ""
150
- say "Superglue can add runtime type validation during development."
151
- say " deepkit - works with TypeScript 5 and below (uses bundler plugin)"
152
- say " typia - works with TypeScript 6 and above (uses ttsc)"
153
- say " none - skip runtime type validation"
154
- ask("Which runtime type validator would you like to use?", limited_to: VALIDATORS, default: "none")
196
+ def default_validator
197
+ typescript_supports_typia = @typescript_major.nil? || @typescript_major >= TYPIA_MIN_TYPESCRIPT
198
+
199
+ if typescript_supports_typia
200
+ "typia"
201
+ else
202
+ "deepkit"
203
+ end
204
+ end
205
+
206
+ def ensure_validator_supported(validator)
207
+ typescript_detected = !@typescript_major.nil?
208
+ typia_unsupported = validator == "typia" && typescript_detected && @typescript_major < TYPIA_MIN_TYPESCRIPT
209
+ deepkit_unsupported = validator == "deepkit" && typescript_detected && @typescript_major > DEEPKIT_MAX_TYPESCRIPT
210
+
211
+ if !VALIDATORS.include?(validator)
212
+ raise Thor::Error, "Unknown validator '#{validator}'. Must be one of: #{VALIDATORS.join(", ")}"
213
+ elsif typia_unsupported
214
+ raise Thor::Error, "typia requires TypeScript #{TYPIA_MIN_TYPESCRIPT} or later, found TypeScript #{@typescript_major}"
215
+ elsif deepkit_unsupported
216
+ raise Thor::Error, "deepkit requires TypeScript #{DEEPKIT_MAX_TYPESCRIPT} or earlier, found TypeScript #{@typescript_major}"
217
+ end
155
218
  end
156
219
 
157
- def ask_svgr
158
- unless options["svgr"].nil?
159
- say "SVGR: #{options["svgr"] ? "enabled" : "disabled"}", :green
160
- return options["svgr"]
220
+ # The TypeScript package to install, or nil to keep the project's own.
221
+ # Re-adding an existing typescript would upgrade it to the latest release.
222
+ def typescript_package
223
+ typescript_missing = @typescript_major.nil?
224
+
225
+ if typescript_missing && @validator == "deepkit"
226
+ "typescript@^#{DEEPKIT_MAX_TYPESCRIPT}"
227
+ elsif typescript_missing
228
+ "typescript@^#{TYPIA_MIN_TYPESCRIPT}"
229
+ end
230
+ end
231
+
232
+ # Pins each package to the range @thoughtbot/superglue declares as a
233
+ # peer, so the validator toolchain matches what superglue was tested
234
+ # against. Runs after superglue is installed; a package superglue
235
+ # doesn't declare installs at its latest version.
236
+ def superglue_peer_packages(*package_names)
237
+ manifest_path = "node_modules/@thoughtbot/superglue/package.json"
238
+ peer_ranges = if File.exist?(manifest_path)
239
+ JSON.parse(File.read(manifest_path))["peerDependencies"] || {}
240
+ else
241
+ {}
242
+ end
243
+
244
+ package_names.map do |package_name|
245
+ peer_range = peer_ranges[package_name]
246
+
247
+ if peer_range
248
+ Shellwords.escape("#{package_name}@#{peer_range}")
249
+ else
250
+ package_name
251
+ end
252
+ end
253
+ end
254
+
255
+ def say_choices
256
+ bundler_source = options["bundler"] ? "from --bundler" : "detected"
257
+ typescript_source = if @typescript_major
258
+ "TypeScript #{@typescript_major} detected"
259
+ else
260
+ "will install #{typescript_package}"
261
+ end
262
+ validator_source = case options["validator"]
263
+ when AUTO_VALIDATOR then typescript_source
264
+ when nil then "from --no-validator"
265
+ else "from --validator"
161
266
  end
162
267
 
163
268
  say ""
164
- say "SVGR lets you import SVGs as React components."
165
- say "e.g., import Logo from '@images/logo.svg'"
166
- yes?("Would you like to enable SVGR? [y/N]")
269
+ say "Installing Superglue with:", :green
270
+ say " Bundler: #{@bundler} (#{bundler_source}; change with --bundler)"
271
+ if @use_typescript
272
+ say " TypeScript: yes (#{typescript_source}; --no-typescript for JavaScript)"
273
+ say " Validator: #{@validator} (#{validator_source}; change with --validator=deepkit|typia or --no-validator)"
274
+ else
275
+ say " TypeScript: no (--typescript to enable)"
276
+ end
277
+ say " SVGR: #{@use_svgr ? "yes (--no-svgr to disable)" : "no (--svgr to enable)"}"
278
+ say ""
167
279
  end
168
280
 
169
281
  def update_build_script
@@ -243,18 +355,19 @@ module Superglue
243
355
 
244
356
  def install_packages
245
357
  say "Installing Superglue and friends"
246
- run "yarn add react react-dom @thoughtbot/superglue@^2.0.0-rc.2"
358
+ run "yarn add react react-dom @thoughtbot/superglue@^2.0.0-rc.3"
247
359
 
248
360
  if @use_typescript
249
- run "yarn add -D @types/react-dom @types/react @types/node @thoughtbot/candy_wrapper@0.0.4 typescript"
361
+ typescript_dev_packages = ["@types/react-dom", "@types/react", "@types/node", "@thoughtbot/candy_wrapper@0.0.4", typescript_package]
362
+ run "yarn add -D #{typescript_dev_packages.compact.join(" ")}"
250
363
  end
251
364
 
252
365
  if @validator == "deepkit"
253
366
  say "Installing Deepkit for runtime type validation"
254
- run "yarn add -D @deepkit/type @deepkit/core @deepkit/type-compiler"
367
+ run "yarn add -D @deepkit/core #{superglue_peer_packages("@deepkit/type", "@deepkit/type-compiler", "unplugin").join(" ")}"
255
368
  elsif @validator == "typia"
256
369
  say "Installing Typia and ttsc for runtime type validation"
257
- run "yarn add -D typia ttsc @ttsc/unplugin"
370
+ run "yarn add -D #{superglue_peer_packages("typia", "ttsc", "@ttsc/unplugin").join(" ")}"
258
371
  end
259
372
 
260
373
  case @bundler
@@ -268,11 +381,8 @@ module Superglue
268
381
  say "Installing esbuild-loader for JSX support"
269
382
  run "yarn add -D esbuild-loader"
270
383
  when "rollup"
271
- say "Installing rollup plugins for JSX and glob support"
272
- run "yarn add -D @rollup/plugin-babel @babel/core @babel/preset-react @rollup/plugin-commonjs @rollup/plugin-alias @rollup/plugin-replace rollup-plugin-import-meta-glob"
273
- if @use_typescript
274
- run "yarn add -D @babel/preset-typescript"
275
- end
384
+ say "Installing rollup plugins for JSX, TypeScript, and glob support"
385
+ run "yarn add -D rollup-plugin-esbuild esbuild @rollup/plugin-commonjs @rollup/plugin-alias @rollup/plugin-replace rollup-plugin-import-meta-glob"
276
386
  end
277
387
 
278
388
  say "Installing build dependencies"
@@ -347,6 +457,23 @@ module Superglue
347
457
  end
348
458
  end
349
459
 
460
+ def update_application_layout
461
+ layout_path = "app/views/layouts/application.html.erb"
462
+
463
+ inject_into_file layout_path, before: /^\s*<%= javascript_include_tag/ do
464
+ <<-ERB
465
+
466
+ <script type="text/javascript">
467
+ window.SUPERGLUE_INITIAL_PAGE_STATE=<%= render_props %>;<%# erblint:disable ErbSafety %>
468
+ </script>
469
+
470
+ ERB
471
+ end
472
+
473
+ inject_into_file layout_path, '<div id="app">', before: "<%= yield %>"
474
+ inject_into_file layout_path, "</div>", after: "<%= yield %>"
475
+ end
476
+
350
477
  def copy_ssr_files
351
478
  say "Adding ssr_context to ApplicationController"
352
479
  inject_into_file "app/controllers/application_controller.rb", after: "class ApplicationController < ActionController::Base\n" do
@@ -375,11 +502,6 @@ module Superglue
375
502
  when "rollup"
376
503
  copy_file "#{__dir__}/templates/ssr/rollup.build_ssr.config.js", "rollup.config.ssr.js"
377
504
  gsub_file "rollup.config.ssr.js", "server_rendering.jsx", "server_rendering.#{ssr_ext}"
378
- if @use_typescript
379
- gsub_file "rollup.config.ssr.js",
380
- 'presets: [["@babel/preset-react", { runtime: "automatic" }]],',
381
- 'presets: [["@babel/preset-react", { runtime: "automatic" }], "@babel/preset-typescript"],'
382
- end
383
505
  run %(npm pkg set scripts.build:ssr="rollup -c rollup.config.ssr.js")
384
506
  end
385
507
 
@@ -448,7 +570,9 @@ module Superglue
448
570
  gsub_file "webpack.config.ssr.js", " })\n ]\n}", " }),\n #{dev_only_plugin}\n ]\n}"
449
571
  when "rollup"
450
572
  inject_into_file "rollup.config.ssr.js", plugin_import, after: "import importMetaGlob from \"rollup-plugin-import-meta-glob\"\n"
451
- inject_into_file "rollup.config.ssr.js", " #{dev_only_plugin}\n", after: " importMetaGlob(),\n"
573
+ # Before esbuild: the validator rewrites the TypeScript source and
574
+ # esbuild compiles its output. Rollup runs plugins in array order.
575
+ inject_into_file "rollup.config.ssr.js", " #{dev_only_plugin},\n", before: " esbuild({ jsx: \"automatic\" }),\n"
452
576
  end
453
577
  end
454
578
 
@@ -465,7 +589,7 @@ module Superglue
465
589
  when ["typia", "esbuild"]
466
590
  "import ttsc from '@ttsc/unplugin'\nconst ttscPlugin = ttsc.esbuild\n"
467
591
  when ["typia", "bun"]
468
- "import ttsc from '@ttsc/unplugin'\nconst ttscPlugin = ttsc.bun\n"
592
+ "import ttscPlugin from '@ttsc/unplugin/bun'\n"
469
593
  when ["typia", "webpack"]
470
594
  "const ttscPlugin = require(\"@ttsc/unplugin\").default.webpack\n"
471
595
  when ["typia", "rollup"]
@@ -1,6 +1,6 @@
1
1
  import { globImportPlugin } from 'bun-plugin-glob-import'
2
- import ttsc from '@ttsc/unplugin'
3
- const ttscPlugin = ttsc.bun
2
+ // Bun's adapter is its own entry point; the main export has no `bun`.
3
+ import ttscPlugin from '@ttsc/unplugin/bun'
4
4
  import { watch } from 'fs'
5
5
 
6
6
  const isWatch = process.argv.includes('--watch')
@@ -3,7 +3,7 @@ import { asObject as pages } from '../views/**/*.{tsx,jsx}'
3
3
  const pageIdentifierToPageComponent = {}
4
4
 
5
5
  for (const key in pages) {
6
- const identifier = key.split('.')[0]
6
+ const identifier = key.replace('../views/', '').split('.')[0]
7
7
  pageIdentifierToPageComponent[identifier] = pages[key].default
8
8
  }
9
9
 
@@ -3,7 +3,7 @@ import { asObject as pages } from '../views/**/*.{tsx,jsx}'
3
3
  const pageIdentifierToPageComponent: Record<string, React.ComponentType> = {}
4
4
 
5
5
  for (const key in pages) {
6
- const identifier = key.split('.')[0]
6
+ const identifier = key.replace('../views/', '').split('.')[0]
7
7
  pageIdentifierToPageComponent[identifier] = (pages[key] as { default: React.ComponentType }).default
8
8
  }
9
9
 
@@ -26,13 +26,11 @@ if (typeof window !== "undefined" && window.SUPERGLUE_INITIAL_PAGE_STATE) {
26
26
  })
27
27
 
28
28
  const app = (
29
- <div onClick={ujs.onClick} onSubmit={ujs.onSubmit}>
30
- <Provider>
31
- <Layout>
32
- <Outlet />
33
- </Layout>
34
- </Provider>
35
- </div>
29
+ <Provider>
30
+ <Layout onClick={ujs.onClick} onSubmit={ujs.onSubmit}>
31
+ <Outlet />
32
+ </Layout>
33
+ </Provider>
36
34
  )
37
35
 
38
36
  if (appEl.hasChildNodes()) {
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "jsx": "react-jsx",
4
- "baseUrl": ".", // Usually the root of your project
5
4
  "paths": {
6
- "@javascript/*": ["app/javascript/*"],
7
- "@views/*": ["app/views/*"]
5
+ "@javascript/*": ["./app/javascript/*"],
6
+ "@views/*": ["./app/views/*"]
8
7
  }
9
8
  }
10
9
  }
@@ -1,10 +1,10 @@
1
1
  import { useAppFlash } from '@javascript/flash'
2
2
 
3
- export const Layout = ({children}) => {
3
+ export const Layout = ({children, onClick, onSubmit}) => {
4
4
  const flash = useAppFlash()
5
5
 
6
6
  return (
7
- <div>
7
+ <div onClick={onClick} onSubmit={onSubmit}>
8
8
  {flash.success && <p>{flash.success}</p>}
9
9
  {flash.notice && <p>{flash.notice}</p>}
10
10
  {flash.error && <p>{flash.error}</p>}
@@ -1,7 +1,7 @@
1
1
  import path from "path"
2
2
  import resolve from "@rollup/plugin-node-resolve"
3
3
  import commonjs from "@rollup/plugin-commonjs"
4
- import babel from "@rollup/plugin-babel"
4
+ import esbuild from "rollup-plugin-esbuild"
5
5
  import alias from "@rollup/plugin-alias"
6
6
  import importMetaGlob from "rollup-plugin-import-meta-glob"
7
7
  import { rollup as deepkitPlugin } from "@thoughtbot/superglue/deepkit"
@@ -21,17 +21,14 @@ export default {
21
21
  { find: "@views", replacement: path.resolve("app/views") }
22
22
  ]
23
23
  }),
24
- resolve({ extensions: [".ts", ".tsx", ".js", ".jsx"] }),
24
+ // @deepkit/type imports "buffer"; bundle the npm polyfill instead of
25
+ // leaving it to a Node built-in the browser does not have.
26
+ resolve({ extensions: [".ts", ".tsx", ".js", ".jsx"], preferBuiltins: false }),
25
27
  commonjs(),
26
- babel({
27
- babelHelpers: "bundled",
28
- presets: [
29
- ["@babel/preset-react", { runtime: "automatic" }],
30
- "@babel/preset-typescript"
31
- ],
32
- extensions: [".ts", ".tsx", ".js", ".jsx"]
33
- }),
28
+ // Before esbuild: it rewrites the TypeScript source, and esbuild compiles
29
+ // its output (rollup runs plugins in order and ignores `enforce`).
30
+ ...(process.env.NODE_ENV === 'production' ? [] : [deepkitPlugin()]),
31
+ esbuild({ jsx: "automatic" }),
34
32
  importMetaGlob(),
35
- ...(process.env.NODE_ENV === 'production' ? [] : [deepkitPlugin()])
36
33
  ]
37
34
  }
@@ -1,7 +1,7 @@
1
1
  import path from "path"
2
2
  import resolve from "@rollup/plugin-node-resolve"
3
3
  import commonjs from "@rollup/plugin-commonjs"
4
- import babel from "@rollup/plugin-babel"
4
+ import esbuild from "rollup-plugin-esbuild"
5
5
  import alias from "@rollup/plugin-alias"
6
6
  import importMetaGlob from "rollup-plugin-import-meta-glob"
7
7
 
@@ -22,11 +22,7 @@ export default {
22
22
  }),
23
23
  resolve({ extensions: [".js", ".jsx"] }),
24
24
  commonjs(),
25
- babel({
26
- babelHelpers: "bundled",
27
- presets: [["@babel/preset-react", { runtime: "automatic" }]],
28
- extensions: [".js", ".jsx"]
29
- }),
25
+ esbuild({ jsx: "automatic" }),
30
26
  importMetaGlob(),
31
27
  ]
32
28
  }
@@ -1,7 +1,7 @@
1
1
  import path from "path"
2
2
  import resolve from "@rollup/plugin-node-resolve"
3
3
  import commonjs from "@rollup/plugin-commonjs"
4
- import babel from "@rollup/plugin-babel"
4
+ import esbuild from "rollup-plugin-esbuild"
5
5
  import alias from "@rollup/plugin-alias"
6
6
  import importMetaGlob from "rollup-plugin-import-meta-glob"
7
7
 
@@ -22,14 +22,7 @@ export default {
22
22
  }),
23
23
  resolve({ extensions: [".ts", ".tsx", ".js", ".jsx"] }),
24
24
  commonjs(),
25
- babel({
26
- babelHelpers: "bundled",
27
- presets: [
28
- ["@babel/preset-react", { runtime: "automatic" }],
29
- "@babel/preset-typescript"
30
- ],
31
- extensions: [".ts", ".tsx", ".js", ".jsx"]
32
- }),
25
+ esbuild({ jsx: "automatic" }),
33
26
  importMetaGlob(),
34
27
  ]
35
28
  }
@@ -1,7 +1,7 @@
1
1
  import path from "path"
2
2
  import resolve from "@rollup/plugin-node-resolve"
3
3
  import commonjs from "@rollup/plugin-commonjs"
4
- import babel from "@rollup/plugin-babel"
4
+ import esbuild from "rollup-plugin-esbuild"
5
5
  import alias from "@rollup/plugin-alias"
6
6
  import importMetaGlob from "rollup-plugin-import-meta-glob"
7
7
  import ttsc from "@ttsc/unplugin"
@@ -24,15 +24,10 @@ export default {
24
24
  }),
25
25
  resolve({ extensions: [".ts", ".tsx", ".js", ".jsx"] }),
26
26
  commonjs(),
27
- babel({
28
- babelHelpers: "bundled",
29
- presets: [
30
- ["@babel/preset-react", { runtime: "automatic" }],
31
- "@babel/preset-typescript"
32
- ],
33
- extensions: [".ts", ".tsx", ".js", ".jsx"]
34
- }),
27
+ // Before esbuild: it rewrites the TypeScript source, and esbuild compiles
28
+ // its output (rollup runs plugins in order and ignores `enforce`).
29
+ ...(process.env.NODE_ENV === 'production' ? [] : [ttscPlugin()]),
30
+ esbuild({ jsx: "automatic" }),
35
31
  importMetaGlob(),
36
- ...(process.env.NODE_ENV === 'production' ? [] : [ttscPlugin()])
37
32
  ]
38
33
  }
@@ -1,7 +1,7 @@
1
1
  import path from "path"
2
2
  import resolve from "@rollup/plugin-node-resolve"
3
3
  import commonjs from "@rollup/plugin-commonjs"
4
- import babel from "@rollup/plugin-babel"
4
+ import esbuild from "rollup-plugin-esbuild"
5
5
  import alias from "@rollup/plugin-alias"
6
6
  import replace from "@rollup/plugin-replace"
7
7
  import importMetaGlob from "rollup-plugin-import-meta-glob"
@@ -29,14 +29,13 @@ export default {
29
29
  resolve({
30
30
  extensions: [".js", ".jsx", ".ts", ".tsx"],
31
31
  browser: true,
32
- exportConditions: ["browser"]
32
+ exportConditions: ["browser"],
33
+ // Bundle npm polyfills (e.g. "buffer", imported by @deepkit/type)
34
+ // rather than Node built-ins, which MiniRacer does not have.
35
+ preferBuiltins: false
33
36
  }),
34
37
  commonjs(),
35
- babel({
36
- babelHelpers: "bundled",
37
- presets: [["@babel/preset-react", { runtime: "automatic" }]],
38
- extensions: [".js", ".jsx", ".ts", ".tsx"]
39
- }),
38
+ esbuild({ jsx: "automatic" }),
40
39
  importMetaGlob(),
41
40
  ]
42
41
  }
@@ -2,10 +2,11 @@ import { createApp } from "@thoughtbot/superglue";
2
2
  import { buildVisitAndRemote } from "./application_visit";
3
3
  import { pageIdentifierToPageComponent } from "./page_to_page_mapping";
4
4
  import { renderToString } from "react-dom/server";
5
+ import { Layout } from "./components";
5
6
 
6
7
  setHumidRenderer((json, baseUrl, path) => {
7
8
  const initialState = JSON.parse(json);
8
- const { Provider, Outlet } = createApp({
9
+ const { Provider, Outlet, ujs } = createApp({
9
10
  baseUrl,
10
11
  initialPage: initialState,
11
12
  path,
@@ -15,7 +16,9 @@ setHumidRenderer((json, baseUrl, path) => {
15
16
 
16
17
  return renderToString(
17
18
  <Provider>
18
- <Outlet />
19
- </Provider>,
19
+ <Layout onClick={ujs.onClick} onSubmit={ujs.onSubmit}>
20
+ <Outlet />
21
+ </Layout>
22
+ </Provider>
20
23
  );
21
24
  });
@@ -2,6 +2,7 @@ import { createApp } from "@thoughtbot/superglue";
2
2
  import { buildVisitAndRemote } from "./application_visit";
3
3
  import { pageIdentifierToPageComponent } from "./page_to_page_mapping";
4
4
  import { renderToString } from "react-dom/server";
5
+ import { Layout } from "./components";
5
6
 
6
7
  declare function setHumidRenderer(
7
8
  fn: (json: string, baseUrl: string, path: string) => string,
@@ -9,7 +10,7 @@ declare function setHumidRenderer(
9
10
 
10
11
  setHumidRenderer((json, baseUrl, path) => {
11
12
  const initialState = JSON.parse(json);
12
- const { Provider, Outlet } = createApp({
13
+ const { Provider, Outlet, ujs } = createApp({
13
14
  baseUrl,
14
15
  initialPage: initialState,
15
16
  path,
@@ -19,7 +20,9 @@ setHumidRenderer((json, baseUrl, path) => {
19
20
 
20
21
  return renderToString(
21
22
  <Provider>
22
- <Outlet />
23
+ <Layout onClick={ujs.onClick} onSubmit={ujs.onSubmit}>
24
+ <Outlet />
25
+ </Layout>
23
26
  </Provider>,
24
27
  );
25
28
  });
@@ -31,13 +31,11 @@ if (typeof window !== "undefined" && window.SUPERGLUE_INITIAL_PAGE_STATE) {
31
31
  });
32
32
 
33
33
  const app = (
34
- <div onClick={ujs.onClick} onSubmit={ujs.onSubmit}>
35
- <Provider>
36
- <Layout>
37
- <Outlet />
38
- </Layout>
39
- </Provider>
40
- </div>
34
+ <Provider>
35
+ <Layout onClick={ujs.onClick} onSubmit={ujs.onSubmit}>
36
+ <Outlet />
37
+ </Layout>
38
+ </Provider>
41
39
  );
42
40
 
43
41
  if (appEl.hasChildNodes()) {
@@ -1,4 +1,5 @@
1
1
  import { useFlash } from "@thoughtbot/superglue";
2
+ import type { ValidationErrors } from "@thoughtbot/candy_wrapper";
2
3
 
3
4
  /**
4
5
  * Customize this type to match the flash keys your application uses.
@@ -8,6 +9,8 @@ export type AppFlash = {
8
9
  notice?: string;
9
10
  alert?: string;
10
11
  error?: string;
12
+ // Form errors the scaffold stores under e.g. flash["postFormErrors"]
13
+ [key: `${string}FormErrors`]: ValidationErrors | undefined;
11
14
  [key: string]: unknown;
12
15
  };
13
16
 
@@ -1,11 +1,12 @@
1
1
  import type { ReactNode } from 'react'
2
+ import type { Handlers } from '@thoughtbot/superglue'
2
3
  import { useAppFlash } from '@javascript/flash'
3
4
 
4
- export const Layout = ({children}: {children: ReactNode}) => {
5
+ export const Layout = ({children, onClick, onSubmit}: {children: ReactNode} & Partial<Handlers>) => {
5
6
  const flash = useAppFlash()
6
7
 
7
8
  return (
8
- <div>
9
+ <div onClick={onClick} onSubmit={onSubmit}>
9
10
  {flash.success && <p>{flash.success}</p>}
10
11
  {flash.notice && <p>{flash.notice}</p>}
11
12
  {flash.error && <p>{flash.error}</p>}
@@ -14,10 +14,9 @@
14
14
  "esModuleInterop": true,
15
15
  "noEmit": true,
16
16
  "allowImportingTsExtensions": true,
17
- "baseUrl": ".", // Usually the root of your project
18
17
  "paths": {
19
- "@javascript/*": ["app/javascript/*"],
20
- "@views/*": ["app/views/*"]
18
+ "@javascript/*": ["./app/javascript/*"],
19
+ "@views/*": ["./app/views/*"]
21
20
  },
22
21
  "types": ["node"]
23
22
  },
@@ -18,7 +18,7 @@ module Superglue
18
18
 
19
19
  class_option :helper, type: :boolean
20
20
  class_option :orm, banner: "NAME", type: :string, required: true,
21
- desc: "ORM to generate the controller for"
21
+ desc: "ORM to generate the controller for"
22
22
 
23
23
  class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb."
24
24
 
@@ -46,17 +46,17 @@ module Superglue
46
46
 
47
47
  private
48
48
 
49
- def permitted_params
50
- attachments, others = attributes_names.partition { |name| attachments?(name) }
51
- params = others.map { |name| ":#{name}" }
52
- params += attachments.map { |name| "#{name}: []" }
53
- params.join(", ")
54
- end
55
-
56
- def attachments?(name)
57
- attribute = attributes.find { |attr| attr.name == name }
58
- attribute&.attachments?
59
- end
49
+ def permitted_params
50
+ attachments, others = attributes_names.partition { |name| attachments?(name) }
51
+ params = others.map { |name| ":#{name}" }
52
+ params += attachments.map { |name| "#{name}: []" }
53
+ params.join(", ")
54
+ end
55
+
56
+ def attachments?(name)
57
+ attribute = attributes.find { |attr| attr.name == name }
58
+ attribute&.attachments?
59
+ end
60
60
  end
61
61
  end
62
62
  end
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  Form,
3
- Layout,
4
3
  <%- attributes.each do |attr| -%>
5
4
  <%= js_component(attr)%>,
6
5
  <%- end -%>
@@ -25,7 +24,7 @@ export default function <%= js_plural_table_name(:upper) %>Edit() {
25
24
  const validationErrors = flash["<%= js_singular_table_name%>FormErrors"]
26
25
 
27
26
  return (
28
- <Layout>
27
+ <>
29
28
  <Form {...form} extras={extras} validationErrors={validationErrors} data-sg-visit>
30
29
  <%- attributes.each do |attr| -%>
31
30
  <<%= js_component(attr)%> {...inputs.<%= attr.column_name.camelize(:lower)%>} label="<%= attr.column_name.humanize %>" errorKey="<%= attr.column_name %>" />
@@ -35,6 +34,6 @@ export default function <%= js_plural_table_name(:upper) %>Edit() {
35
34
 
36
35
  <a href={<%= js_singular_table_name %>Path} data-sg-visit>Show</a>
37
36
  <a href={<%= js_plural_table_name %>Path} data-sg-visit>Back</a>
38
- </Layout>
37
+ </>
39
38
  )
40
39
  }
@@ -1,4 +1,4 @@
1
- import { Form, Layout } from '@javascript/components'
1
+ import { Form } from '@javascript/components'
2
2
  import { useContent } from '@thoughtbot/superglue'
3
3
 
4
4
  export default function <%= js_plural_table_name(:upper) %>Index() {
@@ -37,7 +37,7 @@ export default function <%= js_plural_table_name(:upper) %>Index() {
37
37
  })
38
38
 
39
39
  return (
40
- <Layout>
40
+ <>
41
41
  <h1><%= js_plural_table_name(:upper) %></h1>
42
42
 
43
43
  <table>
@@ -56,6 +56,6 @@ export default function <%= js_plural_table_name(:upper) %>Index() {
56
56
  </table>
57
57
  <br />
58
58
  <a href={new<%= js_singular_table_name(:upper) %>Path} data-sg-visit>New <%= singular_table_name.humanize %></a>
59
- </Layout>
59
+ </>
60
60
  )
61
61
  }
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  Form,
3
- Layout,
4
3
  <%- attributes.each do |attr| -%>
5
4
  <%= js_component(attr)%>,
6
5
  <%- end -%>
@@ -24,7 +23,7 @@ export default function <%= js_plural_table_name(:upper) %>New() {
24
23
  const validationErrors = flash["<%= js_singular_table_name%>FormErrors"]
25
24
 
26
25
  return (
27
- <Layout>
26
+ <>
28
27
  <Form {...form} extras={extras} validationErrors={validationErrors} data-sg-visit>
29
28
  <%- attributes.each do |attr| -%>
30
29
  <<%= js_component(attr)%> {...inputs.<%= attr.column_name.camelize(:lower)%>} label="<%= attr.column_name.humanize %>" errorKey="<%= attr.column_name %>" />
@@ -33,6 +32,6 @@ export default function <%= js_plural_table_name(:upper) %>New() {
33
32
  </Form>
34
33
 
35
34
  <a href={<%= js_plural_table_name %>Path} data-sg-visit>Back</a>
36
- </Layout>
35
+ </>
37
36
  )
38
37
  }
@@ -1,4 +1,3 @@
1
- import { Layout } from '@javascript/components'
2
1
  import { useContent } from '@thoughtbot/superglue'
3
2
 
4
3
  export default function <%= js_plural_table_name(:upper) %>Show() {
@@ -11,7 +10,7 @@ export default function <%= js_plural_table_name(:upper) %>Show() {
11
10
  } = useContent()
12
11
 
13
12
  return (
14
- <Layout>
13
+ <>
15
14
  <%- showable_attributes.each do |attr| -%>
16
15
  <p>
17
16
  <strong><%= attr.column_name.humanize %>:</strong>
@@ -20,6 +19,6 @@ export default function <%= js_plural_table_name(:upper) %>Show() {
20
19
  <%- end -%>
21
20
  <a href={ edit<%= js_singular_table_name(:upper) %>Path } data-sg-visit>Edit</a>
22
21
  <a href={ <%= js_plural_table_name %>Path } data-sg-visit>Back</a>
23
- </Layout>
22
+ </>
24
23
  )
25
24
  }
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  Form,
3
3
  FormProps,
4
- Layout,
5
4
  <%- attributes.each do |attr| -%>
6
5
  <%= js_component(attr)%>,
7
6
  Rails<%= js_component(attr)%>Props,
@@ -39,7 +38,7 @@ export default function <%= js_plural_table_name(:upper) %>Edit() {
39
38
  const validationErrors = flash["<%= js_singular_table_name%>FormErrors"]
40
39
 
41
40
  return (
42
- <Layout>
41
+ <>
43
42
  <Form {...form} extras={extras} validationErrors={validationErrors} data-sg-visit>
44
43
  <%- attributes.each do |attr| -%>
45
44
  <<%= js_component(attr)%> {...inputs.<%= attr.column_name.camelize(:lower)%>} label="<%= attr.column_name.humanize %>" errorKey="<%= attr.column_name %>" />
@@ -49,6 +48,6 @@ export default function <%= js_plural_table_name(:upper) %>Edit() {
49
48
 
50
49
  <a href={<%= js_singular_table_name %>Path} data-sg-visit>Show</a>
51
50
  <a href={<%= js_plural_table_name %>Path} data-sg-visit>Back</a>
52
- </Layout>
51
+ </>
53
52
  )
54
53
  }
@@ -1,4 +1,4 @@
1
- import { Form, FormProps, Layout } from '@javascript/components'
1
+ import { Form, FormProps } from '@javascript/components'
2
2
  import { useContent } from '@thoughtbot/superglue'
3
3
 
4
4
  type ContentProps = {
@@ -52,7 +52,7 @@ export default function <%= js_plural_table_name(:upper) %>Index() {
52
52
  })
53
53
 
54
54
  return (
55
- <Layout>
55
+ <>
56
56
  <h1><%= js_plural_table_name(:upper) %></h1>
57
57
 
58
58
  <table>
@@ -71,6 +71,6 @@ export default function <%= js_plural_table_name(:upper) %>Index() {
71
71
  </table>
72
72
  <br />
73
73
  <a href={new<%= js_singular_table_name(:upper) %>Path} data-sg-visit>New <%= singular_table_name.humanize %></a>
74
- </Layout>
74
+ </>
75
75
  )
76
76
  }
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  Form,
3
3
  FormProps,
4
- Layout,
5
4
  <%- attributes.each do |attr| -%>
6
5
  <%= js_component(attr)%>,
7
6
  Rails<%= js_component(attr)%>Props,
@@ -36,7 +35,7 @@ export default function <%= js_plural_table_name(:upper) %>New() {
36
35
  const validationErrors = flash["<%= js_singular_table_name%>FormErrors"]
37
36
 
38
37
  return (
39
- <Layout>
38
+ <>
40
39
  <Form {...form} extras={extras} validationErrors={validationErrors} data-sg-visit>
41
40
  <%- attributes.each do |attr| -%>
42
41
  <<%= js_component(attr)%> {...inputs.<%= attr.column_name.camelize(:lower)%>} label="<%= attr.column_name.humanize %>" errorKey="<%= attr.column_name %>" />
@@ -45,6 +44,6 @@ export default function <%= js_plural_table_name(:upper) %>New() {
45
44
  </Form>
46
45
 
47
46
  <a href={<%= js_plural_table_name %>Path} data-sg-visit>Back</a>
48
- </Layout>
47
+ </>
49
48
  )
50
49
  }
@@ -1,8 +1,7 @@
1
- import { Layout } from '@javascript/components'
2
1
  import { useContent } from '@thoughtbot/superglue'
3
2
 
4
3
  type ContentProps = {
5
- id: string
4
+ id: number
6
5
  <%- attributes.each do |attr| -%>
7
6
  <%= attr.column_name.camelize(:lower)%>: <%= json_mappable_type(attr)%>
8
7
  <%- end -%>
@@ -22,7 +21,7 @@ export default function <%= js_plural_table_name(:upper) %>Show() {
22
21
  } = useContent<ContentProps>()
23
22
 
24
23
  return (
25
- <Layout>
24
+ <>
26
25
  <%- showable_attributes.each do |attr| -%>
27
26
  <p>
28
27
  <strong><%= attr.column_name.humanize %>:</strong>
@@ -31,6 +30,6 @@ export default function <%= js_plural_table_name(:upper) %>Show() {
31
30
  <%- end -%>
32
31
  <a href={ edit<%= js_singular_table_name(:upper) %>Path } data-sg-visit>Edit</a>
33
32
  <a href={ <%= js_plural_table_name %>Path } data-sg-visit>Back</a>
34
- </Layout>
33
+ </>
35
34
  )
36
35
  }
@@ -43,32 +43,6 @@ module Superglue
43
43
  end
44
44
  end
45
45
 
46
- def append_mapping
47
- available_views.each do |action|
48
- app_js = if use_typescript?
49
- "#{app_js_path}/page_to_page_mapping.ts"
50
- else
51
- "#{app_js_path}/page_to_page_mapping.js"
52
- end
53
-
54
- component_name = [plural_table_name, action].map(&:camelcase).join
55
-
56
- js_ext = use_typescript? ? "html.tsx" : "html.jsx"
57
-
58
- if match_file(app_js, /pageIdentifierToPageComponent = {$/)
59
- prepend_to_file app_js do
60
- "import #{component_name} from '#{view_path}/#{controller_file_path}/#{action}.#{js_ext}'\n"
61
- end
62
-
63
- inject_into_file app_js, after: /pageIdentifierToPageComponent = {$/ do
64
- "\n '#{[controller_file_path, action].join("/")}': #{component_name},"
65
- end
66
- else
67
- say "Skipping appending to #{app_js}, you may be using a bundler that supports globing."
68
- end
69
- end
70
- end
71
-
72
46
  protected
73
47
 
74
48
  def use_typescript?
@@ -131,14 +105,6 @@ module Superglue
131
105
  %w[index edit show new]
132
106
  end
133
107
 
134
- def view_path
135
- "@views"
136
- end
137
-
138
- def app_js_path
139
- "app/javascript"
140
- end
141
-
142
108
  attr_reader :action_name
143
109
 
144
110
  def attributes_names
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: superglue
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.rc.3
4
+ version: 2.0.0.rc.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johny Ho