Skip to content
This repository has been archived by the owner on Jan 24, 2025. It is now read-only.

Commit

Permalink
feat(docz-core): add htmlContext and mini-html-webpack-plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
pedronauck committed Aug 10, 2018
1 parent 8b90666 commit 4b6ec0f
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 190 deletions.
5 changes: 4 additions & 1 deletion packages/docz-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@mdx-js/mdx": "^0.15.0-2",
"@mdx-js/mdxast": "^0.15.0-2",
"@sindresorhus/slugify": "^0.3.0",
"@vxna/mini-html-webpack-template": "^0.1.7",
"art-template": "^4.12.2",
"babel-loader": "^8.0.0-beta.4",
"babel-plugin-react-docgen": "^2.0.0-rc.1",
Expand All @@ -38,6 +39,7 @@
"babylon": "^6.18.0",
"chalk": "^2.4.1",
"chokidar": "^2.0.4",
"common-tags": "^1.8.0",
"connect-history-api-fallback": "^1.5.0",
"cpy": "^7.0.1",
"deepmerge": "^2.1.1",
Expand All @@ -52,7 +54,7 @@
"fs-extra": "^7.0.0",
"get-pkg-repo": "^2.0.0",
"happypack": "^5.0.0",
"html-webpack-plugin": "^3.2.0",
"html-minifier": "^3.5.19",
"humanize-string": "^1.0.2",
"koa": "^2.5.2",
"koa-connect": "^2.0.1",
Expand All @@ -61,6 +63,7 @@
"koa-static": "^5.0.0",
"load-cfg": "^0.9.4",
"lodash.get": "^4.4.2",
"mini-html-webpack-plugin": "^0.2.3",
"prettier": "^1.14.0",
"react-dev-utils": "^5.0.1",
"react-docgen-typescript-loader": "^2.1.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/docz-core/src/Bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface BundlerServer {
start(): Promise<Server>
}

export type ConfigFn<C> = (babelrc: BabelRC) => C
export type ConfigFn<C> = (babelrc: BabelRC) => Promise<C>
export type BuildFn<C> = (config: C, dist: string) => void

