├── .demo
├── async-component.js
└── jqvm-loader
│ ├── .gitignore
│ ├── compile.js
│ ├── component-a.html
│ ├── index.html
│ ├── index.js
│ ├── package-lock.json
│ ├── package.json
│ └── webpack.config.js
├── .gitignore
├── .npmignore
├── README.md
├── README_zh.md
├── index.html
├── jqvm.png
├── loader.js
├── package-lock.json
├── package.json
├── src
├── async.js
├── index.js
├── jqvm.js
├── router.js
├── store.js
└── utils.js
├── webpack.config.js
└── webpack.dev.config.js
/.demo/async-component.js:
--------------------------------------------------------------------------------
1 | const template = `
2 |
3 |
{{title}}
4 |
{{content}}
5 |
6 | `
7 | export default $(template)
8 | .vm(() => ({ title: 'Title', content: 'Content' }))
9 |
--------------------------------------------------------------------------------
/.demo/jqvm-loader/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist.js
3 | component-a.js
4 |
--------------------------------------------------------------------------------
/.demo/jqvm-loader/compile.js:
--------------------------------------------------------------------------------
1 | const { compile } = require('../../loader')
2 | const fs = require('fs')
3 |
4 | const content = fs.readFileSync(__dirname + '/component-a.html').toString()
5 | const output = compile(content, { $: 'jQuery' })
6 | fs.writeFileSync(__dirname + '/component-a.js', output)
7 |
--------------------------------------------------------------------------------
/.demo/jqvm-loader/component-a.html:
--------------------------------------------------------------------------------
1 |
2 | {{title}}
3 | {{content}}'
4 |
5 |
6 |
11 |
12 |
21 |
--------------------------------------------------------------------------------
/.demo/jqvm-loader/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jQvm Loader Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.demo/jqvm-loader/index.js:
--------------------------------------------------------------------------------
1 | import ComponentA from './component-a.html'
2 |
3 | import jQuery from 'jquery'
4 | import { useJQuery } from 'jqvm'
5 |
6 | const $ = useJQuery(jQuery)
7 |
8 | $('#app').vm({})
9 | .component('comp-a', ComponentA)
10 | .mount()
11 |
--------------------------------------------------------------------------------
/.demo/jqvm-loader/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jqvm-loader",
3 | "version": "1.1.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@discoveryjs/json-ext": {
8 | "version": "0.5.3",
9 | "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz",
10 | "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g=="
11 | },
12 | "@types/eslint": {
13 | "version": "7.2.13",
14 | "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.13.tgz",
15 | "integrity": "sha512-LKmQCWAlnVHvvXq4oasNUMTJJb2GwSyTY8+1C7OH5ILR8mPLaljv1jxL1bXW3xB3jFbQxTKxJAvI8PyjB09aBg==",
16 | "requires": {
17 | "@types/estree": "*",
18 | "@types/json-schema": "*"
19 | }
20 | },
21 | "@types/eslint-scope": {
22 | "version": "3.7.0",
23 | "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz",
24 | "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==",
25 | "requires": {
26 | "@types/eslint": "*",
27 | "@types/estree": "*"
28 | }
29 | },
30 | "@types/estree": {
31 | "version": "0.0.47",
32 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.47.tgz",
33 | "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg=="
34 | },
35 | "@types/json-schema": {
36 | "version": "7.0.7",
37 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
38 | "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA=="
39 | },
40 | "@types/node": {
41 | "version": "15.12.2",
42 | "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
43 | "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww=="
44 | },
45 | "@webassemblyjs/ast": {
46 | "version": "1.11.0",
47 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz",
48 | "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==",
49 | "requires": {
50 | "@webassemblyjs/helper-numbers": "1.11.0",
51 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0"
52 | }
53 | },
54 | "@webassemblyjs/floating-point-hex-parser": {
55 | "version": "1.11.0",
56 | "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz",
57 | "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA=="
58 | },
59 | "@webassemblyjs/helper-api-error": {
60 | "version": "1.11.0",
61 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz",
62 | "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w=="
63 | },
64 | "@webassemblyjs/helper-buffer": {
65 | "version": "1.11.0",
66 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz",
67 | "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA=="
68 | },
69 | "@webassemblyjs/helper-numbers": {
70 | "version": "1.11.0",
71 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz",
72 | "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==",
73 | "requires": {
74 | "@webassemblyjs/floating-point-hex-parser": "1.11.0",
75 | "@webassemblyjs/helper-api-error": "1.11.0",
76 | "@xtuc/long": "4.2.2"
77 | }
78 | },
79 | "@webassemblyjs/helper-wasm-bytecode": {
80 | "version": "1.11.0",
81 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz",
82 | "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA=="
83 | },
84 | "@webassemblyjs/helper-wasm-section": {
85 | "version": "1.11.0",
86 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz",
87 | "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==",
88 | "requires": {
89 | "@webassemblyjs/ast": "1.11.0",
90 | "@webassemblyjs/helper-buffer": "1.11.0",
91 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
92 | "@webassemblyjs/wasm-gen": "1.11.0"
93 | }
94 | },
95 | "@webassemblyjs/ieee754": {
96 | "version": "1.11.0",
97 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz",
98 | "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==",
99 | "requires": {
100 | "@xtuc/ieee754": "^1.2.0"
101 | }
102 | },
103 | "@webassemblyjs/leb128": {
104 | "version": "1.11.0",
105 | "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz",
106 | "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==",
107 | "requires": {
108 | "@xtuc/long": "4.2.2"
109 | }
110 | },
111 | "@webassemblyjs/utf8": {
112 | "version": "1.11.0",
113 | "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz",
114 | "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw=="
115 | },
116 | "@webassemblyjs/wasm-edit": {
117 | "version": "1.11.0",
118 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz",
119 | "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==",
120 | "requires": {
121 | "@webassemblyjs/ast": "1.11.0",
122 | "@webassemblyjs/helper-buffer": "1.11.0",
123 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
124 | "@webassemblyjs/helper-wasm-section": "1.11.0",
125 | "@webassemblyjs/wasm-gen": "1.11.0",
126 | "@webassemblyjs/wasm-opt": "1.11.0",
127 | "@webassemblyjs/wasm-parser": "1.11.0",
128 | "@webassemblyjs/wast-printer": "1.11.0"
129 | }
130 | },
131 | "@webassemblyjs/wasm-gen": {
132 | "version": "1.11.0",
133 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz",
134 | "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==",
135 | "requires": {
136 | "@webassemblyjs/ast": "1.11.0",
137 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
138 | "@webassemblyjs/ieee754": "1.11.0",
139 | "@webassemblyjs/leb128": "1.11.0",
140 | "@webassemblyjs/utf8": "1.11.0"
141 | }
142 | },
143 | "@webassemblyjs/wasm-opt": {
144 | "version": "1.11.0",
145 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz",
146 | "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==",
147 | "requires": {
148 | "@webassemblyjs/ast": "1.11.0",
149 | "@webassemblyjs/helper-buffer": "1.11.0",
150 | "@webassemblyjs/wasm-gen": "1.11.0",
151 | "@webassemblyjs/wasm-parser": "1.11.0"
152 | }
153 | },
154 | "@webassemblyjs/wasm-parser": {
155 | "version": "1.11.0",
156 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz",
157 | "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==",
158 | "requires": {
159 | "@webassemblyjs/ast": "1.11.0",
160 | "@webassemblyjs/helper-api-error": "1.11.0",
161 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
162 | "@webassemblyjs/ieee754": "1.11.0",
163 | "@webassemblyjs/leb128": "1.11.0",
164 | "@webassemblyjs/utf8": "1.11.0"
165 | }
166 | },
167 | "@webassemblyjs/wast-printer": {
168 | "version": "1.11.0",
169 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz",
170 | "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==",
171 | "requires": {
172 | "@webassemblyjs/ast": "1.11.0",
173 | "@xtuc/long": "4.2.2"
174 | }
175 | },
176 | "@webpack-cli/configtest": {
177 | "version": "1.0.4",
178 | "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.4.tgz",
179 | "integrity": "sha512-cs3XLy+UcxiP6bj0A6u7MLLuwdXJ1c3Dtc0RkKg+wiI1g/Ti1om8+/2hc2A2B60NbBNAbMgyBMHvyymWm/j4wQ=="
180 | },
181 | "@webpack-cli/info": {
182 | "version": "1.3.0",
183 | "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.3.0.tgz",
184 | "integrity": "sha512-ASiVB3t9LOKHs5DyVUcxpraBXDOKubYu/ihHhU+t1UPpxsivg6Od2E2qU4gJCekfEddzRBzHhzA/Acyw/mlK/w==",
185 | "requires": {
186 | "envinfo": "^7.7.3"
187 | }
188 | },
189 | "@webpack-cli/serve": {
190 | "version": "1.5.1",
191 | "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.1.tgz",
192 | "integrity": "sha512-4vSVUiOPJLmr45S8rMGy7WDvpWxfFxfP/Qx/cxZFCfvoypTYpPPL1X8VIZMe0WTA+Jr7blUxwUSEZNkjoMTgSw=="
193 | },
194 | "@xtuc/ieee754": {
195 | "version": "1.2.0",
196 | "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
197 | "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
198 | },
199 | "@xtuc/long": {
200 | "version": "4.2.2",
201 | "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
202 | "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
203 | },
204 | "acorn": {
205 | "version": "8.3.0",
206 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.3.0.tgz",
207 | "integrity": "sha512-tqPKHZ5CaBJw0Xmy0ZZvLs1qTV+BNFSyvn77ASXkpBNfIRk8ev26fKrD9iLGwGA9zedPao52GSHzq8lyZG0NUw=="
208 | },
209 | "ajv": {
210 | "version": "6.12.6",
211 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
212 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
213 | "requires": {
214 | "fast-deep-equal": "^3.1.1",
215 | "fast-json-stable-stringify": "^2.0.0",
216 | "json-schema-traverse": "^0.4.1",
217 | "uri-js": "^4.2.2"
218 | }
219 | },
220 | "ajv-keywords": {
221 | "version": "3.5.2",
222 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
223 | "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="
224 | },
225 | "browserslist": {
226 | "version": "4.16.6",
227 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
228 | "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
229 | "requires": {
230 | "caniuse-lite": "^1.0.30001219",
231 | "colorette": "^1.2.2",
232 | "electron-to-chromium": "^1.3.723",
233 | "escalade": "^3.1.1",
234 | "node-releases": "^1.1.71"
235 | }
236 | },
237 | "buffer-from": {
238 | "version": "1.1.1",
239 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
240 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
241 | },
242 | "caniuse-lite": {
243 | "version": "1.0.30001236",
244 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001236.tgz",
245 | "integrity": "sha512-o0PRQSrSCGJKCPZcgMzl5fUaj5xHe8qA2m4QRvnyY4e1lITqoNkr7q/Oh1NcpGSy0Th97UZ35yoKcINPoq7YOQ=="
246 | },
247 | "chrome-trace-event": {
248 | "version": "1.0.3",
249 | "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
250 | "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg=="
251 | },
252 | "clone-deep": {
253 | "version": "4.0.1",
254 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
255 | "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
256 | "requires": {
257 | "is-plain-object": "^2.0.4",
258 | "kind-of": "^6.0.2",
259 | "shallow-clone": "^3.0.0"
260 | }
261 | },
262 | "colorette": {
263 | "version": "1.2.2",
264 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
265 | "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w=="
266 | },
267 | "commander": {
268 | "version": "2.20.3",
269 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
270 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
271 | },
272 | "cross-spawn": {
273 | "version": "7.0.3",
274 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
275 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
276 | "requires": {
277 | "path-key": "^3.1.0",
278 | "shebang-command": "^2.0.0",
279 | "which": "^2.0.1"
280 | }
281 | },
282 | "electron-to-chromium": {
283 | "version": "1.3.752",
284 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz",
285 | "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A=="
286 | },
287 | "enhanced-resolve": {
288 | "version": "5.8.2",
289 | "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz",
290 | "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==",
291 | "requires": {
292 | "graceful-fs": "^4.2.4",
293 | "tapable": "^2.2.0"
294 | }
295 | },
296 | "envinfo": {
297 | "version": "7.8.1",
298 | "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz",
299 | "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw=="
300 | },
301 | "es-module-lexer": {
302 | "version": "0.4.1",
303 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz",
304 | "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA=="
305 | },
306 | "escalade": {
307 | "version": "3.1.1",
308 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
309 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
310 | },
311 | "eslint-scope": {
312 | "version": "5.1.1",
313 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
314 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
315 | "requires": {
316 | "esrecurse": "^4.3.0",
317 | "estraverse": "^4.1.1"
318 | }
319 | },
320 | "esrecurse": {
321 | "version": "4.3.0",
322 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
323 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
324 | "requires": {
325 | "estraverse": "^5.2.0"
326 | },
327 | "dependencies": {
328 | "estraverse": {
329 | "version": "5.2.0",
330 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
331 | "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ=="
332 | }
333 | }
334 | },
335 | "estraverse": {
336 | "version": "4.3.0",
337 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
338 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
339 | },
340 | "events": {
341 | "version": "3.3.0",
342 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
343 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
344 | },
345 | "execa": {
346 | "version": "5.1.1",
347 | "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
348 | "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
349 | "requires": {
350 | "cross-spawn": "^7.0.3",
351 | "get-stream": "^6.0.0",
352 | "human-signals": "^2.1.0",
353 | "is-stream": "^2.0.0",
354 | "merge-stream": "^2.0.0",
355 | "npm-run-path": "^4.0.1",
356 | "onetime": "^5.1.2",
357 | "signal-exit": "^3.0.3",
358 | "strip-final-newline": "^2.0.0"
359 | }
360 | },
361 | "fast-deep-equal": {
362 | "version": "3.1.3",
363 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
364 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
365 | },
366 | "fast-json-stable-stringify": {
367 | "version": "2.1.0",
368 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
369 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
370 | },
371 | "fastest-levenshtein": {
372 | "version": "1.0.12",
373 | "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz",
374 | "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow=="
375 | },
376 | "find-up": {
377 | "version": "4.1.0",
378 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
379 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
380 | "requires": {
381 | "locate-path": "^5.0.0",
382 | "path-exists": "^4.0.0"
383 | }
384 | },
385 | "function-bind": {
386 | "version": "1.1.1",
387 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
388 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
389 | },
390 | "get-stream": {
391 | "version": "6.0.1",
392 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
393 | "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="
394 | },
395 | "glob-to-regexp": {
396 | "version": "0.4.1",
397 | "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
398 | "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
399 | },
400 | "graceful-fs": {
401 | "version": "4.2.6",
402 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
403 | "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
404 | },
405 | "has": {
406 | "version": "1.0.3",
407 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
408 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
409 | "requires": {
410 | "function-bind": "^1.1.1"
411 | }
412 | },
413 | "has-flag": {
414 | "version": "4.0.0",
415 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
416 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
417 | },
418 | "human-signals": {
419 | "version": "2.1.0",
420 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
421 | "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="
422 | },
423 | "import-local": {
424 | "version": "3.0.2",
425 | "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz",
426 | "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==",
427 | "requires": {
428 | "pkg-dir": "^4.2.0",
429 | "resolve-cwd": "^3.0.0"
430 | }
431 | },
432 | "interpret": {
433 | "version": "2.2.0",
434 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
435 | "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw=="
436 | },
437 | "is-core-module": {
438 | "version": "2.4.0",
439 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
440 | "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
441 | "requires": {
442 | "has": "^1.0.3"
443 | }
444 | },
445 | "is-plain-object": {
446 | "version": "2.0.4",
447 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
448 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
449 | "requires": {
450 | "isobject": "^3.0.1"
451 | }
452 | },
453 | "is-stream": {
454 | "version": "2.0.0",
455 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
456 | "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
457 | },
458 | "isexe": {
459 | "version": "2.0.0",
460 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
461 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
462 | },
463 | "isobject": {
464 | "version": "3.0.1",
465 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
466 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
467 | },
468 | "jest-worker": {
469 | "version": "27.0.2",
470 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.2.tgz",
471 | "integrity": "sha512-EoBdilOTTyOgmHXtw/cPc+ZrCA0KJMrkXzkrPGNwLmnvvlN1nj7MPrxpT7m+otSv2e1TLaVffzDnE/LB14zJMg==",
472 | "requires": {
473 | "@types/node": "*",
474 | "merge-stream": "^2.0.0",
475 | "supports-color": "^8.0.0"
476 | }
477 | },
478 | "jquery": {
479 | "version": "3.6.0",
480 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
481 | "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
482 | },
483 | "jqvm": {
484 | "version": "4.1.6",
485 | "resolved": "https://registry.npmjs.org/jqvm/-/jqvm-4.1.6.tgz",
486 | "integrity": "sha512-OMMDKKbsfmHHbwWqq0FtGqxdeSR/VGnoKjHMMuE0W26Vr2F1QoxEEDAeaTqzYniExT2aGLJn2Un5rSeGe1Fv6g==",
487 | "requires": {
488 | "jquery": "^3.6.0",
489 | "scopex": "^3.0.2",
490 | "ts-fns": "^10.3.2"
491 | }
492 | },
493 | "json-parse-better-errors": {
494 | "version": "1.0.2",
495 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
496 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
497 | },
498 | "json-schema-traverse": {
499 | "version": "0.4.1",
500 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
501 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
502 | },
503 | "kind-of": {
504 | "version": "6.0.3",
505 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
506 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
507 | },
508 | "loader-runner": {
509 | "version": "4.2.0",
510 | "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
511 | "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw=="
512 | },
513 | "locate-path": {
514 | "version": "5.0.0",
515 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
516 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
517 | "requires": {
518 | "p-locate": "^4.1.0"
519 | }
520 | },
521 | "merge-stream": {
522 | "version": "2.0.0",
523 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
524 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
525 | },
526 | "mime-db": {
527 | "version": "1.48.0",
528 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz",
529 | "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ=="
530 | },
531 | "mime-types": {
532 | "version": "2.1.31",
533 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz",
534 | "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==",
535 | "requires": {
536 | "mime-db": "1.48.0"
537 | }
538 | },
539 | "mimic-fn": {
540 | "version": "2.1.0",
541 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
542 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
543 | },
544 | "neo-async": {
545 | "version": "2.6.2",
546 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
547 | "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
548 | },
549 | "node-releases": {
550 | "version": "1.1.73",
551 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz",
552 | "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg=="
553 | },
554 | "npm-run-path": {
555 | "version": "4.0.1",
556 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
557 | "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
558 | "requires": {
559 | "path-key": "^3.0.0"
560 | }
561 | },
562 | "onetime": {
563 | "version": "5.1.2",
564 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
565 | "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
566 | "requires": {
567 | "mimic-fn": "^2.1.0"
568 | }
569 | },
570 | "p-limit": {
571 | "version": "3.1.0",
572 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
573 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
574 | "requires": {
575 | "yocto-queue": "^0.1.0"
576 | }
577 | },
578 | "p-locate": {
579 | "version": "4.1.0",
580 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
581 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
582 | "requires": {
583 | "p-limit": "^2.2.0"
584 | },
585 | "dependencies": {
586 | "p-limit": {
587 | "version": "2.3.0",
588 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
589 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
590 | "requires": {
591 | "p-try": "^2.0.0"
592 | }
593 | }
594 | }
595 | },
596 | "p-try": {
597 | "version": "2.2.0",
598 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
599 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
600 | },
601 | "path-exists": {
602 | "version": "4.0.0",
603 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
604 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
605 | },
606 | "path-key": {
607 | "version": "3.1.1",
608 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
609 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
610 | },
611 | "path-parse": {
612 | "version": "1.0.7",
613 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
614 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
615 | },
616 | "pkg-dir": {
617 | "version": "4.2.0",
618 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
619 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
620 | "requires": {
621 | "find-up": "^4.0.0"
622 | }
623 | },
624 | "punycode": {
625 | "version": "2.1.1",
626 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
627 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
628 | },
629 | "randombytes": {
630 | "version": "2.1.0",
631 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
632 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
633 | "requires": {
634 | "safe-buffer": "^5.1.0"
635 | }
636 | },
637 | "rechoir": {
638 | "version": "0.7.0",
639 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz",
640 | "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==",
641 | "requires": {
642 | "resolve": "^1.9.0"
643 | }
644 | },
645 | "resolve": {
646 | "version": "1.20.0",
647 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
648 | "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
649 | "requires": {
650 | "is-core-module": "^2.2.0",
651 | "path-parse": "^1.0.6"
652 | }
653 | },
654 | "resolve-cwd": {
655 | "version": "3.0.0",
656 | "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
657 | "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
658 | "requires": {
659 | "resolve-from": "^5.0.0"
660 | }
661 | },
662 | "resolve-from": {
663 | "version": "5.0.0",
664 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
665 | "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="
666 | },
667 | "safe-buffer": {
668 | "version": "5.2.1",
669 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
670 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
671 | },
672 | "schema-utils": {
673 | "version": "3.0.0",
674 | "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
675 | "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
676 | "requires": {
677 | "@types/json-schema": "^7.0.6",
678 | "ajv": "^6.12.5",
679 | "ajv-keywords": "^3.5.2"
680 | }
681 | },
682 | "scopex": {
683 | "version": "3.0.2",
684 | "resolved": "https://registry.npmjs.org/scopex/-/scopex-3.0.2.tgz",
685 | "integrity": "sha512-yFlUEL6m/VxncnoBdldlKOAN/f2+zcm/3SR5z3Guseh/ayIygbhK5vw18LLENOYDVaYktvUPS28Bk+xNMqUQ8w=="
686 | },
687 | "serialize-javascript": {
688 | "version": "5.0.1",
689 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
690 | "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
691 | "requires": {
692 | "randombytes": "^2.1.0"
693 | }
694 | },
695 | "shallow-clone": {
696 | "version": "3.0.1",
697 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
698 | "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
699 | "requires": {
700 | "kind-of": "^6.0.2"
701 | }
702 | },
703 | "shebang-command": {
704 | "version": "2.0.0",
705 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
706 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
707 | "requires": {
708 | "shebang-regex": "^3.0.0"
709 | }
710 | },
711 | "shebang-regex": {
712 | "version": "3.0.0",
713 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
714 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
715 | },
716 | "signal-exit": {
717 | "version": "3.0.3",
718 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
719 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
720 | },
721 | "source-list-map": {
722 | "version": "2.0.1",
723 | "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
724 | "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
725 | },
726 | "source-map": {
727 | "version": "0.6.1",
728 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
729 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
730 | },
731 | "source-map-support": {
732 | "version": "0.5.19",
733 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
734 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
735 | "requires": {
736 | "buffer-from": "^1.0.0",
737 | "source-map": "^0.6.0"
738 | }
739 | },
740 | "strip-final-newline": {
741 | "version": "2.0.0",
742 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
743 | "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
744 | },
745 | "supports-color": {
746 | "version": "8.1.1",
747 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
748 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
749 | "requires": {
750 | "has-flag": "^4.0.0"
751 | }
752 | },
753 | "tapable": {
754 | "version": "2.2.0",
755 | "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz",
756 | "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw=="
757 | },
758 | "terser": {
759 | "version": "5.7.0",
760 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz",
761 | "integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==",
762 | "requires": {
763 | "commander": "^2.20.0",
764 | "source-map": "~0.7.2",
765 | "source-map-support": "~0.5.19"
766 | },
767 | "dependencies": {
768 | "source-map": {
769 | "version": "0.7.3",
770 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
771 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
772 | }
773 | }
774 | },
775 | "terser-webpack-plugin": {
776 | "version": "5.1.3",
777 | "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.3.tgz",
778 | "integrity": "sha512-cxGbMqr6+A2hrIB5ehFIF+F/iST5ZOxvOmy9zih9ySbP1C2oEWQSOUS+2SNBTjzx5xLKO4xnod9eywdfq1Nb9A==",
779 | "requires": {
780 | "jest-worker": "^27.0.2",
781 | "p-limit": "^3.1.0",
782 | "schema-utils": "^3.0.0",
783 | "serialize-javascript": "^5.0.1",
784 | "source-map": "^0.6.1",
785 | "terser": "^5.7.0"
786 | }
787 | },
788 | "ts-fns": {
789 | "version": "10.3.2",
790 | "resolved": "https://registry.npmjs.org/ts-fns/-/ts-fns-10.3.2.tgz",
791 | "integrity": "sha512-8OtNhaovuSgQloSmMZ2AKFXch0Zum5Cglv/GBeT63GE6RS3CNcoMVa460CF0ABx9uCeBPlncWOB7o2K6idvvTw=="
792 | },
793 | "uri-js": {
794 | "version": "4.4.1",
795 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
796 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
797 | "requires": {
798 | "punycode": "^2.1.0"
799 | }
800 | },
801 | "v8-compile-cache": {
802 | "version": "2.3.0",
803 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
804 | "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA=="
805 | },
806 | "watchpack": {
807 | "version": "2.2.0",
808 | "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz",
809 | "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==",
810 | "requires": {
811 | "glob-to-regexp": "^0.4.1",
812 | "graceful-fs": "^4.1.2"
813 | }
814 | },
815 | "webpack": {
816 | "version": "5.38.1",
817 | "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.38.1.tgz",
818 | "integrity": "sha512-OqRmYD1OJbHZph6RUMD93GcCZy4Z4wC0ele4FXyYF0J6AxO1vOSuIlU1hkS/lDlR9CDYBz64MZRmdbdnFFoT2g==",
819 | "requires": {
820 | "@types/eslint-scope": "^3.7.0",
821 | "@types/estree": "^0.0.47",
822 | "@webassemblyjs/ast": "1.11.0",
823 | "@webassemblyjs/wasm-edit": "1.11.0",
824 | "@webassemblyjs/wasm-parser": "1.11.0",
825 | "acorn": "^8.2.1",
826 | "browserslist": "^4.14.5",
827 | "chrome-trace-event": "^1.0.2",
828 | "enhanced-resolve": "^5.8.0",
829 | "es-module-lexer": "^0.4.0",
830 | "eslint-scope": "5.1.1",
831 | "events": "^3.2.0",
832 | "glob-to-regexp": "^0.4.1",
833 | "graceful-fs": "^4.2.4",
834 | "json-parse-better-errors": "^1.0.2",
835 | "loader-runner": "^4.2.0",
836 | "mime-types": "^2.1.27",
837 | "neo-async": "^2.6.2",
838 | "schema-utils": "^3.0.0",
839 | "tapable": "^2.1.1",
840 | "terser-webpack-plugin": "^5.1.1",
841 | "watchpack": "^2.2.0",
842 | "webpack-sources": "^2.3.0"
843 | }
844 | },
845 | "webpack-cli": {
846 | "version": "4.7.2",
847 | "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.7.2.tgz",
848 | "integrity": "sha512-mEoLmnmOIZQNiRl0ebnjzQ74Hk0iKS5SiEEnpq3dRezoyR3yPaeQZCMCe+db4524pj1Pd5ghZXjT41KLzIhSLw==",
849 | "requires": {
850 | "@discoveryjs/json-ext": "^0.5.0",
851 | "@webpack-cli/configtest": "^1.0.4",
852 | "@webpack-cli/info": "^1.3.0",
853 | "@webpack-cli/serve": "^1.5.1",
854 | "colorette": "^1.2.1",
855 | "commander": "^7.0.0",
856 | "execa": "^5.0.0",
857 | "fastest-levenshtein": "^1.0.12",
858 | "import-local": "^3.0.2",
859 | "interpret": "^2.2.0",
860 | "rechoir": "^0.7.0",
861 | "v8-compile-cache": "^2.2.0",
862 | "webpack-merge": "^5.7.3"
863 | },
864 | "dependencies": {
865 | "commander": {
866 | "version": "7.2.0",
867 | "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
868 | "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
869 | }
870 | }
871 | },
872 | "webpack-merge": {
873 | "version": "5.8.0",
874 | "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz",
875 | "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==",
876 | "requires": {
877 | "clone-deep": "^4.0.1",
878 | "wildcard": "^2.0.0"
879 | }
880 | },
881 | "webpack-sources": {
882 | "version": "2.3.0",
883 | "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.0.tgz",
884 | "integrity": "sha512-WyOdtwSvOML1kbgtXbTDnEW0jkJ7hZr/bDByIwszhWd/4XX1A3XMkrbFMsuH4+/MfLlZCUzlAdg4r7jaGKEIgQ==",
885 | "requires": {
886 | "source-list-map": "^2.0.1",
887 | "source-map": "^0.6.1"
888 | }
889 | },
890 | "which": {
891 | "version": "2.0.2",
892 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
893 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
894 | "requires": {
895 | "isexe": "^2.0.0"
896 | }
897 | },
898 | "wildcard": {
899 | "version": "2.0.0",
900 | "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
901 | "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw=="
902 | },
903 | "yocto-queue": {
904 | "version": "0.1.0",
905 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
906 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
907 | }
908 | }
909 | }
910 |
--------------------------------------------------------------------------------
/.demo/jqvm-loader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jqvm-loader",
3 | "version": "1.1.0",
4 | "description": "",
5 | "main": "jqvm-loader.js",
6 | "scripts": {
7 | "build": "webpack"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "jqvm": "^4.1.6",
13 | "webpack": "^5.38.1",
14 | "webpack-cli": "^4.7.2"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.demo/jqvm-loader/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'none',
3 | target: 'web',
4 | entry: __dirname + '/index.js',
5 | output: {
6 | path: __dirname,
7 | filename: 'dist.js',
8 | },
9 | module: {
10 | rules: [
11 | {
12 | test: /\.html$/,
13 | loader: __dirname + '/../../loader.js',
14 | options: {
15 | // $: 'jQuery',
16 | }
17 | },
18 | ],
19 | },
20 | externals: {
21 | jquery: 'jQuery',
22 | jqvm: true,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | webpack.config.js
2 | webpack.dev.config.js
3 | jqvm.png
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | The world's easiest reactive frontend view-model framework based on jQuery.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | [中文文档](./README_zh.md)
16 | [English](./README.md)
17 |
18 | ## :hear_no_evil: What's all the jQvm?
19 |
20 | JQvm is a library, a jQuery plugin, a frontend reactive view-model framework, which helps JavaScript developers who are familiar with jQuery code more quickly. Boring with React, Vue? Want to taste reactive programming in frontend? Believe me, if you have learned jQuery, you can setup a small application in 10 seconds!
21 |
22 | ## :rocket: Install
23 |
24 | ```
25 | npm i jqvm
26 | ```
27 |
28 | With modules system.
29 |
30 | ```js
31 | import jQuery from 'jquery'
32 | import { useJQuery } from 'jqvm'
33 |
34 | const $ = useJQuery(jQuery)
35 | ```
36 |
37 | You can use cdn of unpkg.
38 |
39 | ```html
40 |
41 |
42 | ```
43 |
44 | ## :zap: Fast implementation
45 |
46 | There is a small [demo](https://unpkg.com/jqvm/index.html), you can try it online.
47 |
48 | **step 1: template**
49 |
50 | You should define your template in your html.
51 |
52 | ```html
53 |
54 |
55 |
56 | {{title}}
57 |
58 | ```
59 |
60 | **step 2: initialize**
61 |
62 | You should create scripts like this:
63 |
64 | ```html
65 |
66 |
67 |
68 |
73 | ```
74 |
75 | Or
76 |
77 | ```js
78 | import jQuery from 'jquery'
79 | import { useJQuery } from 'jqvm'
80 |
81 | const $ = useJQuery(jQuery)
82 | $('#app')
83 | .vm({ title: 'Default Title' })
84 | .mount()
85 | ```
86 |
87 | **step 3: bind event listeners**
88 |
89 | Unlike vue.js, you should must bind event listeners in script, not in template.
90 |
91 | ```html
92 |
100 | ```
101 |
102 | Here, you call the `on` method and pass a action function to change `state`, and the view will be rerendered.
103 |
104 | ## :tada: API
105 |
106 | ### Exports
107 |
108 | ```js
109 | import {
110 | component,
111 | directive,
112 | filter,
113 | View,
114 | useJQuery,
115 | createStore,
116 | createAsyncComponent,
117 | } from 'jqvm'
118 | ```
119 |
120 | Or in browser:
121 |
122 | ```js
123 | const {
124 | component,
125 | directive,
126 | filter,
127 | View,
128 | useJQuery,
129 | createStore,
130 | createAsyncComponent,
131 | } = window.jqvm
132 | ```
133 |
134 | - component(name:string, compile:function, affect:function): global component register function
135 | - directive(name:string, compile:function, affect:function): global directive register function
136 | - filter(name:string, formatter:function): global filter register function
137 | - View: view constructor
138 | - useJQuery: you can use another version jquery by invoke this
139 |
140 | ### $.fn.vm
141 |
142 | JQVM is a jQuery plugin first at all, you should use code like this:
143 |
144 | ```js
145 | const view = $('#app').vm(initState)
146 | ```
147 |
148 | JQVM will treat html string in #app as template, so, it is recommended to use `template` tag to define template.
149 |
150 | The return value is a `view` object which has methods:
151 |
152 | - on(event, selector?, action): bind an action, notice that action function is different from jQuery.fn.on callback function, I will detail later
153 | - once(event, selector?, action): refer to jQuery's `one`
154 | - off(event, selector?, action?): unbind listener which is bound by `on`
155 | - mount(el?): mount view into DOM
156 | - unmount(): unmount view from DOM, `vm` is unusable until you invoke `mount` again
157 | - destroy(): unmount and clear bound actions, after you destroy, you can mount again, but actions should be bound again
158 | - update(nextState?): rerender, you can pass new state into `update()`, the new state will be merge into old state like react setState does.
159 | - find(selector): same as `$.fn.find`, select elements in view container
160 | - component(name, compile, affect): register component only for this vm
161 | - directive(name, compile, affect): register directive only for this vm
162 | - filter(name, formatter): register formatter only for this vm
163 | - fn(name, action?, patch?): register a function on `view`, when you not pass `func`, it means you want to get the function, `action` is the same usage as `on`
164 |
165 | The `mount` method can receive a selector or a jquery element.
166 |
167 | ```js
168 | const template = `
169 |
170 | {{title}}
171 |
172 | `
173 | $(template)
174 | .vm({ title: 'xxx' })
175 | .mount('div#app') // mount view to div#app
176 | ```
177 |
178 | When selector is passed into `mount`, the view will be rendered in the target element (replace with innerHTML). If selector is not passed, you should select a element in DOM and the view will be rendered after the selected element (as the beginning code does).
179 |
180 | `fn` define a new function on view:
181 |
182 | ```js
183 | $(..).vm({ .. })
184 | .fn('fnName', (state, ...args) => (e) => {
185 | ...
186 | })
187 | .fn('doSome', (state) => (xx) => {
188 | ...
189 | }, true)
190 | .on('click', '.some', function() {
191 | this.doSome('xx')
192 | })
193 | ```
194 |
195 | Now, let's look into `action` detail.
196 |
197 | ```js
198 | // a function which return a inner function
199 | // state: the current state in vm
200 | // ...args: those bind on fn in template
201 | function action(state, ...args) {
202 | const view = this // you can do like `view.unmount()`
203 |
204 | state.some = 'next' // `some` should be exisiting in state, and this will trigger rerendering later
205 | // if `some` is not in state, you should MUST use `state.$set('some', 'next')
206 | // async works, i.e. setTimeout(() => state.some = 'next', 1000)
207 | // if you change some instances which is not a plain object, you should invoke `view.update()` manually,
208 | // i.e. `state.myIns.name = 'new name'; view.update()`
209 |
210 | // handle function which is put into jQuery.fn.on as you did like `$('#app').on('click', handle)`
211 | // handle function is optional, when you do not return handle function, action will still be invoked when the event happens, but you have no idea to receive DOM event
212 | return function handle(e) {
213 | const el = this
214 | const $el = $(this)
215 | }
216 | }
217 |
218 | view.on('click', '.some', action)
219 | ```
220 |
221 | No matter you use `fn` in view or to a component by `@`, the `action` function is the same structure.
222 |
223 | ```html
224 |
225 |
226 |
232 | ```
233 |
234 | Inside events:
235 |
236 | - $mount: when you invoke `view.mount()` this event will be triggered
237 | - $unmount: when you invoke `view.unmount()`
238 | - $render: when inner content rendered
239 | - $change: when state change
240 |
241 | ```js
242 | $('#app')
243 | .vm({ name: 'some' })
244 | .on('$mount', state => {
245 | state.name = 'new name'
246 | })
247 | .mount()
248 | ```
249 |
250 | The changing of `state` object you receive in action function will trigger rerendering.
251 | And the scope in template is `state`, so when you write a `{{title}}` syntax in template, you are calling `state.title` in fact.
252 | `state` is only available in `on` action functions.
253 |
254 | *You should always change `state` instead of changing DOM to trigger UI changing. You should not change DOM in `action` function unless you know what you are doing!*
255 |
256 | It is created from `initState` which is received by `$('#app').vm(initState)`, `initState` can be one of:
257 |
258 | - object: a normal object which is used to be vm's default state.
259 | - function: which returns one of above
260 |
261 | *Notice, an instance of some class, for example `new Some()`, should not be passed in, only normal object supported.*
262 |
263 | When you pass a normal object, the original object will be changed by vm. This make it shared amoung different mounting.
264 | To prevent this, you can use a function to return an independent object in the function.
265 |
266 | ```js
267 | const view = $('#app').vm(function init() {
268 | return { title: 'xxx' }
269 | })
270 |
271 | view.mount()
272 | view.unmount() // destory DOM
273 | view.mount() // use `init` function to generate independent initState
274 | ```
275 |
276 | ## :dizzy: Directive
277 |
278 | You can invoke `directive` to create a new attribute.
279 |
280 | ```
281 | directive(name:string, compile:function, affect:function)
282 | ```
283 |
284 | - name: the tag name of the directive
285 | - compile($el, attrs): how to compile this component, should return undefined|$el|htmlstring
286 | - affect($el, attrs): do some side effects after whole template have been compiled, should return a function to abolish side effects, will be invoke after each compilation
287 |
288 | ```js
289 | const { directive } = window.jqvm
290 |
291 | directive('jq-link', function(el, attrs) {
292 | const link = attrs['jq-link']
293 | el.attr('href', link)
294 | // if you return a string, it will be used as this tag's new content
295 | // if you do not return anthing, `el` will be used as content
296 | })
297 | ```
298 |
299 | ```html
300 |
301 | link
302 |
303 | ```
304 |
305 | *Notice that, `el` is a copy element in `directive` and `component`, it is not in real DOM, so you should not bind events on it, binding will not work!*
306 |
307 | Example of `affect`:
308 |
309 | ```js
310 | directive('jq-src', null, function($el, attrs) {
311 | // here $el is real DOM element referer
312 | const attr = attrs['jq-src']
313 | const value = this.scope.interpolate(attr)
314 | $el.attr('src', value)
315 | })
316 | ```
317 |
318 | This is the source code of `jq-src`, by this operation, image will not be loaded when compiling, and will be loaded after insert into DOM.
319 |
320 | ```html
321 |
322 | ```
323 |
324 | You can even bind event listeners to $el:
325 |
326 | ```js
327 | directive('jq-on-click', null, function($el, attrs) {
328 | const callback = () => console.log('click')
329 | $el.on('click', callback)
330 | return () => $el.off('click', callback) // notice, you should must return function to abolish side effects
331 | })
332 | ```
333 |
334 | **BuiltIn Directives**
335 |
336 | Here are builtin directives:
337 |
338 | - `jq-if="!!exp"` whehter to show this tag
339 | - `jq-class="{ 'some-class': !!exp }"` whether patch classes to tag
340 | - `jq-value="exp"` only used on `input` `select` `textarea`
341 | - `jq-disabled="!!exp"` only used on `input` `select` `textarea` `button`
342 | - `jq-checked="!!exp"` only used on `input[type=checkbox]` `input[type=radio]`
343 | - `jq-selected="!!exp"` only used on `select > option`
344 | - `jq-bind="keyPath"` two way binding, only used on `input` `select` `textarea`, when user type in, the `keyPath` value of vm will be update automaticly
345 | - `jq-src="exp"` only used on `img`, you should always use jq-src instead of `src`
346 | - `jq-repeat` print serval times
347 | - `jq-on="event:fn"` bind event callback function
348 |
349 | The `jq-repeat` usage is a little complex:
350 |
351 | ```html
352 |
353 | {{index + 1}}
354 | {{value.name}}
355 | {{value.time}}
356 |
357 | ```
358 |
359 | Notice, `value,index` should have NO space inside, `,index` is optional.
360 |
361 | The `jq-on` directive should must work with `view.fn`, for example:
362 |
363 | ```html
364 | submit
365 |
366 |
371 | ```
372 |
373 | ## :clown_face: Component
374 |
375 | You can invoke `component` to create a new tag.
376 |
377 | ```
378 | component(name:string, view:View)
379 | ```
380 |
381 | - name: the tag name of the component
382 | - view: another view created by $.fn.vm
383 |
384 | ```js
385 | const { component } = window.jqvm
386 |
387 | const icon = $(` `)
388 | .vm(() => ({ type: 'eye' })) // notice here, you should use a function to return initState
389 |
390 | component('icon', icon)
391 | ```
392 |
393 | You should use a function to return initState, so that the state of component is alone.
394 |
395 | Now you can use this `icon` component in template:
396 |
397 | ```html
398 |
399 |
400 |
401 | ```
402 |
403 | When the component rendered, it will use `type="search"` to replace `type` state, so the final html is:
404 |
405 | ```
406 |
407 | ```
408 |
409 | **props**
410 |
411 | When you want to pass props into a component, you should know that:
412 |
413 | - `type="search"` normal string
414 | - `:type="'search'"` expression, read from state of current scope
415 | - `@change="fn"` emitter handler function, read from functions which registered by `fn`
416 |
417 | And, a very important thing: *only those properties on component's state will work (override inner state), others will have no effect, and you have no idea to get them.*. When the value of a property changes, the inner component will rerender with new value. For example:
418 |
419 | ```js
420 | const box = $(`...`).vm({ a: 1, b: 2 })
421 |
422 | const view = $(`
423 |
424 | `)
425 | .vm(() => ({ ... }))
426 | .component('my-box', box)
427 | // :a="2" will work, and c="xxx" will not work (has no effect)
428 | ```
429 |
430 | **emitter**
431 |
432 | Inside a component (sepcial view), you should call `view.emit` to emit a event. For example:
433 |
434 | ```js
435 | $(...).vm(() => ({ ... }))
436 | .fn('change', function(state) {
437 | return (e) => this.emit('change', e) // this -> view, emit is only in view instance
438 | })
439 | ```
440 |
441 | Usage:
442 |
443 | ```js
444 | this.emit(event, ...args)
445 | ```
446 |
447 | Then you can receive the event outside:
448 |
449 | ```html
450 |
451 | ```
452 |
453 | The handler function is an `action` function as metioned.
454 |
455 | Notice: component emitter is different from `jq-on` event. `jq-on` event is refer to DOM Event system, component emitter is just refer to a custom subscriber system.
456 |
457 | ## :bread: Filter
458 |
459 | A filter is a string formatter which used in template.
460 |
461 | ```html
462 |
463 | {{ price | number:2 }}
464 |
465 | ```
466 |
467 | ```js
468 | function number(value, fixed) {
469 | return value.toFixed(fixed)
470 | }
471 |
472 | view.filter('number', number)
473 | ```
474 |
475 | The first paramter of the function is the value receive from the previous before `|`.
476 |
477 | ## Store
478 |
479 | To share state among components, you may use a store to maintain the shared state:
480 |
481 | ```js
482 | // create store and share it between two components by passing `store` into `.vm(store)`
483 | const { createStore } = window.jqvm
484 | const store = createStore({ count: 10 })
485 | const componentA = $('{{count}} ')
486 | .vm(store)
487 | const componentB = $('count: {{count}}
')
488 | .vm(store)
489 |
490 | // using the two components
491 |
492 |
493 |
494 |
495 |
496 | $('#app').vm({})
497 | .component('comp-a', componentA)
498 | .component('comp-b', componentB)
499 | .mount()
500 | ```
501 |
502 | ## Async Component
503 |
504 | To split your code with unit by component, you can use `createAsyncComponent` to implement this.
505 |
506 | ```
507 | createAsyncComponent(loader:Function, callback:Function) -> compile
508 | ```
509 |
510 | - loader: Function to return a jQuery.Deferred or promise which contains a `then` method, a ESModule with `default` export a View instance or a View instance should be put in `then` callback
511 | - callback: invoke after the deferer resolved, you can visti `this` as current view in it
512 |
513 | Example:
514 |
515 | ```js
516 | // https://xxx/some-component.js
517 | export default $(`{{title}} `)
518 | .vm(() => ({ title: '' }))
519 |
520 | // main.js
521 | $('#app').vm(...)
522 | .component('some-component', createAsyncComponent(() => import('https://xxxx/some-component.js')))
523 | .mount()
524 | ```
525 |
526 | ```js
527 | // main.js
528 | $('#app').vm({ loading: true })
529 | .component(
530 | 'my-box',
531 | createAsyncComponent(
532 | () => $.get('https://xxxx/some-component.template.html')
533 | .then((html) => $(html).vm(() => ({ title: '' }))),
534 | function() {
535 | this.update({ loading: false })
536 | },
537 | ),
538 | )
539 | ```
540 |
541 | Or you can use AMD Module system to create a single component as a module to load, so that you can split your code easily.
542 |
543 | ## :see_no_evil: License
544 |
545 | MIT.
546 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 |
2 | 基于jQuery的超简单响应式开发框架
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | [中文文档](./README_zh.md)
16 | [English](./README.md)
17 |
18 | ## :hear_no_evil: 什么是jQvm?
19 |
20 | jQvm是一个jQuery插件,同时也是一个MVVM响应式视图层框架。它帮助熟悉jQuery的开发者实现更便捷的开发。你可能用过其他前端框架,但是,如果你的系统是基于jQuery的老系统,那么根本没法迁移,而使用这个插件,你就既能在原有系统基础上升级,同时又享受现代响应式编程的乐趣。相信我,如果你用过jQuery,你会在10秒内学会jQvm!
21 |
22 | ## :rocket: 安装
23 |
24 | ```
25 | npm i jqvm
26 | ```
27 |
28 | ```js
29 | import jQuery from 'jquery'
30 | import { useJQuery } from 'jqvm'
31 |
32 | const $ = useJQuery(jQuery)
33 | ```
34 |
35 | 或者直接使用CDN引入:
36 |
37 | ```html
38 |
39 |
40 | ```
41 |
42 | ## :zap: 快速上手
43 |
44 | 这里有一个小[demo](https://unpkg.com/jqvm/index.html),你可以提前在线试试。
45 |
46 | **第1步:模板**
47 |
48 | 你可以在HTML中定义好模板。
49 |
50 | ```html
51 |
52 |
53 |
54 | {{title}}
55 |
56 | ```
57 |
58 | **第3步:实例化**
59 |
60 | 接下来,你需要在脚本中实例化视图。
61 |
62 | ```html
63 |
64 |
65 |
66 |
71 | ```
72 |
73 | 或者:
74 |
75 | ```js
76 | import jQuery from 'jquery'
77 | import { useJQuery } from 'jqvm'
78 |
79 | const $ = useJQuery(jQuery)
80 | $('#app')
81 | .vm({ title: 'Default Title' })
82 | .mount()
83 | ```
84 |
85 | **第3步:事件监听**
86 |
87 | 你可以像使用jQuery的on一样,对视图内部的元素进行监听。
88 |
89 | ```html
90 |
98 | ```
99 |
100 | 在这段代码里面,你用`on`方法监听了`click`事件,同时在回调函数中,直接修改`state`触发界面更新。
101 |
102 | 你也可以使用内部指令`jq-on`来完成事件监听:
103 |
104 | ```html
105 |
106 | {{title}}
107 |
108 |
109 |
110 |
118 | ```
119 |
120 | 使用内置的`jq-on`指令,再配合通过`fn`方法定义的一个内部函数,就可以完成对`button`的点击事件监听。
121 |
122 | ## :tada: API
123 |
124 | > jQvm里面有两个概念:View和VM。我们调用`$('#app').vm({ a: 1 })`时会在上下文中建立一个VM,返回的是一个View的实例。VM对开发者不可见,它有一个state作为渲染界面的数据,这个state可在事件监听或方法调用时作为函数的参数被拿到。View实例是开发者使用的主要的对象,这个view提供了一堆方法,在大部分情况下都可以被获取,具体看下文。
125 |
126 | ### 导出接口
127 |
128 | 如果你在模块系统中使用,那么如下引入需要的接口:
129 |
130 | ```js
131 | import {
132 | component,
133 | directive,
134 | filter,
135 | View,
136 | useJQuery,
137 | createStore,
138 | createAsyncComponent,
139 | } from 'jqvm'
140 | ```
141 |
142 | 如果在浏览器中使用,可以快速的按下面的方法引入:
143 |
144 | ```js
145 | const {
146 | component,
147 | directive,
148 | filter,
149 | View,
150 | useJQuery,
151 | createStore,
152 | createAsyncComponent,
153 | } = window.jqvm
154 | ```
155 |
156 | `jqvm`是挂载在window对象上的一个静态属性,它提供了一系列的对象给你使用。其中包含了:
157 |
158 | - component(name:string, compile:function, affect:function): 注册全局组件
159 | - directive(name:string, compile:function, affect:function): 注册全局指令
160 | - filter(name:string, formatter:function): 注册全局过滤器
161 | - View: View构造器。基本上不会用到,只会用来作为一些判断依据。
162 | - useJQuery: 你可以使用另外一个版本的jQuery,一般只在模块系统中使用
163 | - createStore: 用于创建一个store的函数
164 | - createAsyncComponent: 用于创建一个异步组件的函数
165 |
166 | ### $.fn.vm
167 |
168 | jQvm是一个jQuery插件,所以使用的时候,像其他插件一样,你可以这么用:
169 |
170 | ```js
171 | const view = $('#app').vm(initState)
172 | ```
173 |
174 | jQvm把`#app`内部的HTML字符串当作模板,用它们来构建界面。但是,如果你直接在HMTL中写标签,会被渲染到界面上,所以,jQvm强制你用``来写模板,这样就不会在html文件加载好时渲染模板元素。
175 |
176 | `.vm`方法返回一个`view`,这个view包含来如下方法:
177 |
178 | - on(event, selector?, action): 绑定事件,传入对应的行为函数
179 | - once(event, selector?, action): 对应jQuery的one绑定
180 | - off(event, selector?, action?): 解除通过`on`或`once`绑定的事件回调
181 | - mount(el?): 将view挂载到某个节点上,当不传el的时候,挂载到``后面
182 | - unmount(): 将view从DOM中卸载, 但是需要注意,卸载后view并没有被销毁,你可以再次执行mount来挂载
183 | - destroy(): 销毁,卸载并且释放内存,之后不能再调用mount或其他方法进行操作
184 | - update(nextState?): 更新,你可以传入一个对象,这个对象将被合并到当前state上,作为下一次渲染的数据
185 | - find(selector): 和jQuery的find一样的效果,用于找到view渲染出来的DOM内部的节点,一般在回调函数action内使用
186 | - component(name, compile, affect): 注册组件到当前vm
187 | - directive(name, compile, affect): 注册指令到当前vm
188 | - filter(name, formatter): 注册过滤器到当前vm
189 | - fn(name, action): 在当vm上定义一个名为name的行为函数
190 |
191 | 接下来我们来看下mount接收参数的使用方法:
192 |
193 | ```js
194 | const template = `
195 |
196 | {{title}}
197 |
198 | `
199 | // 这里是jQuery的用法,支持传入字符串作为模板
200 | $(template)
201 | .vm({ title: 'xxx' })
202 | .mount('div#app') // 将view挂载到div#app
203 | ```
204 |
205 | 如果你采用了这种字符串模板的形式,那么mount的时候必须挂载到一个具体的节点。但是,如果你是基于HTML中的``标签创建模板,那么可以不用传,mount会把渲染结果挂载在对应的那个``标签后面,这样可以根据你页面中template的位置来决定布局。
206 |
207 | jQvm试图保留jQuery的编程风格,并在其基础上增加一些灵活性,因此,对于事件绑定的函数会和jQuery中的函数稍有不同。在jQvm中,我们多了vm这个部分,因此,我们进行事件处理时,常常是为了修改state,通过state的变化来触发界面的更新。因此,在jQvm的事件绑定函数中,我们首先暴露出state,在函数中任何对state的变更都会引起重新渲染。我们把这个函数称为“行为函数”。*行为函数将在下文“事件系统”中详细讲解。*
208 |
209 | ```js
210 | $().vm({ time: Date.now() })
211 | .on('click', '.button', (state) => {
212 | state.time = Date.now()
213 | })
214 | ```
215 |
216 | > 你应该通过修改state来达到界面更新的效果,而非通过直接在方法内操作DOM的方式来更新界面。
217 |
218 | 通过`fn`定义的函数,往往只会配合事件来使用,而不是真的定义了一个函数。如果你需要复用某个逻辑,应该考虑将函数在vm之外定义,然后在具体的某个action中调用该函数,例如:
219 |
220 | ```js
221 | function check(x, y) {
222 | return x > y
223 | }
224 | $(...).vm(...)
225 | .fn('submit', (state) => () => {
226 | if (check(state.x, state.y)) {
227 | ...
228 | }
229 | })
230 | ```
231 |
232 | ## :dizzy: 指令
233 |
234 | 你需要调用`directive`来注册指令。指令是指特殊的一些元素属性。例如内置的指令`jq-on`,就是让你的某个元素拥有特定能力的指令。
235 |
236 | ```
237 | directive(name:string, compile:function, affect?:function)
238 | ```
239 |
240 | - name: 指令名称
241 | - compile($el, attrs): 编译期对节点的处理方法,必须返回undefined|$el|htmlstring
242 | - affect($el, attrs): 渲染结束后对节点的处理方法,你可以在这里绑定事件,但是注意,如果你绑定了事件,必须返回一个函数,这个函数用于解除事件绑定
243 |
244 | ```html
245 |
246 | link
247 |
248 |
249 |
255 | ```
256 |
257 | ```html
258 |
259 |
260 |
267 | ```
268 |
269 | **内置指令**
270 |
271 | - `jq-if="!!exp"` 根据条件渲染当前节点
272 | - `jq-class="{ 'some-class': !!exp }"` 根据条件决定当前节点是否要加入某些class
273 | - `jq-value="exp"` 动态设定值,仅限`input` `select` `textarea`使用
274 | - `jq-disabled="!!exp"` 动态设定disabled属性,仅限`input` `select` `textarea` `button`
275 | - `jq-checked="!!exp"` 动态设定checked属性,仅限`input[type=checkbox]` `input[type=radio]`
276 | - `jq-selected="!!exp"` 动态设定selected属性,仅限`select > option`
277 | - `jq-bind="keyPath"` 双向数据绑定,仅限 `input` `select` `textarea`, 当用户在输入框输入时,state上的这个属性自动更新值,当state的这个属性值在另外一个地方被修改时,输入框的内容也跟随变化
278 | - `jq-src="exp"` 动态加载src,仅限`img`,而且你应该尽可能的使用`jq-src`替代原始的`src`属性,因为原始的`src`会立即加载图片,但很可能此时你的state上还没有给出正确的值
279 | - `jq-id="exp"` 动态设定id属性
280 | - `jq-repeat` 循环输出,下文详细讲
281 | - `jq-on="event:fn"` 绑定事件
282 |
283 | 循环输出指令`jq-repeat`使用起来比较复杂:
284 |
285 | ```html
286 |
287 | {{index + 1}}
288 | {{value.name}}
289 | {{value.time}}
290 |
291 | ```
292 |
293 | *注意,`value,index`中间不能有空格,`,index`和`traceby value.id`是可选的。*
294 |
295 | ## :clown_face: 组件
296 |
297 | 你需要调用`component`来注册组件。组件是指具有自定义名称的元素。例如你可以自定义一个`my-icon`标签。
298 |
299 | ```html
300 |
301 |
302 |
303 | ```
304 |
305 | 那么要怎么来实现呢?先看下`component`的用法。
306 |
307 | ```
308 | component(name:string, compile:Function|view:View, affect?)
309 | ```
310 |
311 | - name: 新标签的名字
312 | - compile: 编译期处理函数
313 | - view: 不传compile函数,而是传一个view实例,那么这个组件将在标签内完成view对应的vm规定的渲染逻辑
314 | - affect: 渲染结束后的处理函数
315 |
316 | 我们先来看下`my-icon`怎么实现吧:
317 |
318 | ```js
319 | component('my-icon', ($el, attrs) => {
320 | const { type } = attrs
321 | return ` `
322 | })
323 | ```
324 |
325 | 这样就实现了一个简单的组件。对于那种静态的简单组件而言,使用compile非常有用。但是,如果是动态的,有内部状态的,就需要使用view作为组件。
326 |
327 | ```js
328 | const component = $(`
329 |
330 | name: {{name}}
331 |
332 | age: {{age}}
333 | Grow
334 |
335 | color: {{color}}
336 | emit
337 |
338 | `)
339 | .vm(() => ({ name: 'tidy', age: 1, color: 'white' }))
340 | .fn('grow', state => state.age ++)
341 | .fn('change', function(state) {
342 | const color = ['blue', 'yellow', 'red', 'white'].filter(item => item !== state.color)[parseInt(Math.random() * 100) % 3]
343 | this.emit('change', color)
344 | })
345 | $('#component')
346 | .vm({ show: true, color: 'none' })
347 | .component('my-dog', component) // <-- 注册view为组件
348 | .fn('toggle', state => state.show = !state.show)
349 | .fn('change', (state) => (color) => {
350 | state.color = color
351 | })
352 | .mount()
353 | ```
354 |
355 | 上面这段代码中,我们通过`$().vm()`创建了一个view,注意,我没有在这个view的末尾调用.mount进行挂载,另外`.vm()`处,我们传入了一个返回initState的函数,因为作为组件,可能会被实例化多次,所以需要有独立的state数据。如果你这里不使用函数,而是直接传入对象的话,你会发现多个组件实例会相互影响,这是我们不愿意看到的。
356 |
357 | **props**
358 |
359 | 组件接收外部通过属性的方式传入数据,有3种形式:
360 |
361 | - `type="search"` 接收到普通的字符串
362 | - `:type="'search'"` :开头,表达式,组件接收到的是计算后的值
363 | - `@change="fn"` @开头,组件事件系统的回调函数,前文讲过
364 |
365 | 其中,第一种和第二种有可能会覆盖组件内部vm的state上的属性值。但是需要注意,只有那些在内部initState声明的属性,会被覆盖,没有在initState中声明的属性,不会被传入到组件内部。举个例子:
366 |
367 | ```js
368 | const box = $(`...`).vm({ a: 1, b: 2 })
369 |
370 | const view = $(`
371 |
372 | `)
373 | .vm(() => ({ ... }))
374 | .component('my-box', box)
375 | // :a="2" 正常工作,但是c="xxx"没有任何作用(对内部而言)
376 | ```
377 |
378 | *需要注意,`:`开头的属性,仅限于自定义组件,普通HTML元素不支持。`type="{{type}}"`这种形式也可以基于状态动态设定值,但是它和`:type="type"`不同的是,前者传递给组件内部的是纯字符串,而后者传递进去的是表达式的结果,也就可能是数字或对象。*
379 |
380 | **hoist组件**
381 |
382 | 前面的组件定义方法,会让最终的渲染结果被放置在组件标签内部。例如最终渲染为`...
`,如果你想让渲染结果直接替换``需要怎么办呢?你需要在template上添加一个`hoist`属性,例如下面这样做:
383 |
384 | ```js
385 | const component = $(`
386 |
387 | ...
388 |
389 | `).vm({ ... })
390 | ```
391 |
392 | 你只需要在模板中,直接使用非template作为顶层标签,它就会在渲染时替换组件标签``完成渲染。
393 |
394 | ## 事件系统
395 |
396 | jQvm内部存在两套事件系统,不过在用法上它们是一致的,在使用时不用担心写错,但是你必须理解这两套事件系统之后,才能避免在使用过程中出现不可预期的结果。
397 |
398 | ### 使用方法
399 |
400 | ```js
401 | // 第一种:通过view.on()绑定
402 | $().vm()
403 | // 回调函数是可选的
404 | .on('事件名', '子元素选择器', 行为函数(state) => 回调函数?(e))
405 |
406 | // 第二种:通过jq-on绑定
407 | // 先在模板中调用
408 |
409 | // 然后使用view.fn对行为函数进行定义
410 | $().vm()
411 | // 回调函数是可选的
412 | .fn('行为函数', (state, 参数列表) => 回调函数?(e))
413 |
414 | // 第三种:通过@对组建进行事件绑定
415 | // 在调用组件时绑定事件
416 | // 这里的行为函数,在当前调用该组件的视图里面定义
417 |
418 | // 然后在组件内部(注意是组件内部)进行事件抛出
419 | $().vm()
420 | // 在组件内部可能会使用jq-on来使用下面这个定义的行为函数
421 | // 注意,此处的函数必须是function函数,而不能是箭头函数,因为内部要使用this
422 | .fn('某个内部行为函数', function() {
423 | // 要抛出的事件名,对应@后面的事件名
424 | // 参数列表对应@后面的参数列表
425 | this.emit('事件名', 参数列表)
426 | })
427 | ```
428 |
429 | 上面列举了jQvm中的三种事件使用场景,只有这三种!
430 |
431 | ### 行为函数
432 |
433 | 和jQuery的事件回调函数不同,jQvm的行为函数由两层柯里函数组成。具体如下:
434 |
435 | ```js
436 | // state: 当前vm中对应的state
437 | // ...args: 参数列表,对应上面行为函数后面的参数列表
438 | function action(state, ...args) {
439 | // this指向view
440 | // 你可以在这里执行类似`view.unmount()`这样的操作
441 | const view = this
442 |
443 | state.some = 'next' // `some`必须在initState中定义好,否则不会有响应式更新界面效果
444 | // 当然,jQvm提供了解决办法,你可以通过调用`state.$set('some', 'next')`来添加那些没有在initState中定义的属性
445 | // 修改state触发更新支持异步,例如setTimeout(() => state.some = 'next', 1000)也是正常工作的
446 | // 如果你自己写了一个类,并且把它的实例作为state上的属性,那么修改这个实例是没有办法触发更新的,你需要手动调用`view.update()`来触发更新,
447 | // 例如`state.myIns.name = 'new name'; view.update()`
448 |
449 | // 如果是用于事件绑定,action的返回值也必须是一个函数,这个函数将被作为事件的回调函数,你可以在它里面接收event对象作为参数
450 | // 不过,handle函数是可选的,不返回任何内容也是可以的,只是这样你就无法拿到event对象。
451 | // 可以对接jquery的事件系统,你甚至可以通过jquery的trigger来触发它
452 | return function handle(e) {
453 | const el = e.target
454 | const $el = $(this) // el === this
455 | }
456 | }
457 | ```
458 |
459 | ### 举例说明
460 |
461 | **第一种**
462 |
463 | ```html
464 |
465 | {{title}}
466 | change
467 |
468 |
469 |
482 | ```
483 |
484 | 上面的这种方式通过类似jquery的事件绑定形式,直接接管DOM的点击事件。
485 |
486 | **第二种**
487 |
488 | ```html
489 |
490 | {{title}}
491 |
492 |
493 |
494 |
502 | ```
503 |
504 | 上面这种方式通过在模板中使用jq-on进行事件绑定,可以比较好的传递一些局部的参数进来。
505 |
506 | **第三种**
507 |
508 | ```html
509 |
510 | {{title}}
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
532 | ```
533 |
534 | 上面这种方式演示了使用@来承接组件抛出的事件。它其实有两个部分,第一个部分是component的部分,其中核心代码是`this.emit('change', id)`,用来抛出change事件;另一部分是app中使用`@change`来接住component中抛出的该事件,承接时,通过handleChange(id)接住了由emit抛出来的id值。
535 |
536 |
537 | ### 内置事件
538 |
539 | 除了组件的自定义的事件和DOM事件,jQvm的vm内也有一些事件,它们是:
540 |
541 | - $init: 实例化view时被触发,整个生命周期只会被触发一次
542 | - $mount: 调用`view.mount()`时触发
543 | - $unmount: 调用`view.unmount()`时触发
544 | - $render: view的内容被完全渲染后触发
545 | - $change: state变化后触发
546 | - $destroy: view被销毁时触发(仅支持手动调用view.destroy来销毁view)
547 |
548 | 这些内置事件需要使用`on`来监听:
549 |
550 | ```js
551 | $('#app')
552 | .vm({ name: 'some' })
553 | .on('$mount', state => {
554 | state.name = 'new name'
555 | })
556 | .mount()
557 | ```
558 |
559 | *另外,需要注意,凡是以`$`开头的事件名将被认为是内部的特殊事件,其回调函数将被丢弃,你也不能自己在DOM节点上通过on, once等自行注册此类事件。*
560 |
561 | ## :bread: 过滤器
562 |
563 | 过滤器是用来在模板中处理输出结果的处理器。
564 |
565 | ```html
566 |
567 | {{ price | number:2 }}
568 |
569 | ```
570 |
571 | ```js
572 | function number(value, fixed) {
573 | return value.toFixed(fixed)
574 | }
575 |
576 | view.filter('number', number)
577 | ```
578 |
579 | `|`是管道标识符,在很多系统中都有这种用法。简单说,|前面的值,将被|后面的过滤器处理,处理结果又会被再后面的过滤器处理。最后一个过滤器处理输出的结果就是最终的结果。
580 |
581 | ## :construction: 插件
582 |
583 | 我们可以通过view的plugin方法来完成一些复杂的集合处理,可以让我们把一些逻辑复杂的但关系紧密的操作,集合在一起,提供给view使用。你可以理解为插件就是多个view方法调用的封装。
584 |
585 | ```js
586 | function somePlugin(context) {
587 | // 返回生命周期事件的集合,在这些事件节点上,对view作统一处理
588 | return {
589 | $init: () => {
590 | const { view, state } = context
591 | ...
592 | },
593 | $destroy: () => {
594 | ...
595 | },
596 | }
597 | }
598 |
599 | $(...).vm({ ... })
600 | .plugin(somePlugin)
601 | ```
602 |
603 | 插件本身是一个函数,返回一个以view生命周期事件(以$开头的内置事件)为key的对象,并规定对应生命周期事件要做的事情。
604 |
605 | ## 状态管理
606 |
607 | 有些情况下,你不想通过props传递的方式把状态从上往下传递,你希望通过一个桥梁,方便的在多个组件之间共享状态。此时,你可以借助`createStore`来创建一个状态管理器。具体如下使用:
608 |
609 | ```js
610 | import { createStore } from 'jqvm'
611 | ```
612 |
613 | 或者
614 |
615 | ```js
616 | const { createStore } = window.jqvm
617 | ```
618 |
619 | 然后创建一个状态管理器:
620 |
621 | ```js
622 | const store = createStore({ count: 0 }, {
623 | onChange(keyPath, value) {
624 | // 此处用于收集
625 | },
626 | drive(update) {
627 | // 此处用于回放
628 | update(state => state.count ++)
629 | },
630 | })
631 | ```
632 |
633 | 它有两个参数:
634 |
635 | - initState: object 初始状态
636 | - options:
637 | - onChange(keyPath:string[], value:any) 当状态发生变化时被调用执行
638 | - drive(update:Function) 当一个组件被第一次挂载时执行,参数update用法和view.update一致。通过drive参数,你可以实现根据收集到的变化进行回放。
639 |
640 | 通过上面的步骤,你创建了一个store,接下来,将该store作为`.vm(store)`参数进行使用。
641 |
642 | ```js
643 | const componentA = $('{{count}} ')
644 | .vm(store)
645 | const componentB = $('count: {{count}}
')
646 | .vm(store)
647 | ```
648 |
649 | 经过上面步骤,componentA和componentB拥有同一个state的引用,也就是说,它们内部操作state时,两个组件都会被同时更新。
650 |
651 | 最后,就是在其他地方使用这两个组件。
652 |
653 |
654 | ## 异步组件
655 |
656 | 通过异步组件,你可以很好的实现代码分割,起到一定的提升性能的效果。
657 | 你需要使用`createAsyncComponent`来实现一个异步组件。
658 |
659 | ```
660 | createAsyncComponent(loader:Function, callback:Function) -> compile
661 | ```
662 |
663 | - loader: 函数,用于返回一个含有`.then`方法的异步对象,可以是原生的`Promise`对象,也可以是`jQuery.Deferred`对象。在`then`中返回的结果可以是一个ES模块(必须包含`default`接口,并且`default`将被作为结果使用),或者是一个普通的结果。无论是`default`接口,还是结果对象本身,都必须是一个`View`的实例。通过下面的例子你可以理解这一描述。
664 | - callback: 异步模块加载完成后调用执行。你可以在该函数内通过`this`访问当前vm中的view。
665 |
666 | 例子:
667 |
668 | ```js
669 | // https://xxx/some-component.js
670 | export default $(`{{title}} `)
671 | .vm(() => ({ title: '' }))
672 |
673 | // main.js
674 | $('#app').vm(...)
675 | .component('some-component', createAsyncComponent(() => import('https://xxxx/some-component.js')))
676 | .mount()
677 | ```
678 |
679 | ```js
680 | // main.js
681 | $('#app').vm({ loading: true })
682 | .component(
683 | 'my-box',
684 | createAsyncComponent(
685 | () => $.get('https://xxxx/some-component.template.html')
686 | .then((html) => $(html).vm(() => ({ title: '' }))),
687 | function() {
688 | this.update({ loading: false })
689 | },
690 | ),
691 | )
692 | ```
693 |
694 | 通过上面的代码,你可以发现,关键在于你需要异步返回一个View实例给createAsyncComponent。
695 |
696 | 通过这样的操作,你可以把某些组件单独放到系统外给系统使用,从而减少当前系统代码量,提升性能。
697 | 除了上面使用`import()`之外,其实,你也可以利用AMD模块系统来达到同样的效果(AMD兼容较低版本的浏览器)。
698 |
699 | ## slot
700 |
701 | 你可以通过slot来实现内容穿越。简单说,你在组件内模板中使用` `,那么当你在外面使用该组件时,可以让组件渲染结果包含传递结果。举个例子:
702 |
703 | 我们在组件内如此定义模板:
704 |
705 | ```html
706 |
707 |
708 | {{title}}
709 |
710 |
711 |
712 | ```
713 |
714 | 在外部这样使用组件:
715 |
716 | ```html
717 | 文章内容
718 | ```
719 |
720 | 此时,` `将被替换为"文章内容"。
721 |
722 | 在外部传递内容为外部编译的结果。例如:
723 |
724 | ```html
725 | 内容:{{content}}
726 | ```
727 |
728 | 此时,`content`为外部vm中state的content属性值,而非组件内的state属性值。但是,因为开发者可以自己在定义组件时对组件进行编译,所以在不同的组件中,这一效果会稍有不同,部分组件的编译逻辑不同,会同时使组件内和组件外的state对传递的内容生效,这根据组件的开发者自己决定。
729 |
730 | ## jqvm-loader
731 |
732 | 利用jqvm-loader,你可以实现SFC(单文件组件),结合异步组件,你可以将一些复杂的组件拆分出去。例如,你可以这样写一个组件
733 |
734 | ```html
735 |
736 |
737 |
{{title}}
738 | {{content}}
739 |
740 |
741 |
742 |
751 | ```
752 |
753 | template上的`hoist`表示组件将作为hoist组件,最终渲染结果中,直接用模板内容替换组件标签。如果传入hoist,那么内部只允许一个顶层标签。*注意,`template`上的`hoist`属性仅在loader编译系统中有用,在运行时定义组件时无效。*
754 |
755 | 将这个htm/html文件放置在你的项目目录下,然后在webpack中如下使用:
756 |
757 | ```
758 | {
759 | module: {
760 | rules: [
761 | {
762 | test: /\.html$/,
763 | resourceQuery: /jqvm/, // 可选的,当你使用这个规则,那么只有 import .. from './some.html?jqvm' 才走jqvm/loader编译
764 | use: [
765 | {
766 | loader: 'babel-loader', // 这个loader的功能很简单,就是把一个html文件解析出来之后,编译为一个ESModule,这个ESModule就是一个jqvm的组件,再交给babel去进行编译。
767 | },
768 | {
769 | loader: 'jqvm/loader', // jqvm-loader的位置在jqvm包下面
770 | options: {
771 | $: true, // 可选,如果$为true,那么生成的组件代码中,不会引入jquery和jqvm,直接使用全局的$对象,如果传入一个字符串,比如'jQuery',那么会用这个字符串作为全局变量赋值给$作为jquery的引用
772 | },
773 | },
774 | ],
775 | }
776 | ]
777 | }
778 | }
779 | ```
780 |
781 | 你可以通过webpack的能力如下实施:
782 |
783 | ```js
784 | import { createAsyncComponent } from 'jqvm'
785 | const SomeComponent = createAsyncComponent(() => import('./components/some-component.html'))
786 | ```
787 |
788 | 这样既可以在html中撰写一个组件,同时又可以把这个组件代码拆分出去。
789 | 如果你不想使用webpack,而是想把html编译成独立的js文件,可以这样:
790 |
791 | ```js
792 | const { compile } = require('jqvm/loader')
793 | const content = compile(fs.readFileSync(__dirname + '/component.html'), options) // options.$ = false|true|'jQuery'
794 | fs.writeFileSync(.., content)
795 | ```
796 |
797 | 这样就可以获得一个独立的js文件了。
798 |
799 | ## 路由
800 |
801 | ```js
802 | const { createRouter } = window.jqvm
803 | const router = createRouter({ mode: 'history' })
804 |
805 | $(`
806 |
807 |
808 | home
809 |
admin
810 |
811 |
812 | admin
813 |
back
814 |
815 |
816 | `).vm({})
817 | .plugin(router) // <-- 以插件的形式挂载路由
818 | .mount('#app')
819 | ```
820 |
821 | 注意点:
822 |
823 | - `createRouter({ mode: 'history' | 'hash', baseUri: '/xxx' })` mode 默认是 hash,可以都不传
824 | - `view.plugin(router)`
825 | - `{{$route.params.id}}` state上会多出一个$route,可以在视图内使用
826 | - `jq-route="exp"` 用于匹配路由的指令,由于它是表达式,因此你可以传入正则表达式进行匹配
827 | - `jq-navigate` 用于规定跳转方法的指令,只能作用于a标签上,可选方法有:`push | replace | open | back | forward` 默认是 `push`
828 | - `this.$router` view上会多出一个$router,用于在视图内的函数里面读取当前router
829 | - `.on('$route', (state, url) => ...)` 监听 `$route` 事件来感知路由变化
830 |
831 | 对于`this.$router`,它拥有以下方法:
832 |
833 | - getUrl(): string 获取当前路由指向的url
834 | - getLocation(): { pathname: string, search: string, params: object } 其中params是对search解析后得到的对象
835 | - setParams(params): void 调整当前search参数,没有涉及的不发生任何变化
836 | - isMatch(regexp): boolean 用于判断当前路由的url是否匹配某个路径
837 | - navigate(type: push | replace | open | back | forward, to: string): void 导航到一个新地址
838 |
839 | 我们提供的是及其微小的路由导航系统,没有复杂的逻辑,也没有考虑复杂的url设计,只提供了通过search部分来传参,如果你想要传递某些对象的id,必须通过search部分传递。
840 |
841 | ## :see_no_evil: 开源协议
842 |
843 | MIT.
844 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | shown
26 | hidden
27 | toggle
28 |
29 |
30 |
34 |
35 |
39 |
40 |
70 |
71 |
72 |
73 | {{index}}
74 | {{item.name}}
75 |
76 |
77 |
78 |
79 |
80 | {{keep}}
81 | change keep
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | {{count}}
90 | +
91 | -
92 |
93 |
94 |
95 |
96 |
97 | {{content}}
98 | refresh (wait)
99 |
100 |
101 |
189 |
190 |
191 |
192 |
193 | loading
194 |
195 |
196 |
197 |
208 |
209 |
210 |
211 |
212 | content never changed by prevent in $change
213 | {{title}}
214 | change
215 |
216 |
225 |
226 |
227 |
230 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
252 |
253 |
254 |
255 | Component
256 |
257 |
258 |
259 | Use View as a component:
260 | toggle text
261 |
262 | color: {{color}}
263 |
264 |
265 |
266 |
294 |
295 |
296 |
297 |
298 | hoisted component
299 |
300 | plus
301 |
302 |
320 |
321 |
322 | jq-if on component
323 |
324 | Loading
325 |
326 | toggle
327 |
328 |
335 |
336 |
337 |
338 | Shared Store
339 |
340 |
341 | {{name}}
342 | {{age}}
343 | {{body.count}}
344 | grow
345 | reset
346 | plus
347 | gt 10
348 |
349 |
350 | {{name}}
351 | {{age}}
352 | grow
353 |
354 | inner:
355 |
356 |
357 |
385 |
386 |
387 |
388 | Async Component
389 |
390 | Async Load Content:
391 | Loading
392 |
393 |
394 |
406 |
407 |
408 |
409 | Slot
410 |
411 |
412 | {{title + '111'}}
413 | {{title}}
414 |
415 | change
416 |
417 |
429 |
430 |
431 |
432 | Static
433 |
434 | static content:
435 |
436 | {{content}}
437 |
438 |
439 | change
440 | operate
441 |
442 |
460 |
461 |
462 |
463 | Router
464 |
465 |
466 | home
467 |
admin
468 |
469 |
470 | admin
471 |
showId {{$route.params.id}}
472 |
changeId
473 |
back
474 |
475 |
476 |
500 |
501 |
502 |
503 | Debug
504 |
505 |
509 |
510 |
531 |
532 |
533 |
534 |
--------------------------------------------------------------------------------
/jqvm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tangshuang/jqvm/a453df9154b8c9887409521a2c72170382f644ca/jqvm.png
--------------------------------------------------------------------------------
/loader.js:
--------------------------------------------------------------------------------
1 | function compile(content, options = {}) {
2 | const [_t, template] = content.match(/([\s\S]+)<\/template>/m) || []
3 | const [_h, hoist] = content.match(/([\s\S]+)<\/template>/m) || []
4 | const [_s, script] = content.match(/