mirror of https://github.com/mastodon/mastodon
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
6.9 KiB
TypeScript
225 lines
6.9 KiB
TypeScript
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 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 { viteStaticCopy } from 'vite-plugin-static-copy';
|
|
import svgr from 'vite-plugin-svgr';
|
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
|
|
import { MastodonServiceWorkerLocales } from './config/vite/plugin-sw-locales';
|
|
import { MastodonEmojiCompressed } from './config/vite/plugin-emoji-compressed';
|
|
import { MastodonThemes } from './config/vite/plugin-mastodon-themes';
|
|
import { MastodonNameLookup } from './config/vite/plugin-name-lookup';
|
|
import { MastodonAssetsManifest } from './config/vite/plugin-assets-manifest';
|
|
|
|
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: [
|
|
postcssPresetEnv({
|
|
features: {
|
|
'logical-properties-and-values': false,
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
},
|
|
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: {
|
|
// Forcing the protocol to be insecure helps if you are proxying your dev server with SSL,
|
|
// because Vite still tries to connect to localhost.
|
|
protocol: 'ws',
|
|
},
|
|
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) {
|
|
return '[name]-[hash].js';
|
|
}
|
|
if (/mastodon\/locales\/[a-zA-Z\-]+\.json/.exec(facadeModuleId)) {
|
|
// put all locale files in `intl/`
|
|
return 'intl/[name]-[hash].js';
|
|
} else if (/node_modules\/@formatjs\//.exec(facadeModuleId)) {
|
|
// use a custom name for formatjs polyfill files
|
|
const newName = /node_modules\/@formatjs\/([^/]+)\//.exec(
|
|
facadeModuleId,
|
|
);
|
|
|
|
if (newName?.[1]) {
|
|
return `intl/[name]-${newName[1]}-[hash].js`;
|
|
}
|
|
} else if (name === 'index') {
|
|
// Use a custom name for chunks, to avoid having too many of them called "index"
|
|
const parts = facadeModuleId.split('/');
|
|
|
|
const parent = parts.at(-2);
|
|
|
|
if (parent) {
|
|
return `${parent}-[name]-[hash].js`;
|
|
}
|
|
}
|
|
return '[name]-[hash].js';
|
|
},
|
|
},
|
|
},
|
|
},
|
|
worker: {
|
|
format: 'es',
|
|
},
|
|
plugins: [
|
|
tsconfigPaths({ projects: [path.resolve(__dirname, 'tsconfig.json')] }),
|
|
react({
|
|
babel: {
|
|
plugins: ['formatjs', 'transform-react-remove-prop-types'],
|
|
},
|
|
}),
|
|
MastodonThemes(),
|
|
MastodonAssetsManifest(),
|
|
viteStaticCopy({
|
|
targets: [
|
|
{
|
|
src: path.resolve(
|
|
__dirname,
|
|
'node_modules/emojibase-data/**/compact.json',
|
|
),
|
|
dest: 'emoji',
|
|
rename(_name, ext, dir) {
|
|
const locale = path.basename(path.dirname(dir));
|
|
return `${locale}.${ext}`;
|
|
},
|
|
},
|
|
],
|
|
}),
|
|
MastodonServiceWorkerLocales(),
|
|
MastodonEmojiCompressed(),
|
|
legacy({
|
|
renderLegacyChunks: false,
|
|
modernPolyfills: true,
|
|
}),
|
|
isProdBuild && (Compress() as PluginOption),
|
|
command === 'build' &&
|
|
manifestSRI({
|
|
manifestPaths: ['.vite/manifest.json'],
|
|
}),
|
|
VitePWA({
|
|
srcDir: path.resolve(jsRoot, 'mastodon/service_worker'),
|
|
// We need to use injectManifest because we use our own service worker
|
|
strategies: 'injectManifest',
|
|
manifest: false,
|
|
injectRegister: false,
|
|
injectManifest: {
|
|
// Do not inject a manifest, we don't use precache
|
|
injectionPoint: undefined,
|
|
buildPlugins: {
|
|
vite: [
|
|
// Provide a virtual import with only the locales used in the ServiceWorker
|
|
MastodonServiceWorkerLocales(),
|
|
MastodonEmojiCompressed(),
|
|
],
|
|
},
|
|
},
|
|
// Force the output location, because we have a symlink in `public/sw.js`
|
|
outDir: path.resolve(__dirname, 'public/packs'),
|
|
devOptions: {
|
|
enabled: true,
|
|
type: 'module',
|
|
},
|
|
}),
|
|
svgr(),
|
|
// Old library types need to be converted
|
|
optimizeLodashImports() as PluginOption,
|
|
!!process.env.ANALYZE_BUNDLE_SIZE && (visualizer() as PluginOption),
|
|
MastodonNameLookup(),
|
|
],
|
|
} satisfies UserConfig;
|
|
};
|
|
|
|
async function findEntrypoints() {
|
|
const entrypoints: Record<string, string> = {};
|
|
|
|
// First, JS entrypoints
|
|
const jsEntrypointsDir = path.resolve(jsRoot, 'entrypoints');
|
|
const jsEntrypoints = await readdir(jsEntrypointsDir, {
|
|
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(
|
|
jsEntrypointsDir,
|
|
file.name,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Next, SCSS entrypoints
|
|
const scssEntrypointsDir = path.resolve(jsRoot, 'styles/entrypoints');
|
|
const scssEntrypoints = await readdir(scssEntrypointsDir, {
|
|
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(
|
|
scssEntrypointsDir,
|
|
file.name,
|
|
);
|
|
}
|
|
}
|
|
|
|
return entrypoints;
|
|
}
|
|
|
|
export default defineConfig(config);
|