Skip to content

Commit

Permalink
feat(docz-core): add support for parse sections via ast
Browse files Browse the repository at this point in the history
  • Loading branch information
pedronauck committed Apr 19, 2018
1 parent c517f0e commit 6db0cd8
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 67 deletions.
1 change: 1 addition & 0 deletions packages/docz-bundler-webpack/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const createConfig = (args: ConfigArgs) => (): Configuration => {

config.when(debug, cfg => cfg.devtool('source-map'))
config.when(!isProd, cfg => cfg.devtool('cheap-module-eval-source-map'))
config.devServer.quiet(!debug)

config.node.merge({
child_process: 'empty',
Expand Down
11 changes: 7 additions & 4 deletions packages/docz-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,33 @@
"tslint": "tslint --project ."
},
"dependencies": {
"@types/chokidar": "^1.7.5",
"@types/yargs": "^11.0.0",
"art-template": "^4.12.2",
"babel-file-loader": "^1.0.3",
"babel-generator": "^6.26.1",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"chokidar": "^2.0.3",
"del": "3.0.0",
"express": "^4.16.3",
"fast-glob": "^2.2.0",
"load-cfg": "^0.0.1",
"lodash.get": "^4.4.2",
"mkdirp": "^0.5.1",
"prettier": "^1.12.0",
"resolve": "^1.7.1",
"ulid": "^2.3.0",
"yargs": "^11.0.0"
},
"devDependencies": {
"@types/babel-traverse": "^6.25.3",
"@types/babylon": "^6.16.2",
"@types/chokidar": "^1.7.5",
"@types/del": "^3.0.1",
"@types/express": "^4.11.1",
"@types/lodash.get": "^4.4.3",
"@types/mkdirp": "^0.5.2",
"@types/prettier": "^1.12.0",
"@types/resolve": "^0.0.7",
"@types/yargs": "^11.0.0",
"rollup-plugin-cpy": "^1.0.0"
}
}
2 changes: 1 addition & 1 deletion packages/docz-core/src/Bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Plugin } from './Plugin'
import { ConfigArgs } from './Server'

export type TConfigFn<C> = () => C
export type TCompilerFn<C> = (config: C) => Promise<any>
export type TCompilerFn<C> = (config: C) => any
export type TServerFn<S> = (compiler: any) => S

export interface CompilerOpts {
Expand Down
19 changes: 7 additions & 12 deletions packages/docz-core/src/Entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import * as glob from 'fast-glob'
import * as fs from 'fs'
import * as path from 'path'
import * as mkdir from 'mkdirp'
import * as prettier from 'prettier'
import { compile } from 'art-template'
import { ulid } from 'ulid'

import * as paths from './config/paths'
import { propOf } from './utils/helpers'
import { format } from './utils/format'

import { Entry } from './Entry'
import { ConfigArgs } from './Server'
Expand All @@ -19,13 +20,6 @@ const mkd = (dir: string): void => {
}
}

const format = (raw: string) =>
prettier.format(raw, {
semi: false,
singleQuote: true,
trailingComma: 'all',
})

const touch = (file: string, raw: string) => {
const content = /js/.test(path.extname(file)) ? format(raw) : raw

Expand All @@ -51,15 +45,16 @@ export class Entries {
)

this.files = files
this.all = files.filter(Entry.check).map(file => new Entry({ file, src }))
this.all = files.filter(Entry.check).map(file => new Entry(file))
}

