stipa 0.1.0 → 0.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff378718ea5094cf20614ab1fa80b2ab2fe66b96dbedfc448f230a752034d69d
4
- data.tar.gz: db686eb560e89166d73ddf100c6b65695cc288a0f8118e9461edcb0d9b4e8aa8
3
+ metadata.gz: 387748a4e29d9918defffcbf6e020d826933bc96619f9579f27bbcc4bde3a8c8
4
+ data.tar.gz: e7a6122363efaeff5405b48bb5fe8b334af837b9269b8ad854fdc2aa020b8186
5
5
  SHA512:
6
- metadata.gz: bdc3509de75f0de34b898e68ecbd2f3803eaca1959f0c27162c77b2845e8288bf51f0c329be6db0278491a2a5b9c49010952a11bc804fdc79856250b98594c6f
7
- data.tar.gz: '072216312185f44a7f236477fd06cfe1a52f2a33fc286ddd6d00f4bfec92f21054345222fa9a5d6b0e2141ffb5f28ff441999e44895debcf42d9d06c04f2cf3c'
6
+ metadata.gz: b2787738c43370505fec1cecdf96ce6d5ff951abd4eb0c80dfd4328dad29e24c9c322c8f0d85faee29d918d40f904da6bc7cf59bfdca7383420c4644b2a897ba
7
+ data.tar.gz: d3bfdfcd68a3920214ab00547e9272d7d7ad2f721c4062793aa38bddaf0f256d9eeeafcc3d2ed0e49c9d5fd3df15c7cec50d917b6b0073f6667b6d642c3c9f81
data/lib/js/stipa-vue.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Stīpa Vue Bootstrapper
2
+ * Stīpa Vue Bootstrapper (ES Module)
3
3
  *
4
4
  * Automatically mounts Vue 3 components declared with the ERB helper:
5
5
  * <%= vue_component("Counter", props: { initial: 5 }) %>
@@ -7,102 +7,82 @@
7
7
  * Which renders on the page as:
8
8
  * <div data-vue-component="Counter" data-props='{"initial":5}'></div>
9
9
  *
10
- * Usage in your layout (after vue_script and component <script> tags):
11
- * <%= stipa_vue_bootstrap %>
10
+ * Usage in your layout:
11
+ * <script type="importmap">{ "imports": { "vue": "/vendor/vue.esm-browser.prod.js" } }</script>
12
+ * <script type="module" src="/stipa-vue.js"></script>
13
+ * <script type="module" src="/app.js"></script>
12
14
  *
13
- * Register components before DOMContentLoaded fires, or call StipaVue.mount()
14
- * manually after dynamic content is inserted.
15
- *
16
- * Example:
17
- * <script type="module">
18
- * import Counter from '/components/Counter.js'
19
- * StipaVue.register('Counter', Counter)
20
- * </script>
21
- * <%= stipa_vue_bootstrap %>
15
+ * In app/main.ts, register components and call mount():
16
+ * const { StipaVue } = window
17
+ * StipaVue.register('Counter', Counter)
18
+ * StipaVue.mount()
22
19
  */
