我惊了!没想到jQuery也有SFC(单文件组件)

902

SFC是vue提出来的一种组件代码组织形式,在后缀.vue的文件中将一个组件的模板、脚本和样式写在一起进行管理,有助于集中组织单一组件的所有内容。那你有没有想过,在jQuery的系统中,也可以实现SFC呢?本文就带你领略一番SFC在jQuery中的实践。

引入框架

首先,你需要在原有的jQuery系统中,引入一个框架jqvm,它是一个jquery插件,引入方式和其他插件一模一样。

<script src="https://unpkg.com/jqvm/dist/jqvm.min.js"></script>

这样,就有了支撑SFC运行的基本环境。

撰写SFC

接下来,撰写一个.htm文件作为jQuery的SFC:

<template hoist>
  <div class="my-component">
    <div class="my-component__title">{{title}}</div>
    <div class="my-component__cotnent">{{content}}</div>
  </div>
</template>

<style>
  .my-component {
    padding: 10px;
  }
  .my-component__title {
    font-size: 1.4em;
  }
</style>

<script>
  export default ($template, $) => $template
    .vm(() => {
      return {
        title: 'Default Title',
        content: 'Default Content',
      }
    })
    .on('$init', (state) => {
      // 实例化的时候,拉取数据,填充
      $.get('/api/data').then((data) => {
        const { title, content } = data
        state.title = title
        state.content = content
      })
    })
</script>

这样就写好了一个SCF,它由三部分组成,其中<template hoist>中的hosit指使用组件将会直接被div替换。等下你就能看到效果。

编译

接下来,你需要使用jqvm-loader对这个SFC进行编译。如果结合webpack,你可以这样配置:

// 前提先执行 npm i jqvm

{
  module: {
    rules: [
      {
        test: /\.html$/,
        resourceQuery: /jqvm/, // 可选的,当你使用这个规则,那么只有 import .. from './some.html?jqvm' 才走jqvm/loader编译
        use: [
          {
            loader: 'babel-loader', // 这个loader的功能很简单,就是把一个html文件解析出来之后,编译为一个ESModule,这个ESModule就是一个jqvm的组件,再交给babel去进行编译。
          },
          
          // 必须放在babel-loader下面
          {
            loader: 'jqvm/loader', // jqvm-loader的位置在jqvm包下面
            options: {
              $: true, // 可选,如果$为true,那么生成的组件代码中,不会引入jquery和jqvm,直接使用全局的$对象,如果传入一个字符串,比如'jQuery',那么会用这个字符串作为全局变量赋值给$作为jquery的引用
            },
          },
        ],
      }
    ]
  }
}

但是大多数情况下,以前的jquery系统都是直接引入,没有使用编译系统,此时应该这么办呢?你可以使用jqvm-loader暴露出来的编译接口,使用nodejs直接对SFC进行编译。

// compile.js
const { compile } = require('jqvm/loader')
const fs = require('fs')

const content = compile(fs.readFileSync(__dirname + '/my-component.htm').toString(), { $: true })
fs.writeFileSync(__dirname + '/my-component.js', content)

然后用nodejs运行这个编译文件

node compile.js

就可以生成一个my-component.js文件,你可以打开看看它的结果,它是一个ES模块,如果你需要在ES5环境中运行,你还需要用babel转译一次,如果你需要直接在浏览器中引入,还需要再用umd包裹一层,毕竟它只是一个ES模块。

如果想要按照传统的方式使用该组件,你可以使用webpack编译该组件。例如:

// my-component.bundle.js <- 用webpack打包成它
window['my-component'] = require('./my-component.js').default

使用带babel的webpack打包上面这个文件之后,就可以在window上挂载一个'my-component'属性。

使用组件

得到my-component.js之后(未经babel编译和umd包裹,还是一个ES模块),我们就可以使用这个组件了。接下来我们这样使用:

<my-component id="some"></my-component> <!-- 这个标签可以随便用,只要id对应好即可 -->

<script type="module">
  import myComponent from './my-component.js'
  myComponent.mount('#some')
</script>

这里使用了ES的语法import,因为我们直接把编译好的my-component.js拿来用,它又是一个ES模块,当前还只能通过这种方式加载。

经过上面这个操作,界面上就会在template#some下面渲染出SFC组件的结果。

使用type="module"只支持高级浏览器,以前老的jquery系统中很少这样用。你也可以自己经过处理之后,直接引入,以符合我们传统的编程直觉。利用前面编译得到的my-component.bundle.js,上面的脚本就可以重新这么写:

<script src="./my-component.bundle.js"></script> <!-- 加载这个组件,组件实例被挂在window上 -->
<script>
  const myComponent = window['my-component']
  myComponent.mount('#some')
</script>

这种写法和我们传统写jquery应用的写法一致。只不过中间的编译过程比较麻烦。

结束

哈哈,本文只是向你展示了一种在传统jquery中,也能实施一些新的理念的方式,至于你真的要在老的jquery系统中去实施,肯定还是会遇到一些坑,有不懂的地方,可以在下方留言,或者关注我的个人微信公众号 wwwtangshuangnet 一起讨论。最后,如果你觉得这个项目不错,到github给个star,上帝都会感谢你🙏