如何将基于SkinTemplate的skin/Migrating迁移到SkinMustache

在2020年的1.35版本中,MediaWiki框架中增加了 SkinMustache 类。 这个类使我们能够直接使用 Mustache 模板来制作皮肤。 Mustache模板能让皮肤开发者们不用或者少用到PHP,相比之前使用php模板来说便利了许多。 这个教程能帮助你将php SkinTemplate转到SkinMustache。

步骤1:皮肤注册

以前,一套皮肤需要先声明一个Skin的php类,并且继承SkinTemplate。 另外,还需要再定义几个固定的属性来描述皮肤。

1.35版本之后,这几个固定的属性可以直接在皮肤注册的时候也一起定义了。

新的皮肤注册支持更多能力
文件 SkinTemplate - 老的注册流程 SkinTemplate - 新的注册流程
skin.json
{
    "ValidSkinNames": {
        "monobook": "MonoBook"
    }
}
{
    "ValidSkinNames": {
        "monobook": {
			"class": "SkinMonoBook",
			"args": [
				{
					"scripts": [
					    "skins.monobook.scripts"
					],
					"styles": [
					    "skins.monobook.styles"
					],
					"name": "monobook",
					"template": "MonoBookTemplate"
				}
			]
        }
    }
}
SkinMonobook.php
class SkinMonoBook extends SkinTemplate {
	/** 使用上面定义的名字:MonoBook */
	public $skinname = 'monobook';
	public $stylename = 'MonoBook';
	public $template = 'MonoBookTemplate';

	public function initPage( OutputPage $out ) {
	    parent::initPage( $out );
	    $out->addModules( 'skins.monobook.scripts' );
		$out->addModuleStyles( 'skins.monobook.styles' );
	}
}
class SkinMonoBook extends SkinTemplate {
}

以前定义一套皮肤需要2个php的类。 现在最多只需要1个类即可。

甚至,你可以直接设置class的值为SkinTemplate,这个将直接使用框架的默认的SkinTemplate类。

{
    "ValidSkinNames": {
        "monobook": {
			"class": "SkinTemplate",
			"args": [
				{
					"name": "monobook",
					"template": "MonoBookTemplate"
				}
			]
        }
    }
}

另外,如果你的皮肤包含hooks,建议将hook单独写到一个hooks文件中。

可以看下这个例子: gerrit:701142

Step 2: Unlock the power of templating

To follow this next step, it's assumed that a little knowledge of the Mustache template language has been acquired. At minimum, you should know the following Mustache basics described in the example below:

{{ str }} <!-- renders escaped string -->
{{{ html-str }}} <!-- renders RAW HTML -->

<!-- accesses data inside an associative array of the form { 'key' => 'hello' } -->
{{#array-data}} 
{{ key }} <!-- renders hello -->
{{/array-data}}

<!-- for boolean values -->
{{#is-talk-page}}
<!-- conditional rendering if is-talk-page is true -->
{{/is-talk-page}}

Hello World

Create a Mustache file with the relative path templates/skin.mustache

<div>
    <h1>My first Mustache skin</h1>
    <p><strong>Hello world!</strong></p>
</div>

To use the SkinMustache class, declare the class property as SkinMustache.

{
    "ValidSkinNames": {
        "monobook": {
			"class": "SkinMustache",
			"args": [
				{
					"name": "monobook"
				}
			]
        }
    }
}

Load the skin in your browser and you should see the contents of your Mustache file.

In MediaWiki versions prior to 1.37 you should also define templateDirectory and template:
{
    "ValidSkinNames": {
        "monobook": {
			"class": "SkinMustache",
			"args": [
				{
					"name": "monobook",
					"templateDirectory": "skins/Monobook/templates/",
                    "template": "skin"
				}
			]
        }
    }
}

Using a custom class

To aid migration, you'll want to use your own custom class that extends SkinMustache, at least as an interim step and start redirecting the code from your skin template into templates.

Create a new skin class that extends SkinMustache, for example for the MonoBook skin this might be named SkinMonoBook.

When using a PHP based template e.g. MonoBookTemplate, the skin developer is expected to implement an execute function that echoes HTML.

<?php
class MonoBookTemplate extends BaseTemplate {
    public function execute() {
        $html = $this->get( 'headelement' );
        $html .= $this->get( 'bodytext' );
        $html .= $this->getTrail();
		$html .= Html::closeElement( 'body' );
		$html .= Html::closeElement( 'html' );
        echo $html;
    }
}

In SkinMustache, the approach is different. We provide an associative array of template data that is passed to a Mustache template.

In the example that follows, we set new data to pass to the Mustache template

<?php
class SkinMonoBook extends SkinMustache {
    public function getTemplateData() {
        $data = parent::getTemplateData();
        $data['html-hello'] = '<strong>HELLO WORLD</strong>';
        return $data;
    }
}

With this change we can make the following change to our template to output the data.

Template which doesn't use template data Template using newly added template data
<div>
    <h1>My first Mustache skin</h1>
    <p><strong>Hello world!</strong></p>
</div>
<div>
    <h1>My first Mustache skin</h1>
    <p>{{{ html-hello }}}</p>
</div>

Using this technique we can bridge our old skin with SkinMustache.

First we may need to refactor our execute method to separate out a function that returns a string which represents the body of the skin. It's important to note in SkinMustache, you are restricted to templating the content of the body tag.

Original BaseTemplate BaseTemplate for usage in SkinMustache
<?php
class MonoBookTemplate extends BaseTemplate {
    public function execute() {
        $html = $this->get( 'headelement' );
        $html .= $this->get( 'bodytext' );
        $html .= $this->getTrail();
		$html .= Html::closeElement( 'body' );
		$html .= Html::closeElement( 'html' );
        echo $html;
    }
}
<?php
class MonoBookTemplate extends BaseTemplate {
    public function getHTML() {
        return $this->get( 'bodytext' );
    }
    public function execute() {
       /* This function needs to be present to implement BaseTemplate
         but is unused since we are using SkinMustache.
         The getTrail, headelement and closing body tags
         will be handled by SkinMustache.
       */
    }
}

In SkinMustache with this refactor done, we can extend the data we pass to a template as demonstrated with the html-hello example above. We can add a new template variable html-quick-template-migration which can be rendered inside our skin.mustache as raw HTML.

<?php
class SkinMonoBook extends SkinMustache {
    public function getTemplateData() {
        $data = parent::getTemplateData();
        $tpl = $this->prepareQuickTemplate();
		return $data + [ 'html-quick-template-migration' => $tpl->getHTML() ];
    }
}

Step 3: Embrace data

At this point you should have a skin implementing SkinMustache which is leveraging BaseTemplate to generate HTML. The PHP inside your PHP template that implements BaseTemplate should be possible with Mustache.

SkinMustache skins are data driven, which we believe gives better flexibility in how you want to render the data. The data is informed by skins in the wild. This data is documented elsewhere at Manual:SkinMustache.php#Template data.

There is little guidance on this step, other than explore how you can generate HTML code identical to your code in BaseTemplate using only the data and templates. Challenges you face while doing this should be raised on the talk page to help others.

Step 4: Fill out the missing data

During step 3 you may find HTML you are trying to generate cannot be rendered using the data inside SkinMustache. Hopefully from step 2 however you've realized you are not limited to the data you pass to your template.

However, it's useful for us to know about data that was missing, as it's likely your skin is doing something unique that may benefit other skins.

Simply by your code existing, you've communicated bits of data that would be helpful but you are invited to explicitly file Phabricator ticket requesting the data you need.