build: improve chunking strategy & asset loading

This commit is contained in:
2025-11-20 10:06:45 +02:00
parent 699afc3d3d
commit 7782ff66fb
6 changed files with 120 additions and 50 deletions

View File

@@ -29,6 +29,33 @@ class Application extends App implements IBootstrap {
public function boot(IBootContext $context): void {
}
/**
* Helper to parse Vite Manifest
*/
public static 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 '';
}
public static function tableName(string $table): string {
return self::APP_ID . '_' . $table;
}

View File

@@ -2,9 +2,6 @@
declare(strict_types=1);
// SPDX-FileCopyrightText: Your Name <your@email.com>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\NextcloudAppTemplate\Controller;
use OCA\NextcloudAppTemplate\AppInfo\Application;
@@ -21,7 +18,6 @@ class PageController extends Controller {
IRequest $request,
private LoggerInterface $logger,
) {
$this->logger->info('NextcloudAppTemplate page controller loaded');
parent::__construct($appName, $request);
}
@@ -35,9 +31,9 @@ class PageController extends Controller {
#[NoAdminRequired]
#[NoCSRFRequired]
public function index(): TemplateResponse {
$this->logger->info('Forum main page loaded');
return new TemplateResponse(Application::APP_ID, 'app', [
'script' => 'app',
'script' => Application::getViteEntryScript('app.ts'),
'style' => Application::getViteEntryScript('style.css'),
]);
}

View File

@@ -12,7 +12,6 @@ use OCP\AppFramework\Http\TemplateResponse;
use OCP\IAppConfig;
use OCP\IL10N;
use OCP\Settings\ISettings;
use OCP\Util;
class Admin implements ISettings {
public function __construct(
@@ -27,9 +26,10 @@ class Admin implements ISettings {
* @return TemplateResponse
*/
public function getForm(): TemplateResponse {
Util::addScript(Application::APP_ID, Application::JS_DIR . '/nextcloudapptemplate-settings');
Util::addStyle(Application::APP_ID, Application::CSS_DIR . '/nextcloudapptemplate-style');
return new TemplateResponse(Application::APP_ID, 'settings', [], '');
return new TemplateResponse(Application::APP_ID, 'settings', [
'script' => Application::getViteEntryScript('settings.ts'),
'style' => Application::getViteEntryScript('style.css'),
]);
}
public function getSection(): string {

View File

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

View File

@@ -1 +1,12 @@
<?php
use OCA\NextcloudAppTemplate\AppInfo\Application;
use OCP\Util;
/* @var array $_ */
$script = $_['script'];
$style = $_['style'];
Util::addScript(Application::APP_ID, Application::JS_DIR . "/$script");
Util::addStyle(Application::APP_ID, Application::CSS_DIR . "/$style");
?>
<div id="nextcloudapptemplate-settings"></div>

View File

@@ -1,5 +1,33 @@
import { createAppConfig } from '@nextcloud/vite-config'
import path from 'path'
import { visualizer } from 'rollup-plugin-visualizer'
const manualChunksList = [
'emoji-mart-vue-fast',
'date-fns',
'lodash',
'floating-vue',
'vue-material-design-icons',
]
const manualChunksGroups = {
vue: ['vue-router', 'vue'],
}
const nextcloudSharedList = [
'auth',
'axios',
'browser-storage',
'capabilities',
'event-bus',
'files',
'initial-state',
'l10n',
'logger',
'paths',
'router',
'sharing',
]
// https://vite.dev/config/
export default createAppConfig(
@@ -16,51 +44,58 @@ 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
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) {
if (pkgPath.includes(chunk)) {
return chunk
}
}
return 'vendor' // fallback for other deps
if (!id.includes('node_modules')) {
return
}
// Parse package path
const parts = id.split('node_modules/')
const pkgPath = parts[parts.length - 1]
// Check for @nextcloud/xxx or nextcloud-xxx
const ncMatch = pkgPath.match(/^@?nextcloud[/-]([^/]+)/)
// Get the package name (e.g., 'auth', 'vue', 'axios')
const ncPkgName = ncMatch?.[1]
if (ncPkgName) {
if (nextcloudSharedList.includes(ncPkgName)) {
return 'nextcloud-common'
}
return `nextcloud-${ncPkgName}`
}
for (const chunk of manualChunksList) {
if (pkgPath.includes(chunk)) {
return chunk
}
}
for (const [groupName, groupPackages] of Object.entries(manualChunksGroups)) {
if (groupPackages.some((pkg) => pkgPath.includes(pkg))) {
return groupName
}
}
// Fallback
return 'vendor'
},
},
},