lightsource/front-blocks

This package helps combine front-end resources (html, js, css) into reusable blocks (modules)

4.1.2 2023-08-11 09:30 UTC

README

What is it?

This package helps combine front-end resources (HTML, CSS, JS) into reusable blocks (modules).

1. Advantages
2. How to use
3. Block
4. Requirements
5. Examples of usage
6. Advanced
7. Extra loading

Advantages

  • Allows keep all resources grouped, that simplify editing and improving code reading
  • Supports dependencies between blocks, allows to use a block within another block, provides used block resources (CSS, JS) in the right order
  • Uses Twig as a template engine, friendly to scss and webpack (see Examples of usage)
  • Extra loading of the package is insignificant. See Extra loading for details
  • Blocks can be placed in different folders (e.g. WordPress parent and child theme)

How to use

  1. Install the composer package

composer require lightsource/front-blocks

  1. Create a FrontBlocks instance
use LightSource\FrontBlocks\FrontBlocks;
use LightSource\FrontBlocks\Settings;
use LightSource\FrontBlocks\ExternalDependencies;

$settings = new Settings();
// namespace & folder of your blocks
$settings->addBlocksFolder('MyNamespace', 'AbsPathToMyFolder');
// optionally, your psr/container implementation for extra dependencies, see more information about this feature below
$container = null;
// optionally, your PSR-3 compatible logger, will be used when a twig template has syntax errors
$logger               = null;

$frontBlocks = new FrontBlocks($settings, $container, $logger);   
  1. Create blocks
/Blocks
    Homepage
        Homepage.twig
        Homepage.css
        Homepage.php          

Homepage.php

// your block should extend the Block class
// (or implement \LightSource\FrontBlocks\Interfaces\BlockInterface, but only in case you need your own implementation)
class Homepage extends \LightSource\FrontBlocks\Block\Block
{
    protected string $title;

    public function loadById(int $id)
    {
        parent::load();

        // todo
        $this->title = 'Some title';
    }
}

Homepage.twig


<div class="homepage">{{ title }}</div>
  1. Render blocks, get used resources
// Block's creation with :
// a) automatic initialization of class's protected fields (for fields with built-in types or with the Block type)
// b) automatic passing external objects to block's constructors (where it needs)
$homepage = $frontBlocks->getCreator()->create(Homepage::class);
$homepage->loadById(1);

// rendering a target twig template with arguments (from the class's protected fields)
echo $frontBlocks->getRenderer()->render($homepage);

// reading & combining resources from used blocks by an extension (in the right order of rendering and with dependencies)
echo '<style>';
echo $frontBlocks->getRenderer()->getUsedResources('.css');
echo '</style>';

See Examples of usage to get more info.

Block

