diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 518ed3147..cdc164737 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -189,4 +189,21 @@ module ApplicationHelper Rails.env.production? || ENV['VITE_LEGACY'] == 'enabled' end end + + def ds_vite_legacy_javascript_tag(name, asset_type: :javascript) + legacy_name = name.sub(/(\..+)|$/, '-legacy\1') + import_tag = tag(:script) do + # rubocop:disable Rails/OutputSafety + "System.import('#{vite_asset_path(legacy_name, type: asset_type)}')".html_safe + # rubocop:enable Rails/OutputSafety + end + + safe_join [ds_vite_legacy_polyfill_tag, import_tag] + end + + # Internal: Renders the vite-legacy-polyfill to enable code splitting in + # browsers that do not support modules. + def ds_vite_legacy_polyfill_tag + tag(:script, nil, src: vite_asset_path('legacy-polyfills', type: :virtual)) + end end diff --git a/app/javascript/controllers/chartkick_controller.ts b/app/javascript/controllers/chartkick_controller.ts index 5c89d314b..9398eedfc 100644 --- a/app/javascript/controllers/chartkick_controller.ts +++ b/app/javascript/controllers/chartkick_controller.ts @@ -1,10 +1,11 @@ import { Controller } from '@hotwired/stimulus'; import { toggle, delegate } from '@utils'; -import Chartkick from 'chartkick'; -import Highcharts from 'highcharts'; export class ChartkickController extends Controller { async connect() { + const Highcharts = await import('highcharts'); + const Chartkick = await import('chartkick'); + Chartkick.use(Highcharts); const reflow = (nextChartId?: string) => nextChartId && Chartkick.charts[nextChartId]?.getChartObject()?.reflow(); diff --git a/app/javascript/controllers/react_controller.tsx b/app/javascript/controllers/react_controller.tsx index 896f3b273..cdaff89de 100644 --- a/app/javascript/controllers/react_controller.tsx +++ b/app/javascript/controllers/react_controller.tsx @@ -1,38 +1,24 @@ import { Controller } from '@hotwired/stimulus'; -import React from 'react'; +import React, { lazy, Suspense, FunctionComponent } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import invariant from 'tiny-invariant'; type Props = Record; +type Loader = () => Promise<{ default: FunctionComponent }>; +const componentsRegistry = new Map>(); +const components = import.meta.glob('../components/*.tsx'); -const componentsRegistry = new Map(); - -import ComboAdresseSearch from '../components/ComboAdresseSearch'; -import ComboAnnuaireEducationSearch from '../components/ComboAnnuaireEducationSearch'; -import ComboCommunesSearch from '../components/ComboCommunesSearch'; -import ComboDepartementsSearch from '../components/ComboDepartementsSearch'; -import ComboMultiple from '../components/ComboMultiple'; -import ComboMultipleDropdownList from '../components/ComboMultipleDropdownList'; -import ComboPaysSearch from '../components/ComboPaysSearch'; -import ComboRegionsSearch from '../components/ComboRegionsSearch'; -import ComboSearch from '../components/ComboSearch'; -import MapEditor from '../components/MapEditor'; -import MapReader from '../components/MapReader'; - -componentsRegistry.set('ComboAdresseSearch', ComboAdresseSearch); -componentsRegistry.set( - 'ComboAnnuaireEducationSearch', - ComboAnnuaireEducationSearch -); -componentsRegistry.set('ComboCommunesSearch', ComboCommunesSearch); -componentsRegistry.set('ComboDepartementsSearch', ComboDepartementsSearch); -componentsRegistry.set('ComboMultiple', ComboMultiple); -componentsRegistry.set('ComboMultipleDropdownList', ComboMultipleDropdownList); -componentsRegistry.set('ComboPaysSearch', ComboPaysSearch); -componentsRegistry.set('ComboRegionsSearch', ComboRegionsSearch); -componentsRegistry.set('ComboSearch', ComboSearch); -componentsRegistry.set('MapEditor', MapEditor); -componentsRegistry.set('MapReader', MapReader); +for (const [path, loader] of Object.entries(components)) { + const [filename] = path.split('/').reverse(); + const componentClassName = filename.replace(/\.(ts|tsx)$/, ''); + console.debug( + `Registered lazy default export for "${componentClassName}" component` + ); + componentsRegistry.set( + componentClassName, + LoadableComponent(loader as Loader) + ); +} // Initialize React components when their markup appears into the DOM. // @@ -68,7 +54,19 @@ export class ReactController extends Controller { render(, node); } - private getComponent(componentName: string) { + private getComponent(componentName: string): FunctionComponent | null { return componentsRegistry.get(componentName) ?? null; } } + +const Spinner = () =>
; + +function LoadableComponent(loader: Loader): FunctionComponent { + const LazyComponent = lazy(loader); + const Component: FunctionComponent = (props: Props) => ( + }> + + + ); + return Component; +} diff --git a/app/javascript/controllers/trix_controller.ts b/app/javascript/controllers/trix_controller.ts index 1426f91ca..588ef1202 100644 --- a/app/javascript/controllers/trix_controller.ts +++ b/app/javascript/controllers/trix_controller.ts @@ -1,5 +1,8 @@ import { Controller } from '@hotwired/stimulus'; -import 'trix'; -import '@rails/actiontext'; -export class TrixController extends Controller {} +export class TrixController extends Controller { + connect() { + import('trix'); + import('@rails/actiontext'); + } +} diff --git a/app/javascript/entrypoints/application.js b/app/javascript/entrypoints/application.js index a5dd0bb7b..bedb5083d 100644 --- a/app/javascript/entrypoints/application.js +++ b/app/javascript/entrypoints/application.js @@ -68,5 +68,5 @@ Turbo.session.drive = false; // Expose globals window.DS = window.DS || DS; -import '../shared/track/matomo'; -import '../shared/track/sentry'; +import('../shared/track/matomo'); +import('../shared/track/sentry'); diff --git a/app/javascript/shared/polyfills.js b/app/javascript/shared/polyfills.js index 7d9941c78..f3dce4c0f 100644 --- a/app/javascript/shared/polyfills.js +++ b/app/javascript/shared/polyfills.js @@ -1,15 +1,4 @@ import './polyfills/dataset'; -import 'core-js/stable'; -import 'regenerator-runtime/runtime'; -import 'dom4'; -import 'intersection-observer'; -import 'whatwg-fetch'; -import '@webcomponents/custom-elements'; -import '@webcomponents/template'; -import '@stimulus/polyfills'; -import 'formdata-polyfill'; -import 'event-target-polyfill'; -import 'yet-another-abortcontroller-polyfill'; // IE 11 has no baseURI (required by turbo) if (document.baseURI == undefined) { diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index e7507be09..3f2ba2a9b 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -15,9 +15,14 @@ = vite_client_tag = vite_react_refresh_tag - = vite_javascript_tag 'application' - - if administrateur_signed_in? - = vite_javascript_tag 'track-admin' + - if vite_legacy? + = ds_vite_legacy_javascript_tag 'application' + - if administrateur_signed_in? + = ds_vite_legacy_javascript_tag 'track-admin' + - else + = vite_javascript_tag 'application' + - if administrateur_signed_in? + = vite_javascript_tag 'track-admin' = preload_link_tag(asset_url("Muli-Regular.woff2")) = preload_link_tag(asset_url("Muli-Bold.woff2")) @@ -43,9 +48,6 @@ - if content_for?(:footer) = content_for(:footer) - - if vite_legacy? - = vite_legacy_javascript_tag 'application' - = yield :charts_js // Container for custom turbo-stream actions diff --git a/vite.config.ts b/vite.config.ts index ee780b273..c1eefb422 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -44,7 +44,16 @@ if (shouldBuildLegacy()) { export default defineConfig({ resolve: { alias: { '@utils': '/shared/utils.ts' } }, build: { - sourcemap: true + sourcemap: true, + rollupOptions: { + output: { + manualChunks(id) { + if (id.match('maplibre') || id.match('mapbox')) { + return 'maplibre'; + } + } + } + } }, plugins });