build: improve asset/script loading

This commit is contained in:
2025-11-20 03:22:32 +02:00
parent 53130ca10a
commit 2753ecfefb
9 changed files with 198 additions and 31 deletions

1
.gitignore vendored
View File

@@ -19,3 +19,4 @@ tsconfig.app.tsbuildinfo
.env.keys
.envrc
tests/.phpunit.result.cache
stats.html

View File

@@ -22,6 +22,33 @@ class PageController extends Controller {
parent::__construct($appName, $request);
}
/**
* Helper to parse Vite Manifest
*/
private function getViteEntryScript(string $entryName): string {
$jsDir = realpath(__DIR__ . '/../' . Application::JS_DIR);
$manifestPath = dirname($jsDir) . '/.vite/manifest.json';
if (!file_exists($manifestPath)) {
return '';
}
$manifest = json_decode(file_get_contents($manifestPath), true);
if (isset($manifest[$entryName]['file'])) {
$manifestFile = $manifest[$entryName]['file'];
$fullPath = dirname($jsDir) . '/' . $manifestFile;
if (!file_exists($fullPath)) {
return '';
}
return pathinfo($manifestFile, PATHINFO_FILENAME);
}
return '';
}
/**
* Main app page
*
@@ -33,8 +60,10 @@ class PageController extends Controller {
#[NoCSRFRequired]
public function index(): TemplateResponse {
$this->logger->info('Forum main page loaded');
$mainScript = $this->getViteEntryScript('app.ts');
return new TemplateResponse(Application::APP_ID, 'app', [
'script' => 'app',
'script' => $this->getViteEntryScript('app.ts'),
'style' => $this->getViteEntryScript('style.css'),
]);
}

View File

@@ -42,6 +42,7 @@
"lint-staged": "^16.2.6",
"prettier": "^2.8.8",
"prettier-plugin-vue": "^1.1.6",
"rollup-plugin-visualizer": "^6.0.5",
"sass": "^1.94.0",
"sass-embedded": "^1.93.3",
"typescript": "5.9.2",

120
pnpm-lock.yaml generated
View File

@@ -72,6 +72,9 @@ importers:
prettier-plugin-vue:
specifier: ^1.1.6
version: 1.1.6
rollup-plugin-visualizer:
specifier: ^6.0.5
version: 6.0.5(rollup@4.53.2)
sass:
specifier: ^1.94.0
version: 1.94.0
@@ -1478,6 +1481,10 @@ packages:
resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==}
engines: {node: '>=20'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
clone@2.1.2:
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
engines: {node: '>=0.8'}
@@ -1647,6 +1654,10 @@ packages:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
define-lazy-prop@2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
engines: {node: '>=8'}
define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
@@ -2109,6 +2120,10 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-east-asian-width@1.4.0:
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
engines: {node: '>=18'}
@@ -2357,6 +2372,11 @@ packages:
is-decimal@2.0.1:
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
is-docker@2.2.1:
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
engines: {node: '>=8'}
hasBin: true
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -2452,6 +2472,10 @@ packages:
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
engines: {node: '>= 0.4'}
is-wsl@2.2.0:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@@ -2837,6 +2861,10 @@ packages:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
open@8.4.2:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -3085,6 +3113,10 @@ packages:
remark-unlink-protocols@1.0.0:
resolution: {integrity: sha512-5j/F28jhFmxeyz8nuJYYIWdR4nNpKWZ8A+tVwnK/0pq7Rjue33CINEYSckSq2PZvedhKUwbn08qyiuGoPLBung==}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@@ -3155,6 +3187,19 @@ packages:
peerDependencies:
rollup: ^4.0.0
rollup-plugin-visualizer@6.0.5:
resolution: {integrity: sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
rolldown: 1.x || ^1.0.0-beta
rollup: 2.x || 3.x || 4.x
peerDependenciesMeta:
rolldown:
optional: true
rollup:
optional: true
rollup@4.53.2:
resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -3388,6 +3433,10 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
source-map@0.7.6:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
@@ -3906,6 +3955,10 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrap-ansi@9.0.2:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
@@ -3922,6 +3975,10 @@ packages:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -3933,6 +3990,14 @@ packages:
engines: {node: '>= 14.6'}
hasBin: true
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -5461,6 +5526,12 @@ snapshots:
slice-ansi: 7.1.2
string-width: 8.1.0
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
clone@2.1.2: {}
color-convert@2.0.1:
@@ -5622,6 +5693,8 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
define-lazy-prop@2.0.0: {}
define-properties@1.2.1:
dependencies:
define-data-property: 1.1.4
@@ -6214,6 +6287,8 @@ snapshots:
gensync@1.0.0-beta.2: {}
get-caller-file@2.0.5: {}
get-east-asian-width@1.4.0: {}
get-intrinsic@1.3.0:
@@ -6488,6 +6563,8 @@ snapshots:
is-decimal@2.0.1: {}
is-docker@2.2.1: {}
is-extglob@2.1.1: {}
is-finalizationregistry@1.1.1:
@@ -6577,6 +6654,10 @@ snapshots:
call-bound: 1.0.4
get-intrinsic: 1.3.0
is-wsl@2.2.0:
dependencies:
is-docker: 2.2.1
isarray@1.0.0: {}
isarray@2.0.5: {}
@@ -7118,6 +7199,12 @@ snapshots:
dependencies:
mimic-function: 5.0.1
open@8.4.2:
dependencies:
define-lazy-prop: 2.0.0
is-docker: 2.2.1
is-wsl: 2.2.0
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -7415,6 +7502,8 @@ snapshots:
mdast-squeeze-paragraphs: 6.0.0
unist-util-visit: 5.0.0
require-directory@2.1.1: {}
require-from-string@2.0.2: {}
requireindex@1.2.0: {}
@@ -7481,6 +7570,15 @@ snapshots:
dependencies:
rollup: 4.53.2
rollup-plugin-visualizer@6.0.5(rollup@4.53.2):
dependencies:
open: 8.4.2
picomatch: 4.0.3
source-map: 0.7.6
yargs: 17.7.2
optionalDependencies:
rollup: 4.53.2
rollup@4.53.2:
dependencies:
'@types/estree': 1.0.8
@@ -7738,6 +7836,8 @@ snapshots:
source-map@0.6.1: {}
source-map@0.7.6: {}
space-separated-tokens@2.0.2: {}
spdx-compare@1.0.0:
@@ -8376,6 +8476,12 @@ snapshots:
word-wrap@1.2.5: {}
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@9.0.2:
dependencies:
ansi-styles: 6.2.3
@@ -8391,12 +8497,26 @@ snapshots:
xtend@4.0.2: {}
y18n@5.0.8: {}
yallist@3.1.1: {}
yallist@4.0.0: {}
yaml@2.8.1: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
yocto-queue@0.1.0: {}
zwitch@2.0.4: {}

View File

@@ -14,7 +14,7 @@
</template>
</NcButton>
<NcEmojiPicker @select="handleEmojiSelect">
<LazyEmojiPicker @select="handleEmojiSelect">
<NcButton
variant="tertiary"
:aria-label="strings.emojiLabel"
@@ -25,7 +25,7 @@
<EmoticonIcon :size="20" />
</template>
</NcButton>
</NcEmojiPicker>
</LazyEmojiPicker>
<div class="toolbar-spacer"></div>
@@ -49,7 +49,7 @@
<script lang="ts">
import { defineComponent, type PropType } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcEmojiPicker from '@nextcloud/vue/components/NcEmojiPicker'
import LazyEmojiPicker from '@/components/LazyEmojiPicker'
import { getFilePickerBuilder } from '@nextcloud/dialogs'
import FormatBoldIcon from '@icons/FormatBold.vue'
import FormatItalicIcon from '@icons/FormatItalic.vue'
@@ -91,7 +91,7 @@ export default defineComponent({
name: 'BBCodeToolbar',
components: {
NcButton,
NcEmojiPicker,
LazyEmojiPicker,
BBCodeHelpDialog,
EmoticonIcon,
HelpCircleIcon,

View File

@@ -0,0 +1,3 @@
import { defineAsyncComponent } from 'vue'
export default defineAsyncComponent(() => import('@nextcloud/vue/components/NcEmojiPicker'))

View File

@@ -14,11 +14,11 @@
</button>
<!-- Add custom reaction button -->
<NcEmojiPicker @select="handleSelectEmoji" style="display: inline-block">
<LazyEmojiPicker @select="handleSelectEmoji" style="display: inline-block">
<button class="add-reaction-button" :title="strings.addReaction">
<span class="icon">+</span>
</button>
</NcEmojiPicker>
</LazyEmojiPicker>
</div>
</template>
@@ -27,12 +27,12 @@ import { defineComponent, type PropType } from 'vue'
import { t, n } from '@nextcloud/l10n'
import { getCurrentUser } from '@nextcloud/auth'
import { useReactions, type ReactionGroup } from '@/composables/useReactions'
import NcEmojiPicker from '@nextcloud/vue/components/NcEmojiPicker'
import LazyEmojiPicker from '@/components/LazyEmojiPicker'
export default defineComponent({
name: 'PostReactions',
components: {
NcEmojiPicker,
LazyEmojiPicker,
},
props: {
postId: {

View File

@@ -5,7 +5,8 @@ use OCP\Util;
/* @var array $_ */
$script = $_['script'];
Util::addScript(Application::APP_ID, Application::JS_DIR . "/forum-$script");
Util::addStyle(Application::APP_ID, Application::CSS_DIR . '/forum-style');
$style = $_['style'];
Util::addScript(Application::APP_ID, Application::JS_DIR . "/$script");
Util::addStyle(Application::APP_ID, Application::CSS_DIR . "/$style");
?>
<div id="forum-app"></div>

