diff --git a/.circleci/config.yml b/.circleci/config.yml index 988d9f5c4..e00d8173b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,6 +57,10 @@ jobs: - node/install-packages: pkg-manager: pnpm + - run: + name: Build JS assets + command: pnpm build + - run: name: Wait for DB command: dockerize -wait tcp://localhost:5432 -timeout 1m diff --git a/.gitignore b/.gitignore index ec78b324d..c05bd90d3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,7 @@ spec/examples.txt *.local .env +/app/assets/builds/* +!/app/assets/builds/.keep + /node_modules diff --git a/Gemfile b/Gemfile index fb40687b5..c9afd2a8c 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,7 @@ gem "feedbag" gem "feedjira" gem "good_job", "~> 4.13.0" gem "httparty" +gem "jsbundling-rails" gem "nokogiri", "~> 1.19.0" gem "pg" gem "puma", "~> 7.0" @@ -24,7 +25,6 @@ gem "sprockets" gem "sprockets-rails" gem "stripe" gem "thread" -gem "uglifier" gem "will_paginate" group :development do diff --git a/Gemfile.lock b/Gemfile.lock index bdeb193e4..96185a68a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -142,7 +142,6 @@ GEM erubi (1.13.1) et-orbi (1.4.0) tzinfo - execjs (2.10.0) factory_bot (6.5.6) activesupport (>= 6.1.0) feedbag (1.0.2) @@ -178,6 +177,8 @@ GEM pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) + jsbundling-rails (1.3.1) + railties (>= 6.0.0) json (2.18.1) language_server-protocol (3.17.0.5) lint_roller (1.1.0) @@ -396,8 +397,6 @@ GEM tsort (0.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - uglifier (4.2.1) - execjs (>= 0.3.0, < 3) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) @@ -448,6 +447,7 @@ DEPENDENCIES feedjira good_job (~> 4.13.0) httparty + jsbundling-rails nokogiri (~> 1.19.0) pg pry-byebug @@ -470,7 +470,6 @@ DEPENDENCIES sprockets-rails stripe thread - uglifier web-console webdrivers webmock diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 000000000..0eb008ec8 --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +web: PORT=3000 bundle exec puma -C config/puma.rb +js: pnpm build --watch diff --git a/app/assets/builds/.keep b/app/assets/builds/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index 7d52bcf35..23bf581f9 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,3 +1,3 @@ //= link_tree ../images //= link_directory ../stylesheets .css -//= link_directory ../javascripts .js +//= link_directory ../builds .js diff --git a/app/assets/javascripts/application.js b/app/javascript/application.js similarity index 96% rename from app/assets/javascripts/application.js rename to app/javascript/application.js index 70ce426d6..534e9c57b 100644 --- a/app/assets/javascripts/application.js +++ b/app/javascript/application.js @@ -1,9 +1,16 @@ -//= require jquery.min.js -//= require bootstrap.min.js -//= require mousetrap.js -//= require jquery.visible.min.js -//= require underscore.js -//= require backbone.js +import "jquery"; +import "bootstrap"; +import "mousetrap"; +import "jquery-visible"; +import _ from "underscore"; +import Backbone from "backbone"; + +/* global jQuery, Mousetrap */ +var $ = jQuery; + +window.$ = $; + +Backbone.$ = $; _.templateSettings = { interpolate: /\{\{=(.+?)\}\}/g, @@ -337,3 +344,8 @@ $(document).ready(function() { $("#shortcuts").modal('toggle'); }); }); + +window.StoryList = StoryList; +window.AppView = AppView; + +export { Story, StoryView, StoryList, AppView }; diff --git a/bin/dev b/bin/dev index 6981d9176..aac2f73c4 100755 --- a/bin/dev +++ b/bin/dev @@ -1,4 +1,8 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true +#!/usr/bin/env bash -exec "./bin/rails", "server", *ARGV +if ! gem list foreman -i --silent; then + echo "Installing foreman..." + gem install foreman +fi + +exec foreman start -f Procfile.dev "$@" diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 182fd1ad4..e3b8d913d 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -7,13 +7,7 @@ # Add additional assets to the asset load path. Rails.application.config.assets.paths += [ - Rails.root.join("node_modules/backbone"), - Rails.root.join("node_modules/bootstrap/dist/js"), Rails.root.join("node_modules/bootstrap/dist/css"), - Rails.root.join("node_modules/jquery"), - Rails.root.join("node_modules/mousetrap"), - Rails.root.join("node_modules/underscore"), - Rails.root.join("node_modules/jquery-visible"), Rails.root.join("node_modules/@fontsource/lato/files"), Rails.root.join("node_modules/@fontsource/reenie-beanie/files"), Rails.root.join("node_modules/font-awesome/css"), diff --git a/eslint.config.mjs b/eslint.config.mjs index 53e5927fd..dbdcefb7f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,15 +4,11 @@ import globals from "globals"; export default [ js.configs.recommended, { + files: ["app/javascript/**"], languageOptions: { - sourceType: "script", + sourceType: "module", globals: { ...globals.browser, - Backbone: "readonly", - _: "readonly", - $: "readonly", - jQuery: "readonly", - Mousetrap: "readonly", }, }, rules: { @@ -25,6 +21,8 @@ export default [ languageOptions: { sourceType: "module", globals: { + ...globals.browser, + jQuery: "readonly", Story: "readonly", StoryView: "readonly", StoryList: "readonly", @@ -32,6 +30,6 @@ export default [ }, }, { - ignores: ["vendor/", "coverage/", "spec/javascript/support/", "public/"], + ignores: ["vendor/", "coverage/", "spec/javascript/support/", "public/", "app/assets/builds/"], }, ]; diff --git a/package.json b/package.json index 7af7de1c1..ff3402d06 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "private": true, "scripts": { - "eslint": "eslint app/assets/javascripts/ spec/javascript/spec/", + "build": "esbuild app/javascript/*.* --bundle --sourcemap --format=iife --outdir=app/assets/builds --public-path=/assets --alias:jquery=./node_modules/jquery/jquery.js --alias:bootstrap=./node_modules/bootstrap/dist/js/bootstrap.js --alias:jquery-visible=./node_modules/jquery-visible/jquery.visible.min.js", + "eslint": "eslint app/javascript/ spec/javascript/spec/", "stylelint": "stylelint 'app/assets/stylesheets/**/*.css'", "test": "vitest run --coverage" }, @@ -19,6 +20,7 @@ "devDependencies": { "@eslint/js": "^9.21.0", "@vitest/coverage-v8": "4.0.18", + "esbuild": "^0.27.1", "eslint": "^9.21.0", "globals": "^16.0.0", "jsdom": "^28.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03afddc04..5d38a7fb4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: '@vitest/coverage-v8': specifier: 4.0.18 version: 4.0.18(vitest@4.0.18(jsdom@28.0.0)) + esbuild: + specifier: ^0.27.1 + version: 0.27.3 eslint: specifier: ^9.21.0 version: 9.39.2 diff --git a/spec/javascript/setup.js b/spec/javascript/setup.js index c0c930628..f2780e1be 100644 --- a/spec/javascript/setup.js +++ b/spec/javascript/setup.js @@ -1,7 +1,3 @@ -import fs from "node:fs"; -import path from "node:path"; -import vm from "node:vm"; - import "jquery"; import underscore from "underscore"; import Backbone from "backbone"; @@ -89,14 +85,9 @@ const templateHTML = [ document.body.insertAdjacentHTML("beforeend", templateHTML); -// Load application.js class definitions into the global scope -const appJsPath = path.resolve(__dirname, "../../app/assets/javascripts/application.js"); -const appJs = fs.readFileSync(appJsPath, "utf-8"); - -// Strip the sprockets require directives and the $(document).ready block -const strippedJs = appJs - .replace(/^\/\/= require .+$/gm, "") - .replace(/_.templateSettings\s*=\s*\{[^}]+\};/, "") - .replace(/\$\(document\)\.ready\(function\(\)\s*\{[\s\S]*\}\);?\s*$/, ""); +import { Story, StoryView, StoryList, AppView } from "../../app/javascript/application.js"; -vm.runInThisContext(strippedJs, { filename: "application.js" }); +globalThis.Story = Story; +globalThis.StoryView = StoryView; +globalThis.StoryList = StoryList; +globalThis.AppView = AppView; diff --git a/vitest.config.js b/vitest.config.js index 751b40944..e60bcb839 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -5,6 +5,9 @@ export default defineConfig({ resolve: { alias: { jquery: path.resolve(__dirname, "node_modules/jquery/jquery.js"), + bootstrap: path.resolve(__dirname, "node_modules/bootstrap/dist/js/bootstrap.js"), + mousetrap: path.resolve(__dirname, "node_modules/mousetrap/mousetrap.js"), + "jquery-visible": path.resolve(__dirname, "node_modules/jquery-visible/jquery.visible.min.js"), }, }, test: {