Skip to content

wf-components

Intro

Warning

This feature is available in XalokNext starting with version 7.1.

What are wf-components

wf-components are a way of sharing wf-html code between templates.

Example: defining and using a wf-component

html
<!-- in src/App/Bundle/CmsBundle/Resources/views/Template/wfc/board/article-module.html.twig -->
<article 
    :wf-role="wfc.role"
    wf-new>
    <h3
      wf-role="title"></h3>
</article>

<!-- in src/App/Bundle/CmsBundle/Resources/views/Template/Board/home/board1.html.twig -->
<div class="board1">
  <wfc-board--article-module role="article1" />
  <wfc-board--article-module role="article2" />
</div>

Motivation

Traditionally, in wf-html code has been reused by using the {% include %} twig statement. But this can lead to huge HTML documents, since the same module can be included over and over. On v7, this HTML is handed to the vue-loader, that uses quite a lot of memory to parse it and produce the final JS code. In the case of a single board in one of the project, this included 5 different versions of the board, each with 3-6 different boxes for articles, each of these boxes could be switched between 11 different versions, some with 2-3 sub-boxes, each defining its own (duplicated) settings form with 3 fields. This lead in the single <div class="article-module"> container being included 195 times, leading to a final HTML of ~600KB. After being processed by wfcms4-loader, this HTML was converted to a 5MB vue file. Processing this vue file lead vue-loader to use ~1.5GB of RAM - only for this single file, and since the available RAM memory is scarce, this lead to using the swap disk, which in turn lead to very high compilation times (~5 minutes, in some cases).

wf-components solves this issue by eliminating the repeated HTML - define it once, then use the resulting component - a single tag instead of ~140 lines as it was in the case of this project's module.

For these reasons, wf-components is the recommended way to reuse code over using twig's {% include %} in v7.

Progressive adoption

You can identify the largest HTML templates by using the following command:

shell
ls -lR web/bundles/wfcmsbaseadmin/javascripts/html_template | grep '^-' | sort -k 5 -n | tail -n 20

Note: this command assumes you've already ran make wf-assets or make dev in the project.

It will list the 20 biggest HTML files in the project. As a rule of thumb, any HTML file over 300KB must be converted to use wf-components, any file over 100KB should be converted and files bigger than 50KB would be nice to be converted.

Article/default.html.twig is (usually) another good candidate for conversion to wf-components, because this is usually extended by many other templates: platform specific versions (e.g. Article/default-amp.html.twig, Article/default-instant.html.twig), but also other article templates (e.g. Article/opinion.html.twig, Article/brandslab.html.twig - each with their specific platform versions). All these different templates usually result in a lot of duplicated wf-html code.

Usage

Defining a wf-component

To define a wf-component, add an .html.twig file in the src/App/Bundle/CmsBundle/Resources/views/Template/wfc directory (a wfc subdirectory in the same Template directory where the article/board templates are being defined)

Example: defining a wfc-body-text-module wf-component, inside src/App/Bundle/CmsBundle/Resources/views/Template/wfc/body-text-module.html.twig file:

html
<p wf-role="body_text"></p>

Using a wf-component

Every .html.twig file inside the wfc directory creates a component based on its file name. In the example above, the wf-component defined in wfc/body-text-module.html.twig defines a wfc-body-text-module wf-component.

Example: Using the wfc-body-text-module wf-component:

html
<div class="body-modules">
  <wfc-body-text-module></wfc-body-text-module>
</div>

Self closing tags

Do NOT self-close the tags, the HTML specification allows a few specific elements to omit close tags

Example:

html
<!-- BAD -->
<wfc-body-text-module />

<!-- GOOD -->
<wfc-body-text-module></wfc-body-text-module>

The components can be grouped into subdirectories, every / in the path is converted to a double dash (--):

Example:

wfc/home/board1-v1.html.twig -> wfc-home--board1-v1
wfc/home/board1/v1.html.twig -> wfc-home--board1--v1

Naming conventions

Example:

// BAD
wfc/textModule.html.twig
// BAD
wfc/textModule_v1.html.twig

// GOOD
wfc/text-module-v1.html.twig

Passing props to wf-components

You can pass props to a wf-component by setting attributes when using them. All these props will be available using the wfc. prefix.

Example: Passing and using props

html
<!-- in Article/default.html.twig -->
<div class="body-modules">
  <wfc-body-text-module role="paragraph" class="paragraph"></wfc-body-text-module>
  <wfc-body-text-module role="quote" class="quote"></wfc-body-text-module>
</div>

<!-- in wfc/body-text-module.html.twig -->
<p :wf-role="wfc.role" :class="wfc.class"></p>

