-
Notifications
You must be signed in to change notification settings - Fork 14
Build panel app as modules and use async imports for chunk splitting #793
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request transitions the panel application from IIFE bundling to ES modules with code splitting for improved performance. The main changes include adding asset metadata support in PHP for module script tags, switching the build system to esbuild with ESM output, and optimizing TypeScript imports using dynamic imports and import type syntax for better tree-shaking and chunk splitting.
Key Changes
- Asset metadata system in PHP allowing scripts to specify type="module" attribute
- Build configuration migrated from inline esbuild to a dedicated build.js script with module splitting and chunking
- Dynamic imports for heavy dependencies (Sortable.js, Chartist, ProseMirror editors) to enable code splitting and reduce initial bundle size
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| formwork/src/Assets/Asset.php | Adds metadata support to Asset constructor and getMeta() method |
| formwork/src/Assets/Assets.php | Updates add() method to accept and pass metadata array |
| panel/build.js | New esbuild configuration script with ESM format, code splitting, and watch mode |
| panel/package.json | Updates build scripts and adds clean script for chunk management |
| panel/eslint.config.js | Refactors config with defineConfig import and adds TypeScript-specific linting rules |
| panel/tsconfig.json | Updates library target from ES2017 to ES2020 |
| panel/views/partials/scripts.php | Modifies script rendering to support module type and imports app as ES module |
| panel/views/layouts/*.php | Adds module metadata when registering app.min.js asset |
| panel/src/ts/components/inputs/array-input.ts | Converts to dynamic import of Sortable with async initialization |
| panel/src/ts/components/inputs/tags-input.ts | Converts to dynamic import of Sortable with async initialization |
| panel/src/ts/components/inputs/editor-input.ts | Converts editor views to dynamic imports with async methods |
| panel/src/ts/components/statistics-chart.ts | Converts Chartist to dynamic import with async initialization |
| panel/src/ts/components/views/pages.ts | Converts Sortable to dynamic import in initSortable function |
| panel/src/ts/components/inputs/editor/** | Updates to use import type for ProseMirror types |
| panel/src/ts/components/**.ts | Various files updated to use import type for type-only imports |
Comments suppressed due to low confidence (1)
panel/src/ts/components/inputs/editor-input.ts:149
- Making this method
asyncintroduces a breaking change. This method is called from the constructor (outside the diff) withoutawait, which means the editor will not be initialized when subsequent code tries to accessthis.editor. This will cause runtime errors when code immediately after the call attempts to use the editor instance.
Consider keeping the initialization synchronous or refactoring the constructor to handle async initialization properly.
async switchToCode() {
if (!this.container) {
return;
}
this.editor?.destroy();
const { CodeView } = await import("./editor/code/view");
this.editor = new CodeView(this.container, removeBaseUri(this.element.value, this.options.baseUri), this.options.inputEventHandler, {
editable: !(this.element.disabled || this.element.readOnly),
placeholder: this.element.placeholder,
});
this.editor.view.dom.style.height = `${this.options.height}px`;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
panel/src/ts/components/inputs/tags-input.ts:165
- The
createField()method is now async and usesawait, but it's called from the constructor (line 45) without awaiting. This means the Sortable functionality may not be initialized when the constructor completes, potentially causing race conditions. Consider using the same pattern asArrayInputwith a separateinit()method that is called but not awaited from the constructor.
private async createField() {
if ("limit" in this.element.dataset) {
this.options.limit = parseInt(this.element.dataset.limit as string);
}
if (!("orderable" in this.element.dataset)) {
this.options.orderable = false;
}
this.field.className = "form-input-tags-wrap";
this.innerInput.className = "form-input";
this.innerInput.type = "text";
this.innerInput.id = this.element.id;
this.innerInput.placeholder = this.element.placeholder;
this.element.classList.remove("form-input");
this.element.removeAttribute("id");
this.element.removeAttribute("placeholder");
this.element.readOnly = true;
this.element.hidden = true;
this.element.tabIndex = -1;
this.element.ariaHidden = "true";
if (this.element.disabled) {
this.innerInput.disabled = true;
}
(this.element.parentNode as ParentNode).replaceChild(this.field, this.element);
this.field.appendChild(this.list);
this.field.appendChild(this.innerInput);
this.field.appendChild(this.element);
if (this.element.value) {
this.tags = this.element.value.split(", ");
this.tags.forEach((value, index) => {
value = value.trim();
this.tags[index] = value;
this.insertTag(value, this.list);
});
}
if (this.innerInput.placeholder) {
this.placeholder = this.innerInput.placeholder;
this.updatePlaceholder();
} else {
this.placeholder = "";
}
this.field.addEventListener("mousedown", (event) => {
this.innerInput.focus();
event.preventDefault();
});
if (this.options.orderable) {
const { default: Sortable } = await import("sortablejs");
Sortable.create(this.list, {
forceFallback: true,
animation: 150,
filter: ".tag-remove",
onChoose: () => {
if (this.dropdown) {
this.dropdown.style.display = "none";
}
},
onStart: () => {
this.field.classList.add("is-dragging");
},
onFilter: (event: SortableEvent) => {
if (event.target.matches(".tag-remove")) {
this.removeTag(event.item.innerText);
this.list.removeChild(event.item);
}
},
onEnd: (event: SortableEvent) => {
this.field.classList.remove("is-dragging");
const newIndex = event.newIndex;
const oldIndex = event.oldIndex;
this.innerInput.blur();
this.innerInput.focus();
if (newIndex === oldIndex || newIndex === undefined || oldIndex === undefined) {
return;
}
this.tags.splice(newIndex, 0, this.tags.splice(oldIndex, 1)[0]);
this.updateTags();
},
});
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This pull request introduces several improvements and refactorings across both the backend and frontend codebases. The main themes are enhanced asset metadata handling in PHP, improved build and linting configuration for the frontend, and TypeScript optimizations including more efficient imports and dynamic module loading.
Backend (PHP) improvements:
Assetclass constructor now accepts an optionalmetaarray, allowing you to associate metadata with assets. (formwork/src/Assets/Asset.php)getMetamethod to retrieve asset metadata values by key, with support for default values. (formwork/src/Assets/Asset.php)Assetscollection'saddmethod now accepts and passes asset metadata to theAssetconstructor. (formwork/src/Assets/Assets.php)Frontend (JavaScript/TypeScript) improvements:
Build and linting configuration:
esbuildwith support for chunking, ESM output, and watch mode via a newbuild.jsscript; updated relatedpackage.jsonscripts for cleaner builds. (panel/build.js,panel/package.json) [1] [2]defineConfig, updated ECMAScript target, added TypeScript-specific rules, and improved project parsing for.tsfiles. (panel/eslint.config.js) [1] [2] [3]TypeScript import optimizations:
import typefor type-only dependencies throughout many files, reducing bundle size and improving clarity. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]Dynamic imports and performance:
ArrayInput,EditorInput, andTagsInputto dynamically import heavy dependencies (sortablejs, editor views) only when needed, improving initial load performance. (panel/src/ts/components/inputs/array-input.ts,panel/src/ts/components/inputs/editor-input.ts,panel/src/ts/components/inputs/tags-input.ts) [1] [2] [3] [4] [5] [6]Minor bug fixes and code quality:
DurationInputinterval calculation. (panel/src/ts/components/inputs/duration-input.ts)panel/src/ts/components/dropdowns.ts,panel/src/ts/components/inputs/array-input.ts) [1] [2]Let me know if you have questions about any specific change or want to dig deeper into how these improvements affect the codebase!