diff --git a/Gemfile b/Gemfile index 9a2e93fc2..a26e635c2 100644 --- a/Gemfile +++ b/Gemfile @@ -86,7 +86,6 @@ gem 'strong_migrations' # lint database migrations gem 'turbo-rails' gem 'typhoeus' gem 'view_component' -gem 'vite_plugin_legacy' gem 'vite_rails' gem 'warden' gem 'zipline' diff --git a/Gemfile.lock b/Gemfile.lock index 76aca1a31..f4c90b485 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -749,8 +749,6 @@ GEM axiom-types (~> 0.1) coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) - vite_plugin_legacy (3.0.2) - vite_ruby (~> 3.0, >= 3.0.4) vite_rails (3.0.9) railties (>= 5.1, < 8) vite_ruby (~> 3.0) @@ -912,7 +910,6 @@ DEPENDENCIES typhoeus vcr view_component - vite_plugin_legacy vite_rails warden web-console diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cdc164737..518ed3147 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -189,21 +189,4 @@ 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/helpers/vite_helper.rb b/app/helpers/vite_helper.rb new file mode 100644 index 000000000..1101b8f18 --- /dev/null +++ b/app/helpers/vite_helper.rb @@ -0,0 +1,31 @@ +module ViteHelper + SAFARI_10_NO_MODULE_FIX = "!function(){var e=document,t=e.createElement('script');if(!('noModule'in t)&&'onbeforeload'in t){var n=!1;e.addEventListener('beforeload',(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute('nomodule')||!n)return;e.preventDefault()}),!0),t.type='module',t.src='.',e.head.appendChild(t),t.remove()}}();" + + LEGACY_POLYFILL_ID = 'vite-legacy-polyfill' + LEGACY_ENTRY_ID = 'vite-legacy-entry' + SYSTEM_JS_INLINE_CODE = "document.querySelectorAll('script[data-legacy-entry]').forEach((e) => System.import(e.getAttribute('data-src')))" + + DETECT_MODERN_BROWSER_VARNAME = '__vite_is_modern_browser' + DETECT_MODERN_BROWSER_CODE = "try{import.meta.url;import('_').catch(()=>1);}catch(e){}window.#{DETECT_MODERN_BROWSER_VARNAME}=true;" + DYNAMIC_FALLBACK_INLINE_CODE = "!function(){if(window.#{DETECT_MODERN_BROWSER_VARNAME})return;console.warn('vite: loading legacy build because dynamic import or import.meta.url is unsupported, syntax error above should be ignored');var e=document.getElementById('#{LEGACY_POLYFILL_ID}'),n=document.createElement('script');n.src=e.src,n.onload=function(){#{SYSTEM_JS_INLINE_CODE}},document.body.appendChild(n)}();" + + def vite_legacy_javascript_tag(name, asset_type: :javascript) + legacy_name = name.sub(/(\..+)|$/, '-legacy\1') + src = vite_asset_path(legacy_name, type: :virtual) + javascript_include_tag(src, nomodule: true, 'data-legacy-entry': true, 'data-src': src) + end + + def vite_legacy_polyfill_tag + safe_join [ + javascript_tag(SAFARI_10_NO_MODULE_FIX, type: :module, nonce: true), + javascript_include_tag(vite_asset_path('legacy-polyfills', type: :virtual), nomodule: true, id: LEGACY_POLYFILL_ID) + ] + end + + def vite_legacy_fallback_tag + safe_join [ + javascript_tag(DETECT_MODERN_BROWSER_CODE, type: :module, nonce: true), + javascript_tag(DYNAMIC_FALLBACK_INLINE_CODE, type: :module, nonce: true) + ] + 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 4cc295ee8..f3dce4c0f 100644 --- a/app/javascript/shared/polyfills.js +++ b/app/javascript/shared/polyfills.js @@ -1,14 +1,4 @@ -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'; +import './polyfills/dataset'; // IE 11 has no baseURI (required by turbo) if (document.baseURI == undefined) { @@ -47,79 +37,3 @@ function polyfillIsConnected(proto) { if (!('isConnected' in Node.prototype)) { polyfillIsConnected(Node.prototype); } - -/* - @preserve dataset polyfill for IE < 11. See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset and http://caniuse.com/#search=dataset - - @author ShirtlessKirk copyright 2015 - @license WTFPL (http://www.wtfpl.net/txt/copying) -*/ - -const dash = /-([a-z])/gi; -const dataRegEx = /^data-(.+)/; -const hasEventListener = !!document.addEventListener; -const test = document.createElement('_'); -const DOMAttrModified = 'DOMAttrModified'; - -let mutationSupport = false; - -function clearDataset(event) { - delete event.target._datasetCache; -} - -function toCamelCase(string) { - return string.replace(dash, function (_, letter) { - return letter.toUpperCase(); - }); -} - -function getDataset() { - const dataset = {}; - - for (let attribute of this.attributes) { - let match = attribute.name.match(dataRegEx); - if (match) { - dataset[toCamelCase(match[1])] = attribute.value; - } - } - - return dataset; -} - -function mutation() { - if (hasEventListener) { - test.removeEventListener(DOMAttrModified, mutation, false); - } else { - test.detachEvent(`on${DOMAttrModified}`, mutation); - } - - mutationSupport = true; -} - -if (!test.dataset) { - if (hasEventListener) { - test.addEventListener(DOMAttrModified, mutation, false); - } else { - test.attachEvent(`on${DOMAttrModified}`, mutation); - } - - // trigger event (if supported) - test.setAttribute('foo', 'bar'); - - Object.defineProperty(Element.prototype, 'dataset', { - get: mutationSupport - ? function get() { - if (!this._datasetCache) { - this._datasetCache = getDataset.call(this); - } - - return this._datasetCache; - } - : getDataset - }); - - if (mutationSupport && hasEventListener) { - // < IE9 supports neither - document.addEventListener(DOMAttrModified, clearDataset, false); - } -} diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index df26641eb..8e1a4fa1f 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -15,14 +15,16 @@ = 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' + = vite_legacy_polyfill_tag + = 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' + = vite_legacy_javascript_tag 'track-admin' + = vite_legacy_fallback_tag = preload_link_tag(asset_url("Muli-Regular.woff2")) = preload_link_tag(asset_url("Muli-Bold.woff2")) diff --git a/patches/@vitejs+plugin-legacy+1.8.2.patch b/patches/@vitejs+plugin-legacy+1.8.2.patch deleted file mode 100644 index 231d5b148..000000000 --- a/patches/@vitejs+plugin-legacy+1.8.2.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@vitejs/plugin-legacy/index.js b/node_modules/@vitejs/plugin-legacy/index.js -index 2f1b199..1901ef6 100644 ---- a/node_modules/@vitejs/plugin-legacy/index.js -+++ b/node_modules/@vitejs/plugin-legacy/index.js -@@ -23,7 +23,7 @@ const detectDynamicImportVarName = '__vite_is_dynamic_import_support' - const detectDynamicImportCode = `try{import("_").catch(()=>1);}catch(e){}window.${detectDynamicImportVarName}=true;` - const dynamicFallbackInlineCode = `!function(){if(window.${detectDynamicImportVarName})return;console.warn("vite: loading legacy build because dynamic import is unsupported, syntax error above should be ignored");var e=document.getElementById("${legacyPolyfillId}"),n=document.createElement("script");n.src=e.src,n.onload=function(){${systemJSInlineCode}},document.body.appendChild(n)}();` - --const forceDynamicImportUsage = `export function __vite_legacy_guard(){import('data:text/javascript,')};` -+const forceDynamicImportUsage = `` - - const legacyEnvVarMarker = `__VITE_IS_LEGACY__` - diff --git a/vite.config.ts b/vite.config.ts index ee780b273..51733d769 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -43,9 +43,7 @@ if (shouldBuildLegacy()) { export default defineConfig({ resolve: { alias: { '@utils': '/shared/utils.ts' } }, - build: { - sourcemap: true - }, + build: { sourcemap: true }, plugins });