23
- (() => {
24
- const COMPONENT_ATTR = "data-vue-component";
25
- const PROPS_ATTR = "data-props";
26
- const MOUNTED_ATTR = "data-stipa-vue-mounted";
20
+ import { createApp } from 'vue'
27
21
 
28
- const registry = {};
29
- const mounted = [];
22
+ const COMPONENT_ATTR = 'data-vue-component'
23
+ const PROPS_ATTR = 'data-props'
24
+ const MOUNTED_ATTR = 'data-stipa-vue-mounted'
30
25
 
31
- const StipaVue = {
32
- register(name, component) {
33
- registry[name] = component;
34
- },
26
+ const registry = {}
27
+ const mounted = []
35
28
 
36
- mount(root) {
37
- root = root || document;
29
+ const StipaVue = {
30
+ register(name, component) {
31
+ registry[name] = component
32
+ },
38
33
 
39
- if (typeof Vue === "undefined") {
40
- console.error(
41
- "[StipaVue] Vue is not defined. Make sure vue_script() appears before stipa_vue_bootstrap() in your layout.",
42
- );
43
- return;
44
- }
34
+ mount(root) {
35
+ root = root || document
45
36
 
46
- // Prune stale entries for elements no longer in the DOM
47
- for (let i = mounted.length - 1; i >= 0; i--) {
48
- if (!document.contains(mounted[i].el)) {
49
- mounted.splice(i, 1);
50
- }
37
+ // Prune stale entries for elements no longer in the DOM
38
+ for (let i = mounted.length - 1; i >= 0; i--) {
39
+ if (!document.contains(mounted[i].el)) {
40
+ mounted.splice(i, 1)
51
41
  }
42
+ }
52
43
 
53
- const selector = "[" + COMPONENT_ATTR + "]:not([" + MOUNTED_ATTR + "])";
54
- const elements = root.querySelectorAll(selector);
44
+ const selector = `[${COMPONENT_ATTR}]:not([${MOUNTED_ATTR}])`
45
+ const elements = root.querySelectorAll(selector)
55
46
 
56
- elements.forEach((el) => {
57
- const name = el.getAttribute(COMPONENT_ATTR);
58
- const component = registry[name];
47
+ elements.forEach((el) => {
48
+ const name = el.getAttribute(COMPONENT_ATTR)
49
+ const component = registry[name]
59
50
 
60
- if (!component) {
61
- console.warn(
62
- '[StipaVue] Component "' +
63
- name +
64
- '" is not registered. ' +
65
- 'Call StipaVue.register("' +
66
- name +
67
- '", YourComponent) before the DOM loads.',
68
- );
69
- return;
70
- }
51
+ if (!component) {
52
+ console.warn(
53
+ `[StipaVue] Component "${name}" is not registered. ` +
54
+ `Call StipaVue.register("${name}", YourComponent) before calling StipaVue.mount().`
55
+ )
56
+ return
57
+ }
71
58
 
72
- let props = {};
73
- const propsRaw = el.getAttribute(PROPS_ATTR);
74
- if (propsRaw) {
75
- try {
76
- props = JSON.parse(propsRaw);
77
- } catch (e) {
78
- console.error('[StipaVue] Failed to parse props for "' + name + '":', e);
79
- }
59
+ let props = {}
60
+ const propsRaw = el.getAttribute(PROPS_ATTR)
61
+ if (propsRaw) {
62
+ try {
63
+ props = JSON.parse(propsRaw)
64
+ } catch (e) {
65
+ console.error(`[StipaVue] Failed to parse props for "${name}":`, e)
80
66
  }
67
+ }
81
68
 
82
- const app = Vue.createApp(component, props);
83
- app.mount(el);
84
-
85
- el.setAttribute(MOUNTED_ATTR, "1");
86
- mounted.push({ app, el });
87
- });
88
- },
69
+ const app = createApp(component, props)
70
+ app.mount(el)
89
71
 
90
- unmountAll() {
91
- mounted.forEach((entry) => {
92
- entry.app.unmount();
93
- entry.el.removeAttribute(MOUNTED_ATTR);
94
- });
95
- mounted.length = 0;
96
- },
97
- };
72
+ el.setAttribute(MOUNTED_ATTR, '1')
73
+ mounted.push({ app, el })
74
+ })
75
+ },
98
76
 
99
- // DOMContentLoaded handles the synchronous registration pattern:
100
- // components registered via classic <script> tags before this event fires
101
- // will be mounted automatically. For async/module-based registration,
102
- // call StipaVue.mount() manually after registering.
103
- document.addEventListener("DOMContentLoaded", () => {
104
- StipaVue.mount();
105
- });
77
+ unmountAll() {
78
+ mounted.forEach((entry) => {
79
+ entry.app.unmount()
80
+ entry.el.removeAttribute(MOUNTED_ATTR)
81
+ })
82
+ mounted.length = 0
83
+ },
84
+ }
106
85
 
107
- window.StipaVue = StipaVue;
108
- })();
86
+ window.StipaVue = StipaVue
87
+ export { StipaVue }
88
+ export default StipaVue
@@ -9,13 +9,13 @@ module Stipa
9
9
 
10
10
  def dirs
11
11
  %w[
12
- src/config
13
- src/controllers
14
- src/models
15
- src/views/layouts
16
- src/views/home
17
- src/components
18
- public/components
12
+ app/config
13
+ app/controllers
14
+ app/models
15
+ app/views/layouts
16
+ app/views/home
17
+ app/components
18
+ public/vendor
19
19
  ]
20
20
  end
21
21
 
@@ -42,8 +42,12 @@ module Stipa
42
42
  cd #{name}
43
43
  bundle install
44
44
  npm install
45
- npm run build # compile Vue components
45
+ npm run build # copies Vue from node_modules + compiles app bundle
46
46
  bundle exec ruby server.rb
47
+
48
+ To upgrade Vue:
49
+ npm install vue@3.x.x
50
+ npm run build
47
51
  DONE
48
52
  end
49
53
 
@@ -54,18 +58,20 @@ module Stipa
54
58
  'rollup.config.js' => t_rollup_config,
55
59
  'tsconfig.json' => t_tsconfig,
56
60
  'server.rb' => t_server,
57
- 'src/config/routes.rb' => t_routes(
61
+ 'app/config/routes.rb' => t_routes(
58
62
  extra_requires: ['../controllers/home_controller', '../controllers/health_controller'],
59
63
  extra_routes: ["get '/', to: 'home#index'", "get '/api/health', to: 'health#show'"],
60
64
  method_override: true,
61
65
  ),
62
- 'src/controllers/application_controller.rb' => t_application_controller,
63
- 'src/controllers/home_controller.rb' => t_home_controller,
64
- 'src/controllers/health_controller.rb' => t_health_controller,
65
- 'src/views/layouts/application.html.erb' => t_layout,
66
- 'src/views/home/index.html.erb' => t_home_index,
66
+ 'app/controllers/application_controller.rb' => t_application_controller,
67
+ 'app/controllers/home_controller.rb' => t_home_controller,
68
+ 'app/controllers/health_controller.rb' => t_health_controller,
69
+ 'app/views/layouts/application.html.erb' => t_layout,
70
+ 'app/views/home/index.html.erb' => t_home_index,
67
71
  'public/app.css' => t_app_css,
68
- 'src/components/RequestCard.vue' => t_request_card_vue,
72
+ 'app/components/RequestCard.vue' => t_request_card_vue,
73
+ 'app/main.ts' => t_main_ts,
74
+ 'app/shims-vue.d.ts' => t_shims_vue,
69
75
  }
70
76
  end
71
77
 
@@ -75,9 +81,11 @@ module Stipa
75
81
  private: true,
76
82
  type: 'module',
77
83
  scripts: {
78
- build: 'rollup -c',
79
- watch: 'rollup -c --watch',
80
- dev: 'concurrently "bundle exec ruby server.rb" "rollup -c --watch"',
84
+ 'copy:vue' => 'cp node_modules/vue/dist/vue.esm-browser.prod.js public/vendor/vue.esm-browser.prod.js',
85
+ build: 'npm run copy:vue && rollup -c',
86
+ watch: 'rollup -c --watch',
87
+ dev: 'npm run copy:vue && concurrently "bundle exec ruby server.rb" "rollup -c --watch"',
88
+ typecheck: 'vue-tsc --noEmit',
81
89
  },
82
90
  devDependencies: {
83
91
  'concurrently' => '^8.0.0',
@@ -87,6 +95,7 @@ module Stipa
87
95
  '@vue/compiler-sfc' => '^3.4.0',
88
96
  'typescript' => '^5.0.0',
89
97
  'vue' => '^3.4.0',
98
+ 'vue-tsc' => '^2.0.0',
90
99
  },
91
100
  ) + "\n"
92
101
  end
@@ -95,30 +104,18 @@ module Stipa
95
104
  <<~JS
96
105
  import vue from 'rollup-plugin-vue'
97
106
  import typescript from '@rollup/plugin-typescript'
98
- import { readdirSync } from 'fs'
99
-
100
- const src = './src/components'
101
- const out = './public/components'
102
-
103
- const inputs = readdirSync(src).filter(f => f.endsWith('.vue') || f.endsWith('.ts'))
104
-
105
- export default inputs.map(file => {
106
- const name = file.replace(/\\.(vue|ts)$/, '')
107
- const isTs = file.endsWith('.ts')
108
- return {
109
- input: `${src}/${file}`,
110
- output: {
111
- file: `${out}/${name}.js`,
112
- format: 'iife',
113
- name,
114
- globals: { vue: 'Vue' },
115
- },
116
- external: ['vue'],
117
- // rollup-plugin-vue handles <script lang="ts"> internally;
118
- // @rollup/plugin-typescript is only needed for plain .ts files.
119
- plugins: [vue(), ...(isTs ? [typescript({ tsconfig: './tsconfig.json' })] : [])],
120
- }
121
- })
107
+
108
+ export default {
109
+ input: 'app/main.ts',
110
+ output: {
111
+ file: 'public/app.js',
112
+ format: 'es',
113
+ },
114
+ external: ['vue'],
115
+ // rollup-plugin-vue handles <script lang="ts"> in .vue SFCs;
116
+ // @rollup/plugin-typescript compiles app/main.ts and other plain .ts files.
117
+ plugins: [vue(), typescript({ tsconfig: './tsconfig.json' })],
118
+ }
122
119
  JS
123
120
  end
124
121
 
@@ -130,20 +127,21 @@ module Stipa
130
127
  moduleResolution: 'bundler',
131
128
  strict: true,
132
129
  skipLibCheck: true,
130
+ allowJs: true,
133
131
  },
