mirror of
https://github.com/jxnblk/mdx-deck.git
synced 2024-11-26 00:35:02 +03:00
Add Head component and move webpack into repo
This commit is contained in:
parent
fbafcb21b9
commit
8be9abe530
12
cli.js
12
cli.js
@ -3,7 +3,6 @@ const path = require('path')
|
||||
const meow = require('meow')
|
||||
const open = require('react-dev-utils/openBrowser')
|
||||
const chalk = require('chalk')
|
||||
const ok = require('ok-cli')
|
||||
const remark = {
|
||||
emoji: require('remark-emoji'),
|
||||
unwrapImages: require('remark-unwrap-images')
|
||||
@ -132,10 +131,13 @@ const opts = Object.assign({
|
||||
|
||||
opts.outDir = path.resolve(opts.outDir)
|
||||
|
||||
let dev
|
||||
|
||||
switch (cmd) {
|
||||
case 'build':
|
||||
log('building')
|
||||
ok.build(opts)
|
||||
const build = require('./lib/build')
|
||||
build(opts)
|
||||
.then(res => {
|
||||
log('done')
|
||||
})
|
||||
@ -147,7 +149,8 @@ switch (cmd) {
|
||||
case 'pdf':
|
||||
log('exporting to PDF')
|
||||
const pdf = require('./lib/pdf')
|
||||
ok(opts)
|
||||
dev = require('./lib/dev')
|
||||
dev(opts)
|
||||
.then(({ server }) => {
|
||||
log('rendering PDF')
|
||||
pdf(opts)
|
||||
@ -169,7 +172,8 @@ switch (cmd) {
|
||||
case 'dev':
|
||||
default:
|
||||
log('starting dev server')
|
||||
ok(opts)
|
||||
dev = require('./lib/dev')
|
||||
dev(opts)
|
||||
.then(res => {
|
||||
const url = 'http://localhost:' + res.port
|
||||
if (opts.open) open(url)
|
||||
|
@ -3,6 +3,18 @@
|
||||
|
||||
mdx-deck includes a few built-in components to help with creating presentations.
|
||||
|
||||
## Head
|
||||
|
||||
TK
|
||||
|
||||
```mdx
|
||||
import { Head } from 'mdx-deck'
|
||||
|
||||
<Head>
|
||||
<title>My Presentation</title>
|
||||
</Head>
|
||||
```
|
||||
|
||||
## Image
|
||||
|
||||
Use the `<Image />` component to render a fullscreen image (using the CSS `background-image` property).
|
||||
|
23
lib/build.js
Normal file
23
lib/build.js
Normal file
@ -0,0 +1,23 @@
|
||||
const webpack = require('webpack')
|
||||
const createConfig = require('./config')
|
||||
|
||||
const build = async (opts = {}) => {
|
||||
const config = createConfig(opts)
|
||||
config.mode = 'production'
|
||||
config.output = {
|
||||
path: opts.outDir
|
||||
}
|
||||
const compiler = webpack(config)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve(stats)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = build
|
129
lib/config.js
Normal file
129
lib/config.js
Normal file
@ -0,0 +1,129 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const HTMLPlugin = require('mini-html-webpack-plugin')
|
||||
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
|
||||
const chalk = require('chalk')
|
||||
|
||||
const babel = {
|
||||
presets: [
|
||||
'babel-preset-env',
|
||||
'babel-preset-stage-0',
|
||||
'babel-preset-react',
|
||||
].map(require.resolve)
|
||||
}
|
||||
|
||||
const rules = [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: babel
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: path.resolve(__dirname, '../node_modules'),
|
||||
include: [
|
||||
path.resolve(__dirname, '..'),
|
||||
],
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: babel
|
||||
},
|
||||
{
|
||||
test: /\.mdx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: babel
|
||||
},
|
||||
require.resolve('@mdx-js/loader'),
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const template = ({
|
||||
title = 'ok',
|
||||
js,
|
||||
publicPath
|
||||
}) => `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<style>*{box-sizing:border-box}body{font-family:system-ui,sans-serif;margin:0}</style>
|
||||
<title>${title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id=root></div>
|
||||
${HTMLPlugin.generateJSReferences(js, publicPath)}
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
const baseConfig = {
|
||||
stats: 'errors-only',
|
||||
mode: 'development',
|
||||
module: {
|
||||
rules
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
path.relative(process.cwd(), path.join(__dirname, '../node_modules')),
|
||||
'node_modules'
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new ProgressBarPlugin({
|
||||
width: '24',
|
||||
complete: '█',
|
||||
incomplete: chalk.gray('░'),
|
||||
format: [
|
||||
chalk.magenta('[mdx-deck] :bar'),
|
||||
chalk.magenta(':percent'),
|
||||
chalk.gray(':elapseds :msg'),
|
||||
].join(' '),
|
||||
summary: false,
|
||||
customSummary: () => {},
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
const createConfig = (opts = {}) => {
|
||||
baseConfig.context = opts.dirname
|
||||
|
||||
baseConfig.resolve.modules.push(
|
||||
opts.dirname,
|
||||
path.join(opts.dirname, 'node_modules')
|
||||
)
|
||||
|
||||
baseConfig.entry = [
|
||||
path.join(__dirname, '../src/entry.js')
|
||||
]
|
||||
|
||||
const defs = Object.assign({}, opts.globals, {
|
||||
OPTIONS: JSON.stringify(opts),
|
||||
APP_FILENAME: JSON.stringify(opts.entry),
|
||||
HOT_PORT: JSON.stringify(opts.hotPort)
|
||||
})
|
||||
|
||||
baseConfig.plugins.push(
|
||||
new webpack.DefinePlugin(defs),
|
||||
new HTMLPlugin({ template, context: opts })
|
||||
)
|
||||
|
||||
const config = typeof opts.config === 'function'
|
||||
? opts.config(baseConfig)
|
||||
: baseConfig
|
||||
|
||||
if (config.resolve.alias) {
|
||||
const hotAlias = config.resolve.alias['webpack-hot-client/client']
|
||||
if (!fs.existsSync(hotAlias)) {
|
||||
const hotPath = path.dirname(require.resolve('webpack-hot-client/client'))
|
||||
config.resolve.alias['webpack-hot-client/client'] = hotPath
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
module.exports = createConfig
|
45
lib/dev.js
Normal file
45
lib/dev.js
Normal file
@ -0,0 +1,45 @@
|
||||
const path = require('path')
|
||||
const Koa = require('koa')
|
||||
const getPort = require('get-port')
|
||||
const koaWebpack = require('koa-webpack')
|
||||
const koaStatic = require('koa-static')
|
||||
const createConfig = require('./config')
|
||||
|
||||
const devMiddleware = {
|
||||
publicPath: '/',
|
||||
clientLogLevel: 'error',
|
||||
stats: 'errors-only',
|
||||
logLevel: 'error',
|
||||
}
|
||||
|
||||
const start = async (opts = {}) => {
|
||||
const app = new Koa()
|
||||
opts.hotPort = await getPort()
|
||||
const hotClient = {
|
||||
port: opts.hotPort,
|
||||
logLevel: 'error'
|
||||
}
|
||||
opts.dirname = opts.dirname || path.dirname(opts.entry)
|
||||
const config = createConfig(opts)
|
||||
config.entry.push(
|
||||
path.join(__dirname, './overlay.js'),
|
||||
)
|
||||
|
||||
const middleware = await koaWebpack({
|
||||
config,
|
||||
devMiddleware,
|
||||
hotClient
|
||||
})
|
||||
const port = opts.port || await getPort()
|
||||
app.use(middleware)
|
||||
app.use(koaStatic(opts.dirname))
|
||||
|
||||
const server = app.listen(port)
|
||||
return new Promise((resolve) => {
|
||||
middleware.devMiddleware.waitUntilValid(() => {
|
||||
resolve({ server, app, middleware, port })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = start
|
81
lib/overlay.js
Normal file
81
lib/overlay.js
Normal file
@ -0,0 +1,81 @@
|
||||
const ansiHTML = require('ansi-html')
|
||||
const Entities = require('html-entities').AllHtmlEntities
|
||||
const entities = new Entities()
|
||||
|
||||
const colors = {
|
||||
reset: ['transparent', 'transparent'],
|
||||
black: '000000',
|
||||
red: 'FF0000',
|
||||
green: '00FF00',
|
||||
yellow: 'FFFF00',
|
||||
blue: '0000FF',
|
||||
magenta: 'FF00FF',
|
||||
cyan: '00FFFF',
|
||||
lightgrey: 'EEEEEE',
|
||||
darkgrey: '666666'
|
||||
};
|
||||
ansiHTML.setColors(colors)
|
||||
|
||||
let overlay
|
||||
|
||||
const style = (el, styles) => {
|
||||
for (const key in styles) {
|
||||
el.style[key] = styles[key]
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
const show = ({
|
||||
title = '',
|
||||
text = ''
|
||||
}) => {
|
||||
overlay = document.body.appendChild(
|
||||
document.createElement('pre')
|
||||
)
|
||||
style(overlay, {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
boxSizing: 'border-box',
|
||||
fontFamily: 'Menlo, monospace',
|
||||
fontSize: '12px',
|
||||
overflow: 'auto',
|
||||
lineHeight: 1.5,
|
||||
padding: '8px',
|
||||
margin: 0,
|
||||
color: 'magenta',
|
||||
backgroundColor: 'black'
|
||||
})
|
||||
const code = ansiHTML(entities.encode(text))
|
||||
overlay.innerHTML = `<span>${title}</span>
|
||||
<br />
|
||||
<br />${code}
|
||||
`
|
||||
}
|
||||
|
||||
const destroy = () => {
|
||||
if (!overlay) return
|
||||
document.body.removeChild(overlay)
|
||||
}
|
||||
|
||||
const ws = new WebSocket('ws://localhost:' + HOT_PORT)
|
||||
|
||||
ws.addEventListener('message', msg => {
|
||||
const data = JSON.parse(msg.data)
|
||||
switch (data.type) {
|
||||
case 'errors':
|
||||
const [ text ] = data.data.errors
|
||||
console.error(data.data.errors)
|
||||
show({ title: 'failed to compile', text })
|
||||
break
|
||||
case 'ok':
|
||||
destroy()
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
ws.addEventListener('close', () => {
|
||||
show({ title: 'disconnected' })
|
||||
})
|
@ -23,15 +23,21 @@
|
||||
"@compositor/webfont": "^1.0.39",
|
||||
"@mdx-js/mdx": "^0.15.0-1",
|
||||
"@mdx-js/tag": "^0.14.1",
|
||||
"ansi-html": "0.0.7",
|
||||
"chalk": "^2.4.1",
|
||||
"clipboardy": "^1.2.3",
|
||||
"get-port": "^4.0.0",
|
||||
"gray-matter": "^4.0.1",
|
||||
"hhmmss": "^1.0.0",
|
||||
"html-entities": "^1.2.1",
|
||||
"koa": "^2.5.2",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa-webpack": "^5.1.0",
|
||||
"loader-utils": "^1.1.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"meow": "^5.0.0",
|
||||
"mini-html-webpack-plugin": "^0.2.3",
|
||||
"normalize-newline": "^3.0.0",
|
||||
"ok-cli": "^3.1.1",
|
||||
"pkg-conf": "^2.1.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"puppeteer": "^1.6.1",
|
||||
@ -46,6 +52,7 @@
|
||||
"styled-components": ">=3.0.0",
|
||||
"styled-system": "^3.0.2",
|
||||
"superbox": "^2.1.0",
|
||||
"webpack": "^4.16.5",
|
||||
"webpack-hot-client": "^4.1.1"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
|
94
src/Head.js
Normal file
94
src/Head.js
Normal file
@ -0,0 +1,94 @@
|
||||
import React from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
const noop = () => {
|
||||
console.warn('Missing HeadProvider')
|
||||
}
|
||||
|
||||
export const Context = React.createContext({
|
||||
tags: [],
|
||||
push: noop
|
||||
})
|
||||
|
||||
export class HeadProvider extends React.Component {
|
||||
static defaultProps = {
|
||||
tags: []
|
||||
}
|
||||
|
||||
push = (elements) => {
|
||||
this.props.tags.push(...elements)
|
||||
}
|
||||
|
||||
render () {
|
||||
const context = {
|
||||
...this.state,
|
||||
push: this.push
|
||||
}
|
||||
|
||||
return (
|
||||
<Context.Provider value={context}>
|
||||
{this.props.children}
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class Head extends React.Component {
|
||||
state = {
|
||||
didMount: false
|
||||
}
|
||||
|
||||
rehydrate = () => {
|
||||
const children = React.Children.toArray(this.props.children)
|
||||
const nodes = [
|
||||
...document.head.querySelectorAll('[data-head]')
|
||||
]
|
||||
|
||||
nodes.forEach(node => {
|
||||
node.remove()
|
||||
})
|
||||
children.forEach(child => {
|
||||
if (child.type === 'title') {
|
||||
const title = document.head.querySelector('title')
|
||||
if (title) title.remove()
|
||||
}
|
||||
if (child.type === 'meta') {
|
||||
const meta = document.head.querySelector(`meta[name=${child.props.name}]`)
|
||||
if (meta) meta.remove()
|
||||
}
|
||||
})
|
||||
|
||||
this.setState({
|
||||
didMount: true
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.rehydrate()
|
||||
}
|
||||
|
||||
render () {
|
||||
const children = React.Children.toArray(this.props.children)
|
||||
.map(child => React.cloneElement(child, {
|
||||
'data-head': true
|
||||
}))
|
||||
|
||||
const { didMount } = this.state
|
||||
|
||||
if (!didMount) {
|
||||
return (
|
||||
<Context.Consumer
|
||||
children={({ push }) => {
|
||||
push(children)
|
||||
return false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
children,
|
||||
document.head
|
||||
)
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import GoogleFonts from './GoogleFonts'
|
||||
import defaultTheme from './themes'
|
||||
import defaultComponents from './components'
|
||||
|
||||
export { default as Head } from './Head'
|
||||
export { default as Image } from './Image'
|
||||
export { default as Notes } from './Notes'
|
||||
export { default as Appear } from './Appear'
|
||||
|
Loading…
Reference in New Issue
Block a user