Block it's a PHP class and resources near the class file.

  • Static resources
    Files like : template (.twig), CSS (.css, .scss, .min.css), JS (js, min.js), images (.png) and such...

  • Block class - provides data for a twig template, manages dependencies

    • All protected fields will be used as arguments to a twig template.
    • Protected fields can be fields with built-in types (string, int, array...) and other Blocks, if these fields have declared types then they will be auto initialized with a default value (include Block fields, so it'll automatically create an instance of a declared class)
    • All protected fields with a Block type will be marker as dependencies of this block
    • Can have a parent-children relation (so each block can be extended)

Requirements

  • php 7.4+
  • Blocks should have a PSR-4 compatible namespace with an autoloader
  • Resource name should be the same as a Block name
  • Using the BEM methodology isn't required but highly recommended

Examples of usage

  1. Example 1 - without scss & webpack
  2. Example 2 - with scss & webpack

Advanced

1. Loader

Package has the loader class, it gets a list of block classes and calls static setup(?ContainerInterface $container) method for every instance. Very useful if you want to add some listeners for every block, or do another job. E.g. in WordPress you can setup ajax listeners for every block.

$frontBlocks->getLoader()->loadAllBlocks();

2. External dependencies (during auto-creation)

There is automatically creation of inner blocks, it means they should have a constructor without any arguments, but sometimes you may need extra dependencies, like logger or anything else. In such cases the external dependencies feature will help. The FrontBlocks class has the psr/ContainerInterface argument, you can provide any implementation here (e.g. php-di) and then during automatic creation the container will be asked for every constructor's argument, and his response will be automatically passed to the constructor (comparing by types, its possible thanks to PHP reflection opportunities, so order or names of dependencies are not important here).

Block

class MyBlock extends \LightSource\FrontBlocks\Block\Block
{
    private MyExtraClass $myExtraClass;

    // you can have any amount of dependencies, order is not important together with variable names
    // external dependencies are unique per block, so one block can have dependencies, others may not have
    public function __construct(MyExtraClass $variableWithAnyName)
    {
        parent::__construct();
        $this->myExtraClass = $variableWithAnyName;
    }
}

then pass your dependency to your container

$dependency = new MyExtraClass();
// in this sample the php-di\php-di package is being using
$container = new \DI\Container();
$container->set(MyExtraClass::class, $dependency);
$frontBlocks = new FrontBlocks($settings, $container);

3. Twig template

  • additional keys (_template, _isLoaded, _parentTemplate for each block are available (_isLoaded will be true after the load() method call in a related Block)
  • _merge filter (merging arrays recursively unlike of the standard merge)
  • _include function (blockArgs,additionalArgs) which uses the additional key for blocks include, so you can include blocks like it {{ _include(blockName,{classes:['block-name',]} ) and it'll locate a template by the '_template' field and will render only if '_isLoaded' is set
  • to extend a twig template (obviously php block class should extend a parent) - use the ordinary twig way with the pre-defined _parentTemplate variable, so {% extends _parentTemplate %}

4. Array of blocks

Fields with an array of blocks - are also supported, in the same way as the block fields, so getTemplateArgs() will be automatically called for every item during rendering, it means you can have for in a twig template with _include() for every item without extra steps from your side.

5. Tool for copy blocks

Tool for copy blocks (with names replacing) is available
E.g. in the Blocks folder the command
{pathToComposer}/vendor/bin/fbf copy Source/Source.php Target/Target.php
will copy the Block and all siblings files and will do name replacing, so you can create an example block and reproduce new blocks from it.

6. Polymorphism

PHP (7.4) doesn't support polymorphism in fields directly, so unfortunately you can't do something like it:

class Element {

}
class ElementThemeOrange extends Element {
 
}
class Wrapper {
 protected Element $element;
}
class WrapperThemeOrange extends Wrapper {
 protected ElementThemeOrange $element;
}

BUT the package supports a trick below:

class Element {

}
class ElementThemeOrange extends Element {
 
}
class Wrapper {
 protected Element $element;
}
class WrapperThemeOrange extends Wrapper {
 public function __construct() {
  parent::__construct();
  
  $this->element = new ElementThemeOrange();
 }
}

In this case when you use the creator ($wrapper = $frontBlocks->getCreator()->create(WrapperThemeOrange::class);) the package will pick up exactly the ElementThemeOrange class.
(Note: never rely on your initialization of inner blocks in a constructor, because this initialization will be used only to get a class name and then the field will be overridden with a new instance, that will be created using the creator, otherwise the External dependencies feature would be unavailable here. So neither set up any values nor call any methods in a constructor for inner blocks, it'll have no effect)

Extra loading

Extra loading exactly of the package is insignificant and more dependents of Twig. Below you can see rough values which were got on an instance with 4vCPU 16 RAM, SSD. End values will be others, depending on your instance and used blocks.

Auto-loading of 100 blocks (optional) : 9 milliseconds
Twig rendering of 100 blocks - 62 milliseconds
Combining resources of 100 blocks - 3 milliseconds