@darkchest/wck 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -23
- package/package.json +1 -1
- package/src/compilers/compileScriptDefault.js +2 -2
package/README.md
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
# @darkchest/wck
|
|
2
2
|
|
|
3
|
-
## 🌉 Vue 单文件组件 (SFC) 转web-component
|
|
3
|
+
## 🌉 Vue 单文件组件 (SFC) 转web-component组件
|
|
4
4
|
|
|
5
|
-
`@darkchest/wck` (全称@darkchest/WebComponentKit)
|
|
5
|
+
`@darkchest/wck` (全称@darkchest/WebComponentKit)是一个通过在vite中作为插件编译Vue单文件组件 (SFC) 为通用的web-component组件的解决方案。
|
|
6
6
|
|
|
7
7
|
## 前言
|
|
8
8
|
|
|
9
9
|
总会有一些场景, 我们无法使用现代化的框架系统.
|
|
10
10
|
|
|
11
|
-
在习惯了现代化数据驱动的开发模式下再次回到操作dom来更新UI的模式(没有了事件, scss, 模板, 数据监听等), 的确让人感到非常不便. 并且因为全局作用域的关系,
|
|
11
|
+
在习惯了现代化数据驱动的开发模式下再次回到操作dom来更新UI的模式(没有了事件, scss, 模板, 数据监听等), 的确让人感到非常不便. 并且因为全局作用域的关系, 我们很容易碰上js变量, css样式的冲突导致的预期之外的bug. 更不幸的是由于没有固定的模板结构, 导致我们的静态网页代码有很大概率变得臃肿且越来越难以维护.
|
|
12
12
|
|
|
13
|
-
所以, 如果能够以vue2的options
|
|
13
|
+
所以, 如果能够以vue2的options格式去编写web-component组件, 在开发阶段借助vue的ide插件帮助我们进行代码提示及错误警告. 在应用阶段又以web-component组件的方式去使用, 将vue组件内部的逻辑和样式封装在web-component组件内, web-component组件内部负责抹平与vue组件的差异并且对外只暴露出vue组件中定义的props, methods, events, slots等, 这就足够应对大部分的需求了. 这不就是应对困境的最好方案吗?
|
|
14
14
|
|
|
15
15
|
这也是我开发@darkchest/wck的初衷!
|
|
16
16
|
|
|
17
17
|
实际上在这之前我开发过几个版本的解决方案:
|
|
18
18
|
|
|
19
|
-
1. 通过vue3官方提供的方案构建web-component组件,
|
|
19
|
+
1. 通过vue3官方提供的方案构建web-component组件, 最终因为一并将vue3的标准版代码打包到web-component组件中导致整个组件体积巨形膨胀(一个小组件体积上涨到90kb+), 考虑到既然都直接打包了vue3代码, 那么构建web-component组件好像没什么意义(还不如直接使用vue3进行开发), 此方案废弃.
|
|
20
20
|
|
|
21
|
-
2. 通过lit来封装web-component组件, 整体来说是很顺利的. 但是在开发过程中, 由于要使用装饰器(不太熟悉ts), 且要自行实现render, 整个开发过程不太舒适并且没有代码提示, 最终打包后体积大约为30kb+, 此方案搁置.
|
|
21
|
+
2. 通过lit来封装web-component组件, 整体来说是很顺利的. 但是在开发过程中, 由于要使用装饰器(不太熟悉ts), 且要自行实现render更新html, 整个开发过程不太舒适并且没有代码提示, 最终打包后体积大约为30kb+, 此方案搁置.
|
|
22
22
|
|
|
23
|
-
3. 通过官方提供的@vue/compiler-sfc解析vue单文件组件 (SFC), 并将其script
|
|
23
|
+
3. 通过官方提供的@vue/compiler-sfc解析vue单文件组件 (SFC), 并将其script部分进行转换成标准的web-component组件. 逻辑部分已完成, 但在解析template部分时出现重大问题, 因为template部分解析出来属于vue特有的抽象语法树, 手上又没有详尽的资料又不想去啃源码, 导致template部分无法正确转换, 此方案被废弃.
|
|
24
24
|
|
|
25
|
-
4. 最终, 我找到了[petite-vue](https://github.com/vuejs/petite-vue), 根据网络描述, 它是由 Vue.js 团队推出的重量仅约6KB的小型Vue
|
|
25
|
+
4. 最终, 我找到了[petite-vue](https://github.com/vuejs/petite-vue), 根据网络描述, 它是由 Vue.js 团队推出的重量仅约6KB的小型Vue版本,专为网页上的渐进式增强设计。它保留了Vue的核心模板语法与响应式机制,但特别优化用于在已有的HTML页面上增添少量交互效果. 经过测试它无需经过编译即可直接支持template语法(v-for, v-if, v-model, v-html, @click等), 允许直接在dom上应用template语法且无编译直接生效. 于是, 将方案3中的script转换与petite-vue的模板语法合并, 再抹平petite-vue语法与标准web-component的差异后的最终方案终于完成(与标准版vue仍有差距, 但是核心功能都已实现, 用于开发web-component组件应该是足够了), 最小体积降至16kb(未进行gzip压缩).
|
|
26
26
|
|
|
27
27
|
祝好!~
|
|
28
28
|
|
|
29
29
|
## ✨ 核心特性
|
|
30
30
|
|
|
31
|
-
- ✅ 支持props属性(注意vue中允许使用大驼峰和小驼峰定义属性, 但是web
|
|
31
|
+
- ✅ 支持props属性(注意vue中允许使用大驼峰和小驼峰定义属性, 但是web-component组件属性只允许小写, 所以vue中定义的属性在编译成web-component组件时时会转成小写和中划线连字符形式的属性并进行映射)
|
|
32
32
|
- ✅ 支持$el, $parent, $children, $root属性
|
|
33
33
|
- ✅ 支持$emit事件
|
|
34
34
|
- ✅ 支持methods方法
|
|
@@ -36,9 +36,10 @@
|
|
|
36
36
|
- ✅ 支持watch属性
|
|
37
37
|
- ✅ 支持slot匿名和具名插槽
|
|
38
38
|
- ✅ 支持mounted/onMounted, destroyed/unmounted/onUnmounted生命周期
|
|
39
|
+
- ✅ 支持scss样式
|
|
39
40
|
|
|
40
41
|
### 关于props大小写问题
|
|
41
|
-
由于vue中定义props是允许大小写字母的.而web组件的attributes只允许小写字母(包含中划线), 所以当我们在vue组件中定义一个带有大写字母的属性时(例如appTitle),
|
|
42
|
+
由于vue中定义props是允许大小写字母的.而web-component组件的attributes只允许小写字母(包含中划线), 所以当我们在vue组件中定义一个带有大写字母的属性时(例如appTitle), 在@darkchest/wck插件将它转成web-component组件后, 插件会自动在web-component组件中声明apptitle, app-title两种形式(纯小写形式和小写中划线形式)的属性来映射appTitle这个vue组件属性, 所以当我们使用web-component组件时, 可以直接在html上使用全小写的属性名来设置组件的默认属性值:
|
|
42
43
|
```html
|
|
43
44
|
<todo-list id="demo1" apptitle="*我的清单*"></todo-list>
|
|
44
45
|
<todo-list id="demo2" app-title="*我的清单*"></todo-list>
|
|
@@ -46,28 +47,28 @@
|
|
|
46
47
|
|
|
47
48
|
<script type="module">
|
|
48
49
|
var todo1 = document.querySelector('#demo1');
|
|
49
|
-
todo1.apptitle="我的清单1";
|
|
50
|
-
// todo1['app-title'] = "我的清单1";
|
|
50
|
+
todo1.apptitle="我的清单1"; // 设置有效
|
|
51
|
+
// todo1['app-title'] = "我的清单1"; // 设置有效
|
|
51
52
|
|
|
52
53
|
var todo2 = document.querySelector('#demo1');
|
|
53
|
-
todo2.apptitle="我的清单2";
|
|
54
|
-
// todo2['app-title'] = "我的清单2";
|
|
54
|
+
todo2.apptitle="我的清单2"; // 设置有效
|
|
55
|
+
// todo2['app-title'] = "我的清单2"; // 设置有效
|
|
55
56
|
</script>
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
### 关于属性, 方法, 事件, 生命周期钩子
|
|
59
|
-
- 在vue组件中props和methods定义的属性和方法, 都会直接通过web
|
|
60
|
+
- 在vue组件中props和methods定义的属性和方法, 都会直接通过web-component组件的属性和方法暴露给页面.
|
|
60
61
|
- 在vue组件中支持(onMounted/mounted)和(onUnmounted/destroyed)生命周期钩子函数来执行初始化与销毁操作.
|
|
61
|
-
- 所有编译后的web组件都会默认触发mounted/unmounted两个生命周期事件方便页面监听并进行某些初始化操作.
|
|
62
|
-
- 所有编译后的web组件都默认增加$el, $parent, $children, $root属性.
|
|
63
|
-
- 所有编译后的web组件都默认增加$emit(name, data), on(name, handler), off(name, handler), off(name, handler)方法.
|
|
62
|
+
- 所有编译后的web-component组件都会默认触发mounted/unmounted两个生命周期事件方便页面监听并进行某些初始化操作.
|
|
63
|
+
- 所有编译后的web-component组件都默认增加$el, $parent, $children, $root属性.
|
|
64
|
+
- 所有编译后的web-component组件都默认增加$emit(name, data), on(name, handler), off(name, handler), off(name, handler)方法.
|
|
64
65
|
|
|
65
66
|
## 🛠️ 安装与配置
|
|
66
67
|
|
|
67
68
|
### 1. 使用示例(重要: 请参考TodoList.vue文件示例, 该文件中已包含所有核心功能并且开箱即用)
|
|
68
69
|
|
|
69
70
|
```javascript
|
|
70
|
-
// 创建一个项目文件夹, 例如helloworld, 然后在文件夹中执行npm init -y
|
|
71
|
+
// 创建一个项目文件夹, 例如helloworld, 然后在文件夹中执行npm init -y初始化一下(命令执行完会自动生成package.json文件).
|
|
71
72
|
// 然后我们在文件夹中手动创建:
|
|
72
73
|
// - vite.config.js
|
|
73
74
|
// - index.html
|
|
@@ -123,7 +124,7 @@ export default defineConfig({
|
|
|
123
124
|
import TodoList from './components/TodoList.vue';
|
|
124
125
|
|
|
125
126
|
customElements.define('todo-list', TodoList);
|
|
126
|
-
// 注意: web-component
|
|
127
|
+
// 注意: web-component组件名必须为小写+中划线命名(有且至少一个中划线), 否则无法成功注册.
|
|
127
128
|
```
|
|
128
129
|
|
|
129
130
|
```vue
|
|
@@ -808,7 +809,7 @@ footer {
|
|
|
808
809
|
<todo-list app-title="*待办清单*"></todo-list>
|
|
809
810
|
<script type="module">
|
|
810
811
|
import './src/index.js';
|
|
811
|
-
// 执行npm run dev时vite会启动服务并打开index.html, 我们在这个页面中导入src/index.js就能正常执行它里面的代码并注册web组件了
|
|
812
|
+
// 执行npm run dev时vite会启动服务并打开index.html, 我们在这个页面中导入src/index.js就能正常执行它里面的代码并注册web-component组件了
|
|
812
813
|
</script>
|
|
813
814
|
|
|
814
815
|
<script src="./dist/index.iife.min.js"></script>
|
|
@@ -828,8 +829,8 @@ footer {
|
|
|
828
829
|
|
|
829
830
|
您也可以将example文件夹复制到其它地方, 然后执行npm install安装依赖, 最后npm run dev启动开发服务, 并按上面提到的修改index.html里的script部分, 改成直接import 'src/index.js'即可体验效果. 一但正常启动并以localhost打开网页后, 您还可以尝试着修改TodoList.vue代码观察效果.
|
|
830
831
|
|
|
831
|
-
同理, 在开发模式下, 您还可以在src/components中创建更多的组件并在src/index.js
|
|
832
|
+
同理, 在开发模式下, 您还可以在src/components中创建更多的组件并在src/index.js中导入并注册为web-component组件, 在index.html中可以直接使用新注册的web-component组件.
|
|
832
833
|
|
|
833
|
-
最后, 如果需要异步注册,
|
|
834
|
+
最后, 如果需要异步注册, 编译每个组件为独立js文件等功能, 您可能需要自行在vite.config.js中配置多入口编译, 这不属于本教程的讨论范围. 请自行阅读vite的官方文档.
|
|
834
835
|
|
|
835
836
|
祝您使用愉快.
|
package/package.json
CHANGED
|
@@ -144,7 +144,7 @@ export const compileExportDefault = (ast) => {
|
|
|
144
144
|
case 'props':
|
|
145
145
|
if (t.isObjectProperty(propNode)){
|
|
146
146
|
const toLowerCase = (str) => str.toLowerCase();
|
|
147
|
-
const toSnakeCase = (str) => str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
147
|
+
const toSnakeCase = (str) => str.replace(/([A-Z])/g, '-$1').replace(/^[-_]*/i, '').toLowerCase();
|
|
148
148
|
scriptData.attributes = propNode.value.properties?.map?.(item => `"${toLowerCase(item.key.name)}"`).concat(propNode.value.properties?.map?.(item => `"${toSnakeCase(item.key.name)}"`));
|
|
149
149
|
propNode.value.properties?.forEach?.(node => {
|
|
150
150
|
const ret = getNodePropData(node);
|
|
@@ -236,7 +236,7 @@ export const compileExportDefault = (ast) => {
|
|
|
236
236
|
break;
|
|
237
237
|
case 'watch':
|
|
238
238
|
if(t.isObjectProperty(propNode)){
|
|
239
|
-
const toSnakeCase = (str) => str.replace(/([A-Z])/g, '_$1').replace(/[.\s]+/g, '_').toLowerCase();
|
|
239
|
+
const toSnakeCase = (str) => str.replace(/([A-Z])/g, '_$1').replace(/[.\s]+/g, '_').replace(/^[-_]*/i, '').toLowerCase();
|
|
240
240
|
propNode.value.properties?.map(node => {
|
|
241
241
|
const name = t.isStringLiteral(node.key) ? node.key.value : node.key.name;
|
|
242
242
|
const snaked = toSnakeCase(name);
|