Use vite to replace webpack

This commit is contained in:
tusooa 2025-02-28 10:52:04 -05:00
parent 9dcdd421ac
commit 25de264abb
No known key found for this signature in database
GPG key ID: 42AEC43D48433C51
51 changed files with 1210 additions and 79 deletions

2
.gitignore vendored
View file

@ -7,5 +7,5 @@ test/e2e/reports
selenium-debug.log
.idea/
config/local.json
static/emoji.json
src/assets/emoji.json
logs/

View file

@ -0,0 +1,24 @@
import { languages, langCodeToJsonName } from '../src/i18n/languages.js'
import { readFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
export const generateServiceWorkerMessages = async () => {
const i18nDir = resolve(
dirname(dirname(fileURLToPath(import.meta.url))),
'src/i18n'
)
const msgArray = await Promise.all(languages.map(async lang => {
const name = langCodeToJsonName(lang)
const file = resolve(i18nDir, name + '.json')
const fileContent = await readFile(file, 'utf-8')
const msg = {
notifications: JSON.parse(fileContent).notifications || {}
}
return [lang, msg]
}))
return msgArray.reduce((acc, [lang, msg]) => {
acc[lang] = msg
return acc
}, {})
}

128
build/sw_plugin.js Normal file
View file

@ -0,0 +1,128 @@
import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'
import { readFile } from 'node:fs/promises'
import { build } from 'vite'
import { generateServiceWorkerMessages } from './service_worker_messages.js'
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
export const devSwPlugin = ({
swSrc,
swDest,
}) => {
return {
name: 'dev-sw-plugin',
apply: 'serve',
resolveId (id) {
const name = id.startsWith('/') ? id.slice(1) : id
if (name === swDest) {
return swSrc
}
return null
},
async load (id) {
if (id === swSrc) {
return readFile(swSrc, 'utf-8')
}
return null
},
/**
* vite does not bundle the service worker
* during dev, and firefox does not support ESM as service worker
* https://bugzilla.mozilla.org/show_bug.cgi?id=1360870
*/
// async transform (code, id) {
// if (id === swSrc) {
// console.log('load virtual')
// const res = await build({
// entryPoints: [swSrc],
// bundle: true,
// write: false,
// outfile: 'sw-pleroma.js',
// alias: {
// 'src': projectRoot + '/src',
// },
// define: {
// 'import.meta.glob': 'require'
// }
// })
// console.log('res', res)
// const text = res.outputFiles[0].text
// console.log('text', text)
// return text
// }
// }
}
}
// Idea taken from
// https://github.com/vite-pwa/vite-plugin-pwa/blob/main/src/plugins/build.ts
// rollup does not support compiling to iife if we want to code-split;
// however, we must compile the service worker to iife because of browser support.
// Run another vite build just for the service worker targeting iife at
// the end of the build.
export const buildSwPlugin = ({
swSrc,
swDest,
}) => {
let config
return {
name: 'build-sw-plugin',
enforce: 'post',
apply: 'build',
configResolved (resolvedConfig) {
config = {
define: resolvedConfig.define,
resolve: resolvedConfig.resolve,
plugins: [swMessagesPlugin()],
publicDir: false,
build: {
...resolvedConfig.build,
lib: {
entry: swSrc,
formats: ['iife'],
name: 'sw_pleroma'
},
emptyOutDir: false,
rollupOptions: {
output: {
entryFileNames: swDest
}
}
},
configFile: false
}
},
closeBundle: {
order: 'post',
sequential: true,
async handler () {
console.log('Building service worker for production')
await build(config)
}
}
}
}
const swMessagesName = 'virtual:pleroma-fe/service_worker_messages'
const swMessagesNameResolved = '\0' + swMessagesName
export const swMessagesPlugin = () => {
return {
name: 'sw-messages-plugin',
resolveId (id) {
if (id === swMessagesName) {
return swMessagesNameResolved
} else {
return null
}
},
async load (id) {
if (id === swMessagesNameResolved) {
const messages = await generateServiceWorkerMessages()
return `export default ${JSON.stringify(messages, undefined, 2)}`
}
return null
}
}
}

View file

@ -1,27 +1,23 @@
module.exports = {
updateEmoji () {
const emojis = require('@kazvmoe-infra/unicode-emoji-json/data-by-group')
const fs = require('fs')
import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json' }
import fs from 'fs'
Object.keys(emojis)
.map(k => {
emojis[k].map(e => {
delete e.unicode_version
delete e.emoji_version
delete e.skin_tone_support_unicode_version
})
})
Object.keys(emojis)
.map(k => {
emojis[k].map(e => {
delete e.unicode_version
delete e.emoji_version
delete e.skin_tone_support_unicode_version
})
})
const res = {}
Object.keys(emojis)
.map(k => {
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
res[groupId] = emojis[k]
})
const res = {}
Object.keys(emojis)
.map(k => {
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
res[groupId] = emojis[k]
})
console.info('Updating emojis...')
fs.writeFileSync('static/emoji.json', JSON.stringify(res))
console.info('Done.')
}
}
console.info('Updating emojis...')
fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res))
console.info('Done.')

