diff --git a/.dockerignore b/.dockerignore index 41da718049b..3da27ea012e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,6 +9,9 @@ public/system public/assets public/packs public/packs-test +public/vite +public/vite-dev +public/vite-test node_modules neo4j vendor/bundle diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 5f2297381a7..2224dae6130 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -53,7 +53,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 - uses: actions/upload-artifact@v4 if: matrix.mode == 'test' diff --git a/.gitignore b/.gitignore index a70f30f9528..06562e18f83 100644 --- a/.gitignore +++ b/.gitignore @@ -69,5 +69,8 @@ yarn-debug.log # Ignore Docker option files docker-compose.override.yml +# Vite Ruby +/public/vite* + # Ignore dotenv .local files .env*.local diff --git a/.prettierignore b/.prettierignore index 6b2f0c18894..88ae5795389 100644 --- a/.prettierignore +++ b/.prettierignore @@ -22,6 +22,9 @@ /public/assets /public/packs /public/packs-test +/public/vite +/public/vite-dev +/public/vite-test .env .env.production .env.development diff --git a/Gemfile b/Gemfile index 136d074d331..fb8621809a6 100644 --- a/Gemfile +++ b/Gemfile @@ -94,8 +94,8 @@ gem 'strong_migrations', '1.8.0' gem 'tty-prompt', '~> 0.23', require: false gem 'twitter-text', '~> 3.1.0' gem 'tzinfo-data', '~> 1.2023' +gem 'vite_rails', '~> 3.0.17' gem 'webauthn', '~> 3.0' -gem 'webpacker', '~> 5.4' gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9' gem 'json-ld' diff --git a/Gemfile.lock b/Gemfile.lock index 8ff990260b6..3403ac2f250 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -212,6 +212,7 @@ GEM railties (>= 5) dotenv (3.1.2) drb (2.2.1) + dry-cli (1.0.0) ed25519 (1.3.0) elasticsearch (7.17.10) elasticsearch-api (= 7.17.10) @@ -782,7 +783,6 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - semantic_range (3.0.0) sidekiq (6.5.12) connection_pool (>= 2.2.5, < 3) rack (~> 2.0) @@ -865,6 +865,13 @@ GEM validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix + vite_rails (3.0.17) + railties (>= 5.1, < 8) + vite_ruby (~> 3.0, >= 3.2.2) + vite_ruby (3.5.0) + dry-cli (>= 0.7, < 2) + rack-proxy (~> 0.6, >= 0.6.1) + zeitwerk (~> 2.2) warden (1.2.9) rack (>= 2.0.9) webauthn (3.1.0) @@ -883,11 +890,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.8.1) websocket (1.2.10) websocket-driver (0.7.6) @@ -1046,9 +1048,9 @@ DEPENDENCIES tty-prompt (~> 0.23) twitter-text (~> 3.1.0) tzinfo-data (~> 1.2023) + vite_rails (~> 3.0.17) webauthn (~> 3.0) webmock (~> 3.18) - webpacker (~> 5.4) webpush! xorcist (~> 1.1) diff --git a/Procfile.dev b/Procfile.dev index f81333b04ca..4b8e238c0ea 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: bin/vite dev diff --git a/app/helpers/routing_helper.rb b/app/helpers/routing_helper.rb index 15d988f64d2..9426861ba79 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, **options) - asset_pack_path("media/#{source}", **options) + vite_asset_path(source, **options) end def frontend_asset_url(source, **options) diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb index d15259851c0..8528a836d57 100644 --- a/app/helpers/theme_helper.rb +++ b/app/helpers/theme_helper.rb @@ -3,10 +3,10 @@ module ThemeHelper def theme_style_tags(theme) if theme == 'system' - stylesheet_pack_tag('mastodon-light', media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous') + - stylesheet_pack_tag('default', media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous') + vite_stylesheet_tag('styles/mastodon-light.scss', media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous') + + vite_stylesheet_tag('styles/application.scss', media: '(prefers-color-scheme: dark)', 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 225cb16330f..8f5c2d9f5e0 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'; diff --git a/app/javascript/entrypoints/application.ts b/app/javascript/entrypoints/application.ts index 1087b1c4cb5..f1ed46d54dd 100644 --- a/app/javascript/entrypoints/application.ts +++ b/app/javascript/entrypoints/application.ts @@ -1,4 +1,3 @@ -import './public-path'; import main from 'mastodon/main'; import { start } from '../mastodon/common'; diff --git a/app/javascript/entrypoints/error.ts b/app/javascript/entrypoints/error.ts index db68484f3a8..af9d484de18 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/mailer.ts b/app/javascript/entrypoints/mailer.ts index a2ad5e73ac2..22b3ef6ecdc 100644 --- a/app/javascript/entrypoints/mailer.ts +++ b/app/javascript/entrypoints/mailer.ts @@ -1,3 +1 @@ import '../styles/mailer.scss'; - -require.context('../icons'); diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx index 40a9b7c0ca6..7bf348fe759 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'; diff --git a/app/javascript/entrypoints/remote_interaction_helper.ts b/app/javascript/entrypoints/remote_interaction_helper.ts index 419571c8964..f50203747d8 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 79262508510..f6fda68a39b 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 880738fcb77..21fdce190ee 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 511568aa0f7..48fe5d16b7f 100644 --- a/app/javascript/mastodon/common.js +++ b/app/javascript/mastodon/common.js @@ -2,7 +2,7 @@ import Rails from '@rails/ujs'; import 'font-awesome/css/font-awesome.css'; export function start() { - require.context('../images/', true, /\.(jpg|png|svg)$/); + // require.context('../images/', true, /\.(jpg|png|svg)$/); try { Rails.start(); diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.js index ed8e9bbe303..a0636576895 100644 --- a/app/javascript/mastodon/features/emoji/emoji_compressed.js +++ b/app/javascript/mastodon/features/emoji/emoji_compressed.js @@ -1,5 +1,3 @@ -/* eslint-disable import/no-commonjs -- - We need to use CommonJS here due to preval */ // @preval // http://www.unicode.org/Public/emoji/5.0/emoji-test.txt // This file contains the compressed version of the emoji data from @@ -11,16 +9,16 @@ // to ensure that the prevaled file is regenerated by Babel // version: 2 -const { emojiIndex } = require('emoji-mart'); -let data = require('emoji-mart/data/all.json'); -const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data'); +import { emojiIndex } from 'emoji-mart'; +import data from 'emoji-mart/data/all.json'; +import { uncompress as emojiMartUncompress } from 'emoji-mart/dist/utils/data'; -const emojiMap = require('./emoji_map.json'); -const { unicodeToFilename } = require('./unicode_to_filename'); -const { unicodeToUnifiedName } = require('./unicode_to_unified_name'); +import emojiMap from './emoji_map.json'; +import { unicodeToFilename } from './unicode_to_filename'; +import { unicodeToUnifiedName } from './unicode_to_unified_name'; if(data.compressed) { - data = emojiMartUncompress(data); + emojiMartUncompress(data); } const emojiMartData = data; @@ -119,7 +117,7 @@ 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([ +const emojiData = JSON.parse(JSON.stringify([ shortCodesToEmojiData, /* * The property `skins` is not found in the current context. @@ -136,3 +134,5 @@ module.exports = JSON.parse(JSON.stringify([ emojiMartData.aliases, emojisWithoutShortCodes, ])); + +export default emojiData; diff --git a/app/javascript/mastodon/features/emoji/unicode_to_filename.js b/app/javascript/mastodon/features/emoji/unicode_to_filename.js index 3395c77174c..8336b6f977a 100644 --- a/app/javascript/mastodon/features/emoji/unicode_to_filename.js +++ b/app/javascript/mastodon/features/emoji/unicode_to_filename.js @@ -1,9 +1,6 @@ -/* eslint-disable import/no-commonjs -- - We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */ - // taken from: // https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866 -exports.unicodeToFilename = (str) => { +export function unicodeToFilename(str) { let result = ''; let charCode = 0; let p = 0; @@ -26,4 +23,4 @@ exports.unicodeToFilename = (str) => { } } return result; -}; +} 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 108b911222a..019736715b7 100644 --- a/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js +++ b/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js @@ -1,6 +1,3 @@ -/* eslint-disable import/no-commonjs -- - We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */ - function padLeft(str, num) { while (str.length < num) { str = '0' + str; @@ -9,7 +6,7 @@ function padLeft(str, num) { return str; } -exports.unicodeToUnifiedName = (str) => { +export function unicodeToUnifiedName(str) { let output = ''; for (let i = 0; i < str.length; i += 2) { @@ -21,4 +18,4 @@ exports.unicodeToUnifiedName = (str) => { } return output; -}; +} diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx index 7adfc208e72..0c9116b6fe0 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx @@ -12,9 +12,9 @@ import { connect } from 'react-redux'; 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'; +import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js?url'; // eslint-disable-next-line import/no-extraneous-dependencies -import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js'; +import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js?url'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Button } from 'mastodon/components/button'; diff --git a/app/javascript/mastodon/locales/load_locale.ts b/app/javascript/mastodon/locales/load_locale.ts index d21675b1797..8a6116f3245 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/polyfills/intl.ts b/app/javascript/mastodon/polyfills/intl.ts index b825da66214..b7a06e557af 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` ); } diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js index 89ae20007bb..d849f8f175c 100644 --- a/app/javascript/mastodon/service_worker/web_push_locales.js +++ b/app/javascript/mastodon/service_worker/web_push_locales.js @@ -1,10 +1,7 @@ -/* eslint-disable import/no-commonjs -- - We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */ - /* @preval */ -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; const filtered = {}; const filenames = fs.readdirSync(path.resolve(__dirname, '../locales')); @@ -35,4 +32,6 @@ filenames.forEach(filename => { }; }); -module.exports = JSON.parse(JSON.stringify(filtered)); +const locales = JSON.parse(JSON.stringify(filtered)); + +export default locales; diff --git a/app/javascript/mastodon/utils/environment.ts b/app/javascript/mastodon/utils/environment.ts index b6371499f63..5ccd4d27e3f 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/types/image.d.ts b/app/javascript/types/image.d.ts index 8a08eca9f6e..aa4b6ded71e 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 653f1558018..38def20fc5d 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 713f77470d3..ad7ec1336d1 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 d725e65aa9f..d7b5cdca7c6 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 :content do .admin-wrapper diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index ec6caa33ada..ed8fe349e22 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -26,17 +26,17 @@ %title= html_title - = stylesheet_pack_tag 'common', media: 'all', crossorigin: 'anonymous' + = vite_client_tag + = vite_react_refresh_tag = theme_style_tags current_theme -# Needed for the wicg-inert polyfill. It needs to be on it's own