XHP v4: namespaces and updated syntax
Today we are releasing significant changes to XHP - in Hack, HHVM, and in the class/function library; XHP now fully supports namespaces - both using XHP from other namespaces, and defining XHP components in namespaces.
XHP v4 includes several changes to:
- directly support namespaces.
- remove features that would introduce parser ambiguities when combined with namespace support.
- remove features that surprise users and can be expressed in other, more consistent ways.
- make a clear distinction between ‘core’ and HTML functionality, to enable broader use of XHP in new contexts.
- be consistent with other modern Hack libraries, especially the HSL.
We expect future development of XHP to be focused on making XHP more consistent with the rest of Hack, especially from a type system perspective.
Overview
- XHP classes are now declared as
xhp class foo {}
instead ofclass :foo
- in XHP contexts, the
:
token acts as a namespace separator;foo:bar
is equivalent tofoo\bar
(andnamespace\foo\bar
), and:foo:bar
is equivalent to\foo\bar
. - children declarations have been replaced with a trait and are no longer a language feature
- categories have been replaced with interfaces
- the XHP class and function library is now namespaced
renderAsync()
replacesasyncRender()
andrender()
XHP v3 Example
1
2
3
4
5
6
7
8
class :foo:bar extends :x:element {
category %flow;
children (:div, :code*);
protected function render(): void {
return <x:frag>{$this->getChildren());
}
}
XHP v4 Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
namespace foo;
use namespace Facebook\XHP\Core as x;
use namespace Facebook\XHP\ChildValidation as XHPChild;
use type Facebook\XHP\HTML\{div, code};
xhp class bar extends x\element
implements \Facebook\XHP\HTML\Category\Flow {
use XHPChild\Validation;
protected static function getChildrenDeclaration(): XHPChild\Constraint {
return XHPChild\any_of(
XHPChild\of_type<div>(),
XHPChild\at_least_one_of(XHPChild\of_type<code>()),
);
}
protected async function renderAsync(): Awaitable<XHPRoot> {
return <x:frag>{$this->getChildren()}</x:frag>;
// alternatively:
return
<:Facebook:XHP:Core:frag>
{$this->getChildren()}
</:Facebook:XHP:Core:frag>;
}
}
Migration
Automated migrations for all language changes, and for most classes and functions renamed in xhp-lib v4, are available in HHAST 4.64.6 or newer (some also available in older HHAST versions).
hhast-migrate --add-xhp-children-declaration-method
adds theChildValidation
trait andgetChildrenDeclaration()
method based on existingchildren
declarations.hhast-migrate --remove-xhp-child-declarations
(run after the above migration) will remove the now redundantchildren
declarations.hhast-migrate --demangle-xhp-class-names
replaces-
in XHP class names with_
hhast-migrate --xhp-class-modifier
migratesclass :foo:bar
toxhp class foo:bar
hhast-migrate --xhp-lib-v3-to-v4
migrates most class and function names changed in xhp-lib v4, adds the necessaryuse
clauses (this includes all HTML tags)
In rare cases, these migrations can result in typechecker errors (e.g. naming
conflicts due to the removal of XHP class name mangling) or hhast-lint errors
(e.g. redundant use
clauses) which need to be fixed manually.
Additional changes that may require manual migration:
- Consider moving any XHP class declarations from the global namespace into namespaces consistent with the rest of your codebase.
- Optionally change
xhp class foo:bar:baz { ... }
to the equivalentnamespace foo\bar; xhp class baz { ... }
based on your preferred style. - Replace
category
declarations with interfaces (new or existing; common ones can be found in theFacebook\XHP\HTML\Category
namespace). - Calls to
$xhp->toString()
need to be updated to$xhp->toStringAsync()
. - Calls to
$xhp->getChildren(...)
,$xhp->getFirstChild(...)
and other methods that had their argument removed need to be updated to$xhp->getChildrenOfType<...>()
,$xhp->getFirstChildOfType<...>()
, etc. - Most deprecated features (e.g. attribute clobbering,
forceAttribute
) are not handled by the automated migrations, we recommend replacing them with equivalent or similar non-deprecated features.
Details
Language changes
A more formal description of these changes is also available.
Syntax
Previously, XHP classes were declared with a leading colon, e.g.
class :foo:bar {}
XHP classes are now declared with the following syntax:
namespace foo;
xhp class bar {}
This new syntax avoids ambiguities that would otherwise be introduced by the addition of namespace support.
xhp class foo:bar {}
is also permitted - it is intended to be used:
- by existing codebases, as a migration aid
- as a stylistic preference by codebases that choose to minimize use of namespaces
Naming
Previously, XHP class names were ‘mangled’ into a form that meets the usual restrictions for Hack class names; for example, class :foo:bar-baz {}
actually defined the class xhp_foo__bar_baz
.
XHP class names are no longer mangled: xhp class foo
defines the class foo
, and xhp class foo:bar
defines the class foo\bar
; this may cause problems if you are storing or otherwise interacting with classname<T>
of XHP classes, or storing serialized instances of XHP classes.
This also means that XHP class names must now be valid Hack XHP class names; the most significant effect is that the -
character is no longer permitted in XHP class names.
Colons in XHP class types
Colons are now a namespace separator (largely equivalent to \
), but are only permitted in some contexts:
- in XHP-specific language constructs, such as
xhp class foo:bar
,<foo:bar>
- when fully-qualified, in other contexts where qualified names are permitted - for example,
function foo(:bar:baz $_)
is permitted, butfunction foo(bar:baz $_)
is not permitted
\
is not permitted in XHP constructor calls (e.g. <foo\bar>
is an error), or XHP class declaration names (e.g. xhp class foo\bar
). It is permitted in parent class references, e.g. xhp class foo extends bar\baz {}
.
Like \
, :
can be used to indicate that a name is fully qualified; for example, foo\bar
and foo:bar
both refer to namespace\foo\bar
(unless there is a relevant use
statement), and \foo\bar
and :foo:bar
both always refer to \foo\bar
. Similarly, <foo:bar />
is a reference to namespace\foo\bar
, and <:foo:bar />
is now permitted as a reference to \foo\bar
.
There are several situations where both \
and :
are permitted; we recommend that codebases decide on a consistent style; for example, code bases may wish to ban:
- using
:
for anything that is not an XHP class - using
\
in XHP-specific contexts - declaring elements in sub-namespaces (e.g. permit
namespace foo { xhp class bar {} }
, but banxhp class foo:bar {}
)
Category Declarations
The category %foo, %bar
syntax is no longer supported - we recommend using interfaces instead.
They were largely equivalent to interfaces, but without typechecker support; this both increased complexity, and users reasonably assume there was typechecker support.
Child Declarations
The children (...)
syntax is no longer supported; XHP-Lib v4 supports a variant of the approach introduced in v3.1, however functions have been renamed to lower_camel_case
for consistency with other common Hack libraries.
Core APIs
There is now a clean separation between core APIs and HTML-specific APIs; we hope this will lead to more diverse uses of XHP in the future.
Core XHP classes and functions are now in the Facebook\XHP\Core
namespace; child validation has been moved to Facebook\XHP\ChildValidation
, and is no longer considered a core feature.
This also includes core elements and base classes; in particular:
:x:composable-element
is nowFacebook\XHP\Core\node
:x:element
is nowFacebook\XHP\Core\element
, and:x:frag
is nowFacebook\XHP\Core\frag
HTML Elements
These are now all in the Facebook\XHP\HTML
namespace; they can be used:
- fully-qualified, e.g.
<:facebook:xhp:html:div>
- with a
use type
, e.g.use type Facebook\XHP\HTML\{code, div};
$_ = <div />
- with a
use namespace
, e.g.use namespace Facebook\XHP\HTML as h;
$_ = <h:div />
Additionally:
- the categories have been updated to match the specification; in some cases, this has meant removing categories, which may mean markup must be restructured to be valid
- the categories are now interfaces in the
Facebook\XHP\HTML\Category
namespace
Other changes
HasXHPAttributeClobbering_DEPRECATED
has been removed; use attribute splat insteadUnsafeAttributeValue
has been renamed toUnsafeAttributeValue_DEPRECATED
as it bypassing the type system by design, and will need to be removed in a future releaseforceAttribute()
has been renamed toforceAttribute_DEPRECATED()
as it bypassing the type system by design, and will need to be removed in a future release- it is now an error to render an element twice, or to mutate it after it has been rendered
asyncRender()
has been renamed torenderAsync()
, for consistency with other popular Hack projects and the HSLrender()
has been removed; always userenderAsync()
instead.- the
XHPRoot
interface and:xhp
classes have been removed as unnecessary; useFacebook\XHP\Core\Node
orXHPChild
instead. - removed selectors from
getFirstChild()
and related functions; addedgetFirstChildOfType<T as XHPChild>()
with improved typechecker support. An interface can be specified to replace previous category specifications.
Thanks
It has been a very long path to get here, and much more involved than we expected. I’d like to thank our friends at Slack - especially Johan Oskarsson - who made this possible, and also Lexidor Digital whose contributions to type safety are greatly appreciated.