134
- include: ['src/**/*'],
132
+ include: ['app/**/*'],
135
133
  ) + "\n"
136
134
  end
137
135
 
138
136
  def t_server
139
137
  <<~RUBY
140
138
  require 'stipa'
141
- require_relative 'src/config/routes'
139
+ require_relative 'app/config/routes'
142
140
 
143
141
  APP_DIR = __dir__
144
142
 
145
143
  app = Stipa::App.new(
146
- views: "\#{APP_DIR}/src/views",
144
+ views: "\#{APP_DIR}/app/views",
147
145
  public: "\#{APP_DIR}/public",
148
146
  )
149
147
 
@@ -261,17 +259,16 @@ module Stipa
261
259
  <link rel="icon" href="/favicon.ico">
262
260
  <%= stylesheet_tag '/app.css' %>
263
261
 
264
- <%# Vue 3 global buildsets window.Vue %>
265
- <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
262
+ <%# Pin Vue version via importmapupgrade by changing vue in package.json and rebuilding %>
263
+ <script type="importmap">
264
+ { "imports": { "vue": "/vendor/vue.esm-browser.prod.js" } }
265
+ </script>
266
266
 
267
- <%# Stipa Vue bootstrapper — sets window.StipaVue, auto-mounts on DOMContentLoaded %>
267
+ <%# Stipa Vue bootstrapper — exposes window.StipaVue %>
268
268
  <%= stipa_vue_bootstrap %>
