Building Vue3 Component Library from Scratch #7 Using Gulp to Implement On-Demand Import
Tags: gulp, vue
Published: 2024-05-28 16:11:10

When we use Vite to package the component library, Vite will package all style files into a single file. This means that we have to import all style files in bulk each time, which prevents on-demand loading. Therefore, during packaging, we can configure Vite to not package the style files, and instead use Gulp to package the style files. This article will introduce how to use Gulp to package style files and how to load style files on demand.

## Automatic On-Demand Import Plugin

Nowadays, many component libraries solve on-demand imports with the help of plugins. For example, ElementPlus uses unplugin-vue-components and unplugin-auto-import. These two plugins can achieve on-demand imports.

```javascript
import { Button } from "stellarnovaui";

//means to be
import "stellarnovaui/es/src/button/style/index.css";
import "stellarnovaui/es/src/button/index.mjs";
```

Thus, on-demand imports can be achieved. We will not go into detail about the use of these plugins here, as this article does not focus on that topic. If you are interested, you can directly look up the usage of [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components).

## Delete Package Files

As we all know, it's necessary to delete the previously packaged files before packaging again. So, we need to write a delete function first. Before doing this, we will create a new script folder in the components directory to store our script-related content. The contents of the build folder under the script directory will be the packaging-related content introduced in this article.

Create paths.ts file in script/utils to maintain the paths of the component library. Remember to install the necessary dependencies first.

```bash
pnpm add @types/node -D -w
```

```javascript
import { resolve } from "path";

export const componentPath = resolve(__dirname, "../../");

export const pkgPath = resolve(__dirname, "../../../");
```

The function to delete the packaging directory can be placed in build/utils/delpath.ts. Note that since the packaged stellarnovaui folder is the one we will ultimately publish, we need to retain the package.json and README.md files.

```javascript
import fs from "fs";
import { resolve } from "path";
import { pkgPath } from "./paths";
//keep files
const stayFile = ["package.json", "README.md"];

const delPath = async (path: string) => {
  let files: string[] = [];

  if (fs.existsSync(path)) {
    files = fs.readdirSync(path);

    files.forEach(async (file) => {
      let curPath = resolve(path, file);

      if (fs.statSync(curPath).isDirectory()) {
        // recurse
        if (file != "node_modules") await delPath(curPath);
      } else {
        // delete file
        if (!stayFile.includes(file)) {
          fs.unlinkSync(curPath);
        }
      }
    });

    if (path != `${pkgPath}/easyest`) fs.rmdirSync(path);
  }
};
export default delPath;
```

## Use Gulp

We need to use TypeScript and the new ES6 syntax, but Gulp does not support these by default. Therefore, we need to install some dependencies to enable Gulp to support them. Sucrase allows us to execute Gulp using the latest syntax and support TypeScript.

```bash
pnpm i gulp @types/gulp sucrase -D -w
```

Execute the deletion process in build/index.ts.

```javascript
import delPath from "../utils/delpath";
import { series, parallel } from "gulp";
import { pkgPath } from "../utils/paths";

export const removeDist = () => {
  return delPath(`${pkgPath}/stellarnovaui`);
};

export default series(async () => removeDist());
```

Add scripts to easyest/package.json in the root directory.

```json
  "scripts": {
    "build:stellarnovaui": "gulp -f packages/components/script/build/index.ts"
  },
```

run `pnpm run build:stellarnovaui`, You can see files in `stellarnovaui` folder is deleted.

Now we can start to build styles.

## Use Gulp to Build Styles

Since we are using less to write styles, we need to install 'gulp-less'. At the same time, we should install the 'gulp-autoprefixer' plugin for automatically adding CSS prefixes, as well as their type files.

```bash
pnpm add gulp-less @types/gulp-less gulp-autoprefixer @types/gulp-autoprefixer -D -w
```

Then write a function to package the styles. Here we use the 'dest' and 'src' functions of gulp. If you don't know what these mean, you can refer to the previous article introducing gulp.

```javascript
export const buildStyle = () => {
  return src(`${componentPath}/src/**/style/**.less`)
    .pipe(less())
    .pipe(autoprefixer())
    .pipe(dest(`${pkgPath}/stellarnovaui/lib/src`))
    .pipe(dest(`${pkgPath}/stellarnovaui/es/src`));
};
```

## Build Components

Finally, write a function to package the components. Here, we need to write a utility function to execute commands, in 'utils/run.ts'.

```javascript
import { spawn } from 'child_process';

export default async (command: string, path: string) => {
  // cmd' represents the command, and 'args' represents the parameters.
  // For example, in 'rm -rf', 'rm' is the command, and '-rf' are the parameters.
  const [cmd, ...args] = command.split(' ');
  return new Promise((resolve) => {
    const app = spawn(cmd, args, {
      cwd: path, //The path where the command is executed.
      stdio: 'inherit', // Output is shared with the parent process.
      shell: true // Mac does not need to be enabled, but on Windows, Git Bash needs to be enabled for support.
    });
    // After execution is complete, close and resolve.
    app.on('close', resolve);
  });
};
```

Then export the 'run' function.

```javascript
export const buildComponent = async () => {
  run("pnpm run build", componentPath);
};
```

Because packaging styles and packaging components can be done in parallel, the final `build/index.ts` is as follows.

```javascript
import delPath from '../utils/delpath';
import { series, parallel, src, dest } from 'gulp';
import { pkgPath, componentPath } from '../utils/paths';
import less from 'gulp-less';
import autoprefixer from 'gulp-autoprefixer';
import run from '../utils/run';

export const removeDist = () => {
  return delPath(`${pkgPath}/stellarnovaui`);
};

export const buildStyle = () => {
  return src(`${componentPath}/src/**/style/**.less`)
    .pipe(less())
    .pipe(autoprefixer())
    .pipe(dest(`${pkgPath}/stellarnovaui/lib/src`))
    .pipe(dest(`${pkgPath}/stellarnovaui/es/src`));
};

export const buildComponent = async () => {
  run('pnpm run build', componentPath);
};
export default series(
  async () => removeDist(),
  parallel(
    async () => buildStyle(),
    async () => buildComponent()
  )
);
```

Finally, ignore the less files when packaging with Vite, components/vite.config.ts

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2e07i8yzoysi0ygaqpyc.png)

In order to see the result after packaging, we can write a simple Icon component, the directory is as follows.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a0hx95h5vcpzvksl7m6k.png)

Since Vite ignores the packaging of .less files during the build process, the .less files are automatically skipped in the packaged output. However, since we have already packaged the .less files into .css files, we need to replace the .less files in the code with .css."

Add a new content in the plugins of components/vite.config.ts.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n6bvm1913ejkbqwyhc8y.png)

Now execute 'pnpm run build:stellarnovaui', and then look at the file imports after packaging，.less has been replaced with .css, and the packaging is complete.

The next step is to publish it. The following article will introduce how to publish a component library.

The final source code: https://github.com/markliu2013/StellarNovaUI

For more information about build your component library, checkout this article, https://dev.to/markliu2013/how-to-build-component-libraries-8k2