-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Add CodeMirror to Additional CSS / Custom HTML block #60155
base: trunk
Are you sure you want to change the base?
Changes from 1 commit
f23e475
7b38263
b2ceddd
14678ea
421f2d4
4decdff
a3e89a9
9394fc9
15e2486
59a81b5
4c6262c
ee88fcd
a6519f2
57f4770
dff73f6
e7aa264
fb5ba29
a106416
30ae96e
af8af11
b0054fc
ab79885
6a4b77d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
* WordPress dependencies | ||
*/ | ||
import { Notice, __experimentalVStack as VStack } from '@wordpress/components'; | ||
import { useState, useEffect, useRef, useId } from '@wordpress/element'; | ||
import { useState, useEffect, useRef } from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
|
@@ -101,6 +101,8 @@ export default function AdvancedPanel( { | |
* @see https://github.com/WordPress/gutenberg/pull/60155 | ||
*/ | ||
const { EditorView, basicSetup } = await import( 'codemirror' ); | ||
const {indentWithTab} = await import('@codemirror/commands'); | ||
const {keymap} = await import('@codemirror/view'); | ||
const { css } = await import( '@codemirror/lang-css' ); | ||
|
||
if ( editorRef.current ) { | ||
|
@@ -109,6 +111,7 @@ export default function AdvancedPanel( { | |
extensions: [ | ||
basicSetup, | ||
css(), | ||
keymap.of([indentWithTab]), | ||
EditorView.updateListener.of( ( editor ) => { | ||
if ( editor.docChanged ) { | ||
handleOnChange( editor.state.doc.toString() ); | ||
|
@@ -130,7 +133,6 @@ export default function AdvancedPanel( { | |
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
okmttdhr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, [] ); | ||
|
||
const cssEditorId = useId(); | ||
return ( | ||
<VStack spacing={ 3 }> | ||
{ cssError && ( | ||
|
@@ -139,7 +141,7 @@ export default function AdvancedPanel( { | |
</Notice> | ||
) } | ||
<label | ||
htmlFor={ cssEditorId } | ||
htmlFor={ EDITOR_ID } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apparently a generic div is not labelable. (Confirmed not labeled in the accessibility tree.) |
||
className="block-editor-global-styles-advanced-panel__custom-css-label" | ||
> | ||
{ __( 'Additional CSS' ) } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
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.
Importing these individual packages will create four webpack chunks, and the loading will take more time than necessary, because the next load starts only after the previous one has finished.
Better to move the
EditorView
code into a separate module and load it all at once:Then there will be just one
code-mirror-view
chunk that's loaded on demand. There's a bit of trouble with thelang-css
andlang-html
modules. Do we want to bundle them both in a single chunk? That depends on how big they are.Or the dynamic module can contain the React component that shows the editor: the
<div>
together with theuseRef
and theuseEffect
(this time the effect doesn't do dynamicimport
). Then it can be used like:It would be also a good idea to not load the CodeMirror editor not when the
AdvancedPanel
is rendered, that will load it too often. Load it only after clicking on the CSS field, only after the user shows a clear intent to edit.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.
Thank you for your review/insights, @jsnajdr! 😄
Not exactly, in terms of
import
. Fundamentally, based on my understanding of how modules are waited for and executed, the following codewould be roughly equivalent to the following:
At least, Webpack behaves in this manner, even for
import()
. Here’s a screenshot showing that the requests for chunks run in parallel when using the original approach.However, this does not seem to be stated explicitly in the spec of
import()
, and my tests confirmed that native dynamic imports execute requests sequentially both at the top level and within function scopes. I’ll address this later in this comment.This also applies even if we:
React.lazy()
import {} from 'codemirror'
because we are splitting the vendor's chunk.
This approach does not create a single chunk, even with all logic extracted into a component and using static import (although we can tweak chunks further by using the optimization.splitChunks option).
Thus, the requests for the “one-component approach” looked like this, showing no difference from the original approach:
Given the above, I propose the following;
Promise.all([import(), import(), …])
rather than fetching a single larger chunk at once. This ensures that requests run in parallel, even after transitioning to native dynamic import.For now, parallel chunk requests seem to be more efficient.
I don’t think it’s a good idea to load all
lang-*
modules in this component because we may want to support additional language modes in the future. (Even with the “parallel requests” strategy, this could potentially max out the browser's limit on simultaneous requests.)On a slow 3G network, this delay could be around 8 seconds. Making users wait that long for syntax highlighting isn't ideal, IMO.
I'd consider more eager loading strategies (if that doesn't interfere with user interaction). For instance, for Additional CSS, we could load the chunks when the three-dots menu is opened, or for a Custom HTML block, load them when the block appears in the search results. Alternatively,
prefetch
might be a viable approach. WDYT?Once the chunks are loaded, subsequent network requests are unnecessary, even if users access Additional CSS multiple times. Thus, “loading them too often” shouldn’t pose an issue, in my opinion. We just need to be careful about how it affects users’ actual interactions/experiences.
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.
We should consider not doing this. After all, currently Gutenberg doesn't do it on purpose, but only because it's a webpack default and we didn't do any dynamic chunks until now.
In my block lazy loading experiment I disabled it, see this comment: https://github.com/WordPress/gutenberg/pull/55585/files#r1383350711
It helps to keep a nice file structure with human-comprehensible names.