(this.mountNode = ref)} />
{error &&
}
>
);
diff --git a/src/client/rsg-components/ReactComponent/ReactComponent.spec.tsx b/src/client/rsg-components/ReactComponent/ReactComponent.spec.tsx
index a188cb36f..6b6be6f70 100644
--- a/src/client/rsg-components/ReactComponent/ReactComponent.spec.tsx
+++ b/src/client/rsg-components/ReactComponent/ReactComponent.spec.tsx
@@ -5,10 +5,14 @@ import slots from '../slots';
import Context from '../Context';
import { DisplayModes } from '../../consts';
import * as Rsg from '../../../typings';
+import config from '../../../scripts/schemas/config';
+
+const compileExample = config.compileExample.default;
const context = {
config: {
pagePerSection: false,
+ compileExample,
},
displayMode: DisplayModes.all,
slots: slots(),
diff --git a/src/client/rsg-components/ReactExample/ReactExample.spec.tsx b/src/client/rsg-components/ReactExample/ReactExample.spec.tsx
index 0de3acc8d..1142afe5f 100644
--- a/src/client/rsg-components/ReactExample/ReactExample.spec.tsx
+++ b/src/client/rsg-components/ReactExample/ReactExample.spec.tsx
@@ -1,16 +1,24 @@
import React from 'react';
import { shallow, mount } from 'enzyme';
-import noop from 'lodash/noop';
+import config from '../../../scripts/schemas/config';
import ReactExample from '.';
-const evalInContext = (a: string): (() => any) =>
+const compileExample = config.compileExample.default;
+
+// TODO: Enzyme → RTL
+
+const evalInContext = (a: string) =>
// eslint-disable-next-line no-new-func
new Function('require', 'const React = require("react");' + a).bind(null, require);
+const defaultProps = {
+ evalInContext,
+ onError: () => {},
+ compileExample,
+};
+
it('should render code', () => {
- const actual = shallow(
-
OK'} evalInContext={evalInContext} onError={noop} />
- );
+ const actual = shallow(OK'} />);
expect(actual).toMatchSnapshot();
});
@@ -18,7 +26,7 @@ it('should render code', () => {
it('should wrap code in Fragment when it starts with <', () => {
const actual = mount(
-
+
);
@@ -28,7 +36,7 @@ it('should wrap code in Fragment when it starts with <', () => {
it('should handle errors', () => {
const onError = jest.fn();
- shallow();
+ shallow();
expect(onError).toHaveBeenCalledTimes(1);
});
@@ -38,7 +46,7 @@ it('should set initial state with hooks', () => {
const [count, setCount] = React.useState(0);
`;
- const actual = mount();
+ const actual = mount();
expect(actual.find('button').text()).toEqual('0');
});
@@ -48,7 +56,7 @@ it('should update state with hooks', () => {
const [count, setCount] = React.useState(0);
`;
- const actual = mount();
+ const actual = mount();
actual.find('button').simulate('click');
expect(actual.find('button').text()).toEqual('1');
diff --git a/src/client/rsg-components/ReactExample/ReactExample.tsx b/src/client/rsg-components/ReactExample/ReactExample.tsx
index a03f9d970..fb09d9f1e 100644
--- a/src/client/rsg-components/ReactExample/ReactExample.tsx
+++ b/src/client/rsg-components/ReactExample/ReactExample.tsx
@@ -1,17 +1,14 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { TransformOptions } from 'buble';
import Wrapper from 'rsg-components/Wrapper';
import compileCode from '../../utils/compileCode';
-import splitExampleCode from '../../utils/splitExampleCode';
-
-/* eslint-disable react/no-multi-comp */
+import * as Rsg from '../../../typings';
interface ReactExampleProps {
code: string;
- evalInContext(code: string): () => any;
+ evalInContext(code: string): React.ComponentType;
onError(err: Error): void;
- compilerConfig?: TransformOptions;
+ compileExample: Rsg.SanitizedStyleguidistConfig['compileExample'];
}
export default class ReactExample extends Component {
@@ -19,29 +16,33 @@ export default class ReactExample extends Component {
code: PropTypes.string.isRequired,
evalInContext: PropTypes.func.isRequired,
onError: PropTypes.func.isRequired,
- compilerConfig: PropTypes.object,
+ compileExample: PropTypes.func.isRequired,
};
public shouldComponentUpdate(nextProps: ReactExampleProps) {
return this.props.code !== nextProps.code;
}
- // Run example code and return the last top-level expression
- private getExampleComponent(compiledCode: string): () => any {
- return this.props.evalInContext(`
- ${compiledCode}
- `);
+ private compileCode() {
+ const { code, compileExample, onError } = this.props;
+ try {
+ return compileCode(code, compileExample, onError);
+ } catch (err) {
+ if (onError) {
+ onError(err);
+ }
+ return '';
+ }
}
public render() {
- const { code, compilerConfig = {}, onError } = this.props;
- const compiledCode = compileCode(code, compilerConfig, onError);
+ const compiledCode = this.compileCode();
if (!compiledCode) {
return null;
}
- const { example } = splitExampleCode(compiledCode);
- const ExampleComponent = this.getExampleComponent(example);
+ const { onError, evalInContext } = this.props;
+ const ExampleComponent = evalInContext(compiledCode);
const wrappedComponent = (
diff --git a/src/client/rsg-components/StyleGuide/StyleGuide.spec.tsx b/src/client/rsg-components/StyleGuide/StyleGuide.spec.tsx
index 3a129abf4..2a48bbffa 100644
--- a/src/client/rsg-components/StyleGuide/StyleGuide.spec.tsx
+++ b/src/client/rsg-components/StyleGuide/StyleGuide.spec.tsx
@@ -77,7 +77,7 @@ test('should render a sidebar if showSidebar is not set', () => {
'http://localhost/#foo',
'http://localhost/#bar',
]);
- expect(links.map(node => node.textContent)).toEqual(['Foo', 'Bar']);
+ expect(links.map((node) => node.textContent)).toEqual(['Foo', 'Bar']);
});
test('should not render a sidebar if showSidebar is false', () => {
diff --git a/src/client/utils/__tests__/compileCode.spec.ts b/src/client/utils/__tests__/compileCode.spec.ts
index 84c2f64a2..58f2fcdaf 100644
--- a/src/client/utils/__tests__/compileCode.spec.ts
+++ b/src/client/utils/__tests__/compileCode.spec.ts
@@ -1,36 +1,40 @@
import compileCode from '../compileCode';
import config from '../../../scripts/schemas/config';
-const compilerConfig = config.compilerConfig.default;
+const compileExample = config.compileExample.default;
describe('compileCode', () => {
- test('compile ES6 to ES5', () => {
- const result = compileCode(`const {foo, bar} = baz`, compilerConfig);
+ test('strips TypeScript type annotations', () => {
+ const result = compileCode(
+ `const x = (y: number): number => y; console.log(x(1))`,
+ compileExample
+ );
expect(result).toMatchInlineSnapshot(`
-"var foo = baz.foo;
-var bar = baz.bar;"
-`);
+ "\\"use strict\\";const x = (y) => y;
+ return (console.log(x(1)));"
+ `);
});
test('transform imports to require()', () => {
- const result = compileCode(`import foo from 'bar'`, compilerConfig);
+ const result = compileCode(`import foo from 'bar'; foo()`, compileExample);
expect(result).toMatchInlineSnapshot(`
-"const bar$0 = require('bar');
-const foo = bar$0.default || bar$0;"
-`);
+ "\\"use strict\\"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var _bar = require('bar'); var _bar2 = _interopRequireDefault(_bar);
+ return (_bar2.default.call(void 0, ));"
+ `);
});
test('transform async/await is not throw an error', () => {
const onError = jest.fn();
const result = compileCode(
- `async function asyncFunction() { return await Promise.resolve(); }`,
- compilerConfig,
+ `async function asyncFunction() { return await Promise.resolve(); }; asyncFunction()`,
+ compileExample,
onError
);
expect(onError).not.toHaveBeenCalled();
- expect(result).toMatchInlineSnapshot(
- `"async function asyncFunction() { return await Promise.resolve(); }"`
- );
+ expect(result).toMatchInlineSnapshot(`
+ "\\"use strict\\";async function asyncFunction() { return await Promise.resolve(); };
+ return (asyncFunction());"
+ `);
});
test('transform imports to require() in front of JSX', () => {
@@ -39,16 +43,14 @@ const foo = bar$0.default || bar$0;"
import foo from 'bar';
import Button from 'button';
`,
- compilerConfig
+ compileExample
);
expect(result).toMatchInlineSnapshot(`
-"
-const bar$0 = require('bar');
-const foo = bar$0.default || bar$0;
-const button$0 = require('button');
-const Button = button$0.default || button$0;
-React.createElement( Button, null )"
-`);
+ "\\"use strict\\";const _jsxFileName = \\"\\"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+ var _button = require('button'); var _button2 = _interopRequireDefault(_button);
+ return (React.createElement(_button2.default, {__self: this, __source: {fileName: _jsxFileName, lineNumber: 4}} ));"
+ `);
});
test('wrap JSX in Fragment', () => {
@@ -56,13 +58,14 @@ React.createElement( Button, null )"
`
`,
- compilerConfig
+ compileExample
);
expect(result).toMatchInlineSnapshot(`
-"React.createElement( React.Fragment, null, React.createElement( 'div', null,
- React.createElement( 'button', null, \\"Click\\" )
-) );"
-`);
+ "\\"use strict\\";const _jsxFileName = \\"\\";
+ return (React.createElement(React.Fragment, {__self: this, __source: {fileName: _jsxFileName, lineNumber: 1}}, React.createElement('div', {__self: this, __source: {fileName: _jsxFileName, lineNumber: 1}}
+ , React.createElement('button', {__self: this, __source: {fileName: _jsxFileName, lineNumber: 2}}, \\"Click\\")
+ )));"
+ `);
});
test('don’t wrap JSX in Fragment if it’s in the middle', () => {
@@ -71,15 +74,14 @@ React.createElement( Button, null )"
`,
- compilerConfig
+ compileExample
);
expect(result).toMatchInlineSnapshot(`
-"var foo = baz.foo;
-var bar = baz.bar;
-React.createElement( 'div', null,
- React.createElement( 'button', null, \\"Click\\" )
-)"
-`);
+ "\\"use strict\\";const _jsxFileName = \\"\\";const {foo, bar} = baz;
+ return (React.createElement('div', {__self: this, __source: {fileName: _jsxFileName, lineNumber: 2}}
+ , React.createElement('button', {__self: this, __source: {fileName: _jsxFileName, lineNumber: 3}}, \\"Click\\")
+ ));"
+ `);
});
test('tagged template literals', () => {
@@ -89,22 +91,21 @@ React.createElement( 'div', null,
\`;
`,
- compilerConfig
+ compileExample
);
expect(result).toMatchInlineSnapshot(`
-"var templateObject = Object.freeze([\\"\\\\n\\\\tcolor: tomato;\\\\n\\"]);
-var Button = styled.button(templateObject);
-React.createElement( Button, null )
-"
-`);
+ "\\"use strict\\";const _jsxFileName = \\"\\";const Button = styled.button\`
+ color: tomato;
+ \`;
+ return (React.createElement(Button, {__self: this, __source: {fileName: _jsxFileName, lineNumber: 4}} ));"
+ `);
});
test('onError callback', () => {
const onError = jest.fn();
- const result = compileCode(`=`, compilerConfig, onError);
+ const result = compileCode(`=`, compileExample, onError);
expect(result).toBe('');
- expect(onError).toHaveBeenCalledWith(
- expect.objectContaining({ message: 'Unexpected token (1:0)' })
- );
+ expect(onError).toHaveBeenCalledTimes(1);
+ expect(onError.mock.calls[0][0].toString()).toBe('SyntaxError: Unexpected token (1:1)');
});
});
diff --git a/src/client/utils/__tests__/transpileImports.spec.ts b/src/client/utils/__tests__/transpileImports.spec.ts
deleted file mode 100644
index 99ec19046..000000000
--- a/src/client/utils/__tests__/transpileImports.spec.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import transpileImports from '../transpileImports';
-
-describe('transpileImports', () => {
- test('transpile default imports', () => {
- const result = transpileImports(`import B from 'cat'`);
- expect(result).toMatchInlineSnapshot(`
-"const cat$0 = require('cat');
-const B = cat$0.default || cat$0;"
-`);
- });
-
- test('transpile named imports', () => {
- const result = transpileImports(`import {B} from 'cat'`);
- expect(result).toMatchInlineSnapshot(`
-"const cat$0 = require('cat');
-const B = cat$0.B;"
-`);
- });
-
- test('transpile mixed imports', () => {
- const result = transpileImports(`import A, {B} from 'cat'`);
- expect(result).toMatchInlineSnapshot(`
-"const cat$0 = require('cat');
-const A = cat$0.default || cat$0;
-const B = cat$0.B;"
-`);
- });
-
- test('transpile multiple import statements', () => {
- const result = transpileImports(`/**
-* Some important comment
-*/
-import 'dog'
-/* Less important comments */
-import B from 'cat'
-// Absolutely not important comment
-import C from 'capybara'
-import D from 'hamster' // One more comment
-import E from 'snake'
-`);
- expect(result).toMatchInlineSnapshot(`
-"/**
-* Some important comment
-*/
-require('dog');
-/* Less important comments */
-const cat$0 = require('cat');
-const B = cat$0.default || cat$0;
-// Absolutely not important comment
-const capybara$0 = require('capybara');
-const C = capybara$0.default || capybara$0;
-const hamster$0 = require('hamster');
-const D = hamster$0.default || hamster$0; // One more comment
-const snake$0 = require('snake');
-const E = snake$0.default || snake$0;
-"
-`);
- });
-
- test('transpile multiline named imports without trailing comma', () => {
- const result = transpileImports(`import {
- B,
- C
-} from 'cat'
-`);
- expect(result).toMatchInlineSnapshot(`
-"const cat$0 = require('cat');
-const B = cat$0.B;
-const C = cat$0.C;
-"
-`);
- });
-
- test('transpile multiline named imports with trailing comma', () => {
- const result = transpileImports(`import {
- B,
- C,
-} from 'cat'
-`);
- expect(result).toMatchInlineSnapshot(`
-"const cat$0 = require('cat');
-const B = cat$0.B;
-const C = cat$0.C;
-"
-`);
- });
-
- describe.each([
- ['./cat/capybara/hamster', '__cat_capybara_hamster'],
- ['../cat/capybara/hamster', '___cat_capybara_hamster'],
- ['cat/capybara/hamster', 'cat_capybara_hamster'],
- ])('transpile default imports via relative path', (modulePath, transpiled) => {
- test(`${modulePath}`, () => {
- const result = transpileImports(`import B from '${modulePath}'`);
- expect(result).toMatchInlineSnapshot(`
- "const ${transpiled}$0 = require('${modulePath}');
- const B = ${transpiled}$0.default || ${transpiled}$0;"
- `);
- });
- });
-
- test('return code if there are no imports', () => {
- const code = ``;
- const result = transpileImports(code);
- expect(result).toEqual(code);
- });
-
- test('return code if there is an import and a syntax error', () => {
- const code = `import foo from 'foo';&`;
- const result = transpileImports(code);
- expect(result).toEqual(code);
- });
-});
diff --git a/src/client/utils/compileCode.ts b/src/client/utils/compileCode.ts
index 722f15e88..6b6349257 100644
--- a/src/client/utils/compileCode.ts
+++ b/src/client/utils/compileCode.ts
@@ -1,7 +1,7 @@
-import { transform, TransformOptions } from 'buble';
-import transpileImports from './transpileImports';
-
-const compile = (code: string, config: TransformOptions): string => transform(code, config).code;
+// eslint-disable-next-line import/no-unresolved
+import * as compiler from 'rsg-compiler';
+import insertReturnLastExpression from './insertReturnLastExpression';
+import * as Rsg from '../../typings';
const startsWithJsx = (code: string): boolean => !!code.trim().match(/^);
@@ -9,22 +9,22 @@ const wrapCodeInFragment = (code: string): string => `${code} void
): string {
+ const wrappedCode = startsWithJsx(code) ? wrapCodeInFragment(code) : code;
try {
- const wrappedCode = startsWithJsx(code) ? wrapCodeInFragment(code) : code;
- const compiledCode = compile(wrappedCode, compilerConfig);
- return transpileImports(compiledCode);
+ const compiledCode = compileExample(compiler, wrappedCode);
+ return insertReturnLastExpression(compiledCode);
} catch (err) {
if (onError) {
onError(err);
}
+ return '';
}
- return '';
}
diff --git a/src/client/utils/getAst.ts b/src/client/utils/getAst.ts
index 8bc7ac04d..78c55636c 100644
--- a/src/client/utils/getAst.ts
+++ b/src/client/utils/getAst.ts
@@ -14,11 +14,11 @@ export const ACORN_OPTIONS: Options = {
*/
export default function getAst(code: string): Program | undefined {
try {
- return (Parser.parse(code, {
+ return Parser.parse(code, {
...ACORN_OPTIONS,
- // types of acorn are too simplistic and we have to use the body
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- }) as any) as Program;
+ // types of acorn are too simplistic and we have to use the body
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ }) as Program;
} catch (err) {
return undefined;
}
diff --git a/src/client/utils/insertReturnLastExpression.ts b/src/client/utils/insertReturnLastExpression.ts
new file mode 100644
index 000000000..52f03f03b
--- /dev/null
+++ b/src/client/utils/insertReturnLastExpression.ts
@@ -0,0 +1,30 @@
+import find from 'lodash/find';
+import getAst from './getAst';
+
+// Strip semicolon (;) at the end
+const unsemicolon = (s: string): string => s.replace(/;\s*$/, '');
+
+/**
+ * Wraps the last top-level expression in a return statement
+ * (kind of an implicit return).
+ *
+ * Example:
+ * var a = 1; React.createElement('i', null, a); // =>
+ * var a = 1; return (React.createElement('i', null, a));
+ */
+export default function insertReturnLastExpression(code: string): string {
+ const ast = getAst(code);
+ if (!ast) {
+ return code;
+ }
+
+ const lastExpressionPosition = find(ast.body.reverse(), { type: 'ExpressionStatement' });
+ if (!lastExpressionPosition) {
+ return code;
+ }
+
+ const { start, end } = lastExpressionPosition;
+ const head = unsemicolon(code.substring(0, start));
+ const lastExpressionCode = unsemicolon(code.substring(start, end));
+ return `${head};\nreturn (${lastExpressionCode});`;
+}
diff --git a/src/client/utils/rewriteImports.ts b/src/client/utils/rewriteImports.ts
deleted file mode 100644
index c3ef047a3..000000000
--- a/src/client/utils/rewriteImports.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-// Temporary copy to fix
-// https://github.com/lukeed/rewrite-imports/issues/10
-
-const UNNAMED = /import\s*['"]([^'"]+)['"];?/gi;
-const NAMED = /import\s*(\*\s*as)?\s*(\w*?)\s*,?\s*(?:\{([\s\S]*?)\})?\s*from\s*['"]([^'"]+)['"];?/gi;
-
-function alias(key: string): { key: string; name: string } {
- key = key.trim();
- const name = key.split(' as ');
- if (name.length > 1) {
- key = name.shift() as string;
- }
- return { key, name: name[0] };
-}
-
-let num: number;
-function generate(keys: string[], dep: string, base: string, fn: string): string {
- const tmp = dep.replace(/\W/g, '_') + '$' + num++; // uniqueness
- const name = alias(tmp).name;
-
- dep = `${fn}('${dep}')`;
-
- let obj;
- let out = `const ${name} = ${dep};`;
-
- if (base) {
- out += `\nconst ${base} = ${tmp}.default || ${tmp};`;
- }
-
- keys.forEach(key => {
- obj = alias(key);
- out += `\nconst ${obj.name} = ${tmp}.${obj.key};`;
- });
-
- return out;
-}
-
-export default function(str: string, fn = 'require'): string {
- num = 0;
- return str
- .replace(NAMED, (_, asterisk, base, req: string | undefined, dep: string) =>
- generate(req ? req.split(',').filter(d => d.trim()) : [], dep, base, fn)
- )
- .replace(UNNAMED, (_, dep) => `${fn}('${dep}');`);
-}
diff --git a/src/client/utils/transpileImports.ts b/src/client/utils/transpileImports.ts
deleted file mode 100644
index 090981020..000000000
--- a/src/client/utils/transpileImports.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { walk } from 'estree-walker';
-import rewriteImports from './rewriteImports';
-import getAst from './getAst';
-
-const hasImports = (code: string): boolean => !!code.match(/import[\S\s]+?['"]([^'"]+)['"];?/m);
-
-/**
- * Replace ECMAScript imports with require() calls
- */
-export default function transpileImports(code: string): string {
- // Don't do anything when the code has nothing that looks like an import
- if (!hasImports(code)) {
- return code;
- }
-
- // Ignore errors, they should be caught by Buble
- const ast = getAst(code);
- if (!ast) {
- return code;
- }
-
- let offset = 0;
- // estree walkers type is incompatible with acorns output
- // it is working here out of luck and typescript is demonstrating it
- // we have to go through the any part to keep the nodes with their `node.start`
- // and `node.stop`
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- walk(ast as any, {
- // import foo from 'foo'
- // import 'foo'
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- enter: (node: any) => {
- if (node.type === 'ImportDeclaration' && node.source) {
- const start = node.start + offset;
- const end = node.end + offset;
-
- const statement = code.substring(start, end);
- const transpiledStatement = rewriteImports(statement);
-
- code = code.substring(0, start) + transpiledStatement + code.substring(end);
-
- offset += transpiledStatement.length - statement.length;
- }
- },
- });
-
- return code;
-}
diff --git a/src/loaders/__tests__/examples-loader.spec.ts b/src/loaders/__tests__/examples-loader.spec.ts
index f1898903f..31faffc13 100644
--- a/src/loaders/__tests__/examples-loader.spec.ts
+++ b/src/loaders/__tests__/examples-loader.spec.ts
@@ -15,12 +15,9 @@ const subComponentQuery = {
shouldShowDefaultExample: false,
};
-
const getQuery = (options = {}) => encode({ ...query, ...options }, '?');
const getSubComponentQuery = (options = {}) => encode({ ...subComponentQuery, ...options }, '?');
-
-
it('should return valid, parsable JS', () => {
const exampleMarkdown = `
# header
diff --git a/src/loaders/styleguide-loader.ts b/src/loaders/styleguide-loader.ts
index 9fa5d6d24..ba9b92411 100644
--- a/src/loaders/styleguide-loader.ts
+++ b/src/loaders/styleguide-loader.ts
@@ -18,8 +18,8 @@ import * as Rsg from '../typings';
const logger = createLogger('rsg');
// Config options that should be passed to the client
-const CLIENT_CONFIG_OPTIONS = [
- 'compilerConfig',
+const CLIENT_CONFIG_OPTIONS: (keyof Rsg.SanitizedStyleguidistConfig)[] = [
+ 'compileExample',
'tocMode',
'mountPointId',
'pagePerSection',
@@ -35,7 +35,7 @@ const CLIENT_CONFIG_OPTIONS = [
const STYLE_VARIABLE_NAME = '__rsgStyles';
const THEME_VARIABLE_NAME = '__rsgTheme';
-export default function() {}
+export default function () {}
export function pitch(this: Rsg.StyleguidistLoaderContext) {
// Clear cache so it would detect new or renamed files
fileExistsCaseInsensitive.clearCache();
diff --git a/src/loaders/utils/client/getEvalInContext.ts b/src/loaders/utils/client/getEvalInContext.ts
new file mode 100644
index 000000000..1542519ea
--- /dev/null
+++ b/src/loaders/utils/client/getEvalInContext.ts
@@ -0,0 +1,31 @@
+import React from 'react';
+
+/**
+ * Returns a function that evals the example code in a given `require()`
+ * function that allows you to require modules from Markdown examples (this
+ * won’t work dinamically becasue we need to know all required modules in
+ * advance to be able to bundle them with the code).
+ *
+ * Also prepends a given `code` with a `header` that maps required context
+ * modules to local variables: React, current component and modules defined
+ * via the `context` config option.
+ */
+export default function getEvalInContext(
+ header: string,
+ require: (module: string) => any,
+ code: string
+): () => React.ReactElement {
+ // 1. Prepend code with the header
+ // 2. Wrap code in a block (`{}`) to create a new scope, so you could
+ // explicitly import context modules in your examples)
+ const body = `${header}{${code}}`;
+
+ // eslint-disable-next-line no-new-func
+ const func = new Function('require', body);
+
+ // Define custom name instead of default `anonymous`
+ Object.defineProperty(func, 'name', { value: 'evalInContext(Example)', writable: false });
+
+ // Bind the `require` function
+ return func.bind(null, require);
+}
diff --git a/src/loaders/utils/compileCode.ts b/src/loaders/utils/compileCode.ts
new file mode 100644
index 000000000..588099de6
--- /dev/null
+++ b/src/loaders/utils/compileCode.ts
@@ -0,0 +1,22 @@
+// eslint-disable-next-line import/no-unresolved
+import * as compiler from 'rsg-compiler';
+import insertReturnLastExpression from './insertReturnLastExpression';
+import * as Rsg from '../../typings';
+
+const startsWithJsx = (code: string): boolean => !!code.trim().match(/^);
+
+const wrapCodeInFragment = (code: string): string => `${code};`;
+
+/*
+ * 1. Wrap code in React Fragment if it starts with JSX element
+ * 2. Compile code using given compiler
+ * 3. Wrap the last top-level expression in a return statement.
+ */
+export default function compileCode(
+ code: string,
+ compileExample: Rsg.SanitizedStyleguidistConfig['compileExample']
+): string {
+ const wrappedCode = startsWithJsx(code) ? wrapCodeInFragment(code) : code;
+ const compiledCode = compileExample(compiler, wrappedCode);
+ return insertReturnLastExpression(compiledCode);
+}
diff --git a/src/loaders/utils/getAst.ts b/src/loaders/utils/getAst.ts
index 8a07985c4..36bc0f4aa 100644
--- a/src/loaders/utils/getAst.ts
+++ b/src/loaders/utils/getAst.ts
@@ -1,6 +1,10 @@
-import { Parser, Node as AcornNode, Options } from 'acorn';
+import { Parser, Node, Options } from 'acorn';
import Logger from 'glogg';
+interface Program extends Node {
+ body: Node[];
+}
+
const logger = Logger('rsg');
export const ACORN_OPTIONS: Options = {
@@ -14,11 +18,11 @@ export const ACORN_OPTIONS: Options = {
export default function getAst(
code: string,
plugins: ((BaseParser: typeof Parser) => typeof Parser)[] = []
-): AcornNode | undefined {
+): Program | undefined {
const parser = Parser.extend(...plugins);
try {
- return parser.parse(code, ACORN_OPTIONS);
+ return parser.parse(code, ACORN_OPTIONS) as Program;
} catch (err) {
logger.debug(`Acorn cannot parse example code: ${err.message}\n\nCode:\n${code}`);
return undefined;
diff --git a/src/loaders/utils/getComponents.ts b/src/loaders/utils/getComponents.ts
index 2e9679120..2af38da78 100644
--- a/src/loaders/utils/getComponents.ts
+++ b/src/loaders/utils/getComponents.ts
@@ -4,9 +4,8 @@ import * as Rsg from '../../typings';
/**
* Process each component in a list.
*
- * @param {Array} components File names of components.
- * @param {object} config
- * @returns {object|null}
+ * @param components File names of components.
+ * @param config
*/
export default function getComponents(
components: string[],
diff --git a/src/loaders/utils/insertReturnLastExpression.ts b/src/loaders/utils/insertReturnLastExpression.ts
new file mode 100644
index 000000000..52f03f03b
--- /dev/null
+++ b/src/loaders/utils/insertReturnLastExpression.ts
@@ -0,0 +1,30 @@
+import find from 'lodash/find';
+import getAst from './getAst';
+
+// Strip semicolon (;) at the end
+const unsemicolon = (s: string): string => s.replace(/;\s*$/, '');
+
+/**
+ * Wraps the last top-level expression in a return statement
+ * (kind of an implicit return).
+ *
+ * Example:
+ * var a = 1; React.createElement('i', null, a); // =>
+ * var a = 1; return (React.createElement('i', null, a));
+ */
+export default function insertReturnLastExpression(code: string): string {
+ const ast = getAst(code);
+ if (!ast) {
+ return code;
+ }
+
+ const lastExpressionPosition = find(ast.body.reverse(), { type: 'ExpressionStatement' });
+ if (!lastExpressionPosition) {
+ return code;
+ }
+
+ const { start, end } = lastExpressionPosition;
+ const head = unsemicolon(code.substring(0, start));
+ const lastExpressionCode = unsemicolon(code.substring(start, end));
+ return `${head};\nreturn (${lastExpressionCode});`;
+}
diff --git a/src/loaders/utils/resolveESModule.ts b/src/loaders/utils/resolveESModule.ts
index d071cfe29..b6147c354 100644
--- a/src/loaders/utils/resolveESModule.ts
+++ b/src/loaders/utils/resolveESModule.ts
@@ -10,7 +10,7 @@ import requireIt from './requireIt';
*/
export default (requireRequest: string, name: string) => {
// The name could possibly contain invalid characters for a JS variable name
- // such as "." or "-".
+ // such as "." or "-".
const safeName = name ? name.replace(/\W/, '') : name;
return [
@@ -25,9 +25,13 @@ export default (requireRequest: string, name: string) => {
b.logicalExpression(
'||',
b.identifier(`${safeName}$0.default`),
- b.logicalExpression('||', b.identifier(`${safeName}$0['${safeName}']`), b.identifier(`${safeName}$0`))
+ b.logicalExpression(
+ '||',
+ b.identifier(`${safeName}$0['${safeName}']`),
+ b.identifier(`${safeName}$0`)
+ )
)
),
]),
- ]
+ ];
};
diff --git a/src/scripts/__tests__/__snapshots__/make-webpack-config.spec.ts.snap b/src/scripts/__tests__/__snapshots__/make-webpack-config.spec.ts.snap
deleted file mode 100644
index ea750bfa6..000000000
--- a/src/scripts/__tests__/__snapshots__/make-webpack-config.spec.ts.snap
+++ /dev/null
@@ -1,53 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should merge user webpack config 1`] = `
-Object {
- "foo": "bar",
- "rsg-components": "~/src/client/rsg-components",
-}
-`;
-
-exports[`should not owerwrite user DefinePlugin 1`] = `
-Array [
- DefinePlugin {
- "definitions": Object {
- "process.env.NODE_ENV": "\\"test\\"",
- "process.env.STYLEGUIDIST_ENV": "\\"development\\"",
- },
- },
- DefinePlugin {
- "definitions": Object {
- "process.env.PIZZA": "\\"salami\\"",
- },
- },
-]
-`;
-
-exports[`should prepend requires as webpack entries 1`] = `
-Array [
- "a/b.js",
- "c/d.css",
- "~/src/client/index",
- "~/node_modules/react-dev-utils/webpackHotDevClient.js",
-]
-`;
-
-exports[`should set aliases 1`] = `
-Object {
- "rsg-components": "~/src/client/rsg-components",
-}
-`;
-
-exports[`should set aliases from moduleAliases option 1`] = `
-Object {
- "foo": "bar",
- "rsg-components": "~/src/client/rsg-components",
-}
-`;
-
-exports[`should set aliases from styleguideComponents option 1`] = `
-Object {
- "rsg-components": "~/src/client/rsg-components",
- "rsg-components/foo": "bar",
-}
-`;
diff --git a/src/scripts/__tests__/index.esm.spec.ts b/src/scripts/__tests__/index.esm.spec.ts
index 38455b19f..6ad580efb 100644
--- a/src/scripts/__tests__/index.esm.spec.ts
+++ b/src/scripts/__tests__/index.esm.spec.ts
@@ -93,7 +93,7 @@ describe('makeWebpackConfig', () => {
const api = styleguidist({
dangerouslyUpdateWebpackConfig: (webpackConfig, env) => {
if (webpackConfig.resolve && webpackConfig.resolve.extensions) {
- webpackConfig.resolve.extensions.push(env);
+ webpackConfig.resolve.extensions.push(env as string);
}
return webpackConfig;
},
diff --git a/src/scripts/__tests__/make-webpack-config.spec.ts b/src/scripts/__tests__/make-webpack-config.spec.ts
index 5df8b375c..dfb2ff8f9 100644
--- a/src/scripts/__tests__/make-webpack-config.spec.ts
+++ b/src/scripts/__tests__/make-webpack-config.spec.ts
@@ -4,6 +4,7 @@ import CopyWebpackPlugin from 'copy-webpack-plugin';
import makeWebpackConfig from '../make-webpack-config';
import * as Rsg from '../../typings';
+// HACK: For some reason validate() doesn’t exist in webpack types
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { validate } = require('webpack');
@@ -13,12 +14,15 @@ const styleguideConfig = ({
styleguideDir: __dirname,
require: [],
title: 'Style Guide',
-} as unknown) as Rsg.SanitizedStyleguidistConfig;
+ compilerModule: 'sucrase',
+ sections: [],
+ dangerouslyUpdateWebpackConfig: (x: Configuration) => x,
+} as Partial) as Rsg.SanitizedStyleguidistConfig;
const getClasses = (plugins: Tapable.Plugin[] = [], name: string): Tapable.Plugin[] =>
- plugins.filter(x => x.constructor.name === name);
+ plugins.filter((x) => x.constructor.name === name);
const getClassNames = (plugins: Tapable.Plugin[] = []): string[] =>
- plugins.map(x => x.constructor.name);
+ plugins.map((x) => x.constructor.name);
const process$env$nodeEnv = process.env.NODE_ENV;
@@ -73,7 +77,12 @@ it('should return a production config', () => {
it('should set aliases', () => {
const result = makeWebpackConfig(styleguideConfig, 'development');
- expect(result.resolve && result.resolve.alias).toMatchSnapshot();
+ expect(result.resolve?.alias).toMatchInlineSnapshot(`
+ Object {
+ "rsg-compiler": "sucrase",
+ "rsg-components": "~/src/client/rsg-components",
+ }
+ `);
});
it('should set aliases from moduleAliases option', () => {
@@ -86,7 +95,7 @@ it('should set aliases from moduleAliases option', () => {
},
'development'
);
- expect(result.resolve && result.resolve.alias).toMatchSnapshot();
+ expect(result.resolve?.alias?.foo).toBe('bar');
});
it('should set aliases from styleguideComponents option', () => {
@@ -99,7 +108,24 @@ it('should set aliases from styleguideComponents option', () => {
},
'development'
);
- expect(result.resolve && result.resolve.alias).toMatchSnapshot();
+ expect(result.resolve?.alias).toMatchInlineSnapshot(`
+ Object {
+ "rsg-compiler": "sucrase",
+ "rsg-components": "~/src/client/rsg-components",
+ "rsg-components/foo": "bar",
+ }
+ `);
+});
+
+it('should set aliases from compilerModule option', () => {
+ const result = makeWebpackConfig(
+ {
+ ...styleguideConfig,
+ compilerModule: 'babel',
+ },
+ 'development'
+ );
+ expect(result.resolve?.alias?.['rsg-compiler']).toBe('babel');
});
it('should prepend requires as webpack entries', () => {
@@ -107,7 +133,14 @@ it('should prepend requires as webpack entries', () => {
{ ...styleguideConfig, require: ['a/b.js', 'c/d.css'] },
'development'
);
- expect(result.entry).toMatchSnapshot();
+ expect(result.entry).toMatchInlineSnapshot(`
+ Array [
+ "a/b.js",
+ "c/d.css",
+ "~/src/client/index",
+ "~/node_modules/react-dev-utils/webpackHotDevClient.js",
+ ]
+ `);
});
it('should enable verbose mode in CleanWebpackPlugin', () => {
@@ -134,7 +167,13 @@ it('should merge user webpack config', () => {
{ ...styleguideConfig, webpackConfig: { resolve: { alias: { foo: 'bar' } } } },
'development'
);
- expect(result.resolve && result.resolve.alias).toMatchSnapshot();
+ expect(result.resolve?.alias).toMatchInlineSnapshot(`
+ Object {
+ "foo": "bar",
+ "rsg-compiler": "sucrase",
+ "rsg-components": "~/src/client/rsg-components",
+ }
+ `);
});
it('should not owerwrite user DefinePlugin', () => {
@@ -152,10 +191,21 @@ it('should not owerwrite user DefinePlugin', () => {
'development'
);
- // Doesn’t really test that values won’t be overwritten, just that
- // DefinePlugin is applied twice. To write a real test we’d have to run
- // webpack
- expect(getClasses(result.plugins, 'DefinePlugin')).toMatchSnapshot();
+ expect(getClasses(result.plugins, 'DefinePlugin')).toMatchInlineSnapshot(`
+ Array [
+ DefinePlugin {
+ "definitions": Object {
+ "process.env.NODE_ENV": "\\"test\\"",
+ "process.env.STYLEGUIDIST_ENV": "\\"development\\"",
+ },
+ },
+ DefinePlugin {
+ "definitions": Object {
+ "process.env.PIZZA": "\\"salami\\"",
+ },
+ },
+ ]
+ `);
});
it('should update webpack config', () => {
diff --git a/src/scripts/create-server.ts b/src/scripts/create-server.ts
index e38ac3232..310e7c453 100644
--- a/src/scripts/create-server.ts
+++ b/src/scripts/create-server.ts
@@ -1,40 +1,36 @@
-import webpack, { Configuration } from 'webpack';
+import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
-import merge from 'webpack-merge';
import makeWebpackConfig from './make-webpack-config';
import * as Rsg from '../typings';
export default function createServer(
config: Rsg.SanitizedStyleguidistConfig,
env: 'development' | 'production' | 'none'
-): { app: WebpackDevServer; compiler: webpack.Compiler } {
+) {
const webpackConfig = makeWebpackConfig(config, env);
- const webpackDevServerConfig = merge(
- {
- noInfo: true,
- compress: true,
- clientLogLevel: 'none',
- hot: true,
- quiet: true,
- watchOptions: {
- ignored: /node_modules/,
- },
- watchContentBase: config.assetsDir !== undefined,
- stats: webpackConfig.stats || {},
- } as Configuration,
- webpackConfig.devServer as Configuration,
- {
- contentBase: config.assetsDir,
- } as Configuration
- );
- const compiler = webpack(webpackConfig);
- const devServer = new WebpackDevServer(compiler, webpackDevServerConfig);
+ const webpackDevServerConfig: WebpackDevServer.Configuration = {
+ noInfo: true,
+ compress: true,
+ clientLogLevel: 'none',
+ hot: true,
+ watchOptions: {
+ ignored: /node_modules/,
+ },
+ watchContentBase: config.assetsDir !== undefined,
+ stats: webpackConfig.stats,
+ before: app => {
+ // User defined customizations
+ if (config.configureServer) {
+ config.configureServer(app, env);
+ }
+ },
+ ...webpackConfig.devServer,
+ contentBase: config.assetsDir,
+ };
- // User defined customizations
- if (config.configureServer) {
- config.configureServer((devServer as any).app, env);
- }
+ const compiler = webpack(webpackConfig);
+ const app = new WebpackDevServer(compiler, webpackDevServerConfig);
- return { app: devServer, compiler };
+ return { app, compiler };
}
diff --git a/src/scripts/make-webpack-config.ts b/src/scripts/make-webpack-config.ts
index 4a193e201..39c94acf4 100644
--- a/src/scripts/make-webpack-config.ts
+++ b/src/scripts/make-webpack-config.ts
@@ -1,6 +1,6 @@
import path from 'path';
import castArray from 'lodash/castArray';
-import webpack, { Configuration, Resolve } from 'webpack';
+import webpack, { Configuration } from 'webpack';
import TerserPlugin from 'terser-webpack-plugin';
import { MiniHtmlWebpackPlugin } from 'mini-html-webpack-plugin';
import MiniHtmlWebpackTemplate from '@vxna/mini-html-webpack-template';
@@ -9,23 +9,28 @@ import CopyWebpackPlugin from 'copy-webpack-plugin';
import merge from 'webpack-merge';
import forEach from 'lodash/forEach';
import isFunction from 'lodash/isFunction';
-
import StyleguidistOptionsPlugin from './utils/StyleguidistOptionsPlugin';
import mergeWebpackConfig from './utils/mergeWebpackConfig';
import * as Rsg from '../typings';
+type Mode = Configuration['mode'];
+
const RENDERER_REGEXP = /Renderer$/;
const sourceDir = path.resolve(__dirname, '../client');
-interface AliasedConfiguration extends Configuration {
- resolve: Resolve & { alias: Record };
-}
-
-export default function (
- config: Rsg.SanitizedStyleguidistConfig,
- env: 'development' | 'production' | 'none'
-): Configuration {
+const getCustomAliases = (styleguideComponents: Record) => {
+ const customAliases: Record = {};
+ forEach(styleguideComponents, (filepath, name) => {
+ const fullName = name.match(RENDERER_REGEXP)
+ ? `${name.replace(RENDERER_REGEXP, '')}/${name}`
+ : name;
+ customAliases[`rsg-components/${fullName}`] = filepath;
+ });
+ return customAliases;
+};
+
+export default function (config: Rsg.SanitizedStyleguidistConfig, env: Mode): Configuration {
process.env.NODE_ENV = process.env.NODE_ENV || env;
const isProd = env === 'production';
@@ -63,6 +68,16 @@ export default function (
'process.env.STYLEGUIDIST_ENV': JSON.stringify(env),
}),
],
+ module: {
+ rules: [
+ {
+ // Support .mjs modules in dependencies, like Sucrase
+ test: /\.mjs$/,
+ include: /node_modules/,
+ type: 'javascript/auto',
+ },
+ ],
+ },
performance: {
hints: false,
},
@@ -128,31 +143,24 @@ export default function (
webpackConfig = mergeWebpackConfig(webpackConfig, config.webpackConfig, env);
}
- // Custom aliases
- // NOTE: in a sanitized config, moduleAliases are always an object (never null or undefined)
- const aliasedWebpackConfig = merge(webpackConfig, {
- resolve: { alias: config.moduleAliases },
- }) as AliasedConfiguration;
-
- const alias = aliasedWebpackConfig.resolve.alias;
-
- // Custom style guide components
- if (config.styleguideComponents) {
- forEach(config.styleguideComponents, (filepath, name) => {
- const fullName = name.match(RENDERER_REGEXP)
- ? `${name.replace(RENDERER_REGEXP, '')}/${name}`
- : name;
- alias[`rsg-components/${fullName}`] = filepath;
- });
- }
-
- // Add components folder alias at the end, so users can override our components
- // to customize the style guide (their aliases should be before this one)
- alias['rsg-components'] = path.resolve(sourceDir, 'rsg-components');
+ // Aliases
+ webpackConfig = merge(webpackConfig, {
+ resolve: {
+ alias: {
+ // In a sanitized config, moduleAliases is always an object (never null or undefined)
+ ...config.moduleAliases,
+ // Custom styleguide components
+ ...getCustomAliases(config.styleguideComponents),
+ // Code compiler module
+ 'rsg-compiler': config.compilerModule,
+ // Add components folder alias at the end, so users can override our components
+ // to customize the style guide (their aliases should be before this one)
+ 'rsg-components': path.resolve(sourceDir, 'rsg-components'),
+ },
+ },
+ });
- webpackConfig = config.dangerouslyUpdateWebpackConfig
- ? config.dangerouslyUpdateWebpackConfig(aliasedWebpackConfig, env)
- : aliasedWebpackConfig;
+ webpackConfig = config.dangerouslyUpdateWebpackConfig(webpackConfig, env);
return webpackConfig;
}
diff --git a/src/scripts/schemas/config.ts b/src/scripts/schemas/config.ts
index be50e3485..d5dbbe128 100644
--- a/src/scripts/schemas/config.ts
+++ b/src/scripts/schemas/config.ts
@@ -6,11 +6,12 @@ import path from 'path';
import startCase from 'lodash/startCase';
import kleur from 'kleur';
import * as reactDocgen from 'react-docgen';
-import { TransformOptions } from 'buble';
import { createDisplayNameHandler } from 'react-docgen-displayname-handler';
import annotationResolver from 'react-docgen-annotation-resolver';
import { ASTNode } from 'ast-types';
import { NodePath } from 'ast-types/lib/node-path';
+import { Configuration } from 'webpack';
+import * as sucrase from 'sucrase';
import findUserWebpackConfig from '../utils/findUserWebpackConfig';
import getUserPackageJson from '../utils/getUserPackageJson';
import fileExistsCaseInsensitive from '../utils/findFileCaseInsensitive';
@@ -52,22 +53,34 @@ const configSchema: Record
+ compiler.transform(code, {
+ // Compile TypeScript, JSX and ECMAScript imports
+ transforms: ['typescript', 'jsx', 'imports'],
+ }).code,
+ },
compilerConfig: {
type: 'object',
- default: {
- // Don't include an Object.assign ponyfill, we have our own
- objectAssign: 'Object.assign',
- // Transpile only features needed for IE11
- target: { ie: 11 },
- transforms: {
- // Don't throw on ESM imports, we transpile them ourselves
- modules: false,
- // Enable tagged template literals for styled-components
- dangerousTaggedTemplateString: true,
- // to make async/await work by default (no transformation)
- asyncAwait: false,
- },
- } as TransformOptions,
+ deprecated: 'Use compilerModule and compileExample options instead',
+ },
+ compilerModule: {
+ type: 'string',
+ default: 'sucrase',
+ process: (value: string) => {
+ if (value) {
+ try {
+ require.resolve(value);
+ } catch (err) {
+ throw new StyleguidistError(
+ `Module ${kleur.bold(value)}, specified in the compilerModule option, not found.
+Try to install it: npm install --save-dev ${value}`
+ );
+ }
+ }
+ return value;
+ },
},
// `components` is a shortcut for { sections: [{ components }] },
// see `sections` below
@@ -93,6 +106,7 @@ const configSchema: Record config,
},
defaultExample: {
type: ['boolean', 'existing file path'],
@@ -274,6 +288,7 @@ const configSchema: Record {
+ try {
+ return readdirSync(dir);
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ return [];
+ }
+ throw err;
+ }
+};
+
/**
* Find a file in a directory, case-insensitive
*
@@ -13,7 +24,7 @@ const readdirSync = memoize(fs.readdirSync);
export default function findFileCaseInsensitive(filepath: string): string | undefined {
const dir = path.dirname(filepath);
const fileNameLower = path.basename(filepath).toLowerCase();
- const files = readdirSync(dir);
+ const files = getFiles(dir);
const found = files.find(file => file.toLowerCase() === fileNameLower);
return found && path.join(dir, found);
}
diff --git a/src/scripts/utils/mergeWebpackConfig.ts b/src/scripts/utils/mergeWebpackConfig.ts
index 9998b9945..09f7e999f 100644
--- a/src/scripts/utils/mergeWebpackConfig.ts
+++ b/src/scripts/utils/mergeWebpackConfig.ts
@@ -4,13 +4,17 @@ import omit from 'lodash/omit';
import { Configuration } from 'webpack';
import { Tapable } from 'tapable';
-const IGNORE_SECTIONS = ['entry', 'externals', 'output', 'watch', 'stats', 'styleguidist'];
-const IGNORE_SECTIONS_ENV: Record = {
- development: [],
+type Mode = Configuration['mode'];
+type MetaConfig = Configuration | ((env: Mode) => Configuration);
+
+const IGNORE_SECTIONS = ['entry', 'externals', 'output', 'watch', 'stats', 'styleguidist'] as const;
+const IGNORE_SECTIONS_ENV = {
+ development: IGNORE_SECTIONS,
// For production builds, we'll ignore devtool settings to avoid
// source mapping bloat.
- production: ['devtool'],
-};
+ production: [...IGNORE_SECTIONS, 'devtool'],
+ none: [],
+} as const;
const IGNORE_PLUGINS = [
'CommonsChunkPlugins',
@@ -32,8 +36,6 @@ const merge = mergeBase({
),
});
-type MetaConfig = Configuration | ((env?: string) => Configuration);
-
/**
* Merge two Webpack configs.
*
@@ -41,17 +43,16 @@ type MetaConfig = Configuration | ((env?: string) => Configuration);
* - Ignores given sections (options.ignore).
* - Ignores plugins that shouldn’t be used twice or may cause issues.
*
- * @param {object} baseConfig
- * @param {object|Function} userConfig
- * @param {string} env
- * @return {object}
+ * @param baseConfig
+ * @param userConfig
+ * @param env
*/
export default function mergeWebpackConfig(
- baseConfig: MetaConfig,
+ baseConfig: Configuration,
userConfig: MetaConfig,
- env = 'production'
+ env: Mode = 'production'
) {
const userConfigObject = isFunction(userConfig) ? userConfig(env) : userConfig;
- const safeUserConfig = omit(userConfigObject, IGNORE_SECTIONS.concat(IGNORE_SECTIONS_ENV[env]));
+ const safeUserConfig = omit(userConfigObject, IGNORE_SECTIONS_ENV[env]);
return merge(baseConfig, safeUserConfig);
}
diff --git a/src/typings/RsgStyleguidistConfig.ts b/src/typings/RsgStyleguidistConfig.ts
index 73428c34d..512b8b5e2 100644
--- a/src/typings/RsgStyleguidistConfig.ts
+++ b/src/typings/RsgStyleguidistConfig.ts
@@ -1,10 +1,9 @@
-import WebpackDevServer from 'webpack-dev-server';
import { Configuration, loader } from 'webpack';
-import { TransformOptions } from 'buble';
import { Handler, DocumentationObject, PropDescriptor } from 'react-docgen';
import { ASTNode } from 'ast-types';
import { NodePath } from 'ast-types/lib/node-path';
import { Styles } from 'jss';
+import { Application } from 'express';
import { RecursivePartial } from './RecursivePartial';
import { ExpandMode } from './RsgComponent';
import { PropsObject } from './RsgPropsObject';
@@ -12,6 +11,13 @@ import { CodeExample } from './RsgExample';
import { ConfigSection, Section } from './RsgSection';
import { Theme } from './RsgTheme';
+type Mode = Configuration['mode'];
+
+type PropsResolver = (
+ ast: ASTNode,
+ parser: { parse: (code: string) => ASTNode }
+) => NodePath | NodePath[];
+
export interface StyleguidistLoaderContext extends loader.LoaderContext {
_styleguidist: SanitizedStyleguidistConfig;
}
@@ -19,13 +25,16 @@ export interface StyleguidistLoaderContext extends loader.LoaderContext {
interface BaseStyleguidistConfig {
assetsDir: string | string[];
tocMode: ExpandMode;
- compilerConfig: TransformOptions;
+ compileExample: (compiler: unknown, code: string) => string;
+ /** @deprecated */
+ compilerConfig: Record;
+ compilerModule: string;
components: (() => string[]) | string | string[];
configDir: string;
context: Record;
contextDependencies: string[];
- configureServer(server: WebpackDevServer, env: string): string;
- dangerouslyUpdateWebpackConfig: (server: Configuration, env: string) => Configuration;
+ configureServer(server: Application, env: Mode): string;
+ dangerouslyUpdateWebpackConfig: (config: Configuration, env: Mode) => Configuration;
defaultExample: string | false;
exampleMode: ExpandMode;
editorConfig: {
@@ -50,24 +59,20 @@ interface BaseStyleguidistConfig {
propsParser(
filePath: string,
code: string,
- resolver: (
- ast: ASTNode,
- parser: { parse: (code: string) => ASTNode }
- ) => NodePath | NodePath[],
+ resolver: PropsResolver,
handlers: Handler[]
): DocumentationObject;
require: string[];
- resolver(
- ast: ASTNode,
- parser: { parse: (code: string) => ASTNode }
- ): NodePath | NodePath[];
+ resolver: PropsResolver;
ribbon?: {
text?: string;
url: string;
};
serverHost: string;
serverPort: number;
+ /** @deprecated */
showCode: boolean;
+ /** @deprecated */
showUsage: boolean;
showSidebar: boolean;
skipComponentsWithoutExample: boolean;
@@ -75,7 +80,7 @@ interface BaseStyleguidistConfig {
styleguideComponents: Record;
styleguideDir: string;
styles: Styles | string | ((theme: Theme) => Styles);
- template: any;
+ template: any; // TODO
theme: RecursivePartial | string;
title: string;
updateDocs(doc: PropsObject, file: string): PropsObject;
@@ -84,7 +89,7 @@ interface BaseStyleguidistConfig {
usageMode: ExpandMode;
verbose: boolean;
version: string;
- webpackConfig: Configuration | ((env?: string) => Configuration);
+ webpackConfig: Configuration | ((env: Mode) => Configuration);
}
export interface ProcessedStyleguidistConfig extends BaseStyleguidistConfig {
diff --git a/src/typings/dependencies/rsg-compiler.ts b/src/typings/dependencies/rsg-compiler.ts
new file mode 100644
index 000000000..3ea5fe97d
--- /dev/null
+++ b/src/typings/dependencies/rsg-compiler.ts
@@ -0,0 +1 @@
+declare module 'rsg-compiler' {}
diff --git a/tsconfig.json b/tsconfig.json
index e92593686..e0c1f3fa9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,8 @@
"outDir": "./lib",
"lib": ["dom"],
"paths": {
- "rsg-components/*": ["src/client/rsg-components/*"]
+ "rsg-components/*": ["src/client/rsg-components/*"],
+ "rsg-compiler": ["sucrase"]
}
},
"include": ["src"],