Skip to content

Commit

Permalink
feat: add createDiffWatcher function (#1)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ktsn committed Mar 11, 2018
1 parent d41639d commit d6351c6
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 11 deletions.
71 changes: 60 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ console.log(res.script.calcGlobalOffset(5))

## References

* `parseComponent(code: string): SFCDescriptor`
### `parseComponent(code: string): SFCDescriptor`

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

```ts
interface SFCDescriptor {
template: SFCBlock | null
script: SFCBlock | null
styles: SFCBlock[]
customBlocks: SFCBlock[]
}
```
```ts
interface SFCDescriptor {
template: SFCBlock | null
script: SFCBlock | null
styles: SFCBlock[]
customBlocks: SFCBlock[]
}
```

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

### Additional Helpers of SFCBlock

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

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.

### `createDiffWatcher(): SFCDiffWatcher`

Create a watcher object which will detect each SFC block's diff. `SFCDiffWatcher` has following methods:

* `add(filename: string, content: string): void`
* `remove(filename: string): void`
* `diff(filename: string, content: string): SFCDiff`

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.

Example:

```js
const { createDiffWatcher } = require('vue-sfc-parser')
const fs = require('fs')
const chokidar = require('chokidar')

const watcher = createDiffWatcher()

chokidar
.watch('**/*.vue')
.on('add', filename => {
watcher.add(filename, fs.readFileSync(filename, 'utf8'))
})
.on('unlink', filename => {
watcher.add(filename)
})
.on('change', filename => {
watcher
.diff(filename, fs.readFileSync(filename, 'utf8'))
.template(template => {
console.log(template.content)
})
.script(script => {
console.log(script.content)
})
.styles(styles => {
styles.forEach(s => {
console.log(s.content)
})
})
.customBlocks('block-name', blocks => {
blocks.forEach(b => {
console.log(b.content)
})
})
})
```

## License

MIT
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"devDependencies": {
"@types/jest": "^22.1.3",
"@types/lodash.mapvalues": "^4.6.3",
"@types/node": "^9.4.7",
"jest": "^22.4.2",
"prettier": "1.11.0",
"ts-jest": "^22.4.0",
Expand Down
87 changes: 87 additions & 0 deletions src/diff-watcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import assert = require('assert')
import { SFCBlock, parseComponent, SFCDescriptor } from './index'

export class SFCDiffWatcher {
private prevMap: Record<string, SFCDescriptor> = {}

add(filename: string, content: string): void {
this.prevMap[filename] = parseComponent(content)
}

remove(filename: string): void {
delete this.prevMap[filename]
}

diff(filename: string, content: string): SFCDiff {
assert(
this.prevMap.hasOwnProperty(filename),
'must call `add` before calling `diff`'
)

const prev = this.prevMap[filename]
const curr = (this.prevMap[filename] = parseComponent(content))
return new SFCDiff(prev, curr)
}
}

export class SFCDiff {
constructor(private prev: SFCDescriptor, private curr: SFCDescriptor) {}

template(cb: (block: SFCBlock | null) => void): this {
const prev = this.prev.template
const curr = this.curr.template
if (this.hasDiff(prev, curr)) {
cb(curr)
}

return this
}

script(cb: (block: SFCBlock | null) => void): this {
const prev = this.prev.script
const curr = this.curr.script
if (this.hasDiff(prev, curr)) {
cb(curr)
}

return this
}

styles(cb: (blocks: SFCBlock[]) => void): this {
const prev = this.prev.styles
const curr = this.curr.styles
if (this.hasListDiff(prev, curr)) {
cb(curr)
}

return this
}

customBlocks(name: string, cb: (blocks: SFCBlock[]) => void): this {
const prev = this.prev.customBlocks
const curr = this.curr.customBlocks
if (this.hasListDiff(prev, curr)) {
cb(curr)
}

return this
}

private hasDiff(prev: SFCBlock | null, curr: SFCBlock | null): boolean {
if (prev === null || curr === null) {
return prev !== curr
}

return !prev.equals(curr)
}

private hasListDiff(prev: SFCBlock[], curr: SFCBlock[]): boolean {
if (prev.length !== curr.length) {
return true
}

return prev.reduce((acc, p, i) => {
return acc && this.hasDiff(p, curr[i])
}, true)
}
}
24 changes: 24 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
SFCBlockRaw,
SFCDescriptorRaw
} from './sfc-parser'
import { SFCDiffWatcher } from './diff-watcher'
import { equalsRecord } from './utils'

export class SFCBlock {
type!: string
Expand All @@ -23,6 +25,24 @@ export class SFCBlock {
})
}

equals(block: SFCBlock): boolean {
if (this === block) {
return true
}

return (
this.type === block.type &&
this.content === block.content &&
this.start === block.start &&
this.end === block.end &&
this.lang === block.lang &&
this.src === block.src &&
this.scoped === block.scoped &&
this.module === block.module &&
equalsRecord(this.attrs, block.attrs)
)
}

calcGlobalOffset(offset: number): number {
return this.start + offset
}
Expand All @@ -48,3 +68,7 @@ export function parseComponent(code: string): SFCDescriptor {
}
}) as SFCDescriptor
}

export function createDiffWatcher(): SFCDiffWatcher {
return new SFCDiffWatcher()
}
16 changes: 16 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,19 @@ export function makeMap(
}
return expectsLowerCase ? val => map[val.toLowerCase()] : val => map[val]
}

export function equalsRecord(
a: Record<string, any>,
b: Record<string, any>
): boolean {
const aKeys = Object.keys(a)
const bKeys = Object.keys(b)

if (aKeys.length !== bKeys.length) {
return false
}

return aKeys.reduce((acc, key) => {
return acc && key in b && a[key] === b[key]
}, true)
}

0 comments on commit d6351c6

Please sign in to comment.