Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New docs site! #765

Merged
merged 2 commits into from
Jul 17, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
New website
jaredpalmer committed Jul 17, 2020
commit 6dd85e185864b3b8567f361c391fb3f76d8292fe
4 changes: 4 additions & 0 deletions website2/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["next/babel"],
"plugins": ["babel-plugin-macros", "./.nextra/babel-plugin-nextjs-mdx-patch"]
}
4 changes: 4 additions & 0 deletions website2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.next
.DS_Store
yarn-error.log
19 changes: 19 additions & 0 deletions website2/.nextra/arrow-right.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default ({ width = 24, height = 24, ...props }) => {
return (
<svg
width={width}
height={height}
viewBox="0 0 24 24"
fill="none"
{...props}
>
<path
d="M3 12h18m0 0l-6.146-6M21 12l-6.146 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
34 changes: 34 additions & 0 deletions website2/.nextra/babel-plugin-nextjs-mdx-patch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Currently it's not possible to export data fetching functions from MDX pages
* because MDX includes them in `layoutProps`, and Next.js removes them at some
* point, causing a `ReferenceError`.
*
* https://github.com/mdx-js/mdx/issues/742#issuecomment-612652071
*
* This plugin can be removed once MDX removes `layoutProps`, at least that
* seems to be the current plan.
*/

// https://nextjs.org/docs/basic-features/data-fetching
const DATA_FETCH_FNS = ['getStaticPaths', 'getStaticProps', 'getServerProps']

module.exports = () => {
return {
visitor: {
ObjectProperty(path) {
if (
DATA_FETCH_FNS.includes(path.node.value.name) &&
path.findParent(
(path) =>
path.isVariableDeclarator() &&
path.node.id.name === 'layoutProps',
)
) {
path.remove()
}
},
},
}
}

// https://github.com/vercel/next.js/issues/12053#issuecomment-622939046
11 changes: 11 additions & 0 deletions website2/.nextra/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import userConfig from '../nextra.config';

const defaultConfig = {
nextLinks: true,
prevLinks: true,
search: true,
};

export default () => {
return { ...defaultConfig, ...userConfig };
};
96 changes: 96 additions & 0 deletions website2/.nextra/directories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import preval from 'preval.macro'
import title from 'title'

const excludes = ['/_app.js', '/_document.js', '/_error.js']

// watch all meta files
const meta = {}
function importAll(r) {
return r.keys().forEach(key => {
meta[key.slice(1)] = r(key)
})
}
importAll(require.context('../pages/', true, /meta\.json$/))

// use macro to load the file list
const items = preval`
const { readdirSync, readFileSync } = require('fs')
const { resolve, join } = require('path')
const extension = /\.(mdx?|jsx?)$/

function getFiles(dir, route) {
const files = readdirSync(dir, { withFileTypes: true })

// go through the directory
const items = files
.map(f => {
const filePath = resolve(dir, f.name)
const fileRoute = join(route, f.name.replace(extension, '').replace(/^index$/, ''))

if (f.isDirectory()) {
const children = getFiles(filePath, fileRoute)
if (!children.length) return null
return { name: f.name, children, route: fileRoute }
} else if (f.name === 'meta.json') {
return null
} else if (extension.test(f.name)) {
return { name: f.name.replace(extension, ''), route: fileRoute }
}
})
.map(item => {
if (!item) return
return { ...item, metaPath: join(route, 'meta.json') }
})
.filter(Boolean)

return items
}
module.exports = getFiles(join(process.cwd(), 'pages'), '/')
`

const attachPageConfig = function (items) {
let folderMeta = null
let fnames = null

return items
.filter(item => !excludes.includes(item.name))
.map(item => {
const { metaPath, ...rest } = item
folderMeta = meta[metaPath]
if (folderMeta) {
fnames = Object.keys(folderMeta)
}

const pageConfig = folderMeta?.[item.name]

if (rest.children) rest.children = attachPageConfig(rest.children)

if (pageConfig) {
if (typeof pageConfig === 'string') {
return { ...rest, title: pageConfig }
}
return { ...rest, ...pageConfig }
} else {
if (folderMeta) {
return null
}
return { ...rest, title: title(item.name) }
}
})
.filter(Boolean)
.sort((a, b) => {
if (folderMeta) {
return fnames.indexOf(a.name) - fnames.indexOf(b.name)
}
// by default, we put directories first
if (!!a.children !== !!b.children) {
return !!a.children ? -1 : 1
}
// sort by file name
return a.name < b.name ? -1 : 1
})
}

export default () => {
return attachPageConfig(items)
}
44 changes: 44 additions & 0 deletions website2/.nextra/docsearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useRef, useEffect } from 'react'

export default function () {
const input = useRef(null)

useEffect(() => {
const inputs = ['input', 'select', 'button', 'textarea']

const down = (e) => {
if (
document.activeElement &&
inputs.indexOf(document.activeElement.tagName.toLowerCase() !== -1)
) {
if (e.key === '/') {
e.preventDefault()
input.current?.focus()
}
}
}

window.addEventListener('keydown', down)
return () => window.removeEventListener('keydown', down)
}, [])

useEffect(() => {
if (window.docsearch) {
window.docsearch({
apiKey: '247dd86c8ddbbbe6d7a2d4adf4f3a68a',
indexName: 'vercel_swr',
inputSelector: 'input#algolia-doc-search'
})
}
}, [])

return <div className="relative w-full md:w-64 mr-2">
<input
id="algolia-doc-search"
className="appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-full"
type="search"
placeholder='Search ("/" to focus)'
ref={input}
/>
</div>
}
5 changes: 5 additions & 0 deletions website2/.nextra/github-icon.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

332 changes: 332 additions & 0 deletions website2/.nextra/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
import React, {
useState,
useEffect,
useMemo,
useContext,
createContext,
} from 'react';
import { useRouter } from 'next/router';
import Head from 'next/head';
import Link from 'next/link';
import slugify from '@sindresorhus/slugify';
import 'focus-visible';
import cn from 'classnames';
import { SkipNavContent } from '@reach/skip-nav';

import Theme from './theme';
import SSGContext from './ssg';
import Search from './search';
// import DocSearch from './docsearch'
import GitHubIcon from './github-icon';
import ArrowRight from './arrow-right';

import getDirectories from './directories';
import getConfig from './config';
import * as ReactDOM from 'react-dom/server';
import stripHtml from 'string-strip-html';
const config = getConfig();
const directories = getDirectories();
const TreeState = new Map();
const titleType = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
const MenuContext = createContext(false);

const flatten = list => {
return list.reduce((flat, toFlatten) => {
return flat.concat(
toFlatten.children ? flatten(toFlatten.children) : toFlatten
);
}, []);
};

const flatDirectories = flatten(directories);

function Folder({ item, anchors }) {
const route = useRouter().route + '/';
const active = route.startsWith(item.route + '/');
const open = TreeState[item.route] ?? true;
const [_, render] = useState(false);

useEffect(() => {
if (active) {
TreeState[item.route] = true;
}
}, [active]);

return (
<li
className={cn({
'active-route': active,
active: open,
})}
>
<button
onClick={() => {
if (active) return;
TreeState[item.route] = !open;
render(x => !x);
}}
className="focus:shadow-outline"
>
{item.title}
</button>
<div
style={{
display: open ? '' : 'none',
}}
>
<Menu dir={item.children} base={item.route} anchors={anchors} />
</div>
</li>
);
}

function File({ item, anchors }) {
const { setMenu } = useContext(MenuContext);
const route = useRouter().route + '/';
const active = route.startsWith(item.route + '/');

let title = item.title;
// if (item.title.startsWith('> ')) {
// title = title.substr(2)
if (anchors?.length) {
if (active) {
return (
<li className={active ? 'active' : ''}>
<Link href={item.route}>
<a>{title}</a>
</Link>
<ul>
{anchors.map(anchor => {
const slug = slugify(
stripHtml(ReactDOM.renderToString(anchor)) || ''
);
return (
<li key={`a-${slug}`}>
<a
href={'#' + slug}
onClick={() => setMenu(false)}
className="focus:shadow-outline"
>
<span className="flex">
<span className="mr-2 opacity-25">#</span>
<span className="inline-block">{anchor}</span>
</span>
</a>
</li>
);
})}
</ul>
</li>
);
}
}

return (
<li className={active ? 'active' : ''}>
<Link href={item.route}>
<a onClick={() => setMenu(false)} className="focus:shadow-outline">
{title}
</a>
</Link>
</li>
);
}

function Menu({ dir, anchors }) {
return (
<ul>
{dir.map(item => {
if (item.children) {
return <Folder key={item.name} item={item} anchors={anchors} />;
}
return <File key={item.name} item={item} anchors={anchors} />;
})}
</ul>
);
}

function Sidebar({ show, anchors }) {
return (
<aside
className={`h-screen bg-white flex-shrink-0 w-full md:w-64 md:border-r md:block fixed md:sticky z-10 ${
show ? '' : 'hidden'
}`}
style={{
top: '4rem',
height: 'calc(100vh - 4rem)',
}}
>
<div className="sidebar w-full p-4 pb-40 md:pb-16 h-full overflow-y-auto">
<Menu dir={directories} anchors={anchors} />
</div>
</aside>
);
}

const NextLink = ({ currentIndex }) => {
let next = flatDirectories[currentIndex + 1];

if (!config.nextLinks || !next) {
return null;
}

return (
<Link href={next.route}>
<a className="text-lg font-medium p-4 -m-4 no-underline text-gray-600 hover:text-blue-600 flex items-center ml-2">
{next.title}
<ArrowRight className="inline ml-1 flex-shrink-0" />
</a>
</Link>
);
};

const PrevLink = ({ currentIndex }) => {
let prev = flatDirectories[currentIndex - 1];

if (!config.prevLinks || !prev) {
return null;
}

return (
<Link href={prev.route}>
<a className="text-lg font-medium p-4 -m-4 no-underline text-gray-600 hover:text-blue-600 flex items-center mr-2">
<ArrowRight className="transform rotate-180 inline mr-1 flex-shrink-0" />
{prev.title}
</a>
</Link>
);
};

const Layout = ({ filename, full, title: _title, ssg = {}, children }) => {
const [menu, setMenu] = useState(false);
const router = useRouter();
const { route, pathname } = router;

const filepath = route.slice(0, route.lastIndexOf('/') + 1);
const titles = React.Children.toArray(children).filter(child =>
titleType.includes(child.props.mdxType)
);
const anchors = titles
.filter(child => child.props.mdxType === 'h2')
.map(child => child.props.children);

useEffect(() => {
if (menu) {
document.body.classList.add('overflow-hidden');
} else {
document.body.classList.remove('overflow-hidden');
}
}, [menu]);

const currentIndex = useMemo(
() => flatDirectories.findIndex(dir => dir.route === pathname),
[flatDirectories, pathname]
);

const title =
flatDirectories[currentIndex]?.title ||
titles.find(child => child.props.mdxType === 'h1')?.props.children ||
'Untitled';

const props = {
filepath: filepath + filename,
route,
};

return (
<>
<Head>
<title>
{title}
{config.titleSuffix || ''}
</title>
{config.head ? config.head(props) : null}
</Head>
<div className="main-container flex flex-col">
<nav className="flex items-center bg-white z-20 fixed top-0 left-0 right-0 h-16 border-b px-6 ">
<div className="mr-6 md:mr-0 md:w-full flex items-center">
<Link href="/">
<a className="no-underline text-current inline-flex items-center p-2 -m-2 hover:opacity-75">
{config.logo}
</a>
</Link>
</div>

{config.search && <Search directories={flatDirectories} />}
{/* <DocSearch /> */}

{config.github ? (
<a
className="text-current p-2 -mr-2"
href={config.github}
target="_blank"
rel="noopener"
aria-label="GitHub Repository"
>
<GitHubIcon height={28} />
</a>
) : null}
<button
className="block md:hidden p-2 -mr-2 ml-2"
onClick={() => setMenu(!menu)}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
</nav>
<div className="flex flex-1 h-full">
<MenuContext.Provider value={{ setMenu }}>
<Sidebar show={menu} anchors={anchors} />
</MenuContext.Provider>
<SSGContext.Provider value={ssg}>
{full ? (
<content className="relative pt-16 w-full overflow-x-hidden">
{children}
</content>
) : (
<>
<SkipNavContent />
<content className="relative pt-20 pb-16 px-6 md:px-8 w-full max-w-full overflow-x-hidden">
<main className="max-w-screen-md">
<Theme>{children}</Theme>
<footer className="mt-24">
<nav className="flex flex-row items-center justify-between">
<div>
<PrevLink currentIndex={currentIndex} />
</div>

<div>
<NextLink currentIndex={currentIndex} />
</div>
</nav>

<hr />

{config.footer ? config.footer(props) : null}
</footer>
</main>{' '}
</content>
</>
)}
</SSGContext.Provider>
</div>
</div>
</>
);
};

export default filename => {
return props => <Layout filename={filename} {...props} />;
};
8 changes: 8 additions & 0 deletions website2/.nextra/nextra-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = function(source, map) {
this.cacheable()
const fileName = this.resourcePath.slice(this.resourcePath.lastIndexOf('/') + 1)
const prefix = `import withNextraLayout from '.nextra/layout'\n\n`
const suffix = `\n\nexport default withNextraLayout("${fileName}")`
source = prefix + source + suffix
this.callback(null, source, map)
}
33 changes: 33 additions & 0 deletions website2/.nextra/nextra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const path = require('path')

module.exports = (pluginOptions = {
extension: /\.mdx?$/
}) => (nextConfig = {
pageExtensions: ['js', 'jsx', 'md', 'mdx']
}) => {
const extension = pluginOptions.extension || /\.mdx$/

return Object.assign({}, nextConfig, {
webpack(config, options) {
config.module.rules.push({
test: extension,
use: [
options.defaultLoaders.babel,
{
loader: '@mdx-js/loader',
options: pluginOptions.options
},
{
loader: path.resolve('.nextra', 'nextra-loader.js')
}
]
})

if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options)
}

return config
}
})
}
149 changes: 149 additions & 0 deletions website2/.nextra/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { useMemo, useCallback, useRef, useState, useEffect } from 'react';
import matchSorter from 'match-sorter';
import cn from 'classnames';
import { useRouter } from 'next/router';
import Link from 'next/link';

