mirror of
https://github.com/zhigang1992/docz.git
synced 2026-03-27 02:34:14 +08:00
feat(docz-core): add basic plugin logic
This commit is contained in:
@@ -25,7 +25,8 @@
|
||||
"express": "^4.16.3",
|
||||
"fast-glob": "^2.2.0",
|
||||
"load-cfg": "^0.0.1",
|
||||
"mkdirp": "^0.5.1"
|
||||
"mkdirp": "^0.5.1",
|
||||
"prettier": "^1.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/babel-traverse": "^6.25.3",
|
||||
@@ -33,6 +34,7 @@
|
||||
"@types/del": "^3.0.1",
|
||||
"@types/express": "^4.11.1",
|
||||
"@types/mkdirp": "^0.5.2",
|
||||
"@types/prettier": "^1.12.0",
|
||||
"shelljs": "^0.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as mkdir from 'mkdirp'
|
||||
import * as del from 'del'
|
||||
import { compile } from 'art-template'
|
||||
import * as prettier from 'prettier'
|
||||
|
||||
import * as paths from './config/paths'
|
||||
import { Entry } from './Entry'
|
||||
import { Plugin, IPluginFactory } from './Plugin'
|
||||
import { ConfigArgs } from './Server'
|
||||
|
||||
const mkd = (dir: string): void => {
|
||||
@@ -15,7 +18,16 @@ const mkd = (dir: string): void => {
|
||||
}
|
||||
}
|
||||
|
||||
const touch = (file: string, content: string) => {
|
||||
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.playgrodd)
|
||||
fs.writeFileSync(file, content, 'utf-8')
|
||||
}
|
||||
@@ -28,7 +40,7 @@ export type TCompilerFn<C> = (config: C) => Promise<any>
|
||||
export type TServerFn<S> = (compiler: any) => S
|
||||
|
||||
export interface ICompilerOpts {
|
||||
theme: string
|
||||
args: ConfigArgs
|
||||
}
|
||||
|
||||
export interface IConstructorParams<C, S> extends ICompilerOpts {
|
||||
@@ -44,35 +56,85 @@ const html = compiled('index.tpl.html')
|
||||
|
||||
export class Bundler<C = any, S = any> {
|
||||
readonly id: string
|
||||
readonly theme: string
|
||||
readonly args: ConfigArgs
|
||||
private config: TConfigFn<C>
|
||||
private compiler: TCompilerFn<C>
|
||||
private server: TServerFn<S>
|
||||
|
||||
constructor({
|
||||
args,
|
||||
|
||||
id,
|
||||
config,
|
||||
theme,
|
||||
compiler,
|
||||
server,
|
||||
}: IConstructorParams<C, S>) {
|
||||
this.args = args
|
||||
this.id = id
|
||||
this.theme = theme
|
||||
this.config = config
|
||||
this.compiler = compiler
|
||||
this.server = server
|
||||
}
|
||||
|
||||
public async createCompiler(entries: Entry[]) {
|
||||
const { theme } = this
|
||||
const config = this.config(entries)
|
||||
private reduceWithPlugins(dev: boolean) {
|
||||
return (config: C, plugin: Plugin) =>
|
||||
plugin.bundlerConfig(config, dev) || config
|
||||
}
|
||||
|
||||
private mountConfig(entries: Entry[]) {
|
||||
const { plugins, env } = this.args
|
||||
|
||||
const dev = env === 'development'
|
||||
const initialConfig = this.config(entries)
|
||||
|
||||
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
|
||||
|
||||
await del(paths.playgrodd)
|
||||
touch(paths.appJs, app({ theme, entries }))
|
||||
touch(paths.indexJs, js({}))
|
||||
touch(paths.indexHtml, html({}))
|
||||
|
||||
return await this.compiler(config)
|
||||
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[]) {
|
||||
await del(paths.playgrodd)
|
||||
this.generateFilesByTemplate(entries)
|
||||
return await this.compiler(this.mountConfig(entries))
|
||||
}
|
||||
|
||||
public async createServer(compiler: any): Promise<S> {
|
||||
@@ -96,8 +158,8 @@ export function createBundler<C, S>(
|
||||
): IBundlerCreate<C, S> {
|
||||
return (args: ConfigArgs): Bundler<C, S> =>
|
||||
new Bundler({
|
||||
args,
|
||||
id: factory.id,
|
||||
theme: args.theme,
|
||||
config: factory.config(args),
|
||||
compiler: factory.compiler(args),
|
||||
server: factory.server(args),
|
||||
|
||||
@@ -1,3 +1,48 @@
|
||||
export function createPlugin<Config = any, Server = any>() {
|
||||
return null
|
||||
import { isFn } from './utils/helpers'
|
||||
|
||||
export type IBundlerConfig = <Config>(config: Config, dev: boolean) => Config
|
||||
export type IBundlerCompiler = <Compiler>(compiler: Compiler) => void
|
||||
export type IBundlerServer = <Server>(server: Server) => void
|
||||
export type IBeforeRender = () => void
|
||||
export type IAfterRender = () => void
|
||||
export type IWrapper = <R>(props: { children: any }) => R
|
||||
|
||||
export interface IPluginFactory {
|
||||
bundlerConfig: IBundlerConfig
|
||||
bundlerCompiler: IBundlerCompiler
|
||||
bundlerServer: IBundlerServer
|
||||
beforeRender: IBeforeRender
|
||||
afterRender: IAfterRender
|
||||
wrapper: IWrapper
|
||||
}
|
||||
|
||||
export class Plugin {
|
||||
readonly bundlerConfig: IBundlerConfig
|
||||
readonly bundlerCompiler: IBundlerCompiler
|
||||
readonly bundlerServer: IBundlerServer
|
||||
readonly beforeRender: IBeforeRender
|
||||
readonly afterRender: IAfterRender
|
||||
readonly wrapper: IWrapper
|
||||
|
||||
constructor(p: IPluginFactory) {
|
||||
this.bundlerConfig = (config: any, dev: boolean) => {
|
||||
return isFn(p.bundlerConfig) && p.bundlerConfig(config, dev)
|
||||
}
|
||||
|
||||
this.bundlerCompiler = async (compiler: any) => {
|
||||
isFn(p.bundlerCompiler) && (await p.bundlerCompiler(compiler))
|
||||
}
|
||||
|
||||
this.bundlerServer = async (server: any) => {
|
||||
isFn(p.bundlerServer) && (await p.bundlerServer(server))
|
||||
}
|
||||
|
||||
this.beforeRender = p.beforeRender
|
||||
this.afterRender = p.afterRender
|
||||
this.wrapper = p.wrapper
|
||||
}
|
||||
}
|
||||
|
||||
export function createPlugin(factory: IPluginFactory) {
|
||||
return new Plugin(factory)
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@ export { Paths } from './config/Paths'
|
||||
|
||||
export { Entry } from './Entry'
|
||||
export { createBundler, IBundlerCreate } from './Bundler'
|
||||
export { Server, ConfigArgs } from './server'
|
||||
export { createPlugin } from './Plugin'
|
||||
export { Server, ConfigArgs } from './Server'
|
||||
|
||||
@@ -5,6 +5,7 @@ import { pick } from './utils/helpers'
|
||||
|
||||
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'
|
||||
@@ -13,17 +14,6 @@ const ENV = process.env.NODE_ENV
|
||||
const HOST = process.env.HOST || '0.0.0.0'
|
||||
const PROTOCOL = process.env.HTTPS === 'true' ? 'https' : 'http'
|
||||
|
||||
export interface ConfigArgs {
|
||||
paths: any
|
||||
port: number
|
||||
theme: string
|
||||
src: string
|
||||
env: string
|
||||
host: string
|
||||
protocol: string
|
||||
plugins: any[]
|
||||
}
|
||||
|
||||
export interface IConstructorParams {
|
||||
port: number
|
||||
theme: string
|
||||
@@ -32,18 +22,31 @@ export interface IConstructorParams {
|
||||
src: string
|
||||
}
|
||||
|
||||
export interface ConfigArgs extends IConstructorParams {
|
||||
paths: any
|
||||
env: string
|
||||
host: string
|
||||
protocol: string
|
||||
plugins: Plugin[]
|
||||
}
|
||||
|
||||
export class Server {
|
||||
private port: number
|
||||
private src: string
|
||||
private bundler: Bundler
|
||||
private plugins: Plugin[]
|
||||
private entries: Entries
|
||||
private bundler: Bundler
|
||||
|
||||
constructor(args: IConstructorParams) {
|
||||
const initialArgs = this.getInitialArgs(args)
|
||||
const { port, theme, files, bundler, src } = load('playgrodd', initialArgs)
|
||||
const { port, theme, files, bundler, src, plugins } = load(
|
||||
'playgrodd',
|
||||
initialArgs
|
||||
)
|
||||
|
||||
this.port = port
|
||||
this.src = src
|
||||
this.plugins = plugins
|
||||
this.entries = new Entries(files)
|
||||
|
||||
this.bundler = this.getBundler(bundler).bundler({
|
||||
@@ -51,6 +54,7 @@ export class Server {
|
||||
paths,
|
||||
theme,
|
||||
src,
|
||||
plugins,
|
||||
env: ENV,
|
||||
host: HOST,
|
||||
protocol: PROTOCOL,
|
||||
@@ -70,11 +74,18 @@ export class Server {
|
||||
}
|
||||
|
||||
public async start() {
|
||||
const { entries, bundler } = this
|
||||
const { bundler, entries, plugins } = this
|
||||
|
||||
const compiler = await bundler.createCompiler(entries.parse(this.src))
|
||||
const server = await bundler.createServer(compiler)
|
||||
|
||||
if (plugins && plugins.length > 0) {
|
||||
for (const plugin of plugins) {
|
||||
await plugin.bundlerCompiler(compiler)
|
||||
await plugin.bundlerServer(server)
|
||||
}
|
||||
}
|
||||
|
||||
server.listen(this.port)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
<% entries.forEach(function(entry) { %>import '<%- entry.filepath %>'
|
||||
<% ENTRIES.forEach(function(entry) { %>import '<%- entry.filepath %>'
|
||||
<% }); %>
|
||||
import React from 'react'
|
||||
import { hot } from 'react-hot-loader'
|
||||
import { Theme } from '<%- theme %>'
|
||||
import { Theme } from '<%- THEME %>'
|
||||
|
||||
export const App = hot(module)(Theme)
|
||||
const _wrappers = [<%- WRAPPERS %>]
|
||||
|
||||
const recursiveWrappers = ([Wrapper, ...rest], props) => (
|
||||
<Wrapper {...props}>
|
||||
{rest.length ? recursiveWrappers(rest, props) : props.children}
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
const Wrapper = props =>
|
||||
_wrappers.length ? recursiveWrappers(_wrappers, props) : props.children
|
||||
|
||||
const WrappedTheme = () => (
|
||||
<Wrapper>
|
||||
<Theme routes={<%- ROUTES %>} />
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
export const App = hot(module)(WrappedTheme)
|
||||
|
||||
@@ -2,4 +2,11 @@ import React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import { App } from './app'
|
||||
|
||||
render(<App />, document.querySelector('#root'))
|
||||
const _beforeRenders = [<%- BEFORE_RENDERS %>]
|
||||
const _afterRenders = [<%- AFTER_RENDERS %>]
|
||||
|
||||
const beforeRender = () => _beforeRenders.forEach(f => f && f())
|
||||
const afterRender = () => _afterRenders.forEach(f => f && f())
|
||||
|
||||
beforeRender()
|
||||
render(<App />, document.querySelector('#root'), afterRender)
|
||||
|
||||
@@ -1360,6 +1360,10 @@
|
||||
version "9.6.4"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-9.6.4.tgz#0ef7b4cfc3499881c81e0ea1ce61a23f6f4f5b42"
|
||||
|
||||
"@types/prettier@^1.12.0":
|
||||
version "1.12.0"
|
||||
resolved "https://registry.npmjs.org/@types/prettier/-/prettier-1.12.0.tgz#61ed6bdc64386f49c9e1f179cf84ef26ddc4740c"
|
||||
|
||||
"@types/react-dom@^16.0.5":
|
||||
version "16.0.5"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.5.tgz#a757457662e3819409229e8f86795ff37b371f96"
|
||||
@@ -5965,6 +5969,10 @@ prettier@^1.11.1:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
|
||||
|
||||
prettier@^1.12.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-1.12.0.tgz#d26fc5894b9230de97629b39cae225b503724ce8"
|
||||
|
||||
pretty-error@^2.0.2, pretty-error@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3"
|
||||
|
||||
Reference in New Issue
Block a user