diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml index 5da1ec3a24..433729cbc4 100644 --- a/.devcontainer/compose.yaml +++ b/.devcontainer/compose.yaml @@ -9,6 +9,7 @@ services: environment: RAILS_ENV: development NODE_ENV: development + VITE_RUBY_HOST: 0.0.0.0 BIND: 0.0.0.0 BOOTSNAP_CACHE_DIR: /tmp REDIS_HOST: redis @@ -27,6 +28,7 @@ services: ports: - '3000:3000' - '3035:3035' + - '3036:3036' - '4000:4000' networks: - external_network diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e638b9c548..0cf15040cd 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -25,23 +25,6 @@ 'tesseract.js', // Requires code changes 'react-hotkeys', // Requires code changes - // Requires Webpacker upgrade or replacement - '@svgr/webpack', - '@types/webpack', - 'babel-loader', - 'compression-webpack-plugin', - 'css-loader', - 'imports-loader', - 'mini-css-extract-plugin', - 'postcss-loader', - 'sass-loader', - 'terser-webpack-plugin', - 'webpack', - 'webpack-assets-manifest', - 'webpack-bundle-analyzer', - 'webpack-dev-server', - 'webpack-cli', - // react-router: Requires manual upgrade 'history', 'react-router-dom', diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 1f7f8f93a8..2fa28a587c 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -49,7 +49,7 @@ jobs: public/assets public/packs public/packs-test - tmp/cache/webpacker + tmp/cache/vite key: ${{ matrix.mode }}-assets-${{ github.head_ref || github.ref_name }}-${{ github.sha }} restore-keys: | ${{ matrix.mode }}-assets-${{ github.head_ref || github.ref_name }}-${{ github.sha }} @@ -63,7 +63,7 @@ jobs: - name: Archive asset artifacts run: | - tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* + tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* tmp/cache/vite/last-build*.json - uses: actions/upload-artifact@v4 if: matrix.mode == 'test' diff --git a/.gitignore b/.gitignore index a74317bd7d..b4fb2c946b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,10 +21,11 @@ /public/system /public/assets /public/packs +/public/packs-dev /public/packs-test .env .env.production -/node_modules/ +node_modules/ /build/ # Ignore Vagrant files diff --git a/.prettierignore b/.prettierignore index 07f90e4bbd..8f16e731c8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -18,10 +18,6 @@ !/log/.keep /tmp /coverage -/public/system -/public/assets -/public/packs -/public/packs-test .env .env.production .env.development @@ -60,6 +56,7 @@ docker-compose.override.yml /public/packs /public/packs-test /public/system +/public/vite* # Ignore emoji map file /app/javascript/mastodon/features/emoji/emoji_map.json diff --git a/Dockerfile b/Dockerfile index 7e9393efea..389a744b87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -307,6 +307,7 @@ RUN \ ldconfig; \ # Use Ruby on Rails to create Mastodon assets SECRET_KEY_BASE_DUMMY=1 \ + # Do not run `yarn` when precompiling assets, we already ran it before bundle exec rails assets:precompile; \ # Cleanup temporary files rm -fr /opt/mastodon/tmp; diff --git a/Gemfile b/Gemfile index 44c4c9a54d..68fa90f73b 100644 --- a/Gemfile +++ b/Gemfile @@ -95,7 +95,6 @@ gem 'tty-prompt', '~> 0.23', require: false gem 'twitter-text', '~> 3.1.0' gem 'tzinfo-data', '~> 1.2023' gem 'webauthn', '~> 3.0' -gem 'webpacker', '~> 5.4' gem 'webpush', github: 'mastodon/webpush', ref: '9631ac63045cfabddacc69fc06e919b4c13eb913' gem 'json-ld' @@ -230,3 +229,5 @@ gem 'rubyzip', '~> 2.3' gem 'hcaptcha', '~> 7.1' gem 'mail', '~> 2.8' + +gem 'vite_rails', '~> 3.0.19' diff --git a/Gemfile.lock b/Gemfile.lock index 1df8c3ed3d..a9c4ebee9e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -203,6 +203,7 @@ GEM railties (>= 5) dotenv (3.1.8) drb (2.2.1) + dry-cli (1.2.0) elasticsearch (7.17.11) elasticsearch-api (= 7.17.11) elasticsearch-transport (= 7.17.11) @@ -806,7 +807,6 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - semantic_range (3.1.0) shoulda-matchers (6.5.0) activesupport (>= 5.2.0) sidekiq (6.5.12) @@ -892,6 +892,15 @@ GEM validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix + vite_rails (3.0.19) + railties (>= 5.1, < 9) + vite_ruby (~> 3.0, >= 3.2.2) + vite_ruby (3.9.2) + dry-cli (>= 0.7, < 2) + logger (~> 1.6) + mutex_m + rack-proxy (~> 0.6, >= 0.6.1) + zeitwerk (~> 2.2) warden (1.2.9) rack (>= 2.0.9) webauthn (3.4.0) @@ -910,11 +919,6 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webpacker (5.4.4) - activesupport (>= 5.2) - rack-proxy (>= 0.6.1) - railties (>= 5.2) - semantic_range (>= 2.3.0) webrick (1.9.1) websocket (1.2.11) websocket-driver (0.7.7) @@ -1078,9 +1082,9 @@ DEPENDENCIES tty-prompt (~> 0.23) twitter-text (~> 3.1.0) tzinfo-data (~> 1.2023) + vite_rails (~> 3.0.19) webauthn (~> 3.0) webmock (~> 3.18) - webpacker (~> 5.4) webpush! xorcist (~> 1.1) diff --git a/Procfile.dev b/Procfile.dev index f81333b04c..99de0616c5 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,4 +1,4 @@ web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq stream: env PORT=4000 yarn workspace @mastodon/streaming start -webpack: bin/webpack-dev-server +vite: yarn dev diff --git a/app/helpers/routing_helper.rb b/app/helpers/routing_helper.rb index 22efc5f092..1ff8b992c3 100644 --- a/app/helpers/routing_helper.rb +++ b/app/helpers/routing_helper.rb @@ -4,7 +4,7 @@ module RoutingHelper extend ActiveSupport::Concern include ActionView::Helpers::AssetTagHelper - include Webpacker::Helper + include ViteRails::TagHelpers included do include Rails.application.routes.url_helpers @@ -25,7 +25,7 @@ module RoutingHelper end def frontend_asset_path(source, **) - asset_pack_path("media/#{source}", **) + vite_asset_path(source, **) end def frontend_asset_url(source, **) diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb index cda380b3bc..158ac92d6d 100644 --- a/app/helpers/theme_helper.rb +++ b/app/helpers/theme_helper.rb @@ -4,11 +4,14 @@ module ThemeHelper def theme_style_tags(theme) if theme == 'system' ''.html_safe.tap do |tags| - tags << stylesheet_pack_tag('mastodon-light', media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous') - tags << stylesheet_pack_tag('default', media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous') + tags << vite_stylesheet_tag('styles/mastodon-light.scss', media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous') + tags << vite_stylesheet_tag('styles/application.scss', media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous') end + # TODO: Determine why default doesn't map correctly. + elsif theme == 'default' + vite_stylesheet_tag 'styles/application.scss', media: 'all', crossorigin: 'anonymous' else - stylesheet_pack_tag theme, media: 'all', crossorigin: 'anonymous' + vite_stylesheet_tag "styles/#{theme}.scss", media: 'all', crossorigin: 'anonymous' end end diff --git a/app/javascript/entrypoints/admin.tsx b/app/javascript/entrypoints/admin.tsx index 225cb16330..a60778f0c0 100644 --- a/app/javascript/entrypoints/admin.tsx +++ b/app/javascript/entrypoints/admin.tsx @@ -1,4 +1,3 @@ -import './public-path'; import { createRoot } from 'react-dom/client'; import Rails from '@rails/ujs'; @@ -273,7 +272,7 @@ async function mountReactComponent(element: Element) { ); const { default: Component } = (await import( - `@/mastodon/components/admin/${componentName}` + `@/mastodon/components/admin/${componentName}.jsx` )) as { default: React.ComponentType }; const root = createRoot(element); diff --git a/app/javascript/entrypoints/application.ts b/app/javascript/entrypoints/application.ts index 1087b1c4cb..a2b9fce06c 100644 --- a/app/javascript/entrypoints/application.ts +++ b/app/javascript/entrypoints/application.ts @@ -1,9 +1,7 @@ -import './public-path'; +import { start } from 'mastodon/common'; +import { loadLocale } from 'mastodon/locales'; import main from 'mastodon/main'; - -import { start } from '../mastodon/common'; -import { loadLocale } from '../mastodon/locales'; -import { loadPolyfills } from '../mastodon/polyfills'; +import { loadPolyfills } from 'mastodon/polyfills'; start(); diff --git a/app/javascript/entrypoints/embed.tsx b/app/javascript/entrypoints/embed.tsx index 6c091e4d07..c1cd32e6a2 100644 --- a/app/javascript/entrypoints/embed.tsx +++ b/app/javascript/entrypoints/embed.tsx @@ -1,4 +1,3 @@ -import './public-path'; import { createRoot } from 'react-dom/client'; import { afterInitialRender } from 'mastodon/hooks/useRenderSignal'; diff --git a/app/javascript/entrypoints/error.ts b/app/javascript/entrypoints/error.ts index db68484f3a..af9d484de1 100644 --- a/app/javascript/entrypoints/error.ts +++ b/app/javascript/entrypoints/error.ts @@ -1,4 +1,3 @@ -import './public-path'; import ready from '../mastodon/ready'; ready(() => { diff --git a/app/javascript/entrypoints/index.html b/app/javascript/entrypoints/index.html new file mode 100644 index 0000000000..025030ba46 --- /dev/null +++ b/app/javascript/entrypoints/index.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/app/javascript/entrypoints/inert.ts b/app/javascript/entrypoints/inert.ts deleted file mode 100644 index 7c04a97faf..0000000000 --- a/app/javascript/entrypoints/inert.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* Placeholder file to have `inert.scss` compiled by Webpack - This is used by the `wicg-inert` polyfill */ - -import '../styles/inert.scss'; diff --git a/app/javascript/entrypoints/mailer.ts b/app/javascript/entrypoints/mailer.ts deleted file mode 100644 index a2ad5e73ac..0000000000 --- a/app/javascript/entrypoints/mailer.ts +++ /dev/null @@ -1,3 +0,0 @@ -import '../styles/mailer.scss'; - -require.context('../icons'); diff --git a/app/javascript/entrypoints/public-path.ts b/app/javascript/entrypoints/public-path.ts deleted file mode 100644 index ac4b9355b9..0000000000 --- a/app/javascript/entrypoints/public-path.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Dynamically set webpack's loading path depending on a meta header, in order -// to share the same assets regardless of instance configuration. -// See https://webpack.js.org/guides/public-path/#on-the-fly - -function removeOuterSlashes(string: string) { - return string.replace(/^\/*/, '').replace(/\/*$/, ''); -} - -function formatPublicPath(host = '', path = '') { - let formattedHost = removeOuterSlashes(host); - if (formattedHost && !/^http/i.test(formattedHost)) { - formattedHost = `//${formattedHost}`; - } - const formattedPath = removeOuterSlashes(path); - return `${formattedHost}/${formattedPath}/`; -} - -const cdnHost = document.querySelector('meta[name=cdn-host]'); - -__webpack_public_path__ = formatPublicPath( - cdnHost ? cdnHost.content : '', - process.env.PUBLIC_OUTPUT_PATH, -); diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx index 9374d6b2d1..e3b45ef3f2 100644 --- a/app/javascript/entrypoints/public.tsx +++ b/app/javascript/entrypoints/public.tsx @@ -1,7 +1,5 @@ import { createRoot } from 'react-dom/client'; -import './public-path'; - import { IntlMessageFormat } from 'intl-messageformat'; import type { MessageDescriptor, PrimitiveType } from 'react-intl'; import { defineMessages } from 'react-intl'; @@ -18,8 +16,6 @@ import { loadLocale, getLocale } from '../mastodon/locales'; import { loadPolyfills } from '../mastodon/polyfills'; import ready from '../mastodon/ready'; -import 'cocoon-js-vanilla'; - start(); const messages = defineMessages({ @@ -153,9 +149,7 @@ function loaded() { const reactComponents = document.querySelectorAll('[data-component]'); if (reactComponents.length > 0) { - import( - /* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container' - ) + import('../mastodon/containers/media_container') .then(({ default: MediaContainer }) => { reactComponents.forEach((component) => { Array.from(component.children).forEach((child) => { diff --git a/app/javascript/entrypoints/remote_interaction_helper.ts b/app/javascript/entrypoints/remote_interaction_helper.ts index 419571c896..f50203747d 100644 --- a/app/javascript/entrypoints/remote_interaction_helper.ts +++ b/app/javascript/entrypoints/remote_interaction_helper.ts @@ -8,8 +8,6 @@ and performs no other task. */ -import './public-path'; - import axios from 'axios'; interface JRDLink { diff --git a/app/javascript/entrypoints/share.tsx b/app/javascript/entrypoints/share.tsx index 7926250851..f6fda68a39 100644 --- a/app/javascript/entrypoints/share.tsx +++ b/app/javascript/entrypoints/share.tsx @@ -1,4 +1,3 @@ -import './public-path'; import { createRoot } from 'react-dom/client'; import { start } from '../mastodon/common'; diff --git a/app/javascript/entrypoints/sign_up.ts b/app/javascript/entrypoints/sign_up.ts index 87100be56d..09884476ec 100644 --- a/app/javascript/entrypoints/sign_up.ts +++ b/app/javascript/entrypoints/sign_up.ts @@ -1,4 +1,3 @@ -import './public-path'; import axios from 'axios'; import ready from '../mastodon/ready'; diff --git a/app/javascript/mastodon/common.js b/app/javascript/mastodon/common.js index c61e02250c..32c5ce6f25 100644 --- a/app/javascript/mastodon/common.js +++ b/app/javascript/mastodon/common.js @@ -1,7 +1,8 @@ import Rails from '@rails/ujs'; export function start() { - require.context('../images/', true, /\.(jpg|png|svg)$/); + // TODO: Find alternative to this + // require.context('../images/', true, /\.(jpg|png|svg)$/); try { Rails.start(); diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index 25116d19b0..ec5a9780cb 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -9,7 +9,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg'; +import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; diff --git a/app/javascript/mastodon/features/alt_text_modal/index.tsx b/app/javascript/mastodon/features/alt_text_modal/index.tsx index 08e4a8917c..f24a3b6f70 100644 --- a/app/javascript/mastodon/features/alt_text_modal/index.tsx +++ b/app/javascript/mastodon/features/alt_text_modal/index.tsx @@ -15,10 +15,6 @@ import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import { useSpring, animated } from '@react-spring/web'; import Textarea from 'react-textarea-autosize'; import { length } from 'stringz'; -// eslint-disable-next-line import/extensions -import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js'; -// eslint-disable-next-line import/no-extraneous-dependencies -import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js'; import { showAlertForError } from 'mastodon/actions/alerts'; import { uploadThumbnail } from 'mastodon/actions/compose'; @@ -350,9 +346,15 @@ export const AltTextModal = forwardRef>( fetchTesseract() .then(async ({ createWorker }) => { + const [tesseractWorkerPath, tesseractCorePath] = await Promise.all([ + // eslint-disable-next-line import/extensions + import('tesseract.js/dist/worker.min.js?url'), + // eslint-disable-next-line import/no-extraneous-dependencies + import('tesseract.js-core/tesseract-core.wasm.js?url'), + ]); const worker = await createWorker('eng', 1, { - workerPath: tesseractWorkerPath as string, - corePath: tesseractCorePath as string, + workerPath: tesseractWorkerPath.default, + corePath: tesseractCorePath.default, langPath: `${assetHost}/ocr/lang-data`, cacheMethod: 'write', }); @@ -501,5 +503,4 @@ export const AltTextModal = forwardRef>( ); }, ); - AltTextModal.displayName = 'AltTextModal'; diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx index 2bd6af4cf4..e2c32dcdb9 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx @@ -12,7 +12,7 @@ import Overlay from 'react-overlays/Overlay'; import MoodIcon from '@/material-icons/400-20px/mood.svg?react'; import { IconButton } from 'mastodon/components/icon_button'; -import emojiCompressed from 'mastodon/features/emoji/emoji_compressed'; +import emojiCompressed from '@/mastodon/features/emoji/emoji_compressed.mjs'; import { assetHost } from 'mastodon/utils/config'; import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji'; diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.d.ts b/app/javascript/mastodon/features/emoji/emoji_compressed.d.mts similarity index 100% rename from app/javascript/mastodon/features/emoji/emoji_compressed.d.ts rename to app/javascript/mastodon/features/emoji/emoji_compressed.d.mts diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.mjs similarity index 58% rename from app/javascript/mastodon/features/emoji/emoji_compressed.js rename to app/javascript/mastodon/features/emoji/emoji_compressed.mjs index 7cc1de7087..2fe3d5fe6f 100644 --- a/app/javascript/mastodon/features/emoji/emoji_compressed.js +++ b/app/javascript/mastodon/features/emoji/emoji_compressed.mjs @@ -9,28 +9,27 @@ // to ensure that the prevaled file is regenerated by Babel // version: 4 -const { NimbleEmojiIndex } = require('emoji-mart'); -const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data'); +import { NimbleEmojiIndex } from 'emoji-mart'; +import { uncompress as emojiMartUncompress } from 'emoji-mart/dist/utils/data'; - -let data = require('./emoji_data.json'); -const emojiMap = require('./emoji_map.json'); -const { unicodeToFilename } = require('./unicode_to_filename'); -const { unicodeToUnifiedName } = require('./unicode_to_unified_name'); +import data from './emoji_data.json'; +import emojiMap from './emoji_map.json'; +import { unicodeToFilename } from './unicode_to_filename'; +import { unicodeToUnifiedName } from './unicode_to_unified_name'; emojiMartUncompress(data); const emojiMartData = data; const emojiIndex = new NimbleEmojiIndex(emojiMartData); -const excluded = ['®', '©', '™']; -const skinTones = ['🏻', '🏼', '🏽', '🏾', '🏿']; -const shortcodeMap = {}; +const excluded = ['®', '©', '™']; +const skinTones = ['🏻', '🏼', '🏽', '🏾', '🏿']; +const shortcodeMap = {}; const shortCodesToEmojiData = {}; const emojisWithoutShortCodes = []; -Object.keys(emojiIndex.emojis).forEach(key => { +Object.keys(emojiIndex.emojis).forEach((key) => { let emoji = emojiIndex.emojis[key]; // Emojis with skin tone modifiers are stored like this @@ -41,22 +40,22 @@ Object.keys(emojiIndex.emojis).forEach(key => { shortcodeMap[emoji.native] = emoji.id; }); -const stripModifiers = unicode => { - skinTones.forEach(tone => { +const stripModifiers = (unicode) => { + skinTones.forEach((tone) => { unicode = unicode.replace(tone, ''); }); return unicode; }; -Object.keys(emojiMap).forEach(key => { +Object.keys(emojiMap).forEach((key) => { if (excluded.includes(key)) { delete emojiMap[key]; return; } const normalizedKey = stripModifiers(key); - let shortcode = shortcodeMap[normalizedKey]; + let shortcode = shortcodeMap[normalizedKey]; if (!shortcode) { shortcode = shortcodeMap[normalizedKey + '\uFE0F']; @@ -82,7 +81,7 @@ Object.keys(emojiMap).forEach(key => { } }); -Object.keys(emojiIndex.emojis).forEach(key => { +Object.keys(emojiIndex.emojis).forEach((key) => { let emoji = emojiIndex.emojis[key]; // Emojis with skin tone modifiers are stored like this @@ -94,9 +93,11 @@ Object.keys(emojiIndex.emojis).forEach(key => { let { short_names, search, unified } = emojiMartData.emojis[key]; if (short_names[0] !== key) { - throw new Error('The compressor expects the first short_code to be the ' + - 'key. It may need to be rewritten if the emoji change such that this ' + - 'is no longer the case.'); + throw new Error( + 'The compressor expects the first short_code to be the ' + + 'key. It may need to be rewritten if the emoji change such that this ' + + 'is no longer the case.', + ); } short_names = short_names.slice(1); // first short name can be inferred from the key @@ -117,21 +118,23 @@ Object.keys(emojiIndex.emojis).forEach(key => { // JSON.parse/stringify is to emulate what @preval is doing and avoid any // inconsistent behavior in dev mode -module.exports = JSON.parse(JSON.stringify([ - shortCodesToEmojiData, - /* - * The property `skins` is not found in the current context. - * This could potentially lead to issues when interacting with modules or data structures - * that expect the presence of `skins` property. - * Currently, no definitions or references to `skins` property can be found in: - * - {@link node_modules/emoji-mart/dist/utils/data.js} - * - {@link node_modules/emoji-mart/data/all.json} - * - {@link app/javascript/mastodon/features/emoji/emoji_compressed.d.ts#Skins} - * Future refactorings or updates should consider adding definitions or handling for `skins` property. - */ - emojiMartData.skins, - emojiMartData.categories, - emojiMartData.aliases, - emojisWithoutShortCodes, - emojiMartData -])); +export default JSON.parse( + JSON.stringify([ + shortCodesToEmojiData, + /* + * The property `skins` is not found in the current context. + * This could potentially lead to issues when interacting with modules or data structures + * that expect the presence of `skins` property. + * Currently, no definitions or references to `skins` property can be found in: + * - {@link node_modules/emoji-mart/dist/utils/data.js} + * - {@link node_modules/emoji-mart/data/all.json} + * - {@link app/javascript/mastodon/features/emoji/emoji_compressed.d.ts#Skins} + * Future refactorings or updates should consider adding definitions or handling for `skins` property. + */ + emojiMartData.skins, + emojiMartData.categories, + emojiMartData.aliases, + emojisWithoutShortCodes, + emojiMartData, + ]), +); diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts index 8eeb457055..bcbc93bf18 100644 --- a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts +++ b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts @@ -4,8 +4,8 @@ import type { BaseEmoji } from 'emoji-mart'; import type { Emoji } from 'emoji-mart/dist-es/utils/data'; -import type { Search, ShortCodesToEmojiData } from './emoji_compressed'; -import emojiCompressed from './emoji_compressed'; +import type { Search, ShortCodesToEmojiData } from './emoji_compressed.mjs'; +import emojiCompressed from './emoji_compressed.mjs'; import { unicodeToUnifiedName } from './unicode_to_unified_name'; type Emojis = Record< diff --git a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts index 0a5a4c1d76..02bc85dc80 100644 --- a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts +++ b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts @@ -5,8 +5,8 @@ import type { FilenameData, ShortCodesToEmojiDataKey, -} from './emoji_compressed'; -import emojiCompressed from './emoji_compressed'; +} from './emoji_compressed.mjs'; +import emojiCompressed from './emoji_compressed.mjs'; import { unicodeToFilename } from './unicode_to_filename'; type UnicodeMapping = Record< diff --git a/app/javascript/mastodon/features/emoji/unicode_to_filename.js b/app/javascript/mastodon/features/emoji/unicode_to_filename.js index c75c4cd7d0..cfe5539c7b 100644 --- a/app/javascript/mastodon/features/emoji/unicode_to_filename.js +++ b/app/javascript/mastodon/features/emoji/unicode_to_filename.js @@ -1,6 +1,6 @@ // taken from: // https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866 -exports.unicodeToFilename = (str) => { +export const unicodeToFilename = (str) => { let result = ''; let charCode = 0; let p = 0; diff --git a/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js b/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js index d29550f122..15f60aa7c3 100644 --- a/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js +++ b/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js @@ -6,7 +6,7 @@ function padLeft(str, num) { return str; } -exports.unicodeToUnifiedName = (str) => { +export const unicodeToUnifiedName = (str) => { let output = ''; for (let i = 0; i < str.length; i += 2) { diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index ec493ae283..d865f7d7a5 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -1,235 +1,235 @@ export function EmojiPicker () { - return import(/* webpackChunkName: "emoji_picker" */'../../emoji/emoji_picker'); + return import('../../emoji/emoji_picker'); } export function Compose () { - return import(/* webpackChunkName: "features/compose" */'../../compose'); + return import('../../compose'); } export function Notifications () { - return import(/* webpackChunkName: "features/notifications" */'../../notifications_v2'); + return import('../../notifications_v2'); } export function HomeTimeline () { - return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline'); + return import('../../home_timeline'); } export function PublicTimeline () { - return import(/* webpackChunkName: "features/public_timeline" */'../../public_timeline'); + return import('../../public_timeline'); } export function CommunityTimeline () { - return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline'); + return import('../../community_timeline'); } export function Firehose () { - return import(/* webpackChunkName: "features/firehose" */'../../firehose'); + return import('../../firehose'); } export function HashtagTimeline () { - return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline'); + return import('../../hashtag_timeline'); } export function DirectTimeline() { - return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline'); + return import('../../direct_timeline'); } export function ListTimeline () { - return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline'); + return import('../../list_timeline'); } export function Lists () { - return import(/* webpackChunkName: "features/lists" */'../../lists'); + return import('../../lists'); } export function Status () { - return import(/* webpackChunkName: "features/status" */'../../status'); + return import('../../status'); } export function GettingStarted () { - return import(/* webpackChunkName: "features/getting_started" */'../../getting_started'); + return import('../../getting_started'); } export function KeyboardShortcuts () { - return import(/* webpackChunkName: "features/keyboard_shortcuts" */'../../keyboard_shortcuts'); + return import('../../keyboard_shortcuts'); } export function PinnedStatuses () { - return import(/* webpackChunkName: "features/pinned_statuses" */'../../pinned_statuses'); + return import('../../pinned_statuses'); } export function AccountTimeline () { - return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline'); + return import('../../account_timeline'); } export function AccountGallery () { - return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery'); + return import('../../account_gallery'); } export function AccountFeatured() { - return import(/* webpackChunkName: "features/account_featured" */'../../account_featured'); + return import('../../account_featured'); } export function Followers () { - return import(/* webpackChunkName: "features/followers" */'../../followers'); + return import('../../followers'); } export function Following () { - return import(/* webpackChunkName: "features/following" */'../../following'); + return import('../../following'); } export function Reblogs () { - return import(/* webpackChunkName: "features/reblogs" */'../../reblogs'); + return import('../../reblogs'); } export function Favourites () { - return import(/* webpackChunkName: "features/favourites" */'../../favourites'); + return import('../../favourites'); } export function FollowRequests () { - return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests'); + return import('../../follow_requests'); } export function FavouritedStatuses () { - return import(/* webpackChunkName: "features/favourited_statuses" */'../../favourited_statuses'); + return import('../../favourited_statuses'); } export function FollowedTags () { - return import(/* webpackChunkName: "features/followed_tags" */'../../followed_tags'); + return import('../../followed_tags'); } export function BookmarkedStatuses () { - return import(/* webpackChunkName: "features/bookmarked_statuses" */'../../bookmarked_statuses'); + return import('../../bookmarked_statuses'); } export function Blocks () { - return import(/* webpackChunkName: "features/blocks" */'../../blocks'); + return import('../../blocks'); } export function DomainBlocks () { - return import(/* webpackChunkName: "features/domain_blocks" */'../../domain_blocks'); + return import('../../domain_blocks'); } export function Mutes () { - return import(/* webpackChunkName: "features/mutes" */'../../mutes'); + return import('../../mutes'); } export function MuteModal () { - return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal'); + return import('../components/mute_modal'); } export function BlockModal () { - return import(/* webpackChunkName: "modals/block_modal" */'../components/block_modal'); + return import('../components/block_modal'); } export function DomainBlockModal () { - return import(/* webpackChunkName: "modals/domain_block_modal" */'../components/domain_block_modal'); + return import('../components/domain_block_modal'); } export function ReportModal () { - return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal'); + return import('../components/report_modal'); } export function IgnoreNotificationsModal () { - return import(/* webpackChunkName: "modals/domain_block_modal" */'../components/ignore_notifications_modal'); + return import('../components/ignore_notifications_modal'); } export function MediaGallery () { - return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery'); + return import('../../../components/media_gallery'); } export function Video () { - return import(/* webpackChunkName: "features/video" */'../../video'); + return import('../../video'); } export function EmbedModal () { - return import(/* webpackChunkName: "modals/embed_modal" */'../components/embed_modal'); + return import('../components/embed_modal'); } export function ListAdder () { - return import(/*webpackChunkName: "features/list_adder" */'../../list_adder'); + return import('../../list_adder'); } export function Tesseract () { - return import(/*webpackChunkName: "tesseract" */'tesseract.js'); + return import('tesseract.js'); } export function Audio () { - return import(/* webpackChunkName: "features/audio" */'../../audio'); + return import('../../audio'); } export function Directory () { - return import(/* webpackChunkName: "features/directory" */'../../directory'); + return import('../../directory'); } export function OnboardingProfile () { - return import(/* webpackChunkName: "features/onboarding" */'../../onboarding/profile'); + return import('../../onboarding/profile'); } export function OnboardingFollows () { - return import(/* webpackChunkName: "features/onboarding" */'../../onboarding/follows'); + return import('../../onboarding/follows'); } export function CompareHistoryModal () { - return import(/*webpackChunkName: "modals/compare_history_modal" */'../components/compare_history_modal'); + return import('../components/compare_history_modal'); } export function Explore () { - return import(/* webpackChunkName: "features/explore" */'../../explore'); + return import('../../explore'); } export function Search () { - return import(/* webpackChunkName: "features/explore" */'../../search'); + return import('../../search'); } export function FilterModal () { - return import(/*webpackChunkName: "modals/filter_modal" */'../components/filter_modal'); + return import('../components/filter_modal'); } export function InteractionModal () { - return import(/*webpackChunkName: "modals/interaction_modal" */'../../interaction_modal'); + return import('../../interaction_modal'); } export function SubscribedLanguagesModal () { - return import(/*webpackChunkName: "modals/subscribed_languages_modal" */'../../subscribed_languages_modal'); + return import('../../subscribed_languages_modal'); } export function ClosedRegistrationsModal () { - return import(/*webpackChunkName: "modals/closed_registrations_modal" */'../../closed_registrations_modal'); + return import('../../closed_registrations_modal'); } export function About () { - return import(/*webpackChunkName: "features/about" */'../../about'); + return import('../../about'); } export function PrivacyPolicy () { - return import(/*webpackChunkName: "features/privacy_policy" */'../../privacy_policy'); + return import('../../privacy_policy'); } export function TermsOfService () { - return import(/*webpackChunkName: "features/terms_of_service" */'../../terms_of_service'); + return import('../../terms_of_service'); } export function NotificationRequests () { - return import(/*webpackChunkName: "features/notifications/requests" */'../../notifications/requests'); + return import('../../notifications/requests'); } export function NotificationRequest () { - return import(/*webpackChunkName: "features/notifications/request" */'../../notifications/request'); + return import('../../notifications/request'); } export function LinkTimeline () { - return import(/*webpackChunkName: "features/link_timeline" */'../../link_timeline'); + return import('../../link_timeline'); } export function AnnualReportModal () { - return import(/*webpackChunkName: "modals/annual_report_modal" */'../components/annual_report_modal'); + return import('../components/annual_report_modal'); } export function ListEdit () { - return import(/*webpackChunkName: "features/lists" */'../../lists/new'); + return import('../../lists/new'); } export function ListMembers () { - return import(/* webpackChunkName: "features/lists" */'../../lists/members'); + return import('../../lists/members'); } diff --git a/app/javascript/mastodon/load_keyboard_extensions.js b/app/javascript/mastodon/load_keyboard_extensions.js index 2dd0e45fa7..ebdf94561c 100644 --- a/app/javascript/mastodon/load_keyboard_extensions.js +++ b/app/javascript/mastodon/load_keyboard_extensions.js @@ -3,7 +3,7 @@ // can at least log in using KaiOS devices). function importArrowKeyNavigation() { - return import(/* webpackChunkName: "arrow-key-navigation" */ 'arrow-key-navigation'); + return import('arrow-key-navigation'); } export default function loadKeyboardExtensions() { diff --git a/app/javascript/mastodon/locales/load_locale.ts b/app/javascript/mastodon/locales/load_locale.ts index d21675b179..94c7db1141 100644 --- a/app/javascript/mastodon/locales/load_locale.ts +++ b/app/javascript/mastodon/locales/load_locale.ts @@ -5,6 +5,10 @@ import { isLocaleLoaded, setLocale } from './global_locale'; const localeLoadingSemaphore = new Semaphore(1); +const localeFiles = import.meta.glob<{ default: LocaleData['messages'] }>([ + './*.json', +]); + export async function loadLocale() { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- we want to match empty strings const locale = document.querySelector('html')?.lang || 'en'; @@ -17,13 +21,14 @@ export async function loadLocale() { // if the locale is already set, then do nothing if (isLocaleLoaded()) return; - const localeData = (await import( - /* webpackMode: "lazy" */ - /* webpackChunkName: "locale/[request]" */ - /* webpackInclude: /\.json$/ */ - /* webpackPreload: true */ - `mastodon/locales/${locale}.json` - )) as LocaleData['messages']; + // If there is no locale file, then fallback to english + const localeFile = Object.hasOwn(localeFiles, `./${locale}.json`) + ? localeFiles[`./${locale}.json`] + : localeFiles['./en.json']; + + if (!localeFile) throw new Error('Could not load the locale JSON file'); + + const { default: localeData } = await localeFile(); setLocale({ messages: localeData, locale }); }); diff --git a/app/javascript/mastodon/main.jsx b/app/javascript/mastodon/main.tsx similarity index 58% rename from app/javascript/mastodon/main.jsx rename to app/javascript/mastodon/main.tsx index e7979d56a1..a9696ac50e 100644 --- a/app/javascript/mastodon/main.jsx +++ b/app/javascript/mastodon/main.tsx @@ -7,17 +7,19 @@ import * as perf from 'mastodon/performance'; import ready from 'mastodon/ready'; import { store } from 'mastodon/store'; -import { isProduction } from './utils/environment'; +import { isProduction, isDevelopment } from './utils/environment'; -/** - * @returns {Promise} - */ function main() { perf.start('main()'); return ready(async () => { const mountNode = document.getElementById('mastodon'); - const props = JSON.parse(mountNode.getAttribute('data-props')); + if (!mountNode) { + throw new Error('Mount node not found'); + } + const props = JSON.parse( + mountNode.getAttribute('data-props') ?? '{}', + ) as Record; const root = createRoot(mountNode); root.render(); @@ -25,8 +27,10 @@ function main() { if (isProduction() && me && 'serviceWorker' in navigator) { const { Workbox } = await import('workbox-window'); - const wb = new Workbox('/sw.js'); - /** @type {ServiceWorkerRegistration} */ + const wb = new Workbox( + isDevelopment() ? '/packs-dev/dev-sw.js?dev-sw' : '/sw.js', + { type: 'module', scope: '/' }, + ); let registration; try { @@ -35,8 +39,14 @@ function main() { console.error(err); } - if (registration && 'Notification' in window && Notification.permission === 'granted') { - const registerPushNotifications = await import('mastodon/actions/push_notifications'); + if ( + registration && + 'Notification' in window && + Notification.permission === 'granted' + ) { + const registerPushNotifications = await import( + 'mastodon/actions/push_notifications' + ); store.dispatch(registerPushNotifications.register()); } @@ -46,4 +56,5 @@ function main() { }); } +// eslint-disable-next-line import/no-default-export export default main; diff --git a/app/javascript/mastodon/performance.js b/app/javascript/mastodon/performance.js index 3bca95e85e..dd002ab609 100644 --- a/app/javascript/mastodon/performance.js +++ b/app/javascript/mastodon/performance.js @@ -1,7 +1,6 @@ // // Tools for performance debugging, only enabled in development mode. // Open up Chrome Dev Tools, then Timeline, then User Timing to see output. -// Also see config/webpack/loaders/mark.js for the webpack loader marks. import * as marky from 'marky'; diff --git a/app/javascript/mastodon/polyfills/index.ts b/app/javascript/mastodon/polyfills/index.ts index 431c5b0f30..c001421c36 100644 --- a/app/javascript/mastodon/polyfills/index.ts +++ b/app/javascript/mastodon/polyfills/index.ts @@ -2,10 +2,13 @@ // If there are no polyfills, then this is just Promise.resolve() which means // it will execute in the same tick of the event loop (i.e. near-instant). +// eslint-disable-next-line import/extensions -- This file is virtual so it thinks it has an extension +import 'vite/modulepreload-polyfill'; + import { loadIntlPolyfills } from './intl'; function importExtraPolyfills() { - return import(/* webpackChunkName: "extra_polyfills" */ './extra_polyfills'); + return import('./extra_polyfills'); } export function loadPolyfills() { diff --git a/app/javascript/mastodon/polyfills/intl.ts b/app/javascript/mastodon/polyfills/intl.ts index b825da6621..b1157557e5 100644 --- a/app/javascript/mastodon/polyfills/intl.ts +++ b/app/javascript/mastodon/polyfills/intl.ts @@ -54,11 +54,9 @@ async function loadIntlPluralRulesPolyfills(locale: string) { return; } // Load the polyfill 1st BEFORE loading data + await import('@formatjs/intl-pluralrules/polyfill-force'); await import( - /* webpackChunkName: "i18n-pluralrules-polyfill" */ '@formatjs/intl-pluralrules/polyfill-force' - ); - await import( - /* webpackChunkName: "i18n-pluralrules-polyfill-[request]" */ `@formatjs/intl-pluralrules/locale-data/${unsupportedLocale}` + `../../../../node_modules/@formatjs/intl-pluralrules/locale-data/${unsupportedLocale}.js` ); } @@ -70,11 +68,9 @@ async function loadIntlPluralRulesPolyfills(locale: string) { // } // // Load the polyfill 1st BEFORE loading data // await import( -// /* webpackChunkName: "i18n-relativetimeformat-polyfill" */ // '@formatjs/intl-relativetimeformat/polyfill-force' // ); // await import( -// /* webpackChunkName: "i18n-relativetimeformat-polyfill-[request]" */ // `@formatjs/intl-relativetimeformat/locale-data/${unsupportedLocale}` // ); // } diff --git a/app/javascript/mastodon/service_worker/entry.js b/app/javascript/mastodon/service_worker/sw.js similarity index 95% rename from app/javascript/mastodon/service_worker/entry.js rename to app/javascript/mastodon/service_worker/sw.js index a4aebcd3a7..4609a8fc97 100644 --- a/app/javascript/mastodon/service_worker/entry.js +++ b/app/javascript/mastodon/service_worker/sw.js @@ -1,5 +1,5 @@ import { ExpirationPlugin } from 'workbox-expiration'; -import { precacheAndRoute } from 'workbox-precaching'; +// import { precacheAndRoute } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; import { CacheFirst } from 'workbox-strategies'; @@ -15,10 +15,10 @@ function fetchRoot() { return fetch('/', { credentials: 'include', redirect: 'manual' }); } -precacheAndRoute(self.__WB_MANIFEST); +// precacheAndRoute(self.__WB_MANIFEST); registerRoute( - /locale_.*\.js$/, + /intl\/.*\.js$/, new CacheFirst({ cacheName: `${CACHE_NAME_PREFIX}locales`, plugins: [ diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js deleted file mode 100644 index f3d61e0195..0000000000 --- a/app/javascript/mastodon/service_worker/web_push_locales.js +++ /dev/null @@ -1,41 +0,0 @@ -/* @preval */ - -const fs = require('fs'); -const path = require('path'); - -const { defineMessages } = require('react-intl'); - -const messages = defineMessages({ - mentioned_you: { id: 'notification.mentioned_you', defaultMessage: '{name} mentioned you' }, -}); - -const filtered = {}; -const filenames = fs.readdirSync(path.resolve(__dirname, '../locales')); - -filenames.forEach(filename => { - if (!filename.match(/\.json$/)) return; - - const content = fs.readFileSync(path.resolve(__dirname, `../locales/${filename}`), 'utf-8'); - const full = JSON.parse(content); - const locale = filename.split('.')[0]; - - filtered[locale] = { - 'notification.favourite': full['notification.favourite'] || '', - 'notification.follow': full['notification.follow'] || '', - 'notification.follow_request': full['notification.follow_request'] || '', - 'notification.mention': full[messages.mentioned_you.id] || '', - 'notification.reblog': full['notification.reblog'] || '', - 'notification.poll': full['notification.poll'] || '', - 'notification.status': full['notification.status'] || '', - 'notification.update': full['notification.update'] || '', - 'notification.admin.sign_up': full['notification.admin.sign_up'] || '', - - 'status.show_more': full['status.show_more'] || '', - 'status.reblog': full['status.reblog'] || '', - 'status.favourite': full['status.favourite'] || '', - - 'notifications.group': full['notifications.group'] || '', - }; -}); - -module.exports = JSON.parse(JSON.stringify(filtered)); diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js index 77187a59ed..5a43449b0d 100644 --- a/app/javascript/mastodon/service_worker/web_push_notifications.js +++ b/app/javascript/mastodon/service_worker/web_push_notifications.js @@ -1,8 +1,10 @@ import { IntlMessageFormat } from 'intl-messageformat'; import { unescape } from 'lodash'; - -import locales from './web_push_locales'; +// see config/vite/plugins/sw-locales +// it needs to be updated when new locale keys are used in this file +// eslint-disable-next-line import/no-unresolved +import locales from "virtual:mastodon-sw-locales"; const MAX_NOTIFICATIONS = 5; const GROUP_TAG = 'tag'; diff --git a/app/javascript/mastodon/utils/environment.ts b/app/javascript/mastodon/utils/environment.ts index b6371499f6..5ccd4d27e3 100644 --- a/app/javascript/mastodon/utils/environment.ts +++ b/app/javascript/mastodon/utils/environment.ts @@ -1,7 +1,11 @@ export function isDevelopment() { - return process.env.NODE_ENV === 'development'; + if (typeof process !== 'undefined') + return process.env.NODE_ENV === 'development'; + else return import.meta.env.DEV; } export function isProduction() { - return process.env.NODE_ENV === 'production'; + if (typeof process !== 'undefined') + return process.env.NODE_ENV === 'production'; + else return import.meta.env.PROD; } diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 6ec6a4199f..6a3afeb736 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -393,7 +393,7 @@ code { max-width: 100%; height: auto; border-radius: var(--avatar-border-radius); - background: url('images/void.png'); + background: url('@/images/void.png'); &[src$='missing.png'] { visibility: hidden; diff --git a/app/javascript/types/image.d.ts b/app/javascript/types/image.d.ts index 8a08eca9f6..aa4b6ded71 100644 --- a/app/javascript/types/image.d.ts +++ b/app/javascript/types/image.d.ts @@ -1,3 +1,5 @@ +/// + /* eslint-disable import/no-default-export */ declare module '*.avif' { const path: string; @@ -19,23 +21,6 @@ declare module '*.png' { export default path; } -declare module '*.svg' { - const path: string; - export default path; -} - -declare module '*.svg?react' { - import type React from 'react'; - - interface SVGPropsWithTitle extends React.SVGProps { - title?: string; - } - - const ReactComponent: React.FC; - - export default ReactComponent; -} - declare module '*.webp' { const path: string; export default path; diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml index 653f155801..38def20fc5 100644 --- a/app/views/auth/sessions/two_factor.html.haml +++ b/app/views/auth/sessions/two_factor.html.haml @@ -1,7 +1,7 @@ - content_for :page_title do = t('auth.login') -= javascript_pack_tag 'two_factor_authentication', crossorigin: 'anonymous' += vite_typescript_tag 'two_factor_authentication.ts', crossorigin: 'anonymous' - if webauthn_enabled? = render partial: 'auth/sessions/two_factor/webauthn_form', locals: { hidden: @scheme_type != 'webauthn' } diff --git a/app/views/auth/setup/show.html.haml b/app/views/auth/setup/show.html.haml index 91654ca214..83e0bfd25f 100644 --- a/app/views/auth/setup/show.html.haml +++ b/app/views/auth/setup/show.html.haml @@ -1,7 +1,7 @@ - content_for :page_title do = t('auth.setup.title') -= javascript_pack_tag 'sign_up', crossorigin: 'anonymous' += vite_typescript_tag 'sign_up.ts', crossorigin: 'anonymous' = simple_form_for(@user, url: auth_setup_path) do |f| = render 'auth/shared/progress', stage: 'confirm' diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 3f7727cdfb..08432a177c 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,7 +1,7 @@ - content_for :header_tags do = render_initial_state - = javascript_pack_tag 'public', crossorigin: 'anonymous' - = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + = vite_typescript_tag 'public.tsx', crossorigin: 'anonymous' + = vite_typescript_tag 'admin.tsx', crossorigin: 'anonymous' - content_for :body_classes, 'admin' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 6f016c6cf5..690a610bf3 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -26,11 +26,12 @@ %title= html_title = theme_style_tags current_theme + = vite_client_tag + = vite_react_refresh_tag -# Needed for the wicg-inert polyfill. It needs to be on it's own