diff --git a/app/components/accordion_component.rb b/app/components/accordion_component.rb index 3bbb35a0377..fdc84507e6d 100644 --- a/app/components/accordion_component.rb +++ b/app/components/accordion_component.rb @@ -1,4 +1,4 @@ -class AccordionComponent < ViewComponent::Base +class AccordionComponent < BaseComponent renders_one :header def initialize diff --git a/app/components/base_component.rb b/app/components/base_component.rb new file mode 100644 index 00000000000..e952ee55eae --- /dev/null +++ b/app/components/base_component.rb @@ -0,0 +1,18 @@ +class BaseComponent < ViewComponent::Base + def render_in(view_context, &block) + render_scripts_in(view_context) + super(view_context, &block) + end + + def render_scripts_in(view_context) + return if @rendered_scripts + @rendered_scripts = true + if view_context.respond_to?(:render_component_script) && self.class.scripts.present? + view_context.render_component_script(*self.class.scripts) + end + end + + def self.scripts + @scripts ||= _sidecar_files(['js']).map { |file| File.basename(file, '.js') } + end +end diff --git a/app/helpers/script_helper.rb b/app/helpers/script_helper.rb index 36103ac303a..695646017cb 100644 --- a/app/helpers/script_helper.rb +++ b/app/helpers/script_helper.rb @@ -20,6 +20,8 @@ def javascript_packs_tag_once(*names, prepend: false) nil end + alias_method :render_component_script, :javascript_packs_tag_once + def render_javascript_pack_once_tags return if !@scripts diff --git a/app/javascript/packages/es5-safe/package.json b/app/javascript/packages/es5-safe/package.json index 0ca0483e308..babe449f67d 100644 --- a/app/javascript/packages/es5-safe/package.json +++ b/app/javascript/packages/es5-safe/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "acorn": "^6.4.2", - "fast-glob": "^3.2.4", + "fast-glob": "^3.2.7", "p-all": "^3.0.0" } } diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb index d7b95d4ef8e..810576dc5f3 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -19,12 +19,12 @@ <%= yield(:meta_tags) if content_for?(:meta_tags) %> - + <%= content_tag( 'title', content_for?(:title) ? raw("#{yield(:title)} - #{APP_NAME}") : APP_NAME, ) %> - + <%= nonced_javascript_tag do %> document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/, 'js'); @@ -102,7 +102,7 @@ <% end %> <%= content_tag 'script', '', data: { analytics_endpoint: api_logger_path } %> - <%= javascript_packs_tag_once 'application', prepend: true %> + <%= javascript_packs_tag_once('application', prepend: true) %> <%= render_javascript_pack_once_tags %> <%= render 'shared/dap_analytics' if IdentityConfig.store.participate_in_dap && !session_with_trust? %> diff --git a/config/webpack/environment.js b/config/webpack/environment.js index 028ab782e56..cfc4d214ab4 100644 --- a/config/webpack/environment.js +++ b/config/webpack/environment.js @@ -1,6 +1,12 @@ +const { parse, resolve } = require('path'); const { environment } = require('@rails/webpacker'); +const { sync: glob } = require('fast-glob'); const RailsI18nWebpackPlugin = require('@18f/identity-rails-i18n-webpack-plugin'); +glob('app/components/*.js').forEach((path) => { + environment.entry[parse(path).name] = resolve(path); +}); + environment.loaders.delete('file'); environment.loaders.delete('nodeModules'); environment.loaders.delete('moduleSass'); diff --git a/config/webpacker.yml b/config/webpacker.yml index 00f2406bf15..1814be3e611 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -8,7 +8,7 @@ default: &default # Additional paths webpack should lookup modules # ['app/assets', 'engine/foo/app/assets'] - resolved_paths: [] + additional_paths: ['app/components'] # Reload manifest.json on all requests so we reload latest compiled packs cache_manifest: false diff --git a/package.json b/package.json index aad8c2d3b60..6a792beb2ac 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "cleave.js": "^1.6.0", "clipboard": "^2.0.6", "domready": "^1.0.8", + "fast-glob": "^3.2.7", "focus-trap": "^6.2.3", "identity-style-guide": "^6.2.0", "intl-tel-input": "^17.0.8", diff --git a/spec/components/base_component_spec.rb b/spec/components/base_component_spec.rb new file mode 100644 index 00000000000..c44c5a1a5b1 --- /dev/null +++ b/spec/components/base_component_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +RSpec.describe BaseComponent, type: :component do + class ExampleComponent < BaseComponent + def call + '' + end + end + + let(:lookup_context) { ActionView::LookupContext.new(ActionController::Base.view_paths) } + let(:view_context) { ActionView::Base.new(lookup_context, {}, controller) } + + before do + allow_any_instance_of(ApplicationController).to receive(:view_context).and_return(view_context) + end + + it 'does nothing when rendered' do + expect(view_context).not_to receive(:render_component_script) + + render_inline(ExampleComponent.new) + end + + context 'with sidecar script' do + class ExampleComponentWithScript < BaseComponent + def call + '' + end + + def self._sidecar_files(extensions) + return ['/path/to/app/components/example_component_with_script.js'] if extensions == ['js'] + super(extensions) + end + end + + it 'adds script to class variable when rendered' do + expect(view_context).to receive(:render_component_script). + with('example_component_with_script') + + render_inline(ExampleComponentWithScript.new) + end + end +end diff --git a/spec/features/visitors/js_disabled_spec.rb b/spec/features/visitors/js_disabled_spec.rb index 4a9a3b5066c..d4d6d90dffe 100644 --- a/spec/features/visitors/js_disabled_spec.rb +++ b/spec/features/visitors/js_disabled_spec.rb @@ -15,6 +15,10 @@ visit root_path expect(page).to have_css('#gov-banner', visible: :hidden) + + click_on t('shared.banner.how') + + expect(page).to have_css('#gov-banner', visible: true) end end end diff --git a/tsconfig.json b/tsconfig.json index 31ea0f46682..44f66fbe5d4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "target": "ESNext" }, "include": [ + "app/components", "app/javascript/app/phone-internationalization.js", "app/javascript/packages", "app/javascript/packs/doc-capture-polling.js", diff --git a/yarn.lock b/yarn.lock index 78054bcce12..e54dfc2f642 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4078,7 +4078,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.1.1, fast-glob@^3.2.4: +fast-glob@^3.1.1, fast-glob@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==