Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setup react-styleguidist, Create React App, Typescript, Material UI and styled-components

Im trying to setup react-styleguidist (RSG) from Create React App 3 (CRA), Typescript, Material UI and styled-components. I'm stuck on this error:

./node_modules/react-styleguidist/lib/client/rsg-components/ReactExample/ReactExample.js
Module not found: Can't resolve 'rsg-components/Wrapper' in '/Users/ofj/code/new-core-web/node_modules/react-styleguidist/lib/client/rsg-components/ReactExample'

I followed the docs setting up a wrappers for MUI theme and styled-components:
https://react-styleguidist.js.org/docs/thirdparties.html#styled-components

/styleguidist/MuiThemeWrapper.tsx

const muiTheme = getMuiTheme({});
const MuiThemeWrapper = ({ children, ...rest }) => (
    <MuiThemeProvider muiTheme={muiTheme}>
        <ThemeProvider theme={theme}>
            {children}
        </ThemeProvider>
    </MuiThemeProvider>
);

export default MuiThemeWrapper;

My styleguidist config:

/styleguidist.config.js

const path = require('path');
module.exports = {
    components: "src/components/**/layout.{js,jsx,ts,tsx}",
    propsParser: require('react-docgen-typescript').withCustomConfig(
        './tsconfig.json'
      ).parse,
    serverPort: 6161,
    styleguideComponents: {
      Wrapper: path.join(__dirname, 'styleguidist/MuiThemeWrapper.jsx')
    }
};

My tsconfig follows standard CRA / MUI recommendations
https://material-ui.com/guides/typescript/

tsconfig.json

{
  "compilerOptions": {
    "noImplicitAny": true,
    "noImplicitThis": true,
    "strictNullChecks": true,
    "baseUrl": "src",
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "checkJs": false,
    "pretty": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react"
  },
  "exclude": [
    "node_modules"
  ],
  "types": [
    "./node_modules/@types/"
  ]
}

I don't have custom webpack/babel configuration setup cause I don't know how and not messing up the TS transpilation. Maybe this is what's missing in order for RSG to work...? Or is the error with rsg-components/ReactExample/ReactExample.js a bug?

like image 732
olefrank Avatar asked Sep 17 '19 14:09

olefrank


1 Answers

I got it working and hopefully other people might find this useful too, as there are few and only non-typescript example configs to be found so far.

Things to note for my setup:

  • react mui app is in src/, components are in src/components/, using a modules: webpack configuration to look into src/ for absolute imports.
  • app has been created using the react app creator.
  • tsconfig.json autocreated (overriding noEmit later specifically for the "ts-loader" only, see below); there's one important exception/addition shown below.
  • no separate webpack and babel configurations, included in styleguide.config.js.

tsconfig.json is boilerplate, except for this gem paths:'rsg-components/*' here, which needs to be added manually -- it is hidden in the React Styleguidist's Cookbook. Without it, we need to resort to alias definitions in the webpack configuration, including for replacing wrapper! With the correct paths definition in tsconfig.json things finally start to correctly fall into place.

{
  "compilerOptions": {
    "paths": {
      "rsg-components/*": [
        "node_modules/react-styleguidist/lib/client/rsg-components/*"
      ]
    }
  }
}

styleguide.config.js lives in the project's top-level directory:

const path = require('path')