View file

@ -167,6 +167,7 @@
<div id="app" class="hidden"></div>
<div id="modal"></div>
<!-- built files will be auto injected -->
<div id="popovers" />
<div id="popovers"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View file

@ -5,8 +5,8 @@
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
"private": false,
"scripts": {
"dev": "node build/dev-server.js",
"build": "node build/build.js",
"dev": "node build/update-emoji.js && vite dev",
"build": "node build/update-emoji.js && vite build",
"unit": "karma start test/unit/karma.conf.js --single-run",
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
"e2e": "node test/e2e/runner.js",
@ -27,6 +27,7 @@
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
"@vuelidate/core": "2.0.3",
"@vuelidate/validators": "2.0.4",
"@web3-storage/parse-link-header": "^3.1.0",
"body-scroll-lock": "3.1.5",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
@ -58,6 +59,8 @@
"@babel/register": "7.25.9",
"@intlify/vue-i18n-loader": "5.0.1",
"@ungap/event-target": "0.2.4",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
"@vue/babel-plugin-jsx": "1.2.5",
"@vue/compiler-sfc": "3.5.13",
@ -123,6 +126,8 @@
"stylelint-config-standard": "29.0.0",
"stylelint-rscss": "0.4.0",
"stylelint-webpack-plugin": "^3.3.0",
"vite": "^6.1.0",
"vite-plugin-pwa": "^0.21.1",
"vue-loader": "17.4.2",
"vue-style-loader": "4.1.3",
"webpack": "5.97.1",
@ -130,6 +135,7 @@
"webpack-hot-middleware": "2.26.1",
"webpack-merge": "0.20.0"
},
"type": "module",
"engines": {
"node": ">= 16.0.0"
},

View file

