From 9e6a9efe101b36291897ae9fd4dbc5163a6cef38 Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 17 Jul 2025 12:04:04 +0200 Subject: [PATCH] Replace Ruby Vite plugins (#35195) Co-authored-by: Renaud Chaput --- .storybook/main.ts | 8 ++ config/vite/plugin-mastodon-themes.ts | 16 +++- package.json | 10 +-- vite.config.mts | 105 +++++++++++++++++++++--- vitest.config.mts | 7 +- yarn.lock | 113 ++++++++------------------ 6 files changed, 153 insertions(+), 106 deletions(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index 72321cbf3f..bb69f0c664 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,3 +1,5 @@ +import { resolve } from 'node:path'; + import type { StorybookConfig } from '@storybook/react-vite'; const config: StorybookConfig = { @@ -26,6 +28,12 @@ const config: StorybookConfig = { 'oops.png', ].map((path) => ({ from: `../public/${path}`, to: `/${path}` })), ], + viteFinal(config) { + // For an unknown reason, Storybook does not use the root + // from the Vite config so we need to set it manually. + config.root = resolve(__dirname, '../app/javascript'); + return config; + }, }; export default config; diff --git a/config/vite/plugin-mastodon-themes.ts b/config/vite/plugin-mastodon-themes.ts index 53281e29f4..251d2d8e72 100644 --- a/config/vite/plugin-mastodon-themes.ts +++ b/config/vite/plugin-mastodon-themes.ts @@ -22,7 +22,21 @@ export function MastodonThemes(): Plugin { projectRoot = userConfig.envDir; jsRoot = userConfig.root; - const entrypoints: Record = {}; + let entrypoints: Record = {}; + + const existingInputs = userConfig.build?.rollupOptions?.input; + + if (typeof existingInputs === 'string') { + entrypoints[path.basename(existingInputs)] = existingInputs; + } else if (Array.isArray(existingInputs)) { + for (const input of existingInputs) { + if (typeof input === 'string') { + entrypoints[path.basename(input)] = input; + } + } + } else if (typeof existingInputs === 'object') { + entrypoints = existingInputs; + } // Get all files mentioned in the themes.yml file. const themes = await loadThemesFromConfig(projectRoot); diff --git a/package.json b/package.json index ace593ab01..2ddb85b763 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "test:storybook": "vitest --project=storybook", "typecheck": "tsc --noEmit", "storybook": "storybook dev -p 6006", - "build-storybook": "VITE_RUBY_PUBLIC_OUTPUT_DIR='.' VITE_RUBY_PUBLIC_DIR='./storybook-static' storybook build", + "build-storybook": "storybook build", "chromatic": "npx chromatic -d storybook-static" }, "repository": { @@ -69,6 +69,7 @@ "emojibase": "^16.0.0", "emojibase-data": "^16.0.3", "escape-html": "^1.0.3", + "fast-glob": "^3.3.3", "fuzzysort": "^3.0.0", "history": "^4.10.1", "hoist-non-react-statics": "^3.3.2", @@ -105,6 +106,7 @@ "redux-immutable": "^4.0.0", "regenerator-runtime": "^0.14.0", "requestidlecallback": "^0.3.0", + "rollup-plugin-gzip": "^4.1.1", "rollup-plugin-visualizer": "^6.0.0", "sass": "^1.62.1", "stacktrace-js": "^2.0.2", @@ -115,9 +117,8 @@ "twitter-text": "3.1.0", "use-debounce": "^10.0.0", "vite": "^6.3.5", + "vite-plugin-manifest-sri": "^0.2.0", "vite-plugin-pwa": "^1.0.0", - "vite-plugin-rails": "^0.5.0", - "vite-plugin-ruby": "^5.1.1", "vite-plugin-static-copy": "^3.1.0", "vite-plugin-svgr": "^4.3.0", "vite-tsconfig-paths": "^5.1.4", @@ -187,15 +188,12 @@ "stylelint-config-standard-scss": "^15.0.1", "typescript": "~5.7.3", "typescript-eslint": "^8.29.1", - "vite-plugin-rails": "^0.5.0", - "vite-plugin-svgr": "^4.2.0", "vitest": "^3.2.1" }, "resolutions": { "@types/react": "^18.2.7", "@types/react-dom": "^18.2.4", "kind-of": "^6.0.3", - "vite-plugin-ruby": "^5.1.0", "vite": "^6.3.5" }, "peerDependenciesMeta": { diff --git a/vite.config.mts b/vite.config.mts index 8d0fdfde51..b47bea382c 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -1,19 +1,25 @@ import path from 'node:path'; +import { readdir } from 'node:fs/promises'; import { optimizeLodashImports } from '@optimize-lodash/rollup-plugin'; import legacy from '@vitejs/plugin-legacy'; import react from '@vitejs/plugin-react'; -import { PluginOption } from 'vite'; +import glob from 'fast-glob'; +import postcssPresetEnv from 'postcss-preset-env'; +import Compress from 'rollup-plugin-gzip'; import { visualizer } from 'rollup-plugin-visualizer'; +import { + PluginOption, + defineConfig, + UserConfigFnPromise, + UserConfig, +} from 'vite'; +import manifestSRI from 'vite-plugin-manifest-sri'; import { VitePWA } from 'vite-plugin-pwa'; -import RailsPlugin from 'vite-plugin-rails'; import { viteStaticCopy } from 'vite-plugin-static-copy'; import svgr from 'vite-plugin-svgr'; import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig, UserConfigFnPromise, UserConfig } from 'vite'; -import postcssPresetEnv from 'postcss-preset-env'; - import { MastodonServiceWorkerLocales } from './config/vite/plugin-sw-locales'; import { MastodonEmojiCompressed } from './config/vite/plugin-emoji-compressed'; import { MastodonThemes } from './config/vite/plugin-mastodon-themes'; @@ -22,8 +28,26 @@ import { MastodonNameLookup } from './config/vite/plugin-name-lookup'; const jsRoot = path.resolve(__dirname, 'app/javascript'); export const config: UserConfigFnPromise = async ({ mode, command }) => { + const isProdBuild = mode === 'production' && command === 'build'; + + let outDirName = 'packs-dev'; + if (mode === 'test') { + outDirName = 'packs-test'; + } else if (mode === 'production') { + outDirName = 'packs'; + } + const outDir = path.resolve('public', outDirName); + return { root: jsRoot, + base: `/${outDirName}/`, + envDir: __dirname, + resolve: { + alias: { + '~/': `${jsRoot}/`, + '@/': `${jsRoot}/`, + }, + }, css: { postcss: { plugins: [ @@ -41,12 +65,18 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { // but it needs to be scoped to the whole domain 'Service-Worker-Allowed': '/', }, + port: 3036, }, build: { commonjsOptions: { transformMixedEsModules: true }, chunkSizeWarningLimit: 1 * 1024 * 1024, // 1MB sourcemap: true, + emptyOutDir: mode !== 'production', + manifest: true, + outDir, + assetsDir: 'assets', rollupOptions: { + input: await findEntrypoints(), output: { chunkFileNames({ facadeModuleId, name }) { if (!facadeModuleId) { @@ -84,18 +114,12 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { }, plugins: [ tsconfigPaths({ projects: [path.resolve(__dirname, 'tsconfig.json')] }), - RailsPlugin({ - compress: mode === 'production' && command === 'build', - sri: { - manifestPaths: ['.vite/manifest.json', '.vite/manifest-assets.json'], - }, - }), - MastodonThemes(), react({ babel: { plugins: ['formatjs', 'transform-react-remove-prop-types'], }, }), + MastodonThemes(), viteStaticCopy({ targets: [ { @@ -117,8 +141,13 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { renderLegacyChunks: false, modernPolyfills: true, }), + isProdBuild && (Compress() as PluginOption), + command === 'build' && + manifestSRI({ + manifestPaths: ['.vite/manifest.json', '.vite/manifest-assets.json'], + }), VitePWA({ - srcDir: 'mastodon/service_worker', + srcDir: path.resolve(jsRoot, 'mastodon/service_worker'), // We need to use injectManifest because we use our own service worker strategies: 'injectManifest', manifest: false, @@ -150,4 +179,54 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { } satisfies UserConfig; }; +async function findEntrypoints() { + const entrypoints: Record = {}; + + // First, JS entrypoints + const jsEntrypoints = await readdir(path.resolve(jsRoot, 'entrypoints'), { + withFileTypes: true, + }); + const jsExtTest = /\.[jt]sx?$/; + for (const file of jsEntrypoints) { + if (file.isFile() && jsExtTest.test(file.name)) { + entrypoints[file.name.replace(jsExtTest, '')] = path.resolve( + file.parentPath, + file.name, + ); + } + } + + // Next, SCSS entrypoints + const scssEntrypoints = await readdir( + path.resolve(jsRoot, 'styles/entrypoints'), + { withFileTypes: true }, + ); + const scssExtTest = /\.s?css$/; + for (const file of scssEntrypoints) { + if (file.isFile() && scssExtTest.test(file.name)) { + entrypoints[file.name.replace(scssExtTest, '')] = path.resolve( + file.parentPath, + file.name, + ); + } + } + + // Lastly other assets + const assetEntrypoints = await glob('{fonts,icons,images}/**/*', { + cwd: jsRoot, + absolute: true, + }); + const excludeExts = ['', '.md']; + for (const file of assetEntrypoints) { + const ext = path.extname(file); + if (excludeExts.includes(ext)) { + continue; + } + const name = path.basename(file); + entrypoints[name] = path.resolve(jsRoot, file); + } + + return entrypoints; +} + export default defineConfig(config); diff --git a/vitest.config.mts b/vitest.config.mts index 14aa54778b..7df462ed6d 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,9 +1,6 @@ import { resolve } from 'node:path'; import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; -import react from '@vitejs/plugin-react'; -import svgr from 'vite-plugin-svgr'; -import tsconfigPaths from 'vite-tsconfig-paths'; import { configDefaults, defineConfig, @@ -13,15 +10,13 @@ import { import { config as viteConfig } from './vite.config.mjs'; const storybookTests: TestProjectInlineConfiguration = { + extends: true, plugins: [ // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest storybookTest({ configDir: '.storybook', storybookScript: 'yarn run storybook', }), - react(), - svgr(), - tsconfigPaths(), ], test: { name: 'storybook', diff --git a/yarn.lock b/yarn.lock index 04b93f4ade..3575fbedde 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2680,6 +2680,7 @@ __metadata: eslint-plugin-react: "npm:^7.37.4" eslint-plugin-react-hooks: "npm:^5.2.0" eslint-plugin-storybook: "npm:^9.0.4" + fast-glob: "npm:^3.3.3" fuzzysort: "npm:^3.0.0" globals: "npm:^16.0.0" history: "npm:^4.10.1" @@ -2724,6 +2725,7 @@ __metadata: redux-immutable: "npm:^4.0.0" regenerator-runtime: "npm:^0.14.0" requestidlecallback: "npm:^0.3.0" + rollup-plugin-gzip: "npm:^4.1.1" rollup-plugin-visualizer: "npm:^6.0.0" sass: "npm:^1.62.1" stacktrace-js: "npm:^2.0.2" @@ -2740,11 +2742,10 @@ __metadata: typescript-eslint: "npm:^8.29.1" use-debounce: "npm:^10.0.0" vite: "npm:^6.3.5" + vite-plugin-manifest-sri: "npm:^0.2.0" vite-plugin-pwa: "npm:^1.0.0" - vite-plugin-rails: "npm:^0.5.0" - vite-plugin-ruby: "npm:^5.1.1" vite-plugin-static-copy: "npm:^3.1.0" - vite-plugin-svgr: "npm:^4.2.0" + vite-plugin-svgr: "npm:^4.3.0" vite-tsconfig-paths: "npm:^5.1.4" vitest: "npm:^3.2.1" wicg-inert: "npm:^3.1.2" @@ -3298,7 +3299,7 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.0.5, @rollup/pluginutils@npm:^5.1.0": +"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.1.0": version: 5.1.4 resolution: "@rollup/pluginutils@npm:5.1.4" dependencies: @@ -3314,6 +3315,22 @@ __metadata: languageName: node linkType: hard +"@rollup/pluginutils@npm:^5.1.3": + version: 5.2.0 + resolution: "@rollup/pluginutils@npm:5.2.0" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-walker: "npm:^2.0.2" + picomatch: "npm:^4.0.2" + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 10c0/794890d512751451bcc06aa112366ef47ea8f9125dac49b1abf72ff8b079518b09359de9c60a013b33266541634e765ae61839c749fae0edb59a463418665c55 + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.40.2": version: 4.40.2 resolution: "@rollup/rollup-android-arm-eabi@npm:4.40.2" @@ -6281,7 +6298,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.1": version: 4.4.1 resolution: "debug@npm:4.4.1" dependencies: @@ -10223,7 +10240,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": +"picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 @@ -11800,12 +11817,12 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-gzip@npm:^3.1.0": - version: 3.1.2 - resolution: "rollup-plugin-gzip@npm:3.1.2" +"rollup-plugin-gzip@npm:^4.1.1": + version: 4.1.1 + resolution: "rollup-plugin-gzip@npm:4.1.1" peerDependencies: rollup: ">=2.0.0" - checksum: 10c0/5129d3970cca37bfb5a2fdeddb863bc76be12489ec0a6fcb2be2764902aa2f8548eb8e6532c4e15912d95e8baaa7391a5ed6b58790ed2529c86a98fa75467edc + checksum: 10c0/0ad79a6eb84bb8d88db15a184ca661f44aa6fb3412c98d6a97f1dec365db37945a84c3a2d0bf709ae605ae305a40a0021b2e6d5494c537b029759f3695d9ac96 languageName: node linkType: hard @@ -12536,13 +12553,6 @@ __metadata: languageName: node linkType: hard -"stimulus-vite-helpers@npm:^3.0.0": - version: 3.1.0 - resolution: "stimulus-vite-helpers@npm:3.1.0" - checksum: 10c0/828252f43b238191d71b7b4d2048b7df9845c789963a0a23ea0979941e55ad0e14d2b98646eba328e9f4432cf0c0c8340830c5cde1fc9046077c6f1109b4a671 - languageName: node - linkType: hard - "storybook@npm:^9.0.4": version: 9.0.4 resolution: "storybook@npm:9.0.4" @@ -13828,25 +13838,6 @@ __metadata: languageName: node linkType: hard -"vite-plugin-environment@npm:^1.1.3": - version: 1.1.3 - resolution: "vite-plugin-environment@npm:1.1.3" - peerDependencies: - vite: ">= 2.7" - checksum: 10c0/225986450220bdc6b109be4d05deeb94013d41cc235fe3064bd6c5a1b33c047ba59cac3a34aa240ae735fee6a77ab9ce033053c5ab7c152497bd7136bd3f3a6d - languageName: node - linkType: hard - -"vite-plugin-full-reload@npm:^1.1.0": - version: 1.1.0 - resolution: "vite-plugin-full-reload@npm:1.1.0" - dependencies: - picocolors: "npm:^1.0.0" - picomatch: "npm:^2.3.1" - checksum: 10c0/f33ccb4c58051e43b7d261d60f0078c0e28c49631dd86218cfa1902e0a61f038d1f6839f64a4fb95da0445720612d75656eb9b3d13c8b50d336e2548251c54b8 - languageName: node - linkType: hard - "vite-plugin-manifest-sri@npm:^0.2.0": version: 0.2.0 resolution: "vite-plugin-manifest-sri@npm:0.2.0" @@ -13875,34 +13866,6 @@ __metadata: languageName: node linkType: hard -"vite-plugin-rails@npm:^0.5.0": - version: 0.5.0 - resolution: "vite-plugin-rails@npm:0.5.0" - dependencies: - rollup-plugin-gzip: "npm:^3.1.0" - vite-plugin-environment: "npm:^1.1.3" - vite-plugin-full-reload: "npm:^1.1.0" - vite-plugin-manifest-sri: "npm:^0.2.0" - vite-plugin-ruby: "npm:^5.0.0" - vite-plugin-stimulus-hmr: "npm:^3.0.0" - peerDependencies: - vite: ">=5.0.0" - checksum: 10c0/c1648e87326527ed92339d10f46b7745849a4b1374ed3581410cbd43d9f3ab7aaf4a9285644d2c70a206d8b8330b5949ad69fbe2a2f616b8d8dbec447d75c366 - languageName: node - linkType: hard - -"vite-plugin-ruby@npm:^5.1.0": - version: 5.1.1 - resolution: "vite-plugin-ruby@npm:5.1.1" - dependencies: - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - peerDependencies: - vite: ">=5.0.0" - checksum: 10c0/c14230fef77eb8890897ac71dc56637d49dae8fe5bdb16dcb8fb0d7b7ca068ed30f61940b4ebb0906d03068555156237a84550ec227acde133573078114067ee - languageName: node - linkType: hard - "vite-plugin-static-copy@npm:^3.1.0": version: 3.1.1 resolution: "vite-plugin-static-copy@npm:3.1.1" @@ -13918,26 +13881,16 @@ __metadata: languageName: node linkType: hard -"vite-plugin-stimulus-hmr@npm:^3.0.0": - version: 3.0.0 - resolution: "vite-plugin-stimulus-hmr@npm:3.0.0" +"vite-plugin-svgr@npm:^4.3.0": + version: 4.3.0 + resolution: "vite-plugin-svgr@npm:4.3.0" dependencies: - debug: "npm:^4.3" - stimulus-vite-helpers: "npm:^3.0.0" - checksum: 10c0/964e9713a7402cac0b8a868d7075a35a4a5502ffd11d227aa869da85ab07345af6fc725316bcaf241108076acc0151532f8b3fad6a32225bb279a99e383a2a0c - languageName: node - linkType: hard - -"vite-plugin-svgr@npm:^4.2.0": - version: 4.2.0 - resolution: "vite-plugin-svgr@npm:4.2.0" - dependencies: - "@rollup/pluginutils": "npm:^5.0.5" + "@rollup/pluginutils": "npm:^5.1.3" "@svgr/core": "npm:^8.1.0" "@svgr/plugin-jsx": "npm:^8.1.0" peerDependencies: - vite: ^2.6.0 || 3 || 4 || 5 - checksum: 10c0/0a6400f20905f53d08f1ce7d1f22d9a57db403e110e790f80c2e0411a0064a071a36b781f56f6823654f98052219171003f9ea023d4a31d930b4a4fc01776d1f + vite: ">=2.6.0" + checksum: 10c0/a73f10d319f72cd8c16bf9701cf18170f2300f98c72c6bf939565de0b1e93916bd70c6f5a446dc034b4405c72d382655c7c16be4bd1cbf35bbcde5febf7aeffc languageName: node linkType: hard