export type ServerFnReturn = BundlerServer | Promise<BundlerServer>
Expand Down Expand Up @@ -56,7 +56,7 @@ export class Bundler<C = ConfigObj> {

public async getConfig(env: Env): Promise<C> {
const babelConfig = await getBabelConfig(this.args, env)
const config = this.mountConfig(this.config(babelConfig), env)
const config = this.mountConfig(await this.config(babelConfig), env)

return this.args.modifyBundlerConfig(config, !this.isProd(env), this.args)
}
Expand Down
20 changes: 2 additions & 18 deletions packages/docz-core/src/Entries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,15 @@ import { repoInfo } from './utils/repo-info'

export const fromTemplates = (file: string) => path.join(paths.templates, file)

const getHtmlFilepath = (indexHtml: string | undefined) =>
indexHtml ? path.join(paths.root, indexHtml) : fromTemplates('index.tpl.html')

const getPublicUrl = (config: Config, dev: boolean): string => {
const prefix = config.base === '/' ? '' : config.base
return dev ? prefix : `${prefix}/public`
}

const writeAppFiles = async (config: Config, dev: boolean): Promise<void> => {
const { plugins, title, description, theme, indexHtml } = config
const { plugins, theme } = config
const props = Plugin.propsOfPlugins(plugins)

const onPreRenders = props('onPreRender')
const onPostRenders = props('onPostRender')

const root = await compiled(fromTemplates('root.tpl.js'))
const js = await compiled(fromTemplates('index.tpl.js'))
const html = await compiled(getHtmlFilepath(indexHtml))
const websocketUrl = `ws://${config.websocketHost}:${config.websocketPort}`

const rawRootJs = root({
Expand All @@ -42,20 +33,13 @@ const writeAppFiles = async (config: Config, dev: boolean): Promise<void> => {
})

const rawIndexJs = js({
isProd: !dev,
onPreRenders,
onPostRenders,
})

const rawIndexHtml = html({
title,
description,
publicUrl: getPublicUrl(config, dev),
isProd: !dev,
})

await touch(paths.rootJs, rawRootJs)
await touch(paths.indexJs, rawIndexJs)
await touch(paths.indexHtml, rawIndexHtml)
}

export type EntryMap = Record<string, EntryObj>
Expand Down
10 changes: 5 additions & 5 deletions packages/docz-core/src/Plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ export class Plugin<C = any> implements PluginFactory {
}

public static propsOfPlugins(
plugins: Plugin[] | undefined
): (prop: keyof Plugin) => any {
plugins: Plugin[]
): (prop: keyof Plugin) => any[] {
return prop =>
plugins &&
plugins.length > 0 &&
plugins.map(p => get(p, prop)).filter(m => m)
plugins && plugins.length > 0
? plugins.map(p => get(p, prop)).filter(Boolean)
: []
}

public static reduceFromPlugins<C>(
Expand Down
51 changes: 31 additions & 20 deletions packages/docz-core/src/bundlers/webpack/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import * as path from 'path'
import { Configuration } from 'webpack'
import webpackBarPlugin from 'webpackbar'
import Config from 'webpack-chain'
import { minify } from 'html-minifier'
import miniHtmlWebpack from 'mini-html-webpack-plugin'
import friendlyErrors from 'friendly-errors-webpack-plugin'
import htmlWebpackPlugin from 'html-webpack-plugin'
import manifestPlugin from 'webpack-manifest-plugin'
import UglifyJs from 'uglifyjs-webpack-plugin'

Expand All @@ -12,6 +13,7 @@ import * as paths from '../../config/paths'
import { getClientEnvironment } from '../../config/env'
import { Config as Args, Env } from '../../commands/args'
import { BabelRC } from '../../utils/babel-config'
import { parseHtml, htmlTemplate } from '../../utils/parse-html'

const uglify = new UglifyJs({
parallel: true,
Expand All @@ -37,9 +39,9 @@ const uglify = new UglifyJs({
},
})

export const createConfig = (args: Args, env: Env) => (
export const createConfig = (args: Args, env: Env) => async (
babelrc: BabelRC
): Configuration => {
): Promise<Configuration> => {
const { debug, host, port, protocol } = args

const config = new Config()
Expand Down Expand Up @@ -177,24 +179,33 @@ export const createConfig = (args: Args, env: Env) => (
},
])

config.plugin('html-webpack-plugin').use(htmlWebpackPlugin, [
const dev = !isProd
const template = await htmlTemplate(args.indexHtml)

config.plugin('html-plugin').use(miniHtmlWebpack, [
{
inject: true,
template: paths.indexHtml,
...(isProd && {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
context: {
...args.htmlContext,
trimWhitespace: true,
},
template: (ctx: any) => {
const doc = parseHtml({ ctx, dev, template, config: args })

return dev
? doc
: minify(doc, {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
})
},
},
])

Expand Down
17 changes: 17 additions & 0 deletions packages/docz-core/src/commands/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ const getInitialDescription = (pkg: any): string =>
export type Env = 'production' | 'development'
export type ThemeConfig = Record<string, any>

export interface HtmlContext {
lang: string
favicon?: string
head?: {
meta: any[]
links: any[]
raw: string
scripts: any[]
}
body?: {
raw: string
scripts: any[]
}
}

export interface Argv {
/* io args */
base: string
Expand Down Expand Up @@ -51,11 +66,13 @@ export interface Argv {
}

export interface Config extends Argv {
paths: Record<string, any>
hashRouter: boolean
plugins: Plugin[]
mdPlugins: any[]
hastPlugins: any[]
themeConfig: ThemeConfig
htmlContext: HtmlContext
modifyBundlerConfig<C>(config: C, dev: boolean, args: Config): C
modifyBabelRc(babelrc: BabelRC, args: Config): BabelRC
}
Expand Down
4 changes: 4 additions & 0 deletions packages/docz-core/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ declare module 'unist-util-visit'
declare module 'unist-util-find'
declare module 'unist-util-remove'
declare module 'hast-util-to-string'
declare module 'html-minifer'
declare module 'mini-html-webpack-plugin'
declare module '@vxna/mini-html-webpack-template'
declare module 'happypack'
declare module 'common-tags'
declare module 'remark-parse'
declare module 'remark-slug'
declare module 'remark-parse-yaml'
Expand Down
7 changes: 5 additions & 2 deletions packages/docz-core/src/utils/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export const readIfExist = async (file: string): Promise<string | null> => {
return exist ? read(file) : Promise.resolve(null)
}

export const compiled = async (file: string): Promise<(args: any) => string> =>
export const compiled = async (
file: string,
opts: Record<string, any> = {}
): Promise<(args: any) => string> =>
read(file)
.then(data => compile(data))
.then(data => compile(data, opts))
.catch(err => err)
10 changes: 7 additions & 3 deletions packages/docz-core/src/utils/load-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@ import { BabelRC } from './babel-config'

const toOmit = ['_', '$0', 'version', 'help']

const defaultHtmlContext = {
lang: 'en',
}

export const loadConfig = (args: Config): Config => {
const config = load('docz', {
const config = load<Config>('docz', {
...args,
paths,
hashRouter: false,
plugins: [],
mdPlugins: [],
hastPlugins: [],
themeConfig: {},
htmlContext: defaultHtmlContext,
modifyBundlerConfig: (config: any) => config,
modifyBabelRc: (babelrc: BabelRC) => babelrc,
})

const reduce = Plugin.reduceFromPlugins<Config>(config.plugins)
return omit<Config>(toOmit, reduce('setConfig', config))
return omit<Config>(toOmit, reduce('setConfig', { ...config, paths }))
}
101 changes: 101 additions & 0 deletions packages/docz-core/src/utils/parse-html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as path from 'path'
import * as ctags from 'common-tags'
import {
generateCSSReferences,
generateJSReferences,
} from 'mini-html-webpack-plugin'

import { compiled } from './fs'
import { fromTemplates } from '../Entries'
import * as paths from '../config/paths'
import { Config } from '../commands/args'

const wrapItems = (item: any) =>
Object.keys(item)
.map(key => `${key}="${item[key]}"`)
.join(' ')

const generateMetaTags = (items: any[] = []) => {
return items.map(item => `<meta ${wrapItems(item)}>`)
}

const generateLinkTags = (items: any[] = []) => {
return items.map(item => `<link ${wrapItems(item)}>`)
}

const generateScriptTags = (items: any[] = []) => {
return items.map(item => `<script ${wrapItems(item)}></script>`)
}

const generateRawTags = (items: any[] = []) => {
if (typeof items === 'string' || items instanceof String) return items
return items.map(item => item)
}

const getHtmlFilepath = (indexHtml: string | undefined) =>
indexHtml
? path.resolve(paths.root, indexHtml)
: fromTemplates('index.tpl.html')

const getPublicUrl = (config: Config, dev: boolean): string => {
const prefix = config.base === '/' ? '' : config.base
return dev ? prefix : `${prefix}/public`
}

const emptyLineTrim = new ctags.TemplateTag(
ctags.replaceResultTransformer(/^\s*[\r\n]/gm, ''),
ctags.trimResultTransformer
)

export const htmlTemplate = async (indexHtml: string | undefined) =>
compiled(getHtmlFilepath(indexHtml), {
minimize: false,
escape: false,
})

interface ParseHtmlParams {
config: Config
ctx: Record<string, any>
dev: boolean
template: (props: Record<string, any>) => string
}

export const parseHtml = ({ config, ctx, dev, template }: ParseHtmlParams) => {
const { title, description } = config
const {
publicPath,
css,
js,
lang = 'en',
favicon,
head = [],
body = [],
trimWhitespace,
} = ctx

const headStr = `
${favicon ? `<link rel="icon" type="image/x-icon" href="${favicon}">` : ''}
${head.meta ? generateMetaTags(head.meta) : ''}
${head.links ? generateLinkTags(head.links) : ''}
${head.raw ? generateRawTags(head.raw) : ''}
${head.scripts ? generateScriptTags(head.scripts) : ''}
${generateCSSReferences(css, publicPath)}`

const footerStr = `
${body.raw ? generateRawTags(body.raw) : ''}
${body.scripts ? generateScriptTags(body.scripts) : ''}
${generateJSReferences(js, publicPath)}`

const doc = ctags.html(
template({
title,
description,
lang,
head: headStr,
footer: footerStr,
publicUrl: getPublicUrl(config, dev),
})
)

return trimWhitespace ? ctags.oneLineTrim(doc) : emptyLineTrim(doc)
}
Loading

0 comments on commit 4b6ec0f

Please sign in to comment.