module.exports = {
    components: [
        'src/components/**/*.{ts,tsx}',
        'src/models/**/*.ts',
    ],
    ignore: [
        'src/**/index.{ts,tsx}',
    ],
    // We need to override how to decide on what an example file is, in order
    // to remove default which tries to document undocumented components. Ugh.
    // So, only document components for which we also have an explicit
    // documentation file named the same as the component file, but ending in
    // ".md" instead.
    getExampleFilename: (cpath) => {
        return cpath.replace(/\.(tsx?)$/, '.md')
    },
    // Show import commands without the component filename extension and only
    // for the module; also remove the first "src/" path component.
    getComponentPathLine: (cpath) => {
        const cname = ['.tsx', '.ts'].reduce((name, ext) => path.basename(name, ext), cpath)
        const cdir = path.dirname(cpath).replace(/^src\//, '')
        return `import { ${cname} } from ${cdir}`
    },
    // How uncivilized: do not list components lacking an example.
    skipComponentsWithoutExample: true,
    // Always expand the props and methods of components.
    usageMode: 'expand',
    // Support rendering prop types of typescript components.
    propsParser: require('react-docgen-typescript').withCustomConfig(
        './tsconfig.json',
        {
            "compilerOptions": { "noEmit": false },
        }
    ).parse,
    // Replace the standard wrapper for example component usage code with our
    // own wrapper which brings in the Material UI theme.
    styleguideComponents: {
        Wrapper: path.join(__dirname, 'styleguidist/MuiThemeWrapper.tsx')
    },
    // Tell webpack what to look for and where and how to load it.
    webpackConfig: {
        resolve: {
            extensions: ['.tsx', '.ts', '.js'],
            // https://webpack.js.org/configuration/resolve/#resolvemodules;
            // we're allowing absolute imports to be satisfied from the src/
            // directory.
            modules: [
                path.resolve(__dirname, 'src/'), 
                'node_modules'
            ],
            alias: {
                // Could also be covered by a modules clause, but we are
                // sticking with an alias instead to cover only exactly
                // absolute "styleguidist/..." imports.
                'styleguidist': path.join(__dirname, 'styleguidist'),
            }
        },
        module: {
            rules: [
                {
                    test: /\.tsx?$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                presets: [
                                    "@babel/preset-env",
                                    "@babel/react",
                                ]
                            },
                        },
                        {
                            loader: 'ts-loader',
                            options: {
                                // Important! Avoids "Error: TypeScript emitted no output for..." errors
                                compilerOptions: {
                                    noEmit: false,
                                },
                            },
                        },
                    ],
                },
                {
                    test: /\.css$/,
                    loader: 'style-loader!css-loader?modules',
                },
                {
                    test: /\.svg$/,
                    loader: 'url-loader',
                },
                {
                    test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
                    use: [
                        {
                            loader: 'url-loader',
                            options: {
                                name: '[name].[ext]',
                                outputPath: 'fonts/',
                            }
                        },
                    ]
                }
            ]
        },
    }
}
  • covering styleguidist/ absolute imports could be covered with the modules: clause, but in this case I opted for an alias instead. You might judge this differently :)
  • I'm setting a rather restrictive regime of documenting only components with examples; get rid of it if you don't want that.
  • The difficulties are in configuring webpack with the correct loaders; I've enabled typescript, TSX and JSX, as well as pulling in font resources.
  • Important: wrapper is in styleguidist/MuiThemeWrapper.tsx.

And that's my wrapper styleguidist/MuiThemeWrapper.tsx:

(for MUI v5, please see the answer to MUI v5 + React styleguidist + ScopedCSSBaseline + createTheme styleOverrides: body fontSize change not working)

import React from 'react'

import "fontsource-roboto/400.css"
import "fontsource-roboto/500.css"
import "fontsource-roboto/700.css"
import "fontsource-roboto-mono/400.css"

import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'
import CssBaseline from '@material-ui/core/CssBaseline'

const muiTheme = createMuiTheme({})

const MuiThemeWrapper = ({children}) => (
    <ThemeProvider theme={muiTheme}>
        <CssBaseline />
        {children}
    </ThemeProvider>
)

export default MuiThemeWrapper
  • explicitly pulling in the Roboto fonts I need in my react+Material UI project.
  • applying the CSS baseline definition, as otherwise the font-familys won't be correctly defined.
like image 121
TheDiveO Avatar answered Oct 15 '22 17:10

TheDiveO