Add npm man page, --clean flag for bundle:compile, fix subclass checks
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,8 @@ class Bundle_Compile_Command extends Command
|
|||||||
*/
|
*/
|
||||||
protected $signature = 'rsx:bundle:compile
|
protected $signature = 'rsx:bundle:compile
|
||||||
{bundle? : Bundle class name to compile (optional, compiles all if not specified)}
|
{bundle? : Bundle class name to compile (optional, compiles all if not specified)}
|
||||||
{--build-debug : Enable verbose build output (shows detailed compilation steps)}';
|
{--build-debug : Enable verbose build output (shows detailed compilation steps)}
|
||||||
|
{--clean : Clear all caches before compiling (runs rsx:clean first)}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -47,6 +48,24 @@ class Bundle_Compile_Command extends Command
|
|||||||
// Prevent being called via $this->call() - must use passthru for fresh process
|
// Prevent being called via $this->call() - must use passthru for fresh process
|
||||||
$this->prevent_call_from_another_command();
|
$this->prevent_call_from_another_command();
|
||||||
|
|
||||||
|
// Handle --clean flag: run rsx:clean then re-invoke without --clean
|
||||||
|
if ($this->option('clean')) {
|
||||||
|
$this->info('Clearing caches before compile...');
|
||||||
|
passthru('php artisan rsx:clean');
|
||||||
|
|
||||||
|
// Build command with all args except --clean
|
||||||
|
$cmd = 'php artisan rsx:bundle:compile';
|
||||||
|
if ($this->argument('bundle')) {
|
||||||
|
$cmd .= ' ' . escapeshellarg($this->argument('bundle'));
|
||||||
|
}
|
||||||
|
if ($this->option('build-debug')) {
|
||||||
|
$cmd .= ' --build-debug';
|
||||||
|
}
|
||||||
|
|
||||||
|
passthru($cmd, $exit_code);
|
||||||
|
exit($exit_code);
|
||||||
|
}
|
||||||
|
|
||||||
$bundle_arg = $this->argument('bundle');
|
$bundle_arg = $this->argument('bundle');
|
||||||
|
|
||||||
// Get all MODULE bundle classes from manifest (not asset bundles)
|
// Get all MODULE bundle classes from manifest (not asset bundles)
|
||||||
@@ -56,11 +75,10 @@ class Bundle_Compile_Command extends Command
|
|||||||
$bundle_classes = [];
|
$bundle_classes = [];
|
||||||
|
|
||||||
foreach ($manifest_data as $file_info) {
|
foreach ($manifest_data as $file_info) {
|
||||||
if (isset($file_info['extends']) && $file_info['extends'] === 'Rsx_Module_Bundle_Abstract') {
|
$class_name = $file_info['class'] ?? null;
|
||||||
$fqcn = $file_info['fqcn'] ?? $file_info['class'] ?? null;
|
if ($class_name && Manifest::php_is_subclass_of($class_name, 'Rsx_Module_Bundle_Abstract')) {
|
||||||
if ($fqcn) {
|
$fqcn = $file_info['fqcn'] ?? $class_name;
|
||||||
$bundle_classes[$fqcn] = $file_info['class'] ?? $fqcn;
|
$bundle_classes[$fqcn] = $class_name;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,14 +198,22 @@ class Bundle_Compile_Command extends Command
|
|||||||
$this->line(' JS: ' . $this->format_size($js_size) . " → app.{$bundle_hash}.js");
|
$this->line(' JS: ' . $this->format_size($js_size) . " → app.{$bundle_hash}.js");
|
||||||
$this->line(' CSS: ' . $this->format_size($css_size) . " → app.{$bundle_hash}.css");
|
$this->line(' CSS: ' . $this->format_size($css_size) . " → app.{$bundle_hash}.css");
|
||||||
} else {
|
} else {
|
||||||
// In dev mode, show the actual split files
|
// In dev mode, show the actual split files (vendor and app)
|
||||||
|
if (isset($compiled['vendor_js_bundle_path'])) {
|
||||||
|
$vendor_js_size = filesize("{$bundle_dir}/{$compiled['vendor_js_bundle_path']}");
|
||||||
|
$this->line(' Vendor JS: ' . $this->format_size($vendor_js_size) . ' → ' . basename($compiled['vendor_js_bundle_path']));
|
||||||
|
}
|
||||||
|
if (isset($compiled['vendor_css_bundle_path'])) {
|
||||||
|
$vendor_css_size = filesize("{$bundle_dir}/{$compiled['vendor_css_bundle_path']}");
|
||||||
|
$this->line(' Vendor CSS: ' . $this->format_size($vendor_css_size) . ' → ' . basename($compiled['vendor_css_bundle_path']));
|
||||||
|
}
|
||||||
if (isset($compiled['app_js_bundle_path'])) {
|
if (isset($compiled['app_js_bundle_path'])) {
|
||||||
$app_js_size = filesize("{$bundle_dir}/{$compiled['app_js_bundle_path']}");
|
$app_js_size = filesize("{$bundle_dir}/{$compiled['app_js_bundle_path']}");
|
||||||
$this->line(' JS: ' . $this->format_size($app_js_size) . ' → ' . basename($compiled['app_js_bundle_path']));
|
$this->line(' App JS: ' . $this->format_size($app_js_size) . ' → ' . basename($compiled['app_js_bundle_path']));
|
||||||
}
|
}
|
||||||
if (isset($compiled['app_css_bundle_path'])) {
|
if (isset($compiled['app_css_bundle_path'])) {
|
||||||
$app_css_size = filesize("{$bundle_dir}/{$compiled['app_css_bundle_path']}");
|
$app_css_size = filesize("{$bundle_dir}/{$compiled['app_css_bundle_path']}");
|
||||||
$this->line(' CSS: ' . $this->format_size($app_css_size) . ' → ' . basename($compiled['app_css_bundle_path']));
|
$this->line(' App CSS: ' . $this->format_size($app_css_size) . ' → ' . basename($compiled['app_css_bundle_path']));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ class Bundle_Show_Command extends Command
|
|||||||
$bundle_classes = [];
|
$bundle_classes = [];
|
||||||
|
|
||||||
foreach ($manifest_data as $file_info) {
|
foreach ($manifest_data as $file_info) {
|
||||||
if (isset($file_info['extends']) && $file_info['extends'] === 'Rsx_Bundle_Abstract') {
|
// Only show Module Bundles (compilable page bundles), not Asset Bundles
|
||||||
$fqcn = $file_info['fqcn'] ?? $file_info['class'] ?? null;
|
$class_name = $file_info['class'] ?? null;
|
||||||
if ($fqcn) {
|
if ($class_name && Manifest::php_is_subclass_of($class_name, 'Rsx_Module_Bundle_Abstract')) {
|
||||||
$bundle_classes[$fqcn] = $file_info['class'] ?? $fqcn;
|
$fqcn = $file_info['fqcn'] ?? $class_name;
|
||||||
}
|
$bundle_classes[$fqcn] = $class_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -551,12 +551,10 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
// Search manifest for any class ending with the bundle name that extends Rsx_Bundle_Abstract
|
// Search manifest for any class ending with the bundle name that extends Rsx_Bundle_Abstract
|
||||||
$manifest_data = Manifest::get_all();
|
$manifest_data = Manifest::get_all();
|
||||||
foreach ($manifest_data as $file_info) {
|
foreach ($manifest_data as $file_info) {
|
||||||
if (isset($file_info['class']) &&
|
$class_name = $file_info['class'] ?? null;
|
||||||
str_ends_with($file_info['class'], $bundle) &&
|
if ($class_name &&
|
||||||
isset($file_info['extends']) &&
|
str_ends_with($class_name, $bundle) &&
|
||||||
($file_info['extends'] === 'Rsx_Bundle_Abstract' || $file_info['extends'] === 'RsxBundle' ||
|
Manifest::php_is_subclass_of($class_name, 'Rsx_Bundle_Abstract')) {
|
||||||
$file_info['extends'] === 'App\\RSpade\\Core\\Bundle\\Rsx_Bundle_Abstract' ||
|
|
||||||
$file_info['extends'] === 'App\\RSpade\\Core\\Bundle\\RsxBundle')) {
|
|
||||||
return $file_info['fqcn'];
|
return $file_info['fqcn'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ class Scss_ManifestModule extends ManifestModule_Abstract
|
|||||||
// Check for JavaScript class extending Component
|
// Check for JavaScript class extending Component
|
||||||
if (isset($file_data['extension']) && $file_data['extension'] === 'js' &&
|
if (isset($file_data['extension']) && $file_data['extension'] === 'js' &&
|
||||||
isset($file_data['class']) && $file_data['class'] === $class_name &&
|
isset($file_data['class']) && $file_data['class'] === $class_name &&
|
||||||
isset($file_data['extends']) && $file_data['extends'] === 'Component') {
|
\App\RSpade\Core\Manifest\Manifest::js_is_subclass_of($class_name, 'Component')) {
|
||||||
$found_match = true;
|
$found_match = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,6 +212,32 @@ BUNDLE PLACEMENT
|
|||||||
/rsx/app/login/signup/...
|
/rsx/app/login/signup/...
|
||||||
... and all files in subdirectories
|
... and all files in subdirectories
|
||||||
|
|
||||||
|
ADDING CUSTOM NPM MODULES
|
||||||
|
Application developers can include third-party npm packages in their bundles
|
||||||
|
by creating Asset Bundles co-located with their components:
|
||||||
|
|
||||||
|
// /rsx/theme/components/charts/Chart_JS_Bundle.php
|
||||||
|
class Chart_JS_Bundle extends Rsx_Asset_Bundle_Abstract
|
||||||
|
{
|
||||||
|
public static function define(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'npm' => [
|
||||||
|
'Chart' => "import { Chart } from 'chart.js/auto'",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The 'npm' array maps global variable names to ES module import statements.
|
||||||
|
Each entry creates one global variable accessible in your JavaScript.
|
||||||
|
|
||||||
|
The package is included only when Module Bundles scan that directory.
|
||||||
|
This keeps bundle sizes smaller when components aren't used.
|
||||||
|
|
||||||
|
For detailed npm integration documentation including import formats,
|
||||||
|
troubleshooting, and examples, see npm(3).
|
||||||
|
|
||||||
INCLUDE TYPES
|
INCLUDE TYPES
|
||||||
Module Aliases
|
Module Aliases
|
||||||
Predefined in config/rsx.php:
|
Predefined in config/rsx.php:
|
||||||
@@ -235,9 +261,7 @@ INCLUDE TYPES
|
|||||||
'rsx/theme/variables.scss'
|
'rsx/theme/variables.scss'
|
||||||
|
|
||||||
NPM Modules
|
NPM Modules
|
||||||
Include from node_modules:
|
Use Asset Bundles with the 'npm' array. See npm(3) for details.
|
||||||
'npm:axios'
|
|
||||||
'npm:moment'
|
|
||||||
|
|
||||||
CDN Assets
|
CDN Assets
|
||||||
External resources:
|
External resources:
|
||||||
@@ -551,6 +575,6 @@ TROUBLESHOOTING
|
|||||||
Verify file extension matches.
|
Verify file extension matches.
|
||||||
|
|
||||||
SEE ALSO
|
SEE ALSO
|
||||||
manifest_api(3), jqhtml(3), controller(3)
|
npm(3), manifest_api(3), jqhtml(3), controller(3)
|
||||||
|
|
||||||
RSX Framework 2025-09-17 BUNDLE_API(3)
|
RSX Framework 2025-12-09 BUNDLE_API(3)
|
||||||
241
app/RSpade/man/npm.txt
Executable file
241
app/RSpade/man/npm.txt
Executable file
@@ -0,0 +1,241 @@
|
|||||||
|
NPM(3) RSX Framework Manual NPM(3)
|
||||||
|
|
||||||
|
NAME
|
||||||
|
npm - Including npm packages in RSX bundles
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
// Asset Bundle with npm imports
|
||||||
|
class My_Bundle extends Rsx_Asset_Bundle_Abstract
|
||||||
|
{
|
||||||
|
public static function define(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'npm' => [
|
||||||
|
'moment' => "import moment from 'moment'",
|
||||||
|
'axios' => "import axios from 'axios'",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage in JavaScript
|
||||||
|
const now = moment().format('YYYY-MM-DD');
|
||||||
|
const response = await axios.get('/api/data');
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
RSX bundles can include npm packages through Asset Bundles. The bundle
|
||||||
|
system uses esbuild to compile npm imports into the vendor bundle,
|
||||||
|
exposing them as global variables accessible throughout your application.
|
||||||
|
|
||||||
|
Unlike traditional webpack/vite setups where you write import statements
|
||||||
|
in your JavaScript files, RSX centralizes npm dependencies in Asset
|
||||||
|
Bundle definitions. This provides:
|
||||||
|
|
||||||
|
- Explicit dependency declaration in PHP
|
||||||
|
- Automatic vendor/app bundle splitting
|
||||||
|
- Tree-shaking of unused exports
|
||||||
|
- No import statements needed in application code
|
||||||
|
|
||||||
|
PACKAGE INSTALLATION
|
||||||
|
All npm packages must be installed in the system directory:
|
||||||
|
|
||||||
|
cd /var/www/html/system
|
||||||
|
npm install <package-name>
|
||||||
|
|
||||||
|
The framework's esbuild runs from system/, so packages must exist in
|
||||||
|
system/node_modules/. The project root package.json is for reference
|
||||||
|
only - actual dependencies live in system/.
|
||||||
|
|
||||||
|
ASSET BUNDLE SYNTAX
|
||||||
|
Create an Asset Bundle extending Rsx_Asset_Bundle_Abstract with an
|
||||||
|
'npm' array in the define() method:
|
||||||
|
|
||||||
|
class My_Library_Bundle extends Rsx_Asset_Bundle_Abstract
|
||||||
|
{
|
||||||
|
public static function define(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'npm' => [
|
||||||
|
'global_name' => "import statement",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Each entry maps a global variable name to an ES module import statement.
|
||||||
|
The global becomes available to all JavaScript in bundles that include
|
||||||
|
this Asset Bundle.
|
||||||
|
|
||||||
|
IMPORT FORMATS
|
||||||
|
DEFAULT EXPORT
|
||||||
|
Package exports a single default value:
|
||||||
|
|
||||||
|
'moment' => "import moment from 'moment'",
|
||||||
|
'axios' => "import axios from 'axios'",
|
||||||
|
'lodash' => "import _ from 'lodash'",
|
||||||
|
|
||||||
|
Usage: moment(), axios.get(), _.map()
|
||||||
|
|
||||||
|
NAMED EXPORT
|
||||||
|
Package exports multiple named values. Each named export needs its
|
||||||
|
own entry:
|
||||||
|
|
||||||
|
// CORRECT - separate entries for each named export
|
||||||
|
'createApp' => "import { createApp } from 'vue'",
|
||||||
|
'ref' => "import { ref } from 'vue'",
|
||||||
|
'computed' => "import { computed } from 'vue'",
|
||||||
|
|
||||||
|
Usage: createApp(), ref(), computed()
|
||||||
|
|
||||||
|
IMPORTANT: You cannot combine multiple named exports into one global.
|
||||||
|
This does NOT work:
|
||||||
|
|
||||||
|
// WRONG - creates single value, not object with both
|
||||||
|
'vue' => "import { createApp, ref } from 'vue'",
|
||||||
|
|
||||||
|
The global 'vue' would only contain createApp, not both functions.
|
||||||
|
|
||||||
|
NAMESPACE IMPORT
|
||||||
|
Import all exports as a namespace object:
|
||||||
|
|
||||||
|
'Shiki' => "import * as Shiki from 'shiki'",
|
||||||
|
|
||||||
|
Usage: Shiki.createHighlighter(), Shiki.bundledLanguages
|
||||||
|
|
||||||
|
SUB-PATH IMPORTS
|
||||||
|
Many packages offer sub-path imports for tree-shaking:
|
||||||
|
|
||||||
|
// Full package - includes everything
|
||||||
|
'shiki' => "import { createHighlighter } from 'shiki'",
|
||||||
|
|
||||||
|
// Sub-path - only what you need
|
||||||
|
'createHighlighter' => "import { createHighlighterCore } from '@shikijs/core'",
|
||||||
|
'darkPlus' => "import darkPlus from '@shikijs/themes/dark-plus'",
|
||||||
|
|
||||||
|
Sub-path imports significantly reduce bundle size.
|
||||||
|
|
||||||
|
BUNDLE PLACEMENT
|
||||||
|
Asset Bundles are auto-discovered when Module Bundles scan directories.
|
||||||
|
Place your npm Asset Bundle alongside the components that use it:
|
||||||
|
|
||||||
|
rsx/app/docs/components/
|
||||||
|
shiki_bundle.php # Asset Bundle with npm imports
|
||||||
|
Docs_Code_Block.js # Component using shiki globals
|
||||||
|
Docs_Code_Block.jqhtml
|
||||||
|
|
||||||
|
The Module Bundle scanning rsx/app/docs/ will automatically include
|
||||||
|
the Shiki_Bundle and its npm dependencies.
|
||||||
|
|
||||||
|
VENDOR BUNDLE COMPILATION
|
||||||
|
npm packages compile into the vendor bundle, separate from app code.
|
||||||
|
This provides better caching since vendor code changes less frequently.
|
||||||
|
|
||||||
|
REBUILD TRIGGERS
|
||||||
|
The vendor bundle rebuilds when:
|
||||||
|
- npm entries in Asset Bundles change
|
||||||
|
- First HTTP request after cache clear
|
||||||
|
- Running rsx:clean followed by page request
|
||||||
|
|
||||||
|
IMPORTANT: Running rsx:bundle:compile alone may not rebuild the
|
||||||
|
vendor bundle if the cache key hasn't changed. To force a complete
|
||||||
|
rebuild:
|
||||||
|
|
||||||
|
php artisan rsx:bundle:compile --clean
|
||||||
|
|
||||||
|
Or trigger via HTTP request:
|
||||||
|
|
||||||
|
curl http://localhost/your-page
|
||||||
|
php artisan rsx:debug /your-page
|
||||||
|
|
||||||
|
TROUBLESHOOTING
|
||||||
|
NPM MODULE NOT FOUND ERROR
|
||||||
|
Error: RSX Framework Error: NPM module "xyz" not found.
|
||||||
|
Expected window._rsx_npm.xyz to be defined by the vendor bundle.
|
||||||
|
|
||||||
|
Causes:
|
||||||
|
1. Package not installed in system/node_modules/
|
||||||
|
2. Vendor bundle not rebuilt after adding npm entry
|
||||||
|
3. Import statement syntax error
|
||||||
|
|
||||||
|
Solutions:
|
||||||
|
1. cd system && npm install <package>
|
||||||
|
2. php artisan rsx:bundle:compile --clean
|
||||||
|
3. Verify import syntax matches package's actual exports
|
||||||
|
|
||||||
|
VERIFYING PACKAGE EXPORTS
|
||||||
|
Check what a package actually exports:
|
||||||
|
|
||||||
|
# Default export
|
||||||
|
grep -E "^export default" node_modules/pkg/dist/index.mjs
|
||||||
|
|
||||||
|
# Named exports
|
||||||
|
grep -E "^export \{|^export function|^export const" \
|
||||||
|
node_modules/pkg/dist/index.mjs
|
||||||
|
|
||||||
|
BUNDLE SIZE OPTIMIZATION
|
||||||
|
Use sub-path imports when available:
|
||||||
|
|
||||||
|
// Before: 9.7 MB (all languages, themes)
|
||||||
|
'shiki' => "import { createHighlighter } from 'shiki'",
|
||||||
|
|
||||||
|
// After: 2.4 MB (only what's needed)
|
||||||
|
'createHighlighter' => "import { createHighlighterCore } from '@shikijs/core'",
|
||||||
|
'langJs' => "import js from '@shikijs/langs/javascript'",
|
||||||
|
'themeDark' => "import dark from '@shikijs/themes/dark-plus'",
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
CHART.JS
|
||||||
|
class Chart_Bundle extends Rsx_Asset_Bundle_Abstract
|
||||||
|
{
|
||||||
|
public static function define(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'npm' => [
|
||||||
|
'Chart' => "import { Chart } from 'chart.js/auto'",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
new Chart(ctx, { type: 'bar', data: {...} });
|
||||||
|
|
||||||
|
DATE-FNS (Tree-Shaking Friendly)
|
||||||
|
class DateFns_Bundle extends Rsx_Asset_Bundle_Abstract
|
||||||
|
{
|
||||||
|
public static function define(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'npm' => [
|
||||||
|
'format' => "import { format } from 'date-fns'",
|
||||||
|
'parseISO' => "import { parseISO } from 'date-fns'",
|
||||||
|
'addDays' => "import { addDays } from 'date-fns'",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
format(parseISO('2024-01-15'), 'MMM d, yyyy');
|
||||||
|
|
||||||
|
VUE 3
|
||||||
|
class Vue_Bundle extends Rsx_Asset_Bundle_Abstract
|
||||||
|
{
|
||||||
|
public static function define(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'npm' => [
|
||||||
|
'createApp' => "import { createApp } from 'vue'",
|
||||||
|
'ref' => "import { ref } from 'vue'",
|
||||||
|
'reactive' => "import { reactive } from 'vue'",
|
||||||
|
'computed' => "import { computed } from 'vue'",
|
||||||
|
'watch' => "import { watch } from 'vue'",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
bundle_api(3), jqhtml(3)
|
||||||
|
|
||||||
|
RSX Framework 2025-12-09 NPM(3)
|
||||||
Reference in New Issue
Block a user