const Item = ({ title, active, href, onMouseOver, search }) => {
const highlight = title.toLowerCase().indexOf(search.toLowerCase());

return (
<Link href={href}>
<a className="block no-underline" onMouseOver={onMouseOver}>
<li
className={cn('py-2 px-3 text-gray-800', {
'bg-gray-100': active,
})}
>
{title.substring(0, highlight)}
<span className="bg-teal-300">
{title.substring(highlight, highlight + search.length)}
</span>
{title.substring(highlight + search.length)}
</li>
</a>
</Link>
);
};

const Search = ({ directories }) => {
const router = useRouter();
const [show, setShow] = useState(false);
const [search, setSearch] = useState('');
const [active, setActive] = useState(0);
const input = useRef(null);

const results = useMemo(() => {
if (!search) return [];

// Will need to scrape all the headers from each page and search through them here
// (similar to what we already do to render the hash links in sidebar)
// We could also try to search the entire string text from each page
return matchSorter(directories, search, { keys: ['title'] });
}, [search]);

const handleKeyDown = useCallback(
(e) => {
switch (e.key) {
case 'ArrowDown': {
e.preventDefault();
if (active + 1 < results.length) {
setActive(active + 1);
}
break;
}
case 'ArrowUp': {
e.preventDefault();
if (active - 1 >= 0) {
setActive(active - 1);
}
break;
}
case 'Enter': {
router.push(results[active].route);
break;
}
}
},
[active, results, router]
);

useEffect(() => {
setActive(0);
}, [search]);

useEffect(() => {
const inputs = ['input', 'select', 'button', 'textarea'];

const down = (e) => {
if (
document.activeElement &&
inputs.indexOf(document.activeElement.tagName.toLowerCase() !== -1)
) {
if (e.key === '/') {
e.preventDefault();
input.current.focus();
} else if (e.key === 'Escape') {
setShow(false);
}
}
};

window.addEventListener('keydown', down);
return () => window.removeEventListener('keydown', down);
}, []);

const renderList = show && results.length > 0;

return (
<div className="relative w-full md:w-64 mr-2">
{renderList && (
<div className="search-overlay z-1" onClick={() => setShow(false)} />
)}
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
stroke="currentColor"
className="h-4 w-4 text-gray-400"
>
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
<input
onChange={(e) => {
setSearch(e.target.value);
setShow(true);
}}
className="appearance-none pl-8 border rounded-md py-2 pr-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-full"
type="search"
placeholder='Search ("/" to focus)'
onKeyDown={handleKeyDown}
onFocus={() => setShow(true)}
ref={input}
aria-label="Search documentation"
/>
{renderList && (
<ul className="shadow-md list-none p-0 m-0 absolute left-0 md:right-0 bg-white rounded-md mt-1 border top-100 divide-y divide-gray-300 z-2">
{results.map((res, i) => {
return (
<Item
key={`search-item-${i}`}
title={res.title}
href={res.route}
active={i === active}
search={search}
onMouseOver={() => setActive(i)}
/>
);
})}
</ul>
)}
</div>
);
};

