Skip to content

Commit d6351c6

Browse files
authored
feat: add createDiffWatcher function (#1)
* feat: add diff watcher * test: add basic test cases for diff watcher * test: add a test case for multiple blocks diff watcher * feat: add `add` and `remove` methods on watcher * docs: add createDiffWatcher
1 parent d41639d commit d6351c6

File tree

7 files changed

+381
-11
lines changed

7 files changed

+381
-11
lines changed

README.md

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ console.log(res.script.calcGlobalOffset(5))
2626

2727
## References
2828

29-
* `parseComponent(code: string): SFCDescriptor`
29+
### `parseComponent(code: string): SFCDescriptor`
3030

31-
This is almost same as `vue-template-compiler`'s `parseComponent`. `SFCDescriptor` is looks like following:
31+
This is almost same as `vue-template-compiler`'s `parseComponent`. `SFCDescriptor` is looks like following:
3232

33-
```ts
34-
interface SFCDescriptor {
35-
template: SFCBlock | null
36-
script: SFCBlock | null
37-
styles: SFCBlock[]
38-
customBlocks: SFCBlock[]
39-
}
40-
```
33+
```ts
34+
interface SFCDescriptor {
35+
template: SFCBlock | null
36+
script: SFCBlock | null
37+
styles: SFCBlock[]
38+
customBlocks: SFCBlock[]
39+
}
40+
```
4141

42-
The `SFCBlcok` is similar to `vue-template-compiler` one too, but having additional helper methods.
42+
The `SFCBlcok` is similar to `vue-template-compiler` one too, but having additional helper methods.
4343

4444
### Additional Helpers of SFCBlock
4545

@@ -57,6 +57,55 @@ console.log(res.script.calcGlobalOffset(5))
5757

5858
On the above SFC, if you provide `5` to `template.calcGlobalOffset` which indicates the position from the beggining of template block, it will return `38` which is the position from the beggining of the file.
5959

60+
### `createDiffWatcher(): SFCDiffWatcher`
61+
62+
Create a watcher object which will detect each SFC block's diff. `SFCDiffWatcher` has following methods:
63+
64+
* `add(filename: string, content: string): void`
65+
* `remove(filename: string): void`
66+
* `diff(filename: string, content: string): SFCDiff`
67+
68+
You can add/remove SFC file to the watcher by using `add`/`remove` methods. Then you obtain each SFC block's diff by using `diff` method. It returns an object having some methods which you can register callbacks that will called when the corresponding blocks are changed.
69+
70+
Example:
71+
72+
```js
73+
const { createDiffWatcher } = require('vue-sfc-parser')
74+
const fs = require('fs')
75+
const chokidar = require('chokidar')
76+
77+
const watcher = createDiffWatcher()
78+
79+
chokidar
80+
.watch('**/*.vue')
81+
.on('add', filename => {
82+
watcher.add(filename, fs.readFileSync(filename, 'utf8'))
83+
})
84+
.on('unlink', filename => {
85+
watcher.add(filename)
86+
})
87+
.on('change', filename => {
88+
watcher
89+
.diff(filename, fs.readFileSync(filename, 'utf8'))
90+
.template(template => {
91+
console.log(template.content)
92+
})
93+
.script(script => {
94+
console.log(script.content)
95+
})
96+
.styles(styles => {
97+
styles.forEach(s => {
98+
console.log(s.content)
99+
})
100+
})
101+
.customBlocks('block-name', blocks => {
102+
blocks.forEach(b => {
103+
console.log(b.content)
104+
})
105+
})
106+
})
107+
```
108+
60109
## License
61110

62111
MIT

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"devDependencies": {
5353
"@types/jest": "^22.1.3",
5454
"@types/lodash.mapvalues": "^4.6.3",
55+
"@types/node": "^9.4.7",
5556
"jest": "^22.4.2",
5657
"prettier": "1.11.0",
5758
"ts-jest": "^22.4.0",

src/diff-watcher.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import assert = require('assert')
2+
import { SFCBlock, parseComponent, SFCDescriptor } from './index'
3+
4+
export class SFCDiffWatcher {
5+
private prevMap: Record<string, SFCDescriptor> = {}
6+
7+
add(filename: string, content: string): void {
8+
this.prevMap[filename] = parseComponent(content)
9+
}
10+
11+
remove(filename: string): void {
12+
delete this.prevMap[filename]
13+
}
14+
15+
diff(filename: string, content: string): SFCDiff {
16+
assert(
17+
this.prevMap.hasOwnProperty(filename),
18+
'must call `add` before calling `diff`'
19+
)
20+
21+
const prev = this.prevMap[filename]
22+
const curr = (this.prevMap[filename] = parseComponent(content))
23+
return new SFCDiff(prev, curr)
24+
}
25+
}
26+
27+
export class SFCDiff {
28+
constructor(private prev: SFCDescriptor, private curr: SFCDescriptor) {}
29+
30+
template(cb: (block: SFCBlock | null) => void): this {
31+
const prev = this.prev.template
32+
const curr = this.curr.template
33+
if (this.hasDiff(prev, curr)) {
34+
cb(curr)
35+
}
36+
37+
return this
38+
}
39+
40+
script(cb: (block: SFCBlock | null) => void): this {
41+
const prev = this.prev.script
42+
const curr = this.curr.script
43+
if (this.hasDiff(prev, curr)) {
44+
cb(curr)
45+
}
46+
47+
return this
48+
}
49+
50+
styles(cb: (blocks: SFCBlock[]) => void): this {
51+
const prev = this.prev.styles
52+
const curr = this.curr.styles
53+
if (this.hasListDiff(prev, curr)) {
54+
cb(curr)
55+
}
56+
57+
return this
58+
}
59+
60+
customBlocks(name: string, cb: (blocks: SFCBlock[]) => void): this {
61+
const prev = this.prev.customBlocks
62+
const curr = this.curr.customBlocks
63+
if (this.hasListDiff(prev, curr)) {
64+
cb(curr)
65+
}
66+
67+
return this
68+
}
69+
70+
private hasDiff(prev: SFCBlock | null, curr: SFCBlock | null): boolean {
71+
if (prev === null || curr === null) {
72+
return prev !== curr
73+
}
74+
75+
return !prev.equals(curr)
76+
}
77+
78+
private hasListDiff(prev: SFCBlock[], curr: SFCBlock[]): boolean {
79+
if (prev.length !== curr.length) {
80+
return true
81+
}
82+
83+
return prev.reduce((acc, p, i) => {
84+
return acc && this.hasDiff(p, curr[i])
85+
}, true)
86+
}
87+
}

src/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
SFCBlockRaw,
55
SFCDescriptorRaw
66
} from './sfc-parser'
7+
import { SFCDiffWatcher } from './diff-watcher'
8+
import { equalsRecord } from './utils'
79

810
export class SFCBlock {
911
type!: string
@@ -23,6 +25,24 @@ export class SFCBlock {
2325
})
2426
}
2527

