feat(docz-core): add basic plugin logic

This commit is contained in:
Pedro Nauck
2018-04-13 20:04:19 -03:00
parent 5ec9fde526
commit add17adeec
8 changed files with 188 additions and 35 deletions

View File

@@ -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"
}
}

View File

@@ -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),

View File

@@ -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)
}

View File

@@ -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'

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"