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