Note: XalokNext uses the same convention as Vue for binding dynamic values to the wf-directives by prefixing the attribute name with a semicolon (:).

Prop names with multiple words

A prop with a dash (-) in its name can be referenced by its camelCased version:

Example: Passing and using a prop with a dash (-) in its name:

html
<!-- in Article/default.html.twig -->
<div class="body-modules">
  <wfc-body-text-module 
    role="paragraph" 
    container-class="paragraph" 
    module-class="text-small"></wfc-body-text-module>
</div>

<!-- in wfc/body-text-module.html.twig -->
<div :class="wfc.containerClass">
  <p wf-role="paragraph" :class="wfc.moduleClass"></p>
</div>

Using props as part of expressions

Props can be used as part of any JavaScript expression

Example:

html
<!-- using a prop with string interpolation -->
<div :class="`article-${wfc.class}`">...</div>

<!-- using a prop with string concatenation -->
<div :class="'article-' + wfc.class">...</div>

<!-- using a prop inside a filter -->
<div data-placeholder="filters.trans(wfc.placeholder)">...</div>

Default prop values

You can define a default value for a prop:

Example:

html
<script wfc-defaults>
  const role = "paragraph";
  const allow = "+-";
</script>
<p :wf-role="wfc.role" :wf-allow="wfc.allow" />

If a wf-component is using a prop that doesn't have a default value and you're not passing that prop when using the wf-component, Vue will log a warning in the console:

[Vue warn]: Missing required prop: "allow" 
  at ...

Non-string props

If you define a default prop value with a Number or Boolean type, you must prefix it with a semicolon (:) when using the prop:

Example:

html
<!-- in src/App/Bundle/CmsBundle/Resources/views/Template/wfc/module.html.twig -->
<script wfc-defaults>
// Note: `new` is a reserved keyword in JS, `wf-*` is used internally by XalokNext, 
//this is why we're using `nbNew` here
const nbNew = 1; 

const hasEpigraph = false;
</script>
<div wf-role="article" :wf-new="wfc.nbNew">
  <div v-if="wfc.hasEpigraph">
    <p wf-role="epigraph"></p>
  </div>
</div>

<!-- in src/App/Bundle/CmsBundle/Resources/views/Template/Article/default.html.twig -->
<!-- Note: passing the value prefixing the property name with a `:` -->
<!-- `:nb-new', instead of `nb-new` -->
<!-- `:has-epigraph', instead of `has-epigraph` -->
<wfc-module :nb-new="2" :has-epigraph="true"></wfc-module>

Otherwise Vue will generate a warning

[Vue warn]: Invalid prop: type check failed for nb-new="2". Expected Number, got String.

or

[Vue warn]: Invalid prop: type check failed for has-epigraph="true". Expected Boolean, got String.

Dynamic wf-directives values

Passed props can be used to pass values to wf-directives. For this, prefix the name of the wf-directive with a semicolon (:):

Example:

html
<p :wf-role="wfc.role"></p>

Supported wf-directives

The code of each wf-directive must be tweaked to accept dynamic values. At the moment, only the following wf-directives accept dynamic values:

Adding support to a wf-directive

First, the wf-directive code should be changed to use the isExpression binding key and treat its value accordingly, if necessary.

Then support for evaluating its value at runtime must be added in this file .

Custom setup script

You can include a custom setup script inside a wf-component:

html
<script setup>
    const getBorderWidth = (settings) =>
        settings.type === 'thick' ? 5 : 1;
</script>
<div
    wf-role="test"
    wf-new
    :style="`border: ${getBorderWidth(settings)}px solid red;`">
    <wf-setting name="type">
        <title>Border width</title>
        <option value="">Thin</option>
        <option value="thick">Thick</option>
    </wf-setting>
    test module
</div>

Note: Module's data is not available inside the custom <script setup>, this is why it's being passed from the wf-component's template.

IDE support

PhpStorm

wfcms4-loader emits a web-types.json file in web/dist/web-types.json. To use it in your project, edit package.json of the project and add:

json
{
  ...
  "web-types": "./web/dist/web-types.json",
  ...
}

This will offer autocompletion for the wf-components, both for the names of the components and for the attributes (props). It also warns you if the wf-component has missing props.

At the moment "Go to declaration" doesn't work - there's a github issue filed for it.

VSCode

wfcms4-loader emits a html.html-data.json file in web/dist/html.html-data.json. To use it, edit (or create) .vscode/settings.json and add:

json
{
  ...
  "html.customData": ["./web/dist/html.html-data.json"],
  ...
}

This will offer autocompletion for the wf-components, both for the names of the components and for the attributes (props). Unlike PhpStorm, VSCode doesn't warn if there are missing required props and, it also doesn't seem to support "Go to definition" for custom HTML tags.