针对前端开发可重用组件并发布到NPM

翻译:疯狂的技术宅
原文: https://www.smashingmagazine....

本文首发微信公众号:jingchengyideng
欢迎关注,天天都给你推送新鲜的前端技术文章javascript


摘要:本文着眼于使用具备内置功能和样式的组件来扩充HTML。 咱们还将学习如何经过 NPM 使这些自定义元素在项目中获得重用。css


即使是最简单的组件,人力成本也可能很高。 UX 团队要进行可用性测试。 涉及到的利益相关者必须对设计签字确认。html

以后是开发人员进行 AB 测试,可访问性审计,单元测试和跨浏览器检查。 一旦解决了这个问题,你就不想再次重复这项工做了。 经过构建可重用的组件库(而不是从头开始构建全部内容),咱们就能够不断复用过去的工做,避免从新审视已经解决的设计和开发过程。前端

clipboard.png

构建组件库对于像Google这样的公司尤其重要,他们拥有不少具备相同品牌的网站。 经过把 UI 编码为可组合小部件,这些大公司既能够减小开发时间,又能够实现跨项目的可视化和用户交互设计的一致性。在过去几年中,人们对样式指南和模式库的兴趣不断增长。因为开发人员和设计师通常都分布在多个团队中,因此大公司须要寻求实现一致性的方法,好比提供简单的颜色样本。不过对于咱们来讲,能够比他们作得更好。 咱们须要的是易于分发的代码java

共享和重用代码

手动复制和粘贴代码很容易。可是把代码保持在最新版是维护上的噩梦。因此许多开发者依赖包管理器来跨项目重用代码。 尽管名字是 Node Package Manager, 可是它已成为前端包管理的独一无二的平台。 目前在 NPM 上注册的包超过700,000个,每个月下载数十亿次。 含有 package.json 文件的任何文件夹均可以做为可共享包上传到NPM。 虽然NPM主要与JavaScript相关联,但包中也能够包含 CSS 和标记。 NPM使重用变得很容易,这对更新代码尤其重要:你无需在各类地方修改代码,所作的是只需在包中更新代码便可。node

标记存在的问题

使用 import 语句能够对Sass和Javascript 进行轻松移植。 模板语言赋予了 HTML 相同的能力 —— 模板能以局部形式导入到 HTML 的其余片断。 好比你能够只需为页脚编写一次标记,而后将其包含在其余模板中便可。 另外一种方法是复制并粘贴标记,并只对样式和 javascript 使用NPM。git

这是英国“金融时报”在 Origami 组件库中用到的方法。Alice Bartlett在她的演讲中总结道:“你不能让它更像是 Bootstrap 吗?”,“并无什么好办法能让人们在他们的项目中包含模板”。
Ian Feather谈到他在 Lonely Planet 维护组件库的经历,重申了这种方法存在的问题:github

“一旦复制了这些代码,他们基本上就会削减一个须要无限期维护的版本。当复制工做组件的标记时,它具备到该点的CSS快照的隐式连接。 若是你随后更新模板或重构CSS,则须要更新分散在你网站周围的全部模板的版本。“

解决方案:WEB组件

Web组件经过在 JavaScript 中定义标记来解决这个问题。 组件的做者能够自由地修改标记、CSS 和 Javascript。 组件的使用者能够在这些升级中受益,无需手动修改项目代码。 只须要经过在终端的敲出简短的 npm update 命令,就能够在项目范围内更新到最新版本。固然前提是组件的名称及其 API 须要保持一致。web

安装Web组件就像在终端中键入 npm install component-name 同样简单。 Javascript 能够包含在 import 语句中:npm

<script type="module">
import './node_modules/component-name/index.js';
</script>

clipboard.png

在CodePen上的代码演示:https://codepen.io/cssgrid/pe...

在前端开发中,以组件为中心的方法已经变得无处不在,Facebook 的 React 框架就使用了这种方法。考虑到在现代前端开发工做中框架的广泛性,许多公司已经在用他们选择的框架构建了组件库。这些组件只能在该特定框架内重用。

clipboard.png

IBM Carbon Design System的一个组件。 仅能用于 React 应用。其余在 React 中构建的组件库的主要案例包括Atlassian 的 Atlaskit 和 Shopify 的 Polaris

对规模较大的公司来讲,不多有统一的前端,从一个框架转到另外一个框架的从新布局并不罕见。各类框架你方唱罢我登场。 为了在项目中实现最大程度的潜在重用,咱们须要与框架无关的组件。

经过在npmjs.com对组件的搜索结果揭示了一个支离破碎的Javascript生态系统

经过在npmjs.com对组件的搜索结果揭示了一个支离破碎的Javascript生态系统

随着时间的推移,框架也在不断变化

随着时间的推移,框架也在不断变化

“多年来我使用 Dojo、Mootools、Prototype、jQuery、Backbone、Thorax 和 React 构建了 Web 应用......我但愿能把我开发的 Dojo 组件用到如今的 React 应用中。“

—— 谷歌工程总监Dion Almaer

当咱们谈论Web组件时,咱们讨论的是自定义元素与 shadow DOM 的组合。 自定义元素和 shadow DOM 是W3C DOM 规范和 WHATWG DOM 标准的一部分 —— 这意味着 Web 组件是 Web 标准的一部分。自定义元素和 shadow DOM 最终会实现跨浏览器支持。 经过使用原生 Web 平台的标准部分,咱们确保本身的组件可以在前端重组和不断重构的快速变化周期中生存下来。 Web组件能够与任何一种模板语言和前端框架一块儿使用 —— 它们是真正交叉兼容和可互操做的。 从 Wordpress 博客到单页应用程序,能够在任何场合下使用。

Rob Dodson的Custom Elements Everywhere项目记录了 Web 组件与各类客户端 Javascript 框架的互操做性。 这里面 React 出现的异常值,但愿能在 React 17 中解决

Rob Dodson的 Custom Elements Everywhere 项目记录了 Web 组件与各类客户端 Javascript 框架的互操做性。 这里面 React 出现的异常值,但愿能在 React 17 中解决

制做Web组件

定义一个自定义元素

生成 made-up-tag 标记并使其内容显示在页面上。

<made-up-tag>Hello World!</made-up-tag>

HTML 被设计为可以容错。即便不是有效的HTML元素,它的内容也会被呈现。 并无一个很好的理由这样作 —— 偏离标准化标签在传统上是不好劲的作法。 可是经过用自定义元素 API 定义新的标记,咱们就能够用具备内置功能的可重用元素来扩充HTML。 建立自定义元素很像在 React 中建立一个组件 —— 但在这里是扩展了 HTMLElement

class ExpandableBox extends HTMLElement {
      constructor() {
        super()
      }
    }

构造函数中的第一个语句必须是对 super() 的无参数调用。构造函数应该用于设置初始状态和默认值,以及设置事件侦听器。 须要使用其 HTML 标记的名称和对应的类的元素定义新的自定义元素:

customElements.define('expandable-box', ExpandableBox)

把类名大写是一种惯例。 HTML 标记的语法不只仅是一种约定,若是浏览器想要实现一个新的HTML元素,而且想把它称为可扩展框怎么办?为了防止命名冲突,不是最新标准的 HTML 标记要包含破折号。 因此自定义元素的名称也 必须 包含破折号。

customElements.define('whatever', Whatever) // 无效
customElements.define('what-ever', Whatever) // 有效

定制元素生命周期

API 提供了四种自定义元素响应 —— 能够在类中定义函数,这些函数会自动调用来响应自定义元素生命周期中的某些事件。

当自定义元素添加到 DOM 时会执行 connectedCallback

connectedCallback() {
    console.log("custom element is on the page!")
}

这包括用 Javascript 添加元素:

document.body.appendChild(document.createElement("expandable-box")) // "custom element is on the page"

以及简单地在页面中包含带有 HTML 标记的元素:

<expandable-box></expandable-box> // "custom element is on the page"

全部涉及到获取资源或渲染的工做都应该在这里。

从 DOM 中删除自定义元素时,将运行 disconnectedCallback

disconnectedCallback() {
    console.log("element has been removed")
}
document.querySelector("expandable-box").remove() //"element has been removed"

当自定义元素被采用到新文档中时,将会运行 adoptedCallback 。 你可能不须要太关心这个问题。

在添加、更改或删除属性时运行 attributeChangedCallback 。 它能够用于监听标准化本机属性(如 disabledsrc )的更改,以及咱们定义的任何自定义属性。 这是自定义元素最强大的功能之一,由于它能够建立用户友好的 API。

定制元素属性

由于有不少 HTML 属性,因此当任何属性发生变化时,浏览器都不会浪费时间去调用咱们的 attributeChangedCallback ,所以须要提供一个咱们想要监听的属性更改列表。对于这个例子,咱们只对一个感兴趣。

static get observedAttributes() {
            return ['expanded']
        }

因此如今 attributeChangedCallback 只会在咱们更改自定义元素上expanded属性的值时被调用,由于它是咱们列出的惟一属性。

HTML 属性能够有相应的值(例如 href,src,alt,value 等),而其余值能够是true或false (例如 disabled, selected, required)。 对于具备相应值的属性,咱们将在自定义元素的类定义中包含如下内容。

get yourCustomAttributeName() {
  return this.getAttribute('yourCustomAttributeName');
}
set yourCustomAttributeName(newValue) {
  this.setAttribute('yourCustomAttributeName', newValue);
}

对于例子中的元素,其属性为 true 或 false,所以定义 getter 和 setter 会有所不一样。

get expanded() {
  return this.hasAttribute('expanded')
}
        
// setAttribute的第二个参数是必需的,因此咱们将用一个空字符串填充
set expanded(val) {
  if (val) {
    this.setAttribute('expanded', '');
  }
  else {
    this.removeAttribute('expanded')
  }
}

既然已经处理了样板文件,咱们可使用 attributeChangedCallback

attributeChangedCallback(name, oldval, newval) {
  console.log(`the ${name} attribute has changed from ${oldval} to ${newval}!!`);
  // 每次属性被更改时执行某些操做
}

配置 Javascript 组件会涉及将参数传递给 init 函数。 经过使用 attributeChangedCallback ,能够建立一个可使用标记配置的自定义元素。

Shadow DOM 和自定义元素能够单独使用,你能够找到对本身有用的自定义元素。 与 shadow DOM 不一样,它们能够是 Polyfill。 不过它们配合得很好。

使用SHADOW DOM附加标记和样式

到目前为止,咱们已经处理了自定义元素的行为。 可是关于标记和样式,咱们的自定义元素至关于空的无样式 <span> 。 要将HTML和CSS封装为组件的一部分,还须要附加一个shadow DOM。 最好在构造函数中执行此操做。

class FancyComponent extends HTMLElement {
        constructor() {
            super()
            var shadowRoot = this.attachShadow({mode: 'open'})
            shadowRoot.innerHTML = `<h2>hello world!</h2>`
            }

不要为理解模式的含义担忧——你必须包含它的样板,但你几乎老是想要 open。 这个简单的例子组件将只呈现文本“hello world”。 与大多数其余 HTML 元素同样,自定义元素能够包含子元素 —— 但默认状况下不是。 到目前为止,前面的自定义元素还不能将任何子元素渲染到屏幕上。 要显示标记之间的内容,还须要用到 slot 元素。

shadowRoot.innerHTML = `
<h2>hello world!</h2>
<slot></slot>
`

咱们能够用样式标记将 CSS 应用于组件。

shadowRoot.innerHTML = 
`<style>
p {
color: red;
}
</style>
<h2>hello world!</h2>
<slot>some default content</slot>`

这些样式仅适用于组件,所以咱们能够自由地使用元素选择器,而不会影响页面的任何其余内容。 这就把编写 CSS 的过程变得很是简单,使 BEM 这样的命名约定变得没必要要。

经过 NPM 发布组件

NPM 包经过命令行进行发布。 打开一个终端窗口并切换到你想要变成可重用包的目录中,而后在终端中键入如下命令:

  1. 若是你的项目尚未 package.json, npm init 将会引导你建立一个。
  2. npm adduser 把你的机器连接到你的 NPM 账户。 若是你尚未注册,它将为你建立一个新的帐号。
  3. npm publish

clipboard.png

若是一切顺利的话,在 NPM 列表中会出现你的组件,能够在你本身的项目中安装和使用 —— 并与全世界共享。

clipboard.png

Web组件API并不完美。自定义元素目前还没法在表单提交中包含数据。 渐进式加强并非很好,对可访问性的处理并不怎么容易

尽管最初在 2011 年宣布,但浏览器尚未广泛支持。 Firefox 将在今年(2018)晚些时候提供支持。 尽管如此,一些备受瞩目的网站(如 Youtube )已经在使用它们。 尽管目前还存在缺点,但对于广泛可共享的组件而言,它们是惟一的选择,而且在将来它们提供的使人兴奋的新功能很是值得期待


本文首发微信公众号:jingchengyideng

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

相关文章
相关标签/搜索