View File

@@ -1,5 +1,20 @@
import { createAppConfig } from '@nextcloud/vite-config'
import path from 'path'
import { visualizer } from 'rollup-plugin-visualizer'
const manualChunksList = [
'date-fns',
'lodash',
'dompurify',
'linkifyjs',
'floating-vue',
'focus-trap',
'floating-ui',
'vue-router',
'vue-material-design-icons',
'vue',
'axios',
]
// https://vite.dev/config/
export default createAppConfig(
@@ -15,50 +30,47 @@ export default createAppConfig(
'@': path.resolve(__dirname, 'src'),
},
},
plugins: [
visualizer({
open: process.env.VITE_BUILD_ANALYZE === 'true',
filename: 'stats.html',
template: 'treemap',
}),
],
build: {
outDir: '../dist',
manifest: true,
cssCodeSplit: false,
rollupOptions: {
output: {
entryFileNames: 'js/[name]-[hash].mjs',
chunkFileNames: 'js/[name]-[hash].mjs',
assetFileNames: '[ext]/[name]-[hash].[ext]',
manualChunks(id) {
if (id.includes('node_modules')) {
const manualChunks = [
'date-fns',
'lodash',
'dompurify',
'linkifyjs',
'floating-vue',
'focus-trap',
'floating-ui',
'vue-router',
'vue-material-design-icons',
'vue',
'axios',
]
// Get the part after the last 'node_modules/' to handle pnpm structure
if (id.includes('emoji-mart')) {
return 'emoji-mart'
}
const parts = id.split('node_modules/')
const pkgPath = parts[parts.length - 1]
// Match @nextcloud/xxx packages
const scopedNextcloudMatch = pkgPath.match(/^@nextcloud\/([^/]+)/)
if (scopedNextcloudMatch) {
return `nextcloud-${scopedNextcloudMatch[1]}`
}
// Match nextcloud-xxx packages (without @ scope)
const nextcloudMatch = pkgPath.match(/^nextcloud-([^/]+)/)
if (nextcloudMatch) {
return `nextcloud-${nextcloudMatch[1]}`
}
// Handle other common packages
for (const chunk of manualChunks) {
for (const chunk of manualChunksList) {
if (pkgPath.includes(chunk)) {
return chunk
}
}
return 'vendor' // fallback for other deps
return 'vendor'
}
},
},