re-adds SW Locales and PWA plugins from #24981

This commit is contained in:
ChaosExAnima 2025-04-16 17:13:01 +02:00
parent eae0549178
commit dc3667f50f
No known key found for this signature in database
GPG Key ID: 8F2B333100FB6117
11 changed files with 2033 additions and 87 deletions

3
.gitignore vendored
View File

@ -75,4 +75,5 @@ docker-compose.override.yml
# Ignore local-only rspec configuration
.rspec-local
/.dist
# Vite Ruby output
/public/vite*

View File

@ -60,6 +60,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

View File

@ -1,4 +0,0 @@
/* Placeholder file to have `inert.scss` compiled by Vite
This is used by the `wicg-inert` polyfill */
import '../styles/inert.scss';

View File

@ -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?url';
// eslint-disable-next-line import/no-extraneous-dependencies
import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js?url';
import { showAlertForError } from 'mastodon/actions/alerts';
import { uploadThumbnail } from 'mastodon/actions/compose';
@ -350,9 +346,15 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
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,
corePath: tesseractCorePath,
workerPath: tesseractWorkerPath.default,
corePath: tesseractCorePath.default,
langPath: `${assetHost}/ocr/lang-data`,
cacheMethod: 'write',
});

View File

@ -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));

View File

@ -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';

View File

@ -0,0 +1,82 @@
/* This plugin provides the `virtual:mastodon-sw-locales` import
which exports translations for every locales, but only with the
keys defined below.
This is used by the notifications code in the service-worker, to
provide localised texts without having to load all the translations
*/
import fs from 'node:fs';
import path from 'node:path';
import type { Plugin, ResolvedConfig } from 'vite';
const KEEP_KEYS = [
'notification.favourite',
'notification.follow',
'notification.follow_request',
'notification.mention',
'notification.reblog',
'notification.poll',
'notification.status',
'notification.update',
'notification.admin.sign_up',
'status.show_more',
'status.reblog',
'status.favourite',
'notifications.group',
];
export function MastodonServiceWorkerLocales(): Plugin {
const virtualModuleId = 'virtual:mastodon-sw-locales';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
let config: ResolvedConfig;
return {
name: 'mastodon-sw-locales',
configResolved(resolvedConfig) {
config = resolvedConfig;
},
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
return undefined;
},
load(id) {
if (id === resolvedVirtualModuleId) {
const filteredLocales: Record<string, Record<string, string>> = {};
const localesPath = path.resolve(config.root, 'mastodon/locales');
const filenames = fs.readdirSync(localesPath);
filenames
.filter((filename) => /[a-zA-Z-]+\.json$/.exec(filename))
.forEach((filename) => {
const content = fs.readFileSync(
path.resolve(localesPath, filename),
'utf-8',
);
const full = JSON.parse(content) as Record<string, string>;
const locale = filename.split('.')[0];
if (!locale)
throw new Error('Could not parse locale from filename');
const filteredLocale: Record<string, string> = {};
Object.entries(full).forEach(([key, value]) => {
if (KEEP_KEYS.includes(key)) filteredLocale[key] = value;
});
filteredLocales[locale] = filteredLocale;
});
return `const locales = ${JSON.stringify(filteredLocales)}; \n export default locales;`;
}
return undefined;
},
};
}

View File

@ -11,8 +11,8 @@
],
"scripts": {
"dev": "vite dev",
"build:development": "cross-env RAILS_ENV=development NODE_ENV=development ./bin/vite build",
"build:production": "cross-env RAILS_ENV=production NODE_ENV=production ./bin/vite build",
"build:development": "cross-env RAILS_ENV=development NODE_ENV=development vite build",
"build:production": "cross-env RAILS_ENV=production NODE_ENV=production vite build",
"fix:js": "eslint . --cache --fix",
"fix:css": "stylelint --fix \"**/*.{css,scss}\"",
"fix": "yarn fix:js && yarn fix:css",
@ -42,6 +42,7 @@
"@formatjs/intl-pluralrules": "^5.4.4",
"@gamestdio/websocket": "^0.3.2",
"@github/webauthn-json": "^2.1.1",
"@optimize-lodash/rollup-plugin": "^5.0.2",
"@rails/ujs": "7.1.501",
"@react-spring/web": "^9.7.5",
"@reduxjs/toolkit": "^2.0.1",
@ -103,6 +104,7 @@
"use-debounce": "^10.0.0",
"vite": "^6.3.0",
"vite-bundle-analyzer": "^0.18.1",
"vite-plugin-pwa": "^1.0.0",
"wicg-inert": "^3.1.2",
"workbox-expiration": "^7.0.0",
"workbox-precaching": "^7.0.0",

View File

@ -1,13 +1,16 @@
/// <reference types="vitest/config" />
import path from 'node:path';
import fs from 'node:fs/promises';
import path from 'node:path';
import { optimizeLodashImports } from '@optimize-lodash/rollup-plugin';
import react from '@vitejs/plugin-react';
import { loadEnv } from 'vite';
import { loadEnv, PluginOption } from 'vite';
import svgr from 'vite-plugin-svgr';
import { analyzer } from 'vite-bundle-analyzer';
import RailsPlugin from 'vite-plugin-rails';
import { VitePWA } from 'vite-plugin-pwa';
import {
configDefaults,
defineConfig,
@ -16,7 +19,7 @@ import {
} from 'vitest/config';
import postcssPresetEnv from 'postcss-preset-env';
import { manifestSRI } from './config/vite/plugin-manifest-sri';
import { MastodonServiceWorkerLocales } from './config/vite/plugin-sw-locales';
const jsRoot = path.resolve(__dirname, 'app/javascript');
const entrypointRoot = path.resolve(jsRoot, 'entrypoints');
@ -52,6 +55,11 @@ const config: UserConfigFnPromise = async ({ mode }) => {
},
},
server: {
headers: {
// This is needed in dev environment because we load the worker from `/dev-sw/dev-sw.js`,
// but it needs to be scoped to the whole domain
'Service-Worker-Allowed': '/',
},
hmr: {
clientPort: parseInt(env.VITE_HMR_PORT ?? '3000'),
},
@ -101,7 +109,43 @@ const config: UserConfigFnPromise = async ({ mode }) => {
},
},
},
plugins: [RailsPlugin(), react(), svgr(), manifestSRI(), analyzer()],
plugins: [
RailsPlugin(),
react(),
MastodonServiceWorkerLocales(),
VitePWA({
srcDir: 'mastodon/service_worker',
filename: 'sw.js',
manifest: false,
injectRegister: null,
injectManifest: {
buildPlugins: {
vite: [
// Provide a virtual import with only the locales used in the ServiceWorker
MastodonServiceWorkerLocales(),
],
},
globIgnores: [
// Do not preload those files
'intl/*.js',
'extra_polyfills-*.js',
'polyfill-force-*.js',
'assets/mailer-*.{js,css}',
'**/*tesseract*',
],
maximumFileSizeToCacheInBytes: 2 * 1_024 * 1_024, // 2 MiB
},
devOptions: {
enabled: true,
type: 'module',
},
}),
svgr(),
// manifestSRI(),
// Old library types need to be converted
optimizeLodashImports() as PluginOption,
!!process.env.ANALYZE_BUNDLE_SIZE && analyzer({ analyzerMode: 'static' }),
],
test: {
environment: 'jsdom',
include: [

1911
yarn.lock

File diff suppressed because it is too large Load Diff