@ -1,5 +1,7 @@
module.exports = {
import autoprefixer from 'autoprefixer'
export default {
plugins: [
require('autoprefixer')
autoprefixer
]
}

0
public/static/.gitkeep Normal file
View file

View file

Before

Width:  |  Height:  |  Size: 628 KiB

After

Width:  |  Height:  |  Size: 628 KiB

View file

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

Before

Width:  |  Height:  |  Size: 396 KiB

After

Width:  |  Height:  |  Size: 396 KiB

View file

Before

Width:  |  Height:  |  Size: 521 KiB

After

Width:  |  Height:  |  Size: 521 KiB

View file

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -1 +0,0 @@
../../static/pleromatan_apology.png

View file

@ -1 +0,0 @@
../../static/pleromatan_apology_fox.png

View file

@ -3,7 +3,8 @@ import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_con
import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js'
import StillImage from 'src/components/still-image/still-image.vue'
import MentionsLine, { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.vue'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.js'
import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
import './rich_content.scss'

View file

@ -2,7 +2,8 @@ import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue'
import IntegerSetting from '../helpers/integer_setting.vue'
import FloatSetting from '../helpers/float_setting.vue'
import UnitSetting, { defaultHorizontalUnits } from '../helpers/unit_setting.vue'
import UnitSetting from '../helpers/unit_setting.vue'
import { defaultHorizontalUnits } from '../helpers/unit_setting.js'
import PaletteEditor from 'src/components/palette_editor/palette_editor.vue'
import Preview from './theme_tab/theme_preview.vue'
import FontControl from 'src/components/font_control/font_control.vue'

View file

@ -221,12 +221,15 @@ export default {
// ## Components stuff
// Getting existing components
const componentsContext = require.context('src', true, /\.style.js(on)?$/)
const componentKeysAll = componentsContext.keys()
const componentsContext = import.meta.glob(
['/src/**/*.style.js', '/src/**/*.style.json'],
{ eager: true }
)
const componentKeysAll = Object.keys(componentsContext)
const componentsMap = new Map(
componentKeysAll
.map(
key => [key, componentsContext(key).default]
key => [key, componentsContext[key].default]
).filter(([, component]) => !component.virtual && !component.notEditable)
)
exports.componentsMap = componentsMap

View file

@ -22,4 +22,4 @@
</template>
<script src="./status_bookmark_folder_menu.js"></script>
<stlye src="./status_bookmark_folder_menu.scss" />
<style src="./status_bookmark_folder_menu.scss" />

View file

@ -1,8 +1,8 @@
import Popover from '../popover/popover.vue'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { mapState } from 'vuex'
import { ListsMenuContent } from '../lists_menu/lists_menu_content.vue'
import { BookmarkFoldersMenuContent } from '../bookmark_folders_menu/bookmark_folders_menu_content.vue'
import ListsMenuContent from '../lists_menu/lists_menu_content.vue'
import BookmarkFoldersMenuContent from '../bookmark_folders_menu/bookmark_folders_menu_content.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { TIMELINES } from 'src/components/navigation/navigation.js'
import { filterNavigation } from 'src/components/navigation/filter.js'

View file

@ -1,7 +1,5 @@
import Modal from 'src/components/modal/modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import pleromaTan from 'src/assets/pleromatan_apology.png'
import pleromaTanFox from 'src/assets/pleromatan_apology_fox.png'
import pleromaTanMask from 'src/assets/pleromatan_apology_mask.png'
import pleromaTanFoxMask from 'src/assets/pleromatan_apology_fox_mask.png'
@ -14,6 +12,9 @@ library.add(
export const CURRENT_UPDATE_COUNTER = 1
const pleromaTan = '/static/pleromatan_apology.png'
const pleromaTanFox = '/static/pleromatan_apology_fox.png'
const UpdateNotification = {
data () {
return {

View file

@ -46,7 +46,7 @@ const ensureFinalFallback = codes => {
return codeList.includes('en') ? codeList : codeList.concat(['en'])
}
module.exports = {
export {
languages,
langCodeToJsonName,
langCodeToCldrName,

View file

@ -9,23 +9,23 @@
import { isEqual } from 'lodash'
import { languages, langCodeToJsonName } from './languages.js'
import enMessages from './en.json'
const ULTIMATE_FALLBACK_LOCALE = 'en'
const hasLanguageFile = (code) => languages.includes(code)
const languageFileMap = import.meta.glob('./*.json')
const loadLanguageFile = (code) => {
return import(
/* webpackInclude: /\.json$/ */
/* webpackChunkName: "i18n/[request]" */
`./${langCodeToJsonName(code)}.json`
)
const jsonName = langCodeToJsonName(code)
return languageFileMap[`./${jsonName}.json`]()
}
const messages = {
languages,
default: {
en: require('./en.json').default
en: enMessages
},
setLanguage: async (i18n, language) => {
const languages = (Array.isArray(language) ? language : [language]).filter(k => k)

View file

@ -179,9 +179,9 @@ const defaultState = {
}
const loadAnnotations = (lang) => {
const code = langCodeToCldrName(lang)
return import(
/* webpackChunkName: "emoji-annotations/[request]" */
`@kazvmoe-infra/unicode-emoji-json/annotations/${langCodeToCldrName(lang)}.json`
`../../node_modules/@kazvmoe-infra/unicode-emoji-json/annotations/${code}.json`
)
.then(k => k.default)
}
@ -304,7 +304,7 @@ const instance = {
},
async getStaticEmoji ({ commit }) {
try {
const values = (await import(/* webpackChunkName: 'emoji' */ '../../static/emoji.json')).default
const values = (await import('/src/assets/emoji.json')).default
const emoji = Object.keys(values).reduce((res, groupId) => {
res[groupId] = values[groupId].map(e => ({

View file

@ -1,5 +1,5 @@
import escape from 'escape-html'
import parseLinkHeader from 'parse-link-header'
import { parseLinkHeader } from '@web3-storage/parse-link-header'
import { isStatusNotification } from '../notification_utils/notification_utils.js'
import punycode from 'punycode.js'
@ -484,8 +484,8 @@ export const parseLinkHeaderPagination = (linkHeader, opts = {}) => {
const flakeId = opts.flakeId
const parsedLinkHeader = parseLinkHeader(linkHeader)
if (!parsedLinkHeader) return
const maxId = parsedLinkHeader.next.max_id
const minId = parsedLinkHeader.prev.min_id
const maxId = parsedLinkHeader.next?.max_id
const minId = parsedLinkHeader.prev?.min_id
return {
maxId: flakeId ? maxId : parseInt(maxId, 10),

View file

@ -1,5 +1,3 @@
import runtime from 'serviceworker-webpack5-plugin/lib/runtime'
function urlBase64ToUint8Array (base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4)
const base64 = (base64String + padding)
@ -19,7 +17,8 @@ function isPushSupported () {
}
function getOrCreateServiceWorker () {
return runtime.register()
const swType = process.env.HAS_MODULE_SERVICE_WORKER ? 'module' : 'classic'
return navigator.serviceWorker.register('/sw-pleroma.js', { type: swType })
.catch((err) => console.error('Unable to get or create a service worker.', err))
}
@ -98,14 +97,14 @@ export async function initServiceWorker (store) {
export async function showDesktopNotification (content) {
if (!isSWSupported) return
const { active: sw } = await window.navigator.serviceWorker.getRegistration()
const { active: sw } = (await window.navigator.serviceWorker.getRegistration()) || {}
if (!sw) return console.error('No serviceworker found!')
sw.postMessage({ type: 'desktopNotification', content })
}
export async function closeDesktopNotification ({ id }) {
if (!isSWSupported) return
const { active: sw } = await window.navigator.serviceWorker.getRegistration()
const { active: sw } = (await window.navigator.serviceWorker.getRegistration()) || {}
if (!sw) return console.error('No serviceworker found!')
if (id >= 0) {
sw.postMessage({ type: 'desktopNotificationClose', content: { id } })
@ -116,7 +115,7 @@ export async function closeDesktopNotification ({ id }) {
export async function updateFocus () {
if (!isSWSupported) return
const { active: sw } = await window.navigator.serviceWorker.getRegistration()
const { active: sw } = (await window.navigator.serviceWorker.getRegistration()) || {}
if (!sw) return console.error('No serviceworker found!')
sw.postMessage({ type: 'updateFocus' })
}

View file

@ -146,9 +146,12 @@ const getTextColorAlpha = (directives, intendedTextColor, dynamicVars, staticVar
}
// Loading all style.js[on] files dynamically
const componentsContext = require.context('src', true, /\.style.js(on)?$/)
componentsContext.keys().forEach(key => {
const component = componentsContext(key).default
const componentsContext = import.meta.glob(
['/src/**/*.style.js', '/src/**/*.style.json'],
{ eager: true }
)
Object.keys(componentsContext).forEach(key => {
const component = componentsContext[key].default
if (components[component.name] != null) {
console.warn(`Component in file ${key} is trying to override existing component ${component.name}! You have collisions/duplicates!`)
}

View file

@ -4,7 +4,7 @@ import { storage } from 'src/lib/storage.js'
import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js'
import { prepareNotificationObject } from './services/notification_utils/notification_utils.js'
import { createI18n } from 'vue-i18n'
import messages from './i18n/service_worker_messages.js'
import messages from 'virtual:pleroma-fe/service_worker_messages'
const i18n = createI18n({
// By default, use the browser locale, we will update it if neccessary
@ -139,3 +139,5 @@ self.addEventListener('notificationclick', (event) => {
if (clients.openWindow) return clients.openWindow('/')
}))
})
console.log('sw here')

135
vite.config.js Normal file
View file

@ -0,0 +1,135 @@
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { VitePWA } from 'vite-plugin-pwa'
import { devSwPlugin, buildSwPlugin, swMessagesPlugin } from './build/sw_plugin.js'
const getLocalDevSettings = async () => {
try {
const settings = (await import('./config/local.json')).default
if (settings.target && settings.target.endsWith('/')) {
// replacing trailing slash since it can conflict with some apis
// and that's how actual BE reports its url
settings.target = settings.target.replace(/\/$/, '')
}
console.info('Using local dev server settings (/config/local.json):')
console.info(JSON.stringify(settings, null, 2))
return settings
} catch (e) {
console.info('Local dev server settings not found (/config/local.json)', e)
return {}
}
}
const projectRoot = dirname(fileURLToPath(import.meta.url))
export default defineConfig(async ({ command }) => {
const settings = await getLocalDevSettings()
const target = settings.target || 'http://localhost:4000/'
const proxy = {
'/api': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost',
ws: true
},
'/nodeinfo': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost'
},
'/socket': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost',
ws: true,
headers: {
'Origin': target
}
},
'/oauth': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost'
}
}
const swSrc = 'src/sw.js'
const swDest = 'sw-pleroma.js'
return {
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement(tag) {
if (tag === 'pinch-zoom') {
return true
}
return false
}
}
}
}),
vueJsx(),
devSwPlugin({ swSrc, swDest }),
buildSwPlugin({ swSrc, swDest }),
swMessagesPlugin()
],
resolve: {
alias: {
src: '/src',
components: '/src/components'
}
},
define: {
'process.env': JSON.stringify({
NODE_ENV: command === 'serve' ? 'development' : 'production',
HAS_MODULE_SERVICE_WORKER: command === 'serve'
}),
'COMMIT_HASH': JSON.stringify('DEV'),
'DEV_OVERRIDES': JSON.stringify({})
},
build: {
sourcemap: true,
rollupOptions: {
input: {
main: 'index.html'
},
output: {
inlineDynamicImports: false,
entryFileNames (chunkInfo) {
const id = chunkInfo.facadeModuleId
if (id.endsWith(swSrc)) {
return swDest
} else {
return 'static/js/[name].[hash].js'
}
},
chunkFileNames (chunkInfo) {
return 'static/js/[name].[hash].js'
},
assetFileNames (assetInfo) {
const name = assetInfo.names?.[0] || ''
if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(name)) {
return 'static/img/[name].[hash][extname]'
} else if (/\.css$/.test(name)) {
return 'static/css/[name].[hash][extname]'
} else {
return 'static/misc/[name].[hash][extname]'
}
}
}
},
},
server: {
proxy,
port: Number(process.env.PORT) || 8080
},
preview: {
proxy
}
}
})

861
yarn.lock

File diff suppressed because it is too large Load diff