@abcnews/mount-utils 中文文档教程
@abcnews/mount-utils
用于处理挂载点的实用程序。
Usage
npm i @abcnews/mount-utils
<body>
<div id="abc" data-mount />
<a id="abc123" />
<a name="def" />
<a id="ghi" href="https://www.abc.net.au/news/">
A link that is also a #ghi deep link
</a>
<p>Not a mount</p>
</body>
import {
isMount,
getMountValue,
selectMounts
} from '@abcnews/mount-utils';
selectMounts();
// > [<div id="abc" data-mount>, <div id="abc123" data-mount>, <div id="def" data-mount>]
selectMounts('abc', {exact: true})
// > [<div id="abc" data-mount>]
selectMounts('abc')
// > [<div id="abc" data-mount>, <div id="abc123" data-mount>]
[...document.body.children].filter(isMount).map(getMountValue);
// > ['abc', 'abc123, 'def']
History
Core Media 允许通过编写仅包含 #< /code> 字符后跟一个或多个字母数字字符:
I'm a paragraph before the deep link。
example
我是深层链接之后的一段。
在阶段 1 / 阶段 2 应用程序中,深层链接呈现为 元素,其
name
属性设置为深层链接的值,例如:<一个名字=“例子”/>
。 我们最初的挂载点策略是用类似这样的方式选择这些元素:(value) => document.querySelector(`a[name^="${value}"]`)
,将它们替换成块元素(通常是),然后追加交互内容到他们。
最初引入表示层时,新闻网络团队已经认识到 是无效的 HTML5,因为深度链接应该由
id属性。 因此,PL 应用程序以符合 HTML5 规范的形式首次亮相:
。
这让事情稍微复杂了一些,因为虽然我们可以简单地将我们的选择器扩展到 `a[name^="${value}"],a[id^="${value}"]`
并涵盖这两种变体,如果我们希望它们有机会在 PL 上运行,我们必须更新许多旧的交互/库以支持新模式。 用
替换这些元素来挂载交互也会更危险,因为 PL 的 React DOM 树可以在任何时候重写,恢复我们的修改(或更糟:引入页面崩溃错误)。2020 年初开始努力支持将挂载点作为 PL 中的一等公民,并改变了 PL 在新闻 Web 应用程序上呈现深度链接的方式,使我们的使用更加安全。 它们现在是
元素,保证不会还原附加到它们的任何 DOM 结构。 它们现在看起来像这样:
。当然,这会使我们的挂载点选择进一步复杂化,这是该库试图解决的主要问题(通过将实现细节隐藏在 API 后面),但它将为我们提供更多的功能和一致性,继续前进。
API
selectMounts(selector?: string, options?: SelectMountsOptions): Mount[]
返回与选择器匹配的挂载节点数组,并且可能已根据提供的选项进行了转换。
type SelectMountsOptions{
exact?: boolean; // default false
includeOwnUsed?: boolean; // default false
markAsUsed?: boolean; // default true
convertToBlock?: boolean; // default true
}
任何已标记为已被该库的另一个实例使用的挂载点将始终从结果中排除。
下面的示例假设它们是独立运行的。 实际上,除非 markAsUsed
选项为 false
和/或 includeOwnUsed
选项为 true
,否则相同的安装将不会在后续调用中返回。
<body>
<a name="abc" />
<a id="abcdef" />
<div id="ghi" data-mount />
<div id="abc123" data-mount />
<div id="other" data-mount data-mount-used="some-uuid" />
</body>
import { selectMounts } from '@abcnews/mount-utils';
selectMounts();
// > [<div id="abc">, <a id="abcdef">, <div id="ghi">, <div id="abc123">]
selectMounts('abc');
// > [<div id="abc">, <div id="abcdef">, <div id="abc123">]
selectMounts('abc', { convertToBlock: false });
// > [<a name="abc">, <a id="abcdef">, <div id="abc123">]
selectMounts('abc', { exact: true });
// > [<div id="abc">]
isMount(x: unknown, selector?: string, exact?: boolean): boolean
返回传入的参数是否为挂载点。
即使标记为已使用,也会为有效的挂载点返回 true
,包括由该库的另一个实例标记的挂载点。
<body>
<div id="abc" data-mount />
</body>
import { isMount } from '@abcnews/mount-utils';
isMount(123);
// > false
isMount(document.body);
// > false
isMount(document.body.firstElementChild);
// > true
它首先检查参数是否为 Element
,然后检查它是否具有与有效挂载点匹配的属性。
Use with a selector value
返回传入的第一个参数是否是挂载点并且是否匹配给定的选择器值(默认情况下作为前缀,也可以通过传递 true
作为第三个参数来作为精确匹配).
<body>
<p>Nope</p>
<a name="abc" />
<a id="abc123" />
<div id="abc456" data-mount />
<div id="def" data-mount />
</body>
[...document.body.children].map(el => isMount(el));
// > [false, true, true, true, true]
[...document.body.children].map(el => isMount(el, 'abc'));
// > [false, true, true, true, false]
[...document.body.children].map(el => isMount(el, 'abc', true));
// > [false, true, false, false, false]
getMountValue(el: Element, prefix?: string): string
返回挂载点上适用属性的值,并可选择修剪提供的前缀。
<body>
<a name="abc" />
<a id="abcdef" />
<div id="ghi" data-mount />
<div id="abc123" data-mount />
</body>
import { getMountValue } from '@abcnews/mount-utils';
[...document.body.children].map(el => getMountValue(el));
// > ['abc', 'abcdef, 'ghi', 'abc123']
[...document.body.children].map(el => getMountValue(el, 'abc'));
// > ['', 'def, 'ghi', '123']
ensureBlockMount(el: Element): Element
确保我们有一个块安装(匹配 div[id][data-mount]
)来使用(通常是为了附加内容)。
DOM Before
<body>
<a name="abc" />
<a id="def" />
<div id="ghi" data-mount />
</body>
import { ensureBlockMount } from '@abcnews/mount-utils';
[...document.body.children].map(el => ensureBlockMount(el));
// > [<div id="abc" data-mount>, <div id="def" data-mount>, <div id="ghi" data-mount>]
DOM After
<body>
<div id="abc" data-mount />
<div id="def" data-mount />
<div id="ghi" data-mount />
</body>
如果传入的挂载是块挂载,则按原样返回。
如果传入的挂载是内联挂载(匹配 a[id]
或 a[name]
),它在 DOM 中被新创建的块挂载(保留原始值),然后返回。
注意:如果传递的内联挂载没有父元素(以便于替换),则会引发错误。
import { ensureBlockMount } from '@abcnews/mount-utils';
let el = document.createElement('a');
el.name = 'abc';
el = ensureBlockMount(el);
// > (error: Inline mount has no parent element)
将 ensureBlockMount
视为规范化函数,它将使您能够定位(和设置样式)所有挂载点,就好像它们是表示层的一流挂载点一样。 有时以这种方式使用它们更容易,而不必在整个代码库中考虑三种不同的形式。
useMount(mount: Mount)
将坐骑标记为已使用。 这应该谨慎使用,因为 selectMounts
默认情况下会自动执行此操作。
如果挂载具有 data-mount-used="
属性集,则认为它已被使用。 UUID 对于这个库的实例是唯一的。
<body>
<div id="abc" data-mount />
</body>
const els = selectMounts('abc', { markAsUsed: false });
// > [<div id="abc" data-mount>]
useMount(els[0]);
// > <div id="abc" data-mount data-mount-used="<uuid>">
isUsed(mount: Mount)
检查挂载点是否已标记为已使用。
<body>
<div id="abc" data-mount />
</body>
selectMounts('abc').map(mnt => isUsed(mnt));
// > [true]
selectMounts('abc', { markAsUsed: false }).map(mnt => isUsed(mnt));
// > [false]
Authors
- Colin Gourlay (Gourlay.Colin@abc.net.au)
- Simon Elvery (Elvery.Simon@abc.net.au)
@abcnews/mount-utils
Utilities for working with mount points.
Usage
npm i @abcnews/mount-utils
<body>
<div id="abc" data-mount />
<a id="abc123" />
<a name="def" />
<a id="ghi" href="https://www.abc.net.au/news/">
A link that is also a #ghi deep link
</a>
<p>Not a mount</p>
</body>
import {
isMount,
getMountValue,
selectMounts
} from '@abcnews/mount-utils';
selectMounts();
// > [<div id="abc" data-mount>, <div id="abc123" data-mount>, <div id="def" data-mount>]
selectMounts('abc', {exact: true})
// > [<div id="abc" data-mount>]
selectMounts('abc')
// > [<div id="abc" data-mount>, <div id="abc123" data-mount>]
[...document.body.children].filter(isMount).map(getMountValue);
// > ['abc', 'abc123, 'def']
History
Core Media allows the creation of deep links in Article documents' rich text by writing a line containing only a #
character followed by one or more alphanumeric characters:
I'm a paragraph before the deep link.
example
I'm a paragraph after the deep link.
In Phase 1 / Phase 2 applications, deep links are rendered as <a>
elements with their name
attribute set to the deep link's value, e.g: <a name="example" />
. Our original strategy with mount points was to select these elements with something like: (value) => document.querySelector(`a[name^="${value}"]`)
, replace them with block elements (usually <div>
s), then append interactive content to them.
When Presentation Layer was originally introduced, the News Web team had recognised that <a name="example" />
is invalid HTML5, as deep links should be defined by the id
attribute. Because of this, PL applications debuted with the HTML5-spec-pleasing form: <a id="example" />
.
This made things slightly more complicated, because although we could simply extend our selectors to `a[name^="${value}"],a[id^="${value}"]`
and cover both variants, we'd have to update many older interactives/libraries to support the new pattern if we wanted them to have a chance of running atop PL. It would also be more dangerous to replace these elements with <div>
s to mount interactives to, because PL's React DOM tree could be re-written at any point, reverting our modifications (or worse: introducing page-crashing bugs).
An effort to support mount points as a first-class citizen in PL began in early 2020, and has resulted in changes in how PL renders deep links on the News Web application, making them safer for our use. They are now <div>
elements that are guaranteed to not revert any DOM structure appended to them. They now look like this: <div id="example" data-mount />
.
Of course, this complicates our mount point selection further, which is the main thing this library tries to address (by hiding the implementation details behind an API), but it'll give us more power and consistency, going forward.
API
selectMounts(selector?: string, options?: SelectMountsOptions): Mount[]
Returns an array of mount nodes that match the selector and may have been transformed as per the supplied options.
type SelectMountsOptions{
exact?: boolean; // default false
includeOwnUsed?: boolean; // default false
markAsUsed?: boolean; // default true
convertToBlock?: boolean; // default true
}
Any mount points that have been marked as used by another instance of this library will always be excluded from the results.
The examples below assume that they're run in isolation. In reality, unless the markAsUsed
option is false
and/or the includeOwnUsed
option is true
, the same mounts would not be returned on a subsequent call.
<body>
<a name="abc" />
<a id="abcdef" />
<div id="ghi" data-mount />
<div id="abc123" data-mount />
<div id="other" data-mount data-mount-used="some-uuid" />
</body>
import { selectMounts } from '@abcnews/mount-utils';
selectMounts();
// > [<div id="abc">, <a id="abcdef">, <div id="ghi">, <div id="abc123">]
selectMounts('abc');
// > [<div id="abc">, <div id="abcdef">, <div id="abc123">]
selectMounts('abc', { convertToBlock: false });
// > [<a name="abc">, <a id="abcdef">, <div id="abc123">]
selectMounts('abc', { exact: true });
// > [<div id="abc">]
isMount(x: unknown, selector?: string, exact?: boolean): boolean
Returns whether the argument passed in is a mount point.
Will return true
for valid mount points even if marked as used, including those marked by another instance of this library.
<body>
<div id="abc" data-mount />
</body>
import { isMount } from '@abcnews/mount-utils';
isMount(123);
// > false
isMount(document.body);
// > false
isMount(document.body.firstElementChild);
// > true
It first checks that the argument is an Element
, then checks that it has attributes matching a valid mount point.
Use with a selector value
Returns whether the first argument passed in is a mount point and matches a given selector value (as a prefix by default and optionally as an exact match by passing true
as the third argument).
<body>
<p>Nope</p>
<a name="abc" />
<a id="abc123" />
<div id="abc456" data-mount />
<div id="def" data-mount />
</body>
[...document.body.children].map(el => isMount(el));
// > [false, true, true, true, true]
[...document.body.children].map(el => isMount(el, 'abc'));
// > [false, true, true, true, false]
[...document.body.children].map(el => isMount(el, 'abc', true));
// > [false, true, false, false, false]
getMountValue(el: Element, prefix?: string): string
Returns the value of the applicable attribute on a mount point and optionally trims a supplied prefix.
<body>
<a name="abc" />
<a id="abcdef" />
<div id="ghi" data-mount />
<div id="abc123" data-mount />
</body>
import { getMountValue } from '@abcnews/mount-utils';
[...document.body.children].map(el => getMountValue(el));
// > ['abc', 'abcdef, 'ghi', 'abc123']
[...document.body.children].map(el => getMountValue(el, 'abc'));
// > ['', 'def, 'ghi', '123']
ensureBlockMount(el: Element): Element
Ensures that we have a block mount (matching div[id][data-mount]
) to work with (usually in order to append content).
DOM Before
<body>
<a name="abc" />
<a id="def" />
<div id="ghi" data-mount />
</body>
import { ensureBlockMount } from '@abcnews/mount-utils';
[...document.body.children].map(el => ensureBlockMount(el));
// > [<div id="abc" data-mount>, <div id="def" data-mount>, <div id="ghi" data-mount>]
DOM After
<body>
<div id="abc" data-mount />
<div id="def" data-mount />
<div id="ghi" data-mount />
</body>
If the mount passed in is a block mount, it is returned as-is.
If the mount passed in is an inline mount (matching a[id]
or a[name]
), it is replaced in the DOM by a newly created block mount (retaining the original value), which is then returned.
Note: If an inline mount is passed in which doesn't have a parent element (to facilitate replacement), an error is thrown.
import { ensureBlockMount } from '@abcnews/mount-utils';
let el = document.createElement('a');
el.name = 'abc';
el = ensureBlockMount(el);
// > (error: Inline mount has no parent element)
Think of ensureBlockMount
as a normalisation function that will enable you to target (and style) all mount points as if they were Presentation Layer's first-class mount points. It's sometimes easier to work with them in this way, rather than having to consider three different forms throughout your codebase.
useMount(mount: Mount)
Mark a mount as used. This should be used sparingly since selectMounts
will do this automatically by default.
A mount is considered used if it has a data-mount-used="<some uuid>"
attribute set. The UUID is unique to the instance of this library.
<body>
<div id="abc" data-mount />
</body>
const els = selectMounts('abc', { markAsUsed: false });
// > [<div id="abc" data-mount>]
useMount(els[0]);
// > <div id="abc" data-mount data-mount-used="<uuid>">
isUsed(mount: Mount)
Check if a mount point has been marked as used.
<body>
<div id="abc" data-mount />
</body>
selectMounts('abc').map(mnt => isUsed(mnt));
// > [true]
selectMounts('abc', { markAsUsed: false }).map(mnt => isUsed(mnt));
// > [false]
Authors
- Colin Gourlay (Gourlay.Colin@abc.net.au)
- Simon Elvery (Elvery.Simon@abc.net.au)