Implementing Component Auto Import in React.js
Tags: react, automation, vite
Published: 2023-04-24 04:01:50

In this article, we'll explore how to automatically import React components in your codebase, without the need for manual imports in each file. This can save a lot of time and reduce the risk of human error, especially in large projects. We'll be using the **Auto Import** plugin, which can automatically find and import components based on file patterns

### Step 1: Installing Dependencies
First, we need to install the auto-import and fast-glob packages. These packages will allow us to automatically import components into our project.

```bash
npm install --save-dev unplugin-auto-import fast-glob
```

### Step 2: Configuring the Auto Import Plugin

To begin with, we need to define the directories where our components are located. In this particular case, the components and layouts directories are used. We also use the `omit` key to exclude certain strings in our component names.

For instance, consider a component located at `src/components/base/button.jsx`. We want to name this component `BaseButton` and not `SrcComponentsBaseButton`. To achieve this, we use the `omit` key to remove the `./src/components` prefix from the component name.

```js
  const directories = [
    {
      pattern: './src/components/**/*.{tsx,jsx}',
      omit: './src/components'
    },
    {
      pattern: './src/layouts/*.{tsx,jsx}',
      omit: './src/'
    }
  ]
```

Then we will use `fast-glob` package to search for files that match the patterns specified in the directories array. The `objectMode` option is set to true, so that `fast-glob` will return an array of objects with information about the matched files.

```js
  const entries = fg.sync(
    directories.map(x => x.pattern),
    {
      dot: true,
      objectMode: true
    }
  )
```

Then we will iterate over the matched files returned by the `fast-glob`. For each file, it generates an object with the component name and the import path. The component name is generated by removing the omit string from the file path, and converting the remaining path segments to PascalCase. The import path is generated by replacing the ./src string with @ to indicate that the file is part of the project's source code. Finally, the object is returned as part of an array.

```js
const imports = entries.map(entry => {
    const dirOptions = directories.find(dir => minimatch(entry.path, dir.pattern))

    const componentName = entry.path
      .replace(new RegExp(dirOptions.omit, 'gi'), '')
      .split('/')
      .filter(Boolean)
      .map(pascalCaseWithCapitals)
      .join('')

    const fromPath = entry.path
      .replace(/\.\/src/gi, '@')

    return {
      [fromPath]: [
        [removeExtension(entry.name), removeExtension(componentName)]
      ]
    }
  })
```

Finally, our imports function would look like this:
```js
function getComponentImports() {
  // Define an array of directory objects to search for components
  const directories = [
    {
      pattern: './src/components/**/*.{tsx,jsx}',
      omit: './src/components'
    },
    {
      pattern: './src/layouts/*.{tsx,jsx}',
      omit: './src/'
    }
  ];

  // Search for component files in directories and return their file paths as an array
  const entries = fg.sync(
    directories.map(x => x.pattern),
    {
      dot: true,
      objectMode: true
    }
  );

  // For each component file, extract the component name and fromPath
  const imports = entries.map(entry => {
    // Find the directory object that matches the current file path
    const dirOptions = directories.find(dir => minimatch(entry.path, dir.pattern));

    // Extract the component name from the file path using the directory's omit string
    const componentName = entry.path
      .replace(new RegExp(dirOptions.omit, 'gi'), '')
      .split('/')
      .filter(Boolean)
      .map(pascalCaseWithCapitals)
      .join('');

    // Replace './src' with '@' in the fromPath
    const fromPath = entry.path
      .replace(/\.\/src/gi, '@');

    // Return an object with the fromPath and an array containing the component name and its variable name
    return {
      [fromPath]: [
        [removeExtension(entry.name), removeExtension(componentName)]
      ]
    }
  });

  return imports;
}
```

### Step 3: Using `unplugin-auto-import`
We need to add the plugin to the plugins array in the defineConfig object in vite.config.js. We also need to specify the options for the plugin.

```js
AutoImport({
  // Filepath to generate corresponding .d.ts file.
  // Defaults to './auto-imports.d.ts' when `typescript` is installed locally.
  // Set `false` to disable.
  dts: './auto-imports.d.ts',
   // Generate corresponding .eslintrc-auto-import.json file.
  // eslint globals Docs - https://eslint.org/docs/user-guide/configuring/language-options#specifying-globals
  eslintrc: {
    enabled: true,
    filepath: './.eslintrc-auto-import.json',
    globalsPropValue: true
  },
  // targets to transform
  include: [
    /\.[tj]sx?$/ // .ts, .tsx, .js, .jsx
  ],
  // global imports to register
  imports: [
    // Get component imports
    ...getComponentImports(),
    // Import React
    'react',
    // Import React Router
    'react-router'
  ]
})
```

### Step 4: We add it to our vite.config.js
```js
export default defineConfig({
  plugins: [
    AutoImport({
      dts: './auto-imports.d.ts',
      defaultExportByFilename: false,
      eslintrc: {
        enabled: true,
        filepath: './.eslintrc-auto-import.json',
        globalsPropValue: true
      },
      include: [
        /\.[tj]sx?$/ // .ts, .tsx, .js, .jsx
      ],
      dirs: [
        './src/hooks'
      ],
      imports: [
        // Get component imports
        ...getComponentImports(),
        // Import React
        'react',
        // Import React Router
        'react-router'
      ]
    })
  ],

  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
});
```

Hopefully it makes sense and help you guys. Here's the full code

```js
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'url'
import react from '@vitejs/plugin-react'
import AutoImport from 'unplugin-auto-import/vite'

import fg from 'fast-glob'
import { minimatch } from 'minimatch'
import path from 'path'

function pascalCaseWithCapitals (str) {
  return str
    .split('/')
    .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1))
    .join('')
}

function removeExtension (str: string) {
  return path.basename(str, path.extname(str))
}

function getComponentImports () {
  const directories = [
    {
      pattern: './src/components/**/*.{tsx,jsx}',
      omit: './src/components'
    },
    {
      pattern: './src/layouts/*.{tsx,jsx}',
      omit: './src/'
    }
  ]

  const entries = fg.sync(
    directories.map(x => x.pattern),
    {
      dot: true,
      objectMode: true
    }
  )

  return entries.map(entry => {
    const dirOptions = directories.find(dir => minimatch(entry.path, dir.pattern))

    const componentName = entry.path
      .replace(new RegExp(dirOptions.omit, 'gi'), '')
      .split('/')
      .filter(Boolean)
      .map(pascalCaseWithCapitals)
      .join('')

    const fromPath = entry.path
      .replace(/\.\/src/gi, '@')

    return {
      [fromPath]: [
        [removeExtension(entry.name), removeExtension(componentName)]
      ]
    }
  })
}

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    AutoImport({
      dts: './auto-imports.d.ts',
      defaultExportByFilename: false,
      eslintrc: {
        enabled: true,
        filepath: './.eslintrc-auto-import.json',
        globalsPropValue: true
      },
      include: [
        /\.[tj]sx?$/ // .ts, .tsx, .js, .jsx
      ],
      dirs: [
        './src/hooks'
      ],
      imports: [
        // @ts-ignore
        ...getComponentImports(),
        // @ts-ignore
        'react',
        // @ts-ignore
        'react-router'
      ]
    })
  ],

  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})
```