28+
equals(block: SFCBlock): boolean {
29+
if (this === block) {
30+
return true
31+
}
32+
33+
return (
34+
this.type === block.type &&
35+
this.content === block.content &&
36+
this.start === block.start &&
37+
this.end === block.end &&
38+
this.lang === block.lang &&
39+
this.src === block.src &&
40+
this.scoped === block.scoped &&
41+
this.module === block.module &&
42+
equalsRecord(this.attrs, block.attrs)
43+
)
44+
}
45+
2646
calcGlobalOffset(offset: number): number {
2747
return this.start + offset
2848
}
@@ -48,3 +68,7 @@ export function parseComponent(code: string): SFCDescriptor {
4868
}
4969
}) as SFCDescriptor
5070
}
71+
72+
export function createDiffWatcher(): SFCDiffWatcher {
73+
return new SFCDiffWatcher()
74+
}

src/utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,19 @@ export function makeMap(
1313
}
1414
return expectsLowerCase ? val => map[val.toLowerCase()] : val => map[val]
1515
}
16+
17+
export function equalsRecord(
18+
a: Record<string, any>,
19+
b: Record<string, any>
20+
): boolean {
21+
const aKeys = Object.keys(a)
22+
const bKeys = Object.keys(b)
23+
24+
if (aKeys.length !== bKeys.length) {
25+
return false
26+
}
27+
28+
return aKeys.reduce((acc, key) => {
29+
return acc && key in b && a[key] === b[key]
30+
}, true)
31+
}

0 commit comments

Comments
 (0)