public writeFiles(args: Partial<ConfigArgs>): void {
const { theme, plugins, title, description } = args

const rawIndexHtml = html({
DESCRIPTION: description,
TITLE: title,
DESCRIPTION: description,
ENTRIES_RAW: JSON.stringify(this.map('name')),
})

const rawAppJs = app({
Expand All @@ -78,11 +73,11 @@ export class Entries {
touch(paths.indexJs, rawIndexJs)
}

public map(): Record<string, string> {
public map(key: keyof Entry = 'filepath'): Record<string, string> {
return this.all.reduce(
(obj: any, entry: Entry) => ({
...obj,
[entry.filepath]: entry.name,
[entry[key]]: { id: ulid(), ...entry },
}),
{}
)
Expand Down
103 changes: 56 additions & 47 deletions packages/docz-core/src/Entry.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,82 @@
import * as fs from 'fs'
import * as path from 'path'
import * as t from 'babel-types'
import { NodePath } from 'babel-traverse'
import { File } from 'babel-types'
import { parse } from 'babylon'
import generate from 'babel-generator'
import get from 'lodash.get'

import * as paths from './config/paths'
import { traverseAndAssign } from './utils/traverse'
import { traverseAndAssign, traverseAndAssignEach } from './utils/traverse'
import { isArrEqual } from './utils/helpers'
import { format } from './utils/format'

const convertToAst = (entry: string): File | null => {
try {
return parse(fs.readFileSync(entry, 'utf-8'), {
plugins: ['jsx'],
sourceType: 'module',
})
} catch (err) {
console.log(err)
return null
}
}

const getNameFromDoc = traverseAndAssign<any, string>({
assign: p => p.node.arguments[0].value,
when: p => p.isCallExpression() && p.node.callee.name === 'doc',
})
const hasImport = (p: NodePath<any>): boolean =>
t.isImportDeclaration(p) && get(p, 'node.source.value') === 'docz'

const hasImport = (filepath: NodePath<any>): boolean =>
filepath.isImportDeclaration() &&
filepath.node &&
filepath.node.source &&
filepath.node.source.value === 'docz'

const hasDocFn = (filepath: NodePath<any>): boolean =>
filepath.node.specifiers &&
filepath.node.specifiers.some(
const hasDocFn = (p: NodePath<any>): boolean =>
p.node.specifiers &&
p.node.specifiers.some(
(node: NodePath<any>) =>
t.isImportSpecifier(node) && node.imported.name === 'doc'
)

const checkImport = traverseAndAssign<NodePath<t.Node>, boolean>({
assign: () => true,
when: p => hasImport(p) && hasDocFn(p),
assign: () => true,
})

export interface EntryConstructor {
file: string
src: string
}
const getNameFromDoc = traverseAndAssign<any, string>({
when: p => p.isCallExpression() && get(p, 'node.callee.name') === 'doc',
assign: p => get(p, 'node.arguments[0].value'),
})

const parseSections = traverseAndAssignEach<NodePath<t.Node>, string[]>({
when: 'MemberExpression',
assign: p => {
const name = get(p, 'node.property.name')
const args = get(p, 'parentPath.node.arguments')

if (name === 'section' && args && args.length > 0) {
for (const arg of args) {
if (arg.type !== 'StringLiteral') {
const { code } = generate(arg.body, {})
const formatted: any = format(code)

return formatted.slice(1, Infinity)
}
}
}
},
})

export class Entry {
public static parseName(file: string): string | undefined | null {
const ast = convertToAst(file)
return ast && getNameFromDoc(ast)
readonly [key: string]: any

public static check(file: string): boolean | null {
return checkImport(file)
}

public static parseName(file: string): string | null {
return getNameFromDoc(file)
}

public static check(entry: string): boolean | undefined | null {
const ast = convertToAst(entry)
return ast && checkImport(ast)
public static parseSections(file: string): string[] | null {
const sections = parseSections(file)
return sections && sections.reverse()
}

public name: string | undefined
public filepath: string
public name: string | null
public sections: string[] | null

constructor({ src, file }: EntryConstructor) {
const ast = convertToAst(file)
const name = ast ? getNameFromDoc(ast) : ''
const filepath = path.relative(paths.root, file)
constructor(file: string) {
this.filepath = path.relative(paths.root, file)
this.name = Entry.parseName(file)
this.sections = Entry.parseSections(file)
}

this.name = name
this.filepath = filepath
public diff(entry: any): boolean {
return (
this.name !== entry.name || !isArrEqual(this.sections, entry.sections)
)
}
}
7 changes: 4 additions & 3 deletions packages/docz-core/src/Server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { load } from 'load-cfg'
import { FSWatcher } from 'chokidar'
import * as chokidar from 'chokidar'
import get from 'lodash.get'
import del from 'del'

import * as paths from './config/paths'
Expand Down Expand Up @@ -92,11 +93,11 @@ export class Server {
generateFilesAndUpdateCache(new Entries(files, src))

const parseToUpdate = (path: string) => {
const name = Entry.parseName(path)
const entry = cache.get('map')[path]
const entry = get(cache.get('map'), path)
const newEntry = new Entry(path)
const newEntries = new Entries(files, src)

if (name && name !== entry && newEntries.files.includes(path)) {
if (newEntry.diff(entry) && newEntries.files.includes(path)) {
generateFilesAndUpdateCache(newEntries)
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/docz-core/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
declare module 'art-template'
declare module 'babel-file-loader'
declare module 'babel-generator'
declare module 'babel-collect-imports'
12 changes: 12 additions & 0 deletions packages/docz-core/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,15 @@ export function pick<R = object>(props: string[], obj: any): R {
export function propOf<T>(arr: any[] | undefined, method: keyof T): any {
return arr && arr.map(p => p[method]).filter(m => m)
}

export function isArrEqual(arr: any[] | null, to: any[] | null): boolean {
if (!arr || !to || arr.length !== to.length) return false
// tslint:disable
let equals = false

for (let i = 0; i < arr.length; i++) {
equals = arr[i] === to[i]
}

return equals
}
3 changes: 3 additions & 0 deletions packages/docz-core/templates/index.tpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
</head>
<body>
<div id="root"></div>
<script type="text/javascript">
window.__DOCZ_ENTRIES__ = <%- ENTRIES_RAW %>
</script>
</body>
</html>

0 comments on commit 6db0cd8

Please sign in to comment.