export default Search;
6 changes: 6 additions & 0 deletions website2/.nextra/ssg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createContext, useContext } from 'react'

const SSGContext = createContext({})

export default SSGContext
export const useSSG = () => useContext(SSGContext)
211 changes: 211 additions & 0 deletions website2/.nextra/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
@tailwind base;

html {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
}
@supports (font-variation-settings: normal) {
html {
font-family: 'Inter var', -apple-system, BlinkMacSystemFont, 'Segoe UI',
'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
}
}

html {
@apply subpixel-antialiased;
font-size: 16px;
font-feature-settings: 'rlig' 1, 'calt' 1, 'ss01' 1;
}
body {
@apply bg-white;
}

h1 {
@apply text-4xl font-bold tracking-tight;
}
h2 {
@apply text-3xl font-semibold tracking-tight mt-10;
@apply border-b;
}
h3 {
@apply text-2xl font-semibold tracking-tight mt-8;
}
h4 {
@apply text-xl font-semibold tracking-tight mt-8;
}
h5 {
@apply text-lg font-semibold tracking-tight mt-8;
}
h6 {
@apply text-base font-semibold tracking-tight mt-8;
}
a {
@apply text-blue-600 underline;
}
p {
@apply mt-6 leading-7;
}
hr {
@apply my-8;
}
code {
@apply p-1 text-sm text-gray-800 bg-gray-500 bg-opacity-25 rounded;
}
pre {
@apply p-4 bg-gray-200 rounded-lg mt-6 mb-4 overflow-x-auto scrolling-touch;
}
pre code {
@apply p-0 text-black bg-transparent rounded-none;
}
a code {
@apply text-current no-underline;
}
figure {
@apply mb-8 relative;
}
video {
@apply absolute top-0 left-0;
cursor: pointer;
}
figure figcaption {
@apply text-sm text-gray-600;
}
@media (min-width: 768px) {
figure {
/* allow figures to overflow, but not exceeding the viewport width */
@apply -mr-56;
max-width: calc(100vw - 20rem);
}
}

table {
@apply my-8 w-full text-gray-700 text-sm;
}

table > thead > tr {
@apply border-b border-t rounded-t-lg;
}

table > thead > tr > th {
@apply px-3 py-2 text-left text-sm font-bold bg-gray-200 text-gray-700;
}

table > tbody > tr {
@apply border-b;
}

table > tbody > tr > td {
@apply p-3;
}

table > tbody > tr > td:not(:first-child) > code {
@apply text-xs;
}

table > tbody > tr > td > a > code,
table > tbody > tr > td > code {
@apply text-sm;
}
table > tbody > tr > td > a {
@apply text-blue-600 font-semibold transition-colors duration-150 ease-out;
}
table > tbody > tr > td > a:hover {
@apply text-blue-800 transition-colors duration-150 ease-out;
}
@tailwind components;
@tailwind utilities;

.main-container {
min-height: 100vh;
}

.sidebar {
@apply select-none;
}
.sidebar ul ul {
@apply ml-5;
}
.sidebar a:focus-visible,
.sidebar button:focus-visible {
@apply shadow-outline;
}
.sidebar li.active > a {
@apply font-semibold text-black bg-gray-200;
}
.sidebar button,
.sidebar a {
@apply block w-full text-left text-base text-black no-underline text-gray-600 mt-1 p-2 rounded select-none outline-none;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
.sidebar a:hover,
.sidebar button:hover {
@apply text-gray-900 bg-gray-100;
}

.sidebar .active-route > button {
@apply text-black font-medium;
}

.sidebar .active-route > button:hover {
@apply text-current bg-transparent cursor-default;
}

content ul {
@apply list-disc ml-6 mt-6;
}
content li {
@apply mt-2;
}
.subheading-anchor {
margin-top: -84px;
display: inline-block;
position: absolute;
width: 1px;
}

.subheading-anchor + a:hover .anchor-icon,
.subheading-anchor + a:focus .anchor-icon {
opacity: 1;
}
.anchor-icon {
opacity: 0;
@apply ml-2 text-gray-500;
}

h2 a {
@apply no-underline;
}
input[type='search']::-webkit-search-decoration,
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-results-button,
input[type='search']::-webkit-search-results-decoration {
-webkit-appearance: none;
}

.search-overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}

.algolia-autocomplete .algolia-docsearch-suggestion--category-header span {
display: inline-block;
}
.algolia-autocomplete .ds-dropdown-menu {
width: 500px;
min-width: 300px;
max-width: calc(100vw - 50px);
}

[data-reach-skip-link] {
@apply sr-only;
}

[data-reach-skip-link]:focus {
@apply not-sr-only fixed ml-6 top-0 bg-white text-lg px-6 py-2 mt-2 outline-none shadow-outline z-50;
}
194 changes: 194 additions & 0 deletions website2/.nextra/theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { MDXProvider } from '@mdx-js/react';
import * as ReactDOM from 'react-dom/server';
import Link from 'next/link';
import Highlight, { defaultProps } from 'prism-react-renderer';
import stripHtml from 'string-strip-html';
import slugify from '@sindresorhus/slugify';

const THEME = {
plain: {
color: '#000',
backgroundColor: 'transparent',
},
styles: [
{
types: ['keyword'],
style: {
color: '#ff0078',
fontWeight: 'bold',
},
},
{
types: ['comment'],
style: {
color: '#999',
fontStyle: 'italic',
},
},
{
types: ['string', 'url', 'attr-value'],
style: {
color: '#028265',
},
},
{
types: ['builtin', 'char', 'constant', 'language-javascript'],
style: {
color: '#000',
},
},
{
types: ['attr-name'],
style: {
color: '#d9931e',
fontStyle: 'normal',
},
},
{
types: ['punctuation', 'operator'],
style: {
color: '#333',
},
},
{
types: ['number', 'function', 'tag'],
style: {
color: '#0076ff',
},
},
{
types: ['boolean', 'regex'],
style: {
color: '#d9931e',
},
},
],
};

// Anchor links

const HeaderLink = ({ tag: Tag, children, ...props }) => {
const slug = slugify(ReactDOM.renderToString(children) || '');
return (
<Tag {...props}>
<span className="subheading-anchor" id={slug} />
<a href={'#' + slug} className="text-current no-underline no-outline">
{children}
<span className="anchor-icon select-none" aria-hidden>
#
</span>
</a>
</Tag>
);
};

const H2 = ({ children, ...props }) => {
return (
<HeaderLink tag="h2" {...props}>
{children}
</HeaderLink>
);
};

const H3 = ({ children, ...props }) => {
return (
<HeaderLink tag="h3" {...props}>
{children}
</HeaderLink>
);
};

const H4 = ({ children, ...props }) => {
return (
<HeaderLink tag="h4" {...props}>
{children}
</HeaderLink>
);
};

const H5 = ({ children, ...props }) => {
return (
<HeaderLink tag="h5" {...props}>
{children}
</HeaderLink>
);
};

const H6 = ({ children, ...props }) => {
return (
<HeaderLink tag="h6" {...props}>
{children}
</HeaderLink>
);
};

const A = ({ children, ...props }) => {
const isExternal = props.href?.startsWith('https://');
if (isExternal) {
return (
<a target="_blank" rel="noopener" {...props}>
{children}
</a>
);
}
return (
<Link href={props.href}>
<a {...props}>{children}</a>
</Link>
);
};

const Code = ({ children, className, highlight, ...props }) => {
if (!className) return <code {...props}>{children}</code>;

const highlightedLines = highlight ? highlight.split(',').map(Number) : [];

// https://mdxjs.com/guides/syntax-highlighting#all-together
const language = className.replace(/language-/, '');
return (
<Highlight
{...defaultProps}
code={children.trim()}
language={language}
theme={THEME}
>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<code className={className} style={{ ...style }}>
{tokens.map((line, i) => (
<div
key={i}
{...getLineProps({ line, key: i })}
style={
highlightedLines.includes(i + 1)
? {
background: '#cce0f5',
margin: '0 -1rem',
padding: '0 1rem',
}
: null
}
>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</div>
))}
</code>
)}
</Highlight>
);
};

const components = {
h2: H2,
h3: H3,
h4: H4,
h5: H5,
h6: H6,
a: A,
code: Code,
};

export default ({ children }) => {
return <MDXProvider components={components}>{children}</MDXProvider>;
};
819 changes: 819 additions & 0 deletions website2/components/features.js

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions website2/components/features.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.features {
display: flex;
flex-wrap: wrap;
margin: 2.5rem -0.5rem 2rem;
}

.feature {
flex: 0 0 33%;
align-items: center;
display: inline-flex;
padding: 0 0.5rem 1.5rem;
margin: 0 auto;
}
.feature h4 {
margin: 0 0 0 0.5rem;
font-weight: 700;
font-size: 0.95rem;
white-space: nowrap;
}
@media (max-width: 860px) {
.feature {
padding-left: 0;
}
.feature h4 {
font-size: 0.75rem;
}
}
28 changes: 28 additions & 0 deletions website2/components/logo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';

export const Logo = ({ height }) => (
<svg
height={height}
viewBox="0 0 441 254"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M0 127V254H127H254V127V8.28505e-06H127H0V127Z" fill="#007ACC" />
<path
d="M56.0802 127.508L56 137.925H72.4369H88.8739L88.8739 184.962V232H100.5H112.126V184.962L112.126 137.925H128.563H145V127.71C145 122.057 144.88 117.333 144.719 117.212C144.599 117.051 124.594 116.97 100.34 117.01L56.2004 117.131L56.0802 127.508Z"
fill="white"
/>
<path
d="M204.89 117.051C211.339 118.673 216.256 121.551 220.77 126.254C223.107 128.768 226.573 133.349 226.855 134.444C226.936 134.768 215.893 142.228 209.203 146.403C208.961 146.566 207.994 145.512 206.905 143.89C203.641 139.106 200.215 137.038 194.976 136.673C187.278 136.146 182.32 140.201 182.361 146.971C182.361 148.958 182.643 150.133 183.449 151.755C185.142 155.282 188.285 157.39 198.16 161.688C216.336 169.553 224.115 174.742 228.951 182.121C234.352 190.351 235.561 203.486 231.893 213.257C227.863 223.879 217.868 231.095 203.802 233.487C199.449 234.257 189.132 234.136 184.457 233.284C174.26 231.46 164.587 226.392 158.622 219.744C156.285 217.149 151.73 210.378 152.013 209.892C152.133 209.73 153.181 209.081 154.35 208.392C155.479 207.743 159.751 205.27 163.781 202.919L171.076 198.662L172.607 200.932C174.744 204.216 179.419 208.716 182.24 210.216C190.341 214.514 201.464 213.905 206.946 208.959C209.283 206.811 210.251 204.581 210.251 201.297C210.251 198.337 209.888 197.04 208.356 194.81C206.381 191.972 202.351 189.58 190.905 184.594C177.807 178.918 172.164 175.391 167.005 169.796C164.023 166.553 161.202 161.363 160.033 157.025C159.066 153.417 158.824 144.376 159.59 140.728C162.29 127.998 171.842 119.119 185.625 116.484C190.099 115.632 200.497 115.957 204.89 117.051Z"
fill="white"
/>
<path
d="M305.321 211.872C301.475 211.872 298.425 208.551 298.425 200.183C298.425 186.767 306.648 169.233 315.269 169.233C317.656 169.233 321.767 169.897 326.277 171.358L320.043 203.769C314.871 208.419 309.168 211.872 305.321 211.872ZM350.945 222.1L349.487 212.271C338.478 214.662 337.948 214.529 339.009 208.817L355.19 125L335.03 126.461L329.99 152.629C328.133 152.363 326.409 152.363 324.818 152.363C295.64 152.363 278 179.86 278 206.293C278 222.897 285.295 231 294.048 231C301.74 231 310.892 224.757 318.849 216.92C318.186 231.797 325.481 236.446 350.945 222.1Z"
fill="black"
/>
<path
d="M404.129 229.406L426.941 228.742L409.965 191.815L441 154.356H419.514L400.548 179.727L390.071 154.09L367.259 154.887L383.97 191.682L352.935 229.14H374.553L393.519 203.769L404.129 229.406Z"
fill="black"
/>
</svg>
);
52 changes: 52 additions & 0 deletions website2/components/video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useRef, useCallback, useEffect } from 'react'
import { useInView } from 'react-intersection-observer'
import 'intersection-observer'

export default ({ src, caption, ratio }) => {
const [inViewRef, inView] = useInView({
threshold: 1,
})
const videoRef = useRef()

const setRefs = useCallback(
(node) => {
// Ref's from useRef needs to have the node assigned to `current`
videoRef.current = node
// Callback refs, like the one from `useInView`, is a function that takes the node as an argument
inViewRef(node)

if (node) {
node.addEventListener('click', function () {
if (this.paused) {
this.play()
} else {
this.pause()
}
})
}
},
[inViewRef]
)

useEffect(() => {
if (!videoRef || !videoRef.current) {
return
}

if (inView) {
videoRef.current.play()
} else {
videoRef.current.pause()
}
}, [inView])

return (
<figure>
<div style={{ paddingBottom: ratio * 100 + '%' }}/>
<video loop muted autoPlay playsInline ref={setRefs}>
<source src={src} type="video/mp4" />
</video>
{caption && <figcaption>{caption}</figcaption>}
</figure>
)
}
5 changes: 5 additions & 0 deletions website2/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"baseUrl": "."
}
}
2 changes: 2 additions & 0 deletions website2/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const withNextra = require('./.nextra/nextra')();
module.exports = withNextra();
90 changes: 90 additions & 0 deletions website2/nextra.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Logo } from 'components/logo';

export default {
github: 'https://github.com/formium/tsdx',
titleSuffix: ' – TSDX',
logo: (
<>
<Logo height={36} />
<span className=" font-extrabold hidden md:inline sr-only">TSDX</span>
</>
),
head: () => (
<>
{/* Favicons, meta */}
<link
rel="apple-touch-icon"
sizes="180x180"
href="/favicon/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon/favicon-16x16.png"
/>
<link rel="manifest" href="/favicon/site.webmanifest" />

<meta name="msapplication-TileColor" content="#ffffff" />
<meta name="theme-color" content="#ffffff" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta httpEquiv="Content-Language" content="en" />
<meta
name="description"
content="Build production ready TypeScript packages. The world's leading companies use TSDX to build and test TypeScript packages"
/>
<meta
name="og:description"
content="Build production ready TypeScript packages. The world's leading companies use TSDX to build and test TypeScript packages"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@jaredpalmer" />
<meta name="twitter:image" content="https://tsdx.io/og_image.jpg" />
<meta
name="og:title"
content="TSDX: Modern TypeScript Package Development"
/>
<meta name="og:url" content="https://tsdx.io" />
<meta name="og:image" content="https://tsdx.io/og_image.jpg" />
<meta name="apple-mobile-web-app-title" content="TSDX" />
{/* <link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
media="print"
onload="this.media='all'"
/> */}
</>
),
footer: ({ filepath }) => (
<>
<div className="mt-24 flex justify-between flex-col-reverse md:flex-row items-center md:items-end">
<a
href="https://jaredpalmer.com/?utm_source=tsdx"
target="_blank"
rel="noopener"
className="inline-flex items-center no-underline text-current font-semibold"
>
<span className="mr-1">A Jared Palmer Project</span>
</a>
<div className="mt-6" />
<a
className="text-sm"
href={
'https://github.com/formium/tsdx/tree/master/website/pages' +
filepath
}
target="_blank"
rel="noopener"
>
Edit this page on GitHub
</a>
</div>
</>
),
};
37 changes: 37 additions & 0 deletions website2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "tsdx-site",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "next",
"start": "next",
"build": "next build"
},
"author": "Jared Palmer",
"license": "MIT",
"dependencies": {
"@reach/skip-nav": "^0.10.5",
"@sindresorhus/slugify": "^1.0.0",
"classnames": "^2.2.6",
"focus-visible": "^5.1.0",
"intersection-observer": "^0.10.0",
"markdown-to-jsx": "^6.11.4",
"match-sorter": "^4.1.0",
"next": "^9.4.4",
"next-google-fonts": "^1.1.0",
"prism-react-renderer": "^1.1.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-intersection-observer": "^8.26.2",
"string-strip-html": "^4.5.0"
},
"devDependencies": {
"@mdx-js/loader": "^1.6.5",
"babel-plugin-macros": "^2.8.0",
"postcss-preset-env": "^6.7.0",
"preval.macro": "^5.0.0",
"tailwindcss": "^1.4.6",
"title": "^3.4.2"
}
}
14 changes: 14 additions & 0 deletions website2/pages/_app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import '.nextra/styles.css'
import GoogleFonts from 'next-google-fonts'

export default function Nextra({ Component, pageProps }) {
return (
<>
<GoogleFonts
href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
rel="stylesheet"
/>
<Component {...pageProps} />
</>
)
}
25 changes: 25 additions & 0 deletions website2/pages/_document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { SkipNavLink } from '@reach/skip-nav'

class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head />
<SkipNavLink />
<body>
<Main />
<NextScript />
<script
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
async
defer
/>
</body>
</Html>
)
}
}

export default MyDocument
104 changes: 104 additions & 0 deletions website2/pages/api-reference.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# API Reference

## `tsdx watch`

```sh
Description
Rebuilds on any change

Usage
$ tsdx watch [options]

Options
-i, --entry Entry module(s)
--target Specify your target environment (default web)
--name Specify name exposed in UMD builds
--format Specify module format(s) (default cjs,esm)
--tsconfig Specify your custom tsconfig path (default <root-folder>/tsconfig.json)
--verbose Keep outdated console output in watch mode instead of clearing the screen
--onFirstSuccess Run a command on the first successful build
--onSuccess Run a command on a successful build
--onFailure Run a command on a failed build
--noClean Don't clean the dist folder
--transpileOnly Skip type checking
-h, --help Displays this message
Examples
$ tsdx watch --entry src/foo.tsx
$ tsdx watch --target node
$ tsdx watch --name Foo
$ tsdx watch --format cjs,esm,umd
$ tsdx watch --tsconfig ./tsconfig.foo.json
$ tsdx watch --noClean
$ tsdx watch --onFirstSuccess "echo The first successful build!"
$ tsdx watch --onSuccess "echo Successful build!"
$ tsdx watch --onFailure "echo The build failed!"
$ tsdx watch --transpileOnly
```
## `tsdx build`
```sh
Description
Build your project once and exit
Usage
$ tsdx build [options]
Options
-i, --entry Entry module(s)
--target Specify your target environment (default web)
--name Specify name exposed in UMD builds
--format Specify module format(s) (default cjs,esm)
--extractErrors Opt-in to extracting invariant error codes
--tsconfig Specify your custom tsconfig path (default <root-folder>/tsconfig.json)
--transpileOnly Skip type checking
-h, --help Displays this message
Examples
$ tsdx build --entry src/foo.tsx
$ tsdx build --target node
$ tsdx build --name Foo
$ tsdx build --format cjs,esm,umd
$ tsdx build --extractErrors
$ tsdx build --tsconfig ./tsconfig.foo.json
$ tsdx build --transpileOnly
```
## `tsdx test`
This runs Jest v24.x, forwarding all CLI flags to it. See [https://jestjs.io](https://jestjs.io) for options. For example, if you would like to run in watch mode, you can run `tsdx test --watch`. So you could set up your `package.json` `scripts` like:
```json
{
"scripts": {
"test": "tsdx test",
"test:watch": "tsdx test --watch",
"test:coverage": "tsdx test --coverage"
}
}
```
## `tsdx lint`
```sh
Description
Run eslint with Prettier
Usage
$ tsdx lint [options]
Options
--fix Fixes fixable errors and warnings
--ignore-pattern Ignore a pattern
--write-file Write the config file locally
--report-file Write JSON report to file locally
-h, --help Displays this message
Examples
$ tsdx lint src
$ tsdx lint src --fix
$ tsdx lint src test --ignore-pattern test/foo.ts
$ tsdx lint src --write-file
$ tsdx lint src --report-file report.json
```
38 changes: 38 additions & 0 deletions website2/pages/change-log.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useContext } from 'react';
import Markdown from 'markdown-to-jsx';

import { useSSG } from '.nextra/ssg';

export const getStaticProps = ({ params }) => {
return (
fetch('https://api.github.com/repos/formium/tsdx/releases')
.then(res => res.json())
// we keep the most recent 3 releases here
.then(releases => ({ props: { ssg: releases.slice(0, 10) } }))
);
};

export const ReleasesRenderer = () => {
const releases = useSSG();
return (
<Markdown>
{releases
.map(release => {
return `## <a href="${
release.html_url
}" target="_blank" rel="noopener">${
release.tag_name
}</a>\n\nPublished on ${new Date(
release.published_at
).toDateString()}.\n\n${release.body}`;
})
.join('\n\n')}
</Markdown>
);
};

# Change Log

Please visit the [TSDX release page](https://github.com/formium/tsdx/releases) for all historical releases.

<ReleasesRenderer />
84 changes: 84 additions & 0 deletions website2/pages/customization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Customization

## Rollup

> **❗⚠️❗ Warning**: <br/>
> These modifications will override the default behavior and configuration of TSDX. As such they can invalidate internal guarantees and assumptions. These types of changes can break internal behavior and can be very fragile against updates. Use with discretion!
TSDX uses Rollup under the hood. The defaults are solid for most packages (Formik uses the defaults!). However, if you do wish to alter the rollup configuration, you can do so by creating a file called `tsdx.config.js` at the root of your project like so:

```js
// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js!
module.exports = {
// This function will run for each entry/format/env combination
rollup(config, options) {
return config; // always return a config.
},
};
```

The `options` object contains the following:

```tsx
export interface TsdxOptions {
// path to file
input: string;
// Name of package
name: string;
// JS target
target: 'node' | 'browser';
// Module format
format: 'cjs' | 'umd' | 'esm' | 'system';
// Environment
env: 'development' | 'production';
// Path to tsconfig file
tsconfig?: string;
// Is error extraction running?
extractErrors?: boolean;
// Is minifying?
minify?: boolean;
// Is this the very first rollup config (and thus should one-off metadata be extracted)?
writeMeta?: boolean;
// Only transpile, do not type check (makes compilation faster)
transpileOnly?: boolean;
}
```

### Example: Adding Postcss

```js
const postcss = require('rollup-plugin-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');

module.exports = {
rollup(config, options) {
config.plugins.push(
postcss({
plugins: [
autoprefixer(),
cssnano({
preset: 'default',
}),
],
inject: false,
// only write out CSS for the first bundle (avoids pointless extra files):
extract: !!options.writeMeta,
})
);
return config;
},
};
```

## Babel

You can add your own `.babelrc` to the root of your project and TSDX will **merge** it with [its own Babel transforms](./src/babelPluginTsdx.ts) (which are mostly for optimization), putting any new presets and plugins at the end of its list.

## Jest

You can add your own `jest.config.js` to the root of your project and TSDX will **shallow merge** it with [its own Jest config](./src/createJestConfig.ts).

## ESLint

You can add your own `.eslintrc.js` to the root of your project and TSDX will **deep merge** it with [its own ESLint config](./src/createEslintConfig.ts).
77 changes: 77 additions & 0 deletions website2/pages/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Features from 'components/features';

# TSDX

<Features />

Despite all the recent hype, setting up a new TypeScript (x React)
library can be tough. Between Rollup, Jest, tsconfig, Yarn resolutions,
TSLint, and getting VSCode to play nicely....there is just a whole lot
of stuff to do (and things to screw up).

**TSDX is a zero-config CLI that helps you develop, test, and publish
modern TypeScript packages** with ease--so you can focus on your awesome
new library and not waste another afternoon on the configuration.

## Quick Start

With TSDX, you can quickly bootstrap a new TypeScript project in seconds, instead of hours. Open up Terminal and enter:

```bash
npx tsdx create mylib
```

You'll be prompted to choose from one of three project templates:

| Template | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `basic` | A plain TypeScript project setup you can use for any kind of module. |
| `react` | A React package with necessary development dependencies and `@types` installed. In addition, there is a [Parcel](https://parceljs.org/)-powered React playground you can use while you develop. |
| `react-with-storybook` | Same as the basic React template, but with [React Storybook](https://storybook.js.org/) already setup as well. |

After you select one, TSDX will create a folder with the project template in it and install all dependencies. Once that's done, you're ready-to-rock! Typescript, Rollup, Jest, ESlint and all other plumbing is already setup with best practices. Just start editing `src/index.ts` (or `src/index.tsx` if you chose one of the React templates) and go!

## Useful Commands

Below is a list of commands you will probably find useful:

### `npm start` or `yarn start`

Runs the project in development/watch mode. Your project will be rebuilt upon changes. TSDX has a special logger for your convenience. Error messages are pretty printed and formatted for compatibility VS Code's Problems tab.

<img
src="https://user-images.githubusercontent.com/4060187/52168303-574d3a00-26f6-11e9-9f3b-71dbec9ebfcb.gif"
width="600"
/>

Your library will be rebuilt if you make edits.

### `npm run build` or `yarn build`

Bundles the package to the `dist` folder.
The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module).

<img
src="https://user-images.githubusercontent.com/4060187/52168322-a98e5b00-26f6-11e9-8cf6-222d716b75ef.gif"
width="600"
/>

### `npm test` or `yarn test`

Runs your tests using Jest.

### `npm run lint` or `yarn lint`

Runs Eslint with Prettier on .ts and .tsx files.
If you want to customize eslint you can add an `eslint` block to your package.json, or you can run `yarn lint --write-file` and edit the generated `.eslintrc.js` file.

### `prepare` script

Bundles and packages to the `dist` folder.
Runs automatically when you run either `npm publish` or `yarn publish`. The `prepare` script will run the equivalent of `npm run build` or `yarn build`. It will also be run if your module is installed as a git dependency (ie: `"mymodule": "github:myuser/mymodule#some-branch"`) so it can be depended on without checking the transpiled code into git.

## Best Practices

TSDX includes best-practices and optimizations for modern NPM packages. These include things like the ability to have different development and production builds, multiple bundle formats, proper lodash-optimizations, treeshaking, and minification to name a few. All of these come out-of-the-box with TSDX. While you probably won't need to configure anything, [you totally can do so with tsdx.config.js](customization).

Before you start extending TSDX though, you'll want to fully understand what _exactly_ TSDX does. In the next section, we'll go over all of these optimizations in finer detail.
7 changes: 7 additions & 0 deletions website2/pages/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"index": "Introduction",
"optimization": "Optimization",
"customization": "Customization",
"api-reference": "API Reference",
"change-log": "Change Log"
}
197 changes: 197 additions & 0 deletions website2/pages/optimization.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Optimizations

Aside from just bundling your module into different formats, TSDX comes with some optimizations for your convenience. They yield objectively better code and smaller bundle sizes.

After TSDX compiles your code with TypeScript, it processes your code with 3 Babel plugins:

- [`babel-plugin-annotate-pure-calls`](https://github.com/Andarist/babel-plugin-annotate-pure-calls): Injects for `#__PURE` annotations to enable treeshaking
- [`babel-plugin-dev-expressions`](https://github.com/4Catalyzer/babel-plugin-dev-expression): A mirror of Facebook's dev-expression Babel plugin. It reduces or eliminates development checks from production code
- [`babel-plugin-rename-import`](https://github.com/laat/babel-plugin-transform-rename-import): Used to rewrite any `lodash` imports

## Development-only Expressions + Treeshaking

`babel-plugin-annotate-pure-calls` + `babel-plugin-dev-expressions` work together to fully eliminate dead code (aka treeshake) development checks from your production code. Let's look at an example to see how it works.

Imagine our source code is just this:

```tsx
// ./src/index.ts
export const sum = (a: number, b: number) => {
if (process.env.NODE_ENV !== 'production') {
console.log('Helpful dev-only error message');
}
return a + b;
};
```

`tsdx build` will output an ES module file and 3 CommonJS files (dev, prod, and an entry file). If you want to specify a UMD build, you can do that as well. For brevity, let's examine the CommonJS output (comments added for emphasis):

```js
// Entry File
// ./dist/index.js
'use strict';

// This determines which build to use based on the `NODE_ENV` of your end user.
if (process.env.NODE_ENV === 'production') {
module.exports = require('./mylib.cjs.production.js');
} else {
module.exports = require('./mylib.cjs.development.js');
}
```

```js
// CommonJS Development Build
// ./dist/mylib.cjs.development.js
'use strict';

const sum = (a, b) => {
{
console.log('Helpful dev-only error message');
}

return a + b;
};

exports.sum = sum;
//# sourceMappingURL=mylib.cjs.development.js.map
```

```js
// CommonJS Production Build
// ./dist/mylib.cjs.production.js
'use strict';
exports.sum = (s, t) => s + t;
//# sourceMappingURL=test-react-tsdx.cjs.production.js.map
```

AS you can see, TSDX stripped out the development check from the production code. **This allows you to safely add development-only behavior (like more useful error messages) without any production bundle size impact.**

For ESM build, it's up to end-user to build environment specific build with NODE_ENV replace (done by Webpack 4 automatically).

### Rollup Treeshaking

TSDX's rollup config [removes getters and setters on objects](https://github.com/palmerhq/tsdx/blob/1f6a1b6819bb17678aa417f0df5349bec12f59ac/src/createRollupConfig.ts#L73) so that property access has no side effects. Don't do it.

### Advanced `babel-plugin-dev-expressions`

TSDX will use `babel-plugin-dev-expressions` to make the following replacements _before_ treeshaking.

#### `__DEV__`

Replaces

```ts
if (__DEV__) {
console.log('foo');
}
```

with

```js
if (process.env.NODE_ENV !== 'production') {
console.log('foo');
}
```

**IMPORTANT:** To use `__DEV__` in TypeScript, you need add `declare var __DEV__: boolean` somewhere in your project's type path (e.g. `./types/index.d.ts`).

```ts
// ./types/index.d.ts
declare var __DEV__: boolean;
```

> **Note:** The `dev-expression` transform does not run when `NODE_ENV` is `test`. As such, if you use `__DEV__`, you will need to define it as a global constant in your test environment.
#### `invariant`

Replaces

```js
invariant(condition, 'error message here');
```

with

```js
if (!condition) {
if ('production' !== process.env.NODE_ENV) {
invariant(false, 'error message here');
} else {
invariant(false);
}
}
```

Note: TSDX doesn't supply an `invariant` function for you, you need to import one yourself. We recommend https://github.com/alexreardon/tiny-invariant.

To extract and minify `invariant` error codes in production into a static `codes.json` file, specify the `--extractErrors` flag in command line. For more details see [Error extraction docs](#error-extraction).

#### `warning`

Replaces

```js
warning(condition, 'dev warning here');
```

with

```js
if ('production' !== process.env.NODE_ENV) {
warning(condition, 'dev warning here');
}
```

Note: TSDX doesn't supply a `warning` function for you, you need to import one yourself. We recommend https://github.com/alexreardon/tiny-warning.

## Using lodash

If you want to use a lodash function in your package, TSDX will help you do it the _right_ way so that your library does not get fat shamed on Twitter. However, before you continue, seriously consider rolling whatever function you are about to use on your own. Anyways, here is how to do it right.

First, install `lodash` and `lodash-es` as _dependencies_

```bash
yarn add lodash lodash-es
```

Now install `@types/lodash` to your development dependencies.

```bash
yarn add @types/lodash --dev
```

Import your lodash method however you want, TSDX will optimize it like so.

```tsx
// ./src/index.ts
import kebabCase from 'lodash/kebabCase';

export const KebabLogger = (msg: string) => {
console.log(kebabCase(msg));
};
```

For brevity let's look at the ES module output.

<!-- prettier-ignore -->
```js
import o from"lodash-es/kebabCase";const e=e=>{console.log(o(e))};export{e as KebabLogger};
//# sourceMappingURL=test-react-tsdx.esm.production.js.map
```

TSDX will rewrite your `import kebabCase from 'lodash/kebabCase'` to `import o from 'lodash-es/kebabCase'`. This allows your library to be treeshakable to end consumers while allowing to you to use `@types/lodash` for free.

> Note: TSDX will also transform destructured imports. For example, `import { kebabCase } from 'lodash'` would have also been transformed to `import o from "lodash-es/kebabCase".

## Error extraction

After running `--extractErrors`, you will have a `./errors/codes.json` file with all your extracted `invariant` error codes. This process scans your production code and swaps out your `invariant` error message strings for a corresponding error code (just like React!). This extraction only works if your error checking/warning is done by a function called `invariant`.

Note: We don't provide this function for you, it is up to you how you want it to behave. For example, you can use either `tiny-invariant` or `tiny-warning`, but you must then import the module as a variable called `invariant` and it should have the same type signature.

⚠️Don't forget: you will need to host the decoder somewhere. Once you have a URL, look at `./errors/ErrorProd.js` and replace the `reactjs.org` URL with yours.

> Known issue: our `transformErrorMessages` babel plugin currently doesn't have sourcemap support, so you will see "Sourcemap is likely to be incorrect" warnings. [We would love your help on this.](https://github.com/palmerhq/tsdx/issues/184)
_TODO: Simple guide to host error codes to be completed_
3 changes: 3 additions & 0 deletions website2/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
plugins: ['tailwindcss', 'postcss-preset-env']
}
132 changes: 132 additions & 0 deletions website2/public/bg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website2/public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website2/public/favicon/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website2/public/favicon/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website2/public/favicon/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website2/public/favicon/favicon.ico
Binary file not shown.
19 changes: 19 additions & 0 deletions website2/public/favicon/site.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "TSDX",
"short_name": "TSDX",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
6 changes: 6 additions & 0 deletions website2/public/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website2/public/og_image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website2/public/og_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions website2/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
purge: ['./components/**/*.js', './pages/**/*.md', './pages/**/*.mdx', './.nextra/**/*.js', './nextra.config.js'],
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
},
fontFamily: {
display: ['inter', 'sans-serif'],
},
letterSpacing: {
tight: '-0.015em'
}
}
}
7,181 changes: 7,181 additions & 0 deletions website2/yarn.lock

Large diffs are not rendered by default.