mirror of
https://github.com/zhigang1992/docz.git
synced 2026-03-27 22:49:33 +08:00
feat(docz-core): add chokidar to watch new entries
This commit is contained in:
@@ -1,41 +1,7 @@
|
||||
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 del from 'del'
|
||||
|
||||
import * as paths from './config/paths'
|
||||
import { Entry } from './Entry'
|
||||
import { Plugin, IPluginFactory } from './Plugin'
|
||||
import { Plugin } from './Plugin'
|
||||
import { ConfigArgs } from './Server'
|
||||
|
||||
const mkd = (dir: string): void => {
|
||||
try {
|
||||
fs.lstatSync(dir)
|
||||
} catch (err) {
|
||||
mkdir.sync(dir)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
mkd(paths.docz)
|
||||
fs.writeFileSync(file, content, 'utf-8')
|
||||
}
|
||||
|
||||
const compiled = (templateFile: string) =>
|
||||
compile(fs.readFileSync(`${paths.templatesPath}/${templateFile}`, 'utf-8'))
|
||||
|
||||
export type TConfigFn<C> = (entries: Entry[]) => C
|
||||
export type TConfigFn<C> = () => C
|
||||
export type TCompilerFn<C> = (config: C) => Promise<any>
|
||||
export type TServerFn<S> = (compiler: any) => S
|
||||
|
||||
@@ -50,10 +16,6 @@ export interface IBundlerConstructor<C, S> extends ICompilerOpts {
|
||||
server: TServerFn<S>
|
||||
}
|
||||
|
||||
const app = compiled('app.tpl.js')
|
||||
const js = compiled('index.tpl.js')
|
||||
const html = compiled('index.tpl.html')
|
||||
|
||||
export class Bundler<C = any, S = any> {
|
||||
readonly id: string
|
||||
readonly args: ConfigArgs
|
||||
@@ -76,60 +38,19 @@ export class Bundler<C = any, S = any> {
|
||||
plugin.bundlerConfig(config, dev) || config
|
||||
}
|
||||
|
||||
private mountConfig(entries: Entry[]) {
|
||||
private mountConfig() {
|
||||
const { plugins, env } = this.args
|
||||
|
||||
const dev = env === 'development'
|
||||
const initialConfig = this.config(entries)
|
||||
const initialConfig = this.config()
|
||||
|
||||
return Boolean(plugins) && plugins.length > 0
|
||||
? plugins.reduce(this.reduceWithPlugins(dev), initialConfig)
|
||||
: initialConfig
|
||||
}
|
||||
|
||||
private routesFromEntries(entries: Entry[]) {
|
||||
return (
|
||||
entries &&
|
||||
entries.length > 0 &&
|
||||
entries.reduce((obj, entry) => {
|
||||
return Object.assign({}, obj, { [entry.name]: entry.route })
|
||||
}, {})
|
||||
)
|
||||
}
|
||||
|
||||
private propOfPlugins(method: keyof IPluginFactory) {
|
||||
const { plugins } = this.args
|
||||
return plugins && plugins.map(p => p[method]).filter(m => m)
|
||||
}
|
||||
|
||||
private generateFilesByTemplate(entries: Entry[]) {
|
||||
const { theme } = this.args
|
||||
|
||||
touch(paths.indexHtml, html({}))
|
||||
|
||||
touch(
|
||||
paths.appJs,
|
||||
app({
|
||||
THEME: theme,
|
||||
ENTRIES: entries,
|
||||
ROUTES: JSON.stringify(this.routesFromEntries(entries)),
|
||||
WRAPPERS: this.propOfPlugins('wrapper'),
|
||||
})
|
||||
)
|
||||
|
||||
touch(
|
||||
paths.indexJs,
|
||||
js({
|
||||
BEFORE_RENDERS: this.propOfPlugins('beforeRender'),
|
||||
AFTER_RENDERS: this.propOfPlugins('afterRender'),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
public async createCompiler(entries: Entry[]) {
|
||||
del.sync(paths.docz)
|
||||
this.generateFilesByTemplate(entries)
|
||||
return await this.compiler(this.mountConfig(entries))
|
||||
public async createCompiler() {
|
||||
return await this.compiler(this.mountConfig())
|
||||
}
|
||||
|
||||
public async createServer(compiler: any): Promise<S> {
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import * as glob from 'fast-glob'
|
||||
import * as t from 'babel-types'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as mkdir from 'mkdirp'
|
||||
import * as prettier from 'prettier'
|
||||
import { NodePath } from 'babel-traverse'
|
||||
import { compile } from 'art-template'
|
||||
|
||||
import * as paths from './config/paths'
|
||||
|
||||
import { traverseAndAssign } from './utils/traverse'
|
||||
import { Entry, convertToAst } from './Entry'
|
||||
import { Plugin, IPluginFactory } from './Plugin'
|
||||
|
||||
const hasImport = (path: NodePath<any>): boolean =>
|
||||
path.isImportDeclaration() &&
|
||||
@@ -25,18 +33,82 @@ const checkImport = traverseAndAssign<NodePath<t.Node>, boolean>(
|
||||
|
||||
const isFile = (entry: string) => checkImport(convertToAst(entry))
|
||||
|
||||
export class Entries {
|
||||
private files: string[]
|
||||
|
||||
constructor(pattern: string) {
|
||||
const ignoreGlob = '!node_modules'
|
||||
|
||||
this.files = glob.sync(
|
||||
Array.isArray(pattern) ? [...pattern, ignoreGlob] : [pattern, ignoreGlob]
|
||||
)
|
||||
}
|
||||
|
||||
public parse(src: string): Entry[] {
|
||||
return this.files.filter(isFile).map(file => new Entry({ file, src }))
|
||||
const mkd = (dir: string): void => {
|
||||
try {
|
||||
fs.lstatSync(dir)
|
||||
} catch (err) {
|
||||
mkdir.sync(dir)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
mkd(paths.docz)
|
||||
fs.writeFileSync(file, content, 'utf-8')
|
||||
}
|
||||
|
||||
const compiled = (file: string) =>
|
||||
compile(fs.readFileSync(path.join(paths.templates, file), 'utf-8'))
|
||||
|
||||
const propOf = (arr: any[], method: keyof IPluginFactory) =>
|
||||
arr && arr.map(p => p[method]).filter(m => m)
|
||||
|
||||
const app = compiled('app.tpl.js')
|
||||
const js = compiled('index.tpl.js')
|
||||
const html = compiled('index.tpl.html')
|
||||
|
||||
export interface IGenerateFilesParams {
|
||||
entries: Entry[]
|
||||
plugins: Plugin[]
|
||||
theme: string
|
||||
}
|
||||
|
||||
export class Entries {
|
||||
public files: string[]
|
||||
public all: Entry[]
|
||||
|
||||
constructor(pattern: string, src: string) {
|
||||
const ignoreGlob = '!node_modules'
|
||||
const files: string[] = glob.sync(
|
||||
Array.isArray(pattern) ? [...pattern, ignoreGlob] : [pattern, ignoreGlob]
|
||||
)
|
||||
|
||||
this.files = files
|
||||
this.all = files.filter(isFile).map(file => new Entry({ file, src }))
|
||||
}
|
||||
|
||||
public map() {
|
||||
return this.all.reduce((obj: any, entry: Entry) => {
|
||||
return Object.assign({}, obj, { [entry.filepath]: entry.name })
|
||||
}, {})
|
||||
}
|
||||
|
||||
static generateFiles({ entries, theme, plugins }: IGenerateFilesParams) {
|
||||
touch(paths.indexHtml, html({}))
|
||||
|
||||
touch(
|
||||
paths.appJs,
|
||||
app({
|
||||
THEME: theme,
|
||||
ENTRIES: entries,
|
||||
WRAPPERS: propOf(plugins, 'wrapper'),
|
||||
})
|
||||
)
|
||||
|
||||
touch(
|
||||
paths.indexJs,
|
||||
js({
|
||||
BEFORE_RENDERS: propOf(plugins, 'beforeRender'),
|
||||
AFTER_RENDERS: propOf(plugins, 'afterRender'),
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,18 +25,18 @@ export interface IEntryConstructor {
|
||||
export class Entry {
|
||||
public name: string
|
||||
public filepath: string
|
||||
public route: string
|
||||
|
||||
constructor({ src, file }: IEntryConstructor) {
|
||||
const ast = convertToAst(file)
|
||||
const name = getNameFromDoc(ast) || ''
|
||||
const srcPath = path.resolve(paths.root, src)
|
||||
const filepath = path.relative(path.relative(paths.root, src), file)
|
||||
const dir = path.relative(srcPath, path.parse(file).dir)
|
||||
const route = path.join('/', dir, name)
|
||||
const filepath = path.relative(paths.root, file)
|
||||
|
||||
this.name = name
|
||||
this.route = route
|
||||
this.filepath = filepath
|
||||
}
|
||||
|
||||
static parseName(file: string) {
|
||||
const ast = convertToAst(file)
|
||||
return getNameFromDoc(ast)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { load } from 'load-cfg'
|
||||
import { FSWatcher } from 'chokidar'
|
||||
import * as chokidar from 'chokidar'
|
||||
import del from 'del'
|
||||
|
||||
import * as paths from './config/paths'
|
||||
import { pick } from './utils/helpers'
|
||||
|
||||
import { Entry } from './Entry'
|
||||
import { Entries } from './Entries'
|
||||
import { Bundler } from './Bundler'
|
||||
import { Plugin } from './Plugin'
|
||||
|
||||
process.env['BABEL_ENV'] = process.env['BABEL_ENV'] || 'development'
|
||||
process.env['NODE_ENV'] = process.env['NODE_ENV'] || 'development'
|
||||
process.env.BABEL_ENV = process.env.BABEL_ENV || 'development'
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development'
|
||||
|
||||
const ENV = process.env['NODE_ENV']
|
||||
const ENV = process.env.NODE_ENV
|
||||
const HOST = process.env.HOST || '0.0.0.0'
|
||||
const PROTOCOL = process.env.HTTPS === 'true' ? 'https' : 'http'
|
||||
|
||||
@@ -31,30 +35,22 @@ export interface ConfigArgs extends IServerConstructor {
|
||||
}
|
||||
|
||||
export class Server {
|
||||
private port: number
|
||||
private src: string
|
||||
private plugins: Plugin[]
|
||||
private entries: Entries
|
||||
readonly config: ConfigArgs
|
||||
readonly watcher: FSWatcher
|
||||
private bundler: Bundler
|
||||
|
||||
constructor(args: IServerConstructor) {
|
||||
const initialArgs = this.getInitialArgs(args)
|
||||
const { port, theme, files, bundler, src, plugins } = load(
|
||||
'docz',
|
||||
initialArgs
|
||||
)
|
||||
const config = load('docz', initialArgs)
|
||||
|
||||
this.port = port
|
||||
this.src = src
|
||||
this.plugins = plugins
|
||||
this.entries = new Entries(files)
|
||||
this.config = config
|
||||
this.watcher = chokidar.watch(config.files, {
|
||||
ignored: /(^|[\/\\])\../,
|
||||
})
|
||||
|
||||
this.bundler = this.getBundler(bundler).bundler({
|
||||
port,
|
||||
this.bundler = this.getBundler(config.bundler).bundler({
|
||||
...config,
|
||||
paths,
|
||||
theme,
|
||||
src,
|
||||
plugins,
|
||||
env: ENV,
|
||||
host: HOST,
|
||||
protocol: PROTOCOL,
|
||||
@@ -62,7 +58,10 @@ export class Server {
|
||||
}
|
||||
|
||||
private getInitialArgs(args: IServerConstructor) {
|
||||
return pick(['port', 'theme', 'files', 'bundler', 'src'], args)
|
||||
return {
|
||||
...pick(['port', 'theme', 'files', 'bundler', 'src'], args),
|
||||
plugins: [],
|
||||
}
|
||||
}
|
||||
|
||||
private getBundler(bundler: string) {
|
||||
@@ -73,11 +72,42 @@ export class Server {
|
||||
}
|
||||
}
|
||||
|
||||
public async start() {
|
||||
const { bundler, entries, plugins } = this
|
||||
private processEntries(config: ConfigArgs) {
|
||||
const { files, src, theme, plugins } = config
|
||||
const cache = new Map()
|
||||
|
||||
const compiler = await bundler.createCompiler(entries.parse(this.src))
|
||||
const server = await bundler.createServer(compiler)
|
||||
const generateFilesAndUpdateCache = (entries: Entries) => {
|
||||
cache.set('map', entries.map())
|
||||
Entries.generateFiles({ entries: entries.all, plugins, theme })
|
||||
}
|
||||
|
||||
const updateEntries = () =>
|
||||
generateFilesAndUpdateCache(new Entries(files, src))
|
||||
|
||||
const parseToUpdate = (path: string) => {
|
||||
const name = Entry.parseName(path)
|
||||
const entry = cache.get('map')[path]
|
||||
const newEntries = new Entries(files, src)
|
||||
|
||||
if (name && name !== entry && newEntries.files.includes(path)) {
|
||||
generateFilesAndUpdateCache(newEntries)
|
||||
}
|
||||
}
|
||||
|
||||
this.watcher.on('unlink', updateEntries)
|
||||
this.watcher.on('change', parseToUpdate)
|
||||
|
||||
generateFilesAndUpdateCache(new Entries(files, src))
|
||||
}
|
||||
|
||||
public async start() {
|
||||
const { plugins, port } = this.config
|
||||
|
||||
del.sync(paths.docz)
|
||||
this.processEntries(this.config)
|
||||
|
||||
const compiler = await this.bundler.createCompiler()
|
||||
const server = await this.bundler.createServer(compiler)
|
||||
|
||||
if (plugins && plugins.length > 0) {
|
||||
for (const plugin of plugins) {
|
||||
@@ -86,6 +116,6 @@ export class Server {
|
||||
}
|
||||
}
|
||||
|
||||
server.listen(this.port)
|
||||
server.listen(port)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user