DEV Community

Cover image for Implementing Component Auto Import in React.js
KarmaBlackshaw
KarmaBlackshaw

Posted on • Edited on

Implementing Component Auto Import in React.js

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.

npm install --save-dev unplugin-auto-import fast-glob
Enter fullscreen mode Exit fullscreen mode

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.

  const directories = [
    {
      pattern: './src/components/**/*.{tsx,jsx}',
      omit: './src/components'
    },
    {
      pattern: './src/layouts/*.{tsx,jsx}',
      omit: './src/'
    }
  ]
Enter fullscreen mode Exit fullscreen mode

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.

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

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.

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)]
      ]
    }
  })
Enter fullscreen mode Exit fullscreen mode

Finally, our imports function would look like this:

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;
}
Enter fullscreen mode Exit fullscreen mode

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.

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'
  ]
})
Enter fullscreen mode Exit fullscreen mode

Step 4: We add it to our vite.config.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))
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

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

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))
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Top comments (0)