269
269
 
270
- <%# Compiled Vue components add one block per component %>
271
- <script src="/components/RequestCard.js"></script>
272
- <script>
273
- window.StipaVue.register('RequestCard', window.RequestCard)
274
- </script>
270
+ <%# App bundleregisters components and mounts them %>
271
+ <script type="module" src="/app.js"></script>
275
272
 
276
273
  <%# Measure time from first byte to DOMContentLoaded %>
277
274
  <script>const _t0 = performance.now()</script>
@@ -396,6 +393,28 @@ module Stipa
396
393
 
397
394
 
398
395
 
396
+ def t_shims_vue
397
+ <<~TS
398
+ declare module '*.vue' {
399
+ import type { DefineComponent } from 'vue'
400
+ const component: DefineComponent
401
+ export default component
402
+ }
403
+ TS
404
+ end
405
+
406
+ def t_main_ts
407
+ <<~TS
408
+ import RequestCard from './components/RequestCard.vue'
409
+
410
+ // window.StipaVue is set by /stipa-vue.js, loaded as a module before this script.
411
+ const { StipaVue } = window as any
412
+
413
+ StipaVue.register('RequestCard', RequestCard)
414
+ StipaVue.mount()
415
+ TS
416
+ end
417
+
399
418
  def t_request_card_vue
400
419
  <<~VUE
401
420
  <template>
@@ -156,7 +156,7 @@ module Stipa
156
156
  #
157
157
  # <%= stipa_vue_bootstrap %>
158
158
  def stipa_vue_bootstrap(src: '/stipa-vue.js')
159
- %(<script src="#{src}"></script>)
159
+ %(<script type="module" src="#{src}"></script>)
160
160
  end
161
161
 
162
162
  # Include one or more JavaScript files.
data/lib/stipa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Stipa
2
- VERSION = "0.1.0"
2
+ VERSION = '0.1.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stipa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pedro Harbs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-18 00:00:00.000000000 Z
11
+ date: 2026-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest