55

0

petite-vue

为渐进增强优化的Vue子集

petite-vue

petite-vue是为渐进增强优化的Vue的替代发行版。它提供与标准 Vue 相同的模板语法和反应性思维模型。但是,它专门针对在由服务器框架呈现的现有 HTML 页面上“散布”少量交互进行了优化。查看有关它与标准 Vue 有何不同的更多详细信息。

  • 只有~6kb
  • Vue 兼容的模板语法
  • 基于 DOM,就地变异
  • 通过驱动 @vue/reactivity

地位

  • 这很新。可能存在错误并且可能仍有 API 更改,因此 使用风险自负。 还是可以用吗?非常。查看示例以了解它的功能。
  • 问题列表被故意禁用,因为我现在有更高优先级的事情要关注并且不想分心。如果你发现了一个错误,你必须要么解决它,要么提交一个 PR 来自己修复它。也就是说,请随时使用讨论选项卡来互相帮助。
  • 目前不太可能接受功能请求 - 该项目的范围有意保持在最低限度。

用法

petite-vue无需构建步骤即可使用。只需从 CDN 加载它:

<script src="https://unpkg.com/petite-vue" defer init></script>

<!-- anywhere on the page -->
<div v-scope="{ count: 0 }">
  {{ count }}
  <button @click="count++">inc</button>
</div>
  • 用于 v-scope标记页面上应由 控制的区域 petite-vue
  • defer属性使脚本在 HTML 内容被解析后执行。
  • init属性告诉 petite-vue自动查询和初始化页面上的所有元素 v-scope

手动初始化

如果您不想要自动初始化,请删除该 init属性并将脚本移动到末尾 <body>

<script src="https://unpkg.com/petite-vue"></script>
<script>
  PetiteVue.createApp().mount()
</script>

或者,使用 ES 模块构建:

<script type="module">
  import { createApp } from 'https://unpkg.com/petite-vue?module'
  createApp().mount()
</script>

生产 CDN URL

简短的 CDN URL 用于原型设计。对于生产使用,使用完全解析的 CDN URL 以避免解析和重定向成本:

  • 全局构建:https://unpkg.com/petite-vue@0.2.2/dist/petite-vue.iife.js
    • 暴露 PetiteVue全局,支持自动初始化
  • ESM 构建:https://unpkg.com/petite-vue@0.2.2/dist/petite-vue.es.js
    • 必须与 <script type="module">

根范围

createApp函数接受一个数据对象作为所有表达式的根范围。这可用于引导简单的一次性应用程序:

<script type="module">
  import { createApp } from 'https://unpkg.com/petite-vue?module'

  createApp({
    // exposed to all expressions
    count: 0,
    // getters
    get plusOne() {
      return this.count + 1
    },
    // methods
    increment() {
      this.count++
    }
  }).mount()
</script>

<!-- v-scope value can be omitted -->
<div v-scope>
  <p>{{ count }}</p>
  <p>{{ plusOne }}</p>
  <button @click="increment">increment</button>
</div>

注意 v-scope这里不需要有值,只是作为 petite-vue处理元素的提示。

显式挂载目标

您可以指定一个挂载目标(选择器或元素)petite-vue以仅限于页面的该区域:

createApp().mount('#only-this-div')

这也意味着您可以拥有多个 petite-vue应用程序来控制同一页面上的不同区域:

createApp({
  // root scope for app one
}).mount('#app1')

createApp({
  // root scope for app two
}).mount('#app2')

生命周期事件

您可以监听每个元素的特殊事件 vue:mounted和生命周期事件(从 v0.4.0 开始需要前缀):vue:unmounted``vue:

<div
  v-if="show"
  @vue:mounted="console.log('mounted on: ', $el)"
  @vue:unmounted="console.log('unmounted: ', $el)"
></div>

v-effect

用于 v-effect执行反应式内联语句:

<div v-scope="{ count: 0 }">
  <div v-effect="$el.textContent = count"></div>
  <button @click="count++">++</button>
</div>

效果使用的是响应式数据源,因此每当更改 count时它都会重新运行。count

另一个替换 todo-focus原始 Vue TodoMVC 示例中的指令的示例:

<input v-effect="if (todo === editedTodo) $el.focus()" />

成分

“组件”的概念在 中有所不同 petite-vue,因为它更加简单。

首先,可以使用函数创建可重用范围逻辑:

<script type="module">
  import { createApp } from 'https://unpkg.com/petite-vue?module'

  function Counter(props) {
    return {
      count: props.initialCount,
      inc() {
        this.count++
      },
      mounted() {
        console.log(`I'm mounted!`)
      }
    }
  }

  createApp({
    Counter
  }).mount()
</script>

<div v-scope="Counter({ initialCount: 1 })" @vue:mounted="mounted">
  <p>{{ count }}</p>
  <button @click="inc">increment</button>
</div>

<div v-scope="Counter({ initialCount: 2 })">
  <p>{{ count }}</p>
  <button @click="inc">increment</button>
</div>

带有模板的组件

如果您还想重用一个模板,您可以 $template在范围对象上提供一个特殊键。该值可以是模板字符串,也可以是 <template>元素的 ID 选择器:

<script type="module">
  import { createApp } from 'https://unpkg.com/petite-vue?module'

  function Counter(props) {
    return {
      $template: '#counter-template',
      count: props.initialCount,
      inc() {
        this.count++
      }
    }
  }

  createApp({
    Counter
  }).mount()
</script>

<template id="counter-template">
  My count is {{ count }}
  <button @click="inc">++</button>
</template>

<!-- reuse it -->
<div v-scope="Counter({ initialCount: 1 })"></div>
<div v-scope="Counter({ initialCount: 2 })"></div>

建议使用该 <template>方法而不是内联字符串,因为从本机模板元素克隆更有效。

全局状态管理

您可以使用该 reactive方法(从 重新导出 @vue/reactivity)来创建全局状态单例:

<script type="module">
  import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'

  const store = reactive({
    count: 0,
    inc() {
      this.count++
    }
  })

  // manipulate it here
  store.inc()

  createApp({
    // share it with app scopes
    store
  }).mount()
</script>

<div v-scope="{ localCount: 0 }">
  <p>Global {{ store.count }}</p>
  <button @click="store.inc">increment</button>

  <p>Local {{ localCount }}</p>
  <button @click="localCount++">increment</button>
</div>

自定义指令

还支持自定义指令,但界面不同:

const myDirective = (ctx) => {
  // the element the directive is on
  ctx.el
  // the raw value expression
  // e.g. v-my-dir="x" then this would be "x"
  ctx.exp
  // v-my-dir:foo -> "foo"
  ctx.arg
  // v-my-dir.mod -> { mod: true }
  ctx.modifiers
  // evaluate the expression and get its value
  ctx.get()
  // evaluate arbitrary expression in current scope
  ctx.get(`${ctx.exp} + 10`)

  // run reactive effect
  ctx.effect(() => {
    // this will re-run every time the get() value changes
    console.log(ctx.get())
  })

  return () => {
    // cleanup if the element is unmounted
  }
}

// register the directive
createApp().directive('my-dir', myDirective).mount()

这是如何 v-html实现的:

const html = ({ el, get, effect }) => {
  effect(() => {
    el.innerHTML = get()
  })
}

自定义分隔符 (0.3+)

$delimiters您可以通过传递到根范围来使用自定义分隔符。这在与同样使用胡须的服务器端模板语言一起工作时很有用:

createApp({
  $delimiters: ['${', '}']
}).mount()

例子

查看示例目录

特征

petite-vue只要

  • v-scope
  • v-effect
  • @vue:mounted&@vue:unmounted活动

有不同的行为

  • 在表达式中,$el指向指令绑定到的当前元素(而不是组件根元素)
  • createApp()接受全局状态而不是组件
  • 组件被简化为对象返回函数
  • 自定义指令有不同的界面

Vue 兼容

  • {{ }}文本绑定(可使用自定义分隔符配置)
  • v-bind(包括 :速记和类/样式特殊处理)
  • v-on(包括 @速记和所有修饰语)
  • v-model(所有输入类型 + 非字符串 :value绑定)
  • v-if// v-else_v-else-if
  • v-for
  • v-show
  • v-html
  • v-text
  • v-pre
  • v-once
  • v-cloak
  • reactive()
  • nextTick()
  • 模板参考

不支持

一些特征被丢弃,因为它们在渐进增强的背景下具有相对较低的效用/大小比率。如果你需要这些特性,你可能应该只使用标准的 Vue。

  • ref()computed()
  • 渲染函数(petite-vue没有虚拟 DOM)
  • 集合类型的反应性(地图、集合等,为更小的尺寸而删除)
  • 过渡、保活、传送、悬念
  • v-for深度解构
  • v-on="object"
  • v-is&<component :is="xxx">
  • v-bind:style自动加前缀

与标准 Vue 的比较

的重点 petite-vue不仅仅是小。它是关于为预期的用例使用最佳实现(渐进增强)。

标准 Vue 可以在有或没有构建步骤的情况下使用。当使用构建设置(例如,使用单文件组件)时,我们会预编译所有模板,因此在运行时无需进行模板处理。由于 tree-shaking,我们可以在标准 Vue 中提供可选功能,在不使用时不会增加你的包大小。这是标准 Vue 的最佳用法,但由于它涉及构建设置,因此更适合构建具有相对繁重交互的 SPA 或应用程序。

当使用没有构建步骤并安装到 DOM 内模板的标准 Vue 时,它的优化程度要低得多,因为:

  • 我们必须将 Vue 模板编译器发送到浏览器(13kb 额外大小)
  • 编译器必须从已经实例化的 DOM 中检索模板字符串
  • 然后编译器将字符串编译成 JavaScript 渲染函数
  • 然后 Vue 用渲染函数生成的新 DOM 替换现有的 DOM 模板。

petite-vue通过遍历现有的 DOM 并直接将细粒度的反应效果附加到元素来避免所有这些开销。DOM模板。这意味着 petite-vue在渐进增强场景中效率更高。

这也是 Vue 1 的工作方式。这里的权衡是这种方法与 DOM 耦合,因此不适合与平台无关的渲染或 JavaScript SSR。我们也失去了使用渲染函数进行高级抽象的能力。但是,正如您可能知道的那样,在渐进增强的情况下很少需要这些功能。

与Alpine的比较

petite-vue确实解决了与Alpine类似的范围,但目标是 (1) 更小和 (2) 与 Vue 更兼容。

  • petite-vue大约是alpine的一半大小。
  • petite-vue没有过渡系统(也许这可以是一个可选插件)。
  • 尽管 Alpine 在很大程度上类似于 Vue 的设计,但在许多情况下其行为与 Vue 本身不同。未来它也可能与 Vue 有更多的分歧。这很好,因为 Alpine 不应该将其设计限制为严格遵循 Vue——它应该可以自由地朝着对其目标有意义的方向发展。 相比之下,petite-vue将尽可能与标准 Vue 行为保持一致,以便在需要时减少迁移到标准 Vue 的摩擦。它旨在成为 Vue 生态系统的一部分, 以涵盖标准 Vue 对当今的优化程度较低的渐进增强用例。

安全和 CSP

petite-vue计算模板中的 JavaScript 表达式。这意味着如果 petite-vue安装在 DOM 的某个区域上,该区域包含来自用户数据的未经处理的 HTML,则可能会导致 XSS 攻击。 如果您的页面呈现用户提交的 HTML,您应该更喜欢 petite-vue使用显式安装目标进行初始化,以便它只处理由您控制的部分 。您还可以清理任何用户提交的 v-scope属性 HTML。

petite-vue使用 计算表达式 new Function(),这可能在严格的 CSP 设置中被禁止。没有计划提供 CSP 构建,因为它涉及交付一个表达式解析器,这违背了轻量级的目的。如果您有严格的 CSP 要求,您可能应该使用标准 Vue 并预编译模板。

执照

MIT