(#1) - WebPack bundling of JS and CSS #1

Merged
Martas merged 1 commits from feat/claude/1-webpack-bundling-of-assets into main 2026-06-12 10:57:23 +00:00
23 changed files with 115 additions and 70 deletions
Showing only changes of commit 2a630228ad - Show all commits
+3 -2
View File
@@ -1,9 +1,10 @@
node_modules node_modules
build build/
vendor/ vendor/
dist dist/
# Editors # Editors
.claude/ .claude/
.idea/ .idea/
.zed/
.pytest_cache/ .pytest_cache/
+1 -1
View File
@@ -2,6 +2,6 @@
* Utilities for calling the API * Utilities for calling the API
*/ */
function get_rest_url(resource) { export function get_rest_url(resource) {
return ReservairServiceAPI.restUrl + '/' + resource; return ReservairServiceAPI.restUrl + '/' + resource;
} }
+1 -1
View File
@@ -1,4 +1,4 @@
const RsvDataSource = { export const RsvDataSource = {
create_rsv_resource(base_url, { nonce } = {}) { create_rsv_resource(base_url, { nonce } = {}) {
function request(url, method, body) { function request(url, method, body) {
const headers = { 'Content-Type': 'application/json' }; const headers = { 'Content-Type': 'application/json' };
@@ -1,2 +1,4 @@
const RsvFormDefinitionResource = () => import { RsvDataSource } from './RsvDataSource.js';
export const RsvFormDefinitionResource = () =>
RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + '/form-definition'); RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + '/form-definition');
+1 -1
View File
@@ -1,4 +1,4 @@
const RsvReservationClient = { export const RsvReservationClient = {
accept(reservation_id) { accept(reservation_id) {
return this._post(reservation_id, 'accept'); return this._post(reservation_id, 'accept');
}, },
@@ -1,2 +1,4 @@
const RsvReservationResource = () => import { RsvDataSource } from './RsvDataSource.js';
export const RsvReservationResource = () =>
RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + '/reservation'); RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + '/reservation');
@@ -1,2 +1,4 @@
const RsvTimetableCapacityResource = (id) => import { RsvDataSource } from './RsvDataSource.js';
export const RsvTimetableCapacityResource = (id) =>
RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + `/timetable/${id}/capacity`); RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + `/timetable/${id}/capacity`);
@@ -1,2 +1,4 @@
const RsvTimetableReservationResource = (id) => import { RsvDataSource } from './RsvDataSource.js';
export const RsvTimetableReservationResource = (id) =>
RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + `/timetable/${id}/reservation`); RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + `/timetable/${id}/reservation`);
+3 -1
View File
@@ -1,2 +1,4 @@
const RsvTimetableResource = () => import { RsvDataSource } from './RsvDataSource.js';
export const RsvTimetableResource = () =>
RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + '/timetable'); RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + '/timetable');
+1 -1
View File
@@ -1,4 +1,4 @@
const RsvCalendarPicker = (() => { export const RsvCalendarPicker = (() => {
function get_first_day_of_month(date) { function get_first_day_of_month(date) {
const day = new Date(date.getFullYear(), date.getMonth(), 1).getDay(); const day = new Date(date.getFullYear(), date.getMonth(), 1).getDay();
+4 -2
View File
@@ -2,7 +2,7 @@
* RSV Dynamic datagrid * RSV Dynamic datagrid
* Allows fetching with JS instead of page reload. * Allows fetching with JS instead of page reload.
*/ */
window.RsvDataGrid = window.RsvDataGrid || { const RsvDataGrid = {
create_header(self, columns, has_actions) { create_header(self, columns, has_actions) {
let thead = document.createElement('thead'); let thead = document.createElement('thead');
@@ -419,4 +419,6 @@ window.RsvDataGrid = window.RsvDataGrid || {
return state; return state;
} }
} };
export { RsvDataGrid };
@@ -1,3 +1,5 @@
import { RsvCalendarPicker } from './RsvCalendar.js';
class RsvReservationSelector extends HTMLElement { class RsvReservationSelector extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
return ['timetable-id', 'name', 'price-per-block']; return ['timetable-id', 'name', 'price-per-block'];
+2
View File
@@ -1,3 +1,5 @@
import { RsvTimetableService } from '../services/RsvTimetableService.js';
class RsvTimeline extends HTMLElement { class RsvTimeline extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
return ['timetable-id', 'date']; return ['timetable-id', 'date'];
+4 -1
View File
@@ -1,3 +1,6 @@
import { RsvFormEncoder } from './RsvFormEncoder.js';
import { show_notice } from '../../../src/components/admin.js';
/* /*
* RsvAdminForm — shared submit handler for wp-admin forms. * RsvAdminForm — shared submit handler for wp-admin forms.
* *
@@ -13,7 +16,7 @@
* refresh: () => my_datagrid.refresh(), * refresh: () => my_datagrid.refresh(),
* }); * });
*/ */
const RsvAdminForm = { export const RsvAdminForm = {
// Attach a submit listener that sends the form as JSON. // Attach a submit listener that sends the form as JSON.
bind(form, options = {}) { bind(form, options = {}) {
if (!form) return; if (!form) return;
+1 -1
View File
@@ -1,4 +1,4 @@
const RsvFormEncoder = { export const RsvFormEncoder = {
// Serialize form element into a plain JS object supporting arrays. // Serialize form element into a plain JS object supporting arrays.
// - Nested keys supported with dot notation: 'meta.email' // - Nested keys supported with dot notation: 'meta.email'
// - Array notation supported with trailing [] (e.g. 'times[]') or multiple inputs with same name // - Array notation supported with trailing [] (e.g. 'times[]') or multiple inputs with same name
+1 -1
View File
@@ -1,4 +1,4 @@
const RsvFormSender = { export const RsvFormSender = {
get_form_url(form_id) { get_form_url(form_id) {
return ReservairServiceAPI.restUrl + '/form/' + form_id; return ReservairServiceAPI.restUrl + '/form/' + form_id;
}, },
+1 -1
View File
@@ -1,4 +1,4 @@
const RsvInlineFormBuilder = { export const RsvInlineFormBuilder = {
match_p(name, value) { match_p(name, value) {
return (form) => String(form[name]) === String(value); return (form) => String(form[name]) === String(value);
}, },
+3 -1
View File
@@ -1,4 +1,6 @@
const RsvTimetableService = { import { get_rest_url } from '../RsvApi.js';
export const RsvTimetableService = {
get_all() { get_all() {
return fetch(get_rest_url('timetable'), { method: 'GET' }) return fetch(get_rest_url('timetable'), { method: 'GET' })
.then(r => { .then(r => {
+13 -51
View File
@@ -5,44 +5,21 @@
* admin and the default user. * admin and the default user.
*/ */
function rsv_asset_url(string $relative): string { function rsv_build_url(string $file): string {
return plugin_dir_url(__FILE__) . '../assets/' . $relative; return plugin_dir_url(__FILE__) . '../build/' . $file;
} }
function rsv_asset_file(string $relative): string { function rsv_build_file(string $file): string {
return plugin_dir_path(__FILE__) . '../assets/' . $relative; return plugin_dir_path(__FILE__) . '../build/' . $file;
} }
function rsv_js(string $handle, string $relative, array $deps = []): void { function rsv_localize_api(string $handle): void {
wp_enqueue_script($handle, rsv_asset_url($relative), $deps, filemtime(rsv_asset_file($relative))); wp_localize_script($handle, 'ReservairServiceAPI', [
}
function rsv_css(string $handle, string $relative): void {
wp_enqueue_style($handle, rsv_asset_url($relative), [], filemtime(rsv_asset_file($relative)));
}
// --- Shared between frontend and admin ---
function rsv_enqueue_shared_assets(): void {
rsv_js('rsv_calendar', 'js/elements/RsvCalendar.js');
rsv_js('rsv_timeline', 'js/elements/RsvTimeline.js');
rsv_js('rsv_api', 'js/RsvApi.js');
rsv_js('reservation_selector', 'js/elements/RsvReservationSelector.js');
rsv_js('rsv_reservation_summary', 'js/elements/RsvReservationSummary.js');
rsv_js('rsv_data_source', 'js/datasource/RsvDataSource.js');
rsv_js('rsv_reservation_resource', 'js/datasource/RsvReservationResource.js');
rsv_js('rsv_form_definition_resource', 'js/datasource/RsvFormDefinitionResource.js');
rsv_js('rsv_timetable_resource', 'js/datasource/RsvTimetableResource.js');
rsv_js('rsv_timetable_capacity_resource', 'js/datasource/RsvTimetableCapacityResource.js');
rsv_js('rsv_timetable_reservation_resource', 'js/datasource/RsvTimetableReservationResource.js');
rsv_js('rsv_reservation_client', 'js/datasource/RsvReservationClient.js');
wp_localize_script('rsv_api', 'ReservairServiceAPI', [
'restUrl' => rest_url('reservations/v1'), 'restUrl' => rest_url('reservations/v1'),
'nonce' => wp_create_nonce('wp_rest'), 'nonce' => wp_create_nonce('wp_rest'),
]); ]);
wp_localize_script('rsv_api', 'ReservairStrings', [ wp_localize_script($handle, 'ReservairStrings', [
'timeline' => [ 'timeline' => [
'not_reservable' => 'Tento objekt nelze rezervovat.', 'not_reservable' => 'Tento objekt nelze rezervovat.',
'no_blocks' => 'Tento den není dostupný žádný blok. Vyberte jiné datum.', 'no_blocks' => 'Tento den není dostupný žádný blok. Vyberte jiné datum.',
@@ -63,35 +40,20 @@ function rsv_enqueue_shared_assets(): void {
'error_generic' => 'Něco se pokazilo. Zkuste to prosím znovu.', 'error_generic' => 'Něco se pokazilo. Zkuste to prosím znovu.',
], ],
]); ]);
rsv_css('reservations-styles', 'css/RsvMainStyle.css');
rsv_css('rsv-form-summary-styles', 'css/components/RsvFormSummaryStyles.css');
rsv_css('rsv-calendar-styles', 'css/components/RsvCalendarStyles.css');
rsv_css('rsv-form-styles', 'css/components/RsvFormStyles.css');
rsv_css('rsv-time-slot-styles', 'css/components/RsvTimeSlotsStyles.css');
} }
// --- Public hooks --- // --- Public hooks ---
function rsv_enqueue_assets(): void { function rsv_enqueue_assets(): void {
rsv_enqueue_shared_assets(); wp_enqueue_script('rsv-client', rsv_build_url('client.js'), [], filemtime(rsv_build_file('client.js')));
wp_enqueue_style('rsv-client', rsv_build_url('client.css'), [], filemtime(rsv_build_file('client.css')));
rsv_js('rsv_timetable_service', 'js/services/RsvTimetableService.js'); rsv_localize_api('rsv-client');
rsv_js('rsv_form_sender', 'js/forms/RsvFormSender.js');
rsv_js('rsv_form_encoder', 'js/forms/RsvFormEncoder.js');
} }
function rsv_enqueue_admin_assets(): void { function rsv_enqueue_admin_assets(): void {
rsv_enqueue_shared_assets(); wp_enqueue_script('rsv-admin', rsv_build_url('admin.js'), [], filemtime(rsv_build_file('admin.js')));
wp_enqueue_style('rsv-admin', rsv_build_url('admin.css'), [], filemtime(rsv_build_file('admin.css')));
$admin_js = plugin_dir_path(__FILE__) . '../src/components/admin.js'; rsv_localize_api('rsv-admin');
wp_enqueue_script('admin', plugin_dir_url(__FILE__) . '../src/components/admin.js', [], filemtime($admin_js));
rsv_js('rsv_inline_form_builder', 'js/forms/RsvInlineFormBuilder.js');
rsv_js('datagrid', 'js/elements/RsvDatagrid.js');
rsv_js('rsv_form_encoder', 'js/forms/RsvFormEncoder.js');
// RsvAdminForm needs the encoder, the localized nonce (rsv_api), and
// show_notice() (admin) — declare them as deps so load order is correct.
rsv_js('rsv_admin_form', 'js/forms/RsvAdminForm.js', ['rsv_form_encoder', 'rsv_api', 'admin']);
rsv_css('rsv-admin-style', 'css/RsvAdminStyle.css');
} }
+22
View File
@@ -0,0 +1,22 @@
import './client.js';
import '../assets/css/RsvAdminStyle.css';
import { RsvDataGrid } from '../assets/js/elements/RsvDatagrid.js';
import { RsvInlineFormBuilder } from '../assets/js/forms/RsvInlineFormBuilder.js';
import './components/admin.js';
import { RsvAdminForm } from '../assets/js/forms/RsvAdminForm.js';
import { RsvReservationResource } from '../assets/js/datasource/RsvReservationResource.js';
import { RsvTimetableResource } from '../assets/js/datasource/RsvTimetableResource.js';
import { RsvTimetableCapacityResource } from '../assets/js/datasource/RsvTimetableCapacityResource.js';
import { RsvTimetableReservationResource } from '../assets/js/datasource/RsvTimetableReservationResource.js';
import { RsvReservationClient } from '../assets/js/datasource/RsvReservationClient.js';
window.RsvDataGrid = RsvDataGrid;
window.RsvInlineFormBuilder = RsvInlineFormBuilder;
window.RsvAdminForm = RsvAdminForm;
window.RsvReservationResource = RsvReservationResource;
window.RsvTimetableResource = RsvTimetableResource;
window.RsvTimetableCapacityResource = RsvTimetableCapacityResource;
window.RsvTimetableReservationResource = RsvTimetableReservationResource;
window.RsvReservationClient = RsvReservationClient;
+20
View File
@@ -0,0 +1,20 @@
import '../assets/css/RsvMainStyle.css';
import '../assets/css/components/RsvCalendarStyles.css';
import '../assets/css/components/RsvFormStyles.css';
import '../assets/css/components/RsvFormSummaryStyles.css';
import '../assets/css/components/RsvTimeSlotsStyles.css';
import '../assets/js/elements/RsvTimeline.js';
import '../assets/js/elements/RsvReservationSelector.js';
import '../assets/js/elements/RsvReservationSummary.js';
import { RsvReservationResource } from '../assets/js/datasource/RsvReservationResource.js';
import { RsvFormDefinitionResource } from '../assets/js/datasource/RsvFormDefinitionResource.js';
import { RsvTimetableResource } from '../assets/js/datasource/RsvTimetableResource.js';
import { RsvTimetableCapacityResource } from '../assets/js/datasource/RsvTimetableCapacityResource.js';
import { RsvTimetableReservationResource } from '../assets/js/datasource/RsvTimetableReservationResource.js';
import { RsvReservationClient } from '../assets/js/datasource/RsvReservationClient.js';
import { RsvFormEncoder } from '../assets/js/forms/RsvFormEncoder.js';
import { RsvFormSender } from '../assets/js/forms/RsvFormSender.js';
window.RsvFormSender = RsvFormSender;
+3 -1
View File
@@ -1,3 +1,5 @@
import { get_rest_url } from '../../assets/js/RsvApi.js';
async function fetch_reservations_to_confirm(object_id) { async function fetch_reservations_to_confirm(object_id) {
const url = `/wordpress/wp-json/reservations/v1/object/${object_id}/timetable/reservation/unconfirmed`; const url = `/wordpress/wp-json/reservations/v1/object/${object_id}/timetable/reservation/unconfirmed`;
return await fetch(url, { return await fetch(url, {
@@ -100,7 +102,7 @@ function create_notice(id, type, mesg) {
return container; return container;
} }
function show_notice(target, type, mesg) { export function show_notice(target, type, mesg) {
target.querySelectorAll('.notice').forEach(x => x.remove()); target.querySelectorAll('.notice').forEach(x => x.remove());
const notice = create_notice('test', type, mesg); const notice = create_notice('test', type, mesg);
target.prepend(notice); target.prepend(notice);
+17
View File
@@ -0,0 +1,17 @@
const defaultConfig = require('@wordpress/scripts/config/webpack.config');
const defaultEntry = defaultConfig.entry;
module.exports = {
...defaultConfig,
entry: async () => {
const entries = typeof defaultEntry === 'function'
? await defaultEntry()
: defaultEntry;
return {
...entries,
client: './src/client.js',
admin: './src/admin.js',
};
},
};