Vue3 + Typescript + pnpm + rollup/gulp 工程化搭建组件库(三)

4,594 阅读6分钟

工具库打包流程

本章主要是打包,把组件库进行打包,使用gulp来控制打包流程,采用rollup打包(由于操作过程比较繁琐,将会省略一些步骤,不会太细化了,但是会讲清楚,代码中会有注释,相关的内容可以去看官方文档)

首先来安装工具gulp,在根目录下执行命令:pnpm install gulp @types/gulp sucrase -w -D

在根目录下的package.json中增加build脚本:gulp -f build/gulpfile.ts,指定配置文件为build目录下的gulpfile.ts文件:

image.png

根目录下增加build目录,结构如下:paths.ts存放定义的公共path,index.ts放一些公共方法

image.png

gulpfile.ts代码如下:

// 打包方式:串行(series)  并行(parallel)
import { series, parallel } from "gulp";
import { withTaskName,run } from "./utils"

// gulp 不叫打包,做代码转化 vite

/**
 * 1. 打包样式
 * 2. 打包工具方法
 * 3. 打包所有组件
 * 4. 打包每个组件
 * 5. 生成一个组件库
 * 6. 发布组件
 */
export default series(
  withTaskName("clean", async () => run('rm -rf ./dist')),  // 删除dist目录
);

然后把withTaskName方法封装在utils/index.tsindex.ts的代码如下:

/**
 * 子进程
 * child_process.spawn(command[, args][, options])
 * command <string> 要运行的命令。
 * args <string[]> 字符串参数列表。
 * options <Object>
 *  - cwd <string> | <URL> 子进程的当前工作目录
 *  - stdio <Array> | <string> 子进程的标准输入输出配置。'inherit':通过相应的标准输入输出流传入/传出父进程
 * - shell <boolean> | <string> 如果是 true,则在 shell 内运行 command。 在 Unix 上使用 '/bin/sh',在 Windows 上使用    process.env.ComSpec。 可以将不同的 shell 指定为字符串。 请参阅 shell 的要求和默认的 Windows shell。 默认值: false (没有 shell)x
 */
import { spawn } from "child_process";
import { projectRoot } from "./paths";

// 自定义每个task的name
export const withTaskName = <T>(name: string, fn: T) =>
  Object.assign(fn, { displayName: name });


// 在node中开启一个子进程来运行脚本
export const run = async (command: string) => {
  return new Promise((resolve) => {
    // 将命令分割 例如:rm -rf 分割为['rm', '-rf'],进行解构[cmd,...args]
    const [cmd, ...args] = command.split(" ");
    const app = spawn(cmd, args, {
        cwd:projectRoot,
        stdio:"inherit",
        shell:true  // 默认情况下 linux才支持 rm -rf  windows安装git bash
    });
    // 在进程已结束并且子进程的标准输入输出流已关闭之后,则触发 'close' 事件
    app.on('close',resolve)  // 
  });
};

接下来测试一下,在根目录新建一个dist目录,执行npm run build,看看有没有删除掉,选择vscode下的命令行git bash,不然在windows下会提示rm不是一个命令

image.png 执行完后看到dist目录已经被成功删掉了

image.png 我们要做的是把packages下的三个包components,theme-chalk,utils分别打包,最终生成到根目录下的dist目录,所以我们要在每个目录下的package.json下,增加build脚本,执行build脚本时,会在当前目录下找gulp配置文件gulpfile.ts

image.png

首先先处理样式文件,在theme-chalk下增加gulp配置文件gulpfile.ts

# 安装相关依赖
pnpm install gulp-sass @types/gulp-sass @types/sass gulp-autoprefixer @types/gulp-autoprefixer @types/gulp-clean-css gulp-clean-css -w -D
/**
 * 打包样式
 * 安装相关依赖
 * pnpm install gulp-sass @types/gulp-sass @types/sass gulp-autoprefixer @types/gulp-autoprefixer @types/gulp-clean-css gulp-clean-css -w -D
 * gulp-autoprefixer:添加样式前缀  gulp-clean-css:压缩css
 */
import gulpSass from "gulp-sass";
import dartSass from "sass";
import autoprefixer from "gulp-autoprefixer";
import cleanCss from "gulp-clean-css";
import path from "path";

/**
 * gulp是类似一个管道的方式执行,从入口开始到出口,中间一步步执行
 */
import { series, src, dest } from "gulp";

/**
 * 对sass文件做处理
 */
function compile() {
  const sass = gulpSass(dartSass);
  // 从src下的scss文件开始=>编译成css=>添加前缀=>压缩=>最终输出到当前目录下dist下的css目录
  return src(path.resolve(__dirname, "./src/*.scss"))
    .pipe(sass.sync())
    .pipe(autoprefixer())
    .pipe(cleanCss())
    .pipe(dest("./dist/css"));
}

/**
 * 处理font文件
 */
function copyfont() {
    // 从src下单fonts文件夹下的所有文件开始=>压缩=>最终输出到当前目录下dist下的font目录
    return src(path.resolve(__dirname,'./src/fonts/**')).pipe(cleanCss()).pipe(dest("./dist/fonts"))
}

/**
 * 把打包好的css输出到根目录的dist
 */
function copyfullstyle(){
    const rootDistPath = path.resolve(__dirname,'../../dist/theme-chalk')
    return src(path.resolve(__dirname,'./dist/**')).pipe(dest(rootDistPath))
}

export default series(compile, copyfont,copyfullstyle);

处理好样式文件后,我们在当前目录下生成打包目录dist,并把dist拷贝到根目录下的dist,更改名称为theme-chalk,现在来测试一下,在theme-chalk下执行npm run build,依次执行了我们的三个处理方法,compilecopyfontcopyfullstyle,结果如下:

image.png 生成的目录如下:

image.png

样式文件处理好了,现在我们处理utils,在utils下增加gulp配置文件gulpfile.ts,并且安装依赖gulp-typescript来处理ts文件

import { buildPackages } from "../../build/packages"


export default buildPackages(__dirname,'utils')
# 安装依赖
pnpm install gulp-typescript -w -D

由于处理ts的我们可能会用到多次,所以这里封装一个方法在build目录下新建文件packages.ts

然后在build/utils/paths下定义打包输出目录

image.png 然后我们要将ts分别打包为cjsesm两种模块规范,所以我们先定义一个配置文件在build/utils下新建文件config.ts,代码如下:

import path from "path";
import { outDir } from "./paths";
export const buildConfig = {
  esm: {
    module: "ESNext", // tsconfig输出的结果es6模块
    format: "esm", // 需要配置格式化化后的模块规范
    output: {
      name: "es", // 打包到dist目录下的那个目录
      path: path.resolve(outDir, "es"),
    },
    bundle: {
      path: "z-plus/es",
    },
  },
  cjs: {
    module: "CommonJS",
    format: "cjs",
    output: {
      name: "lib",
      path: path.resolve(outDir, "lib"),
    },
    bundle: {
      path: "z-plus/lib",
    },
  },
};
export type BuildConfig = typeof buildConfig;

最后在刚才定义的packages.ts中处理

// 专门打包util,指令,hook
import { series, parallel, src, dest } from "gulp";
import { buildConfig } from "./utils/config";
import path from "path";
import { outDir, projectRoot } from "./utils/paths";
import ts from "gulp-typescript";
import { withTaskName } from "./utils";

// 打包处理
export const buildPackages = (dirname: string, name: string) => {
  const tasks = Object.entries(buildConfig).map(([module, config]) => {
    const output = path.resolve(dirname, config.output.name);
    // 安装依赖gulp-typescript
    return series(
      // 处理ts文件
      withTaskName(`build${dirname}`, () => {
        const tsConfig = path.resolve(projectRoot, "tsconfig.json"); // ts配置文件路径
        const inputs = ["**/*.ts", "!gulpfile.ts", "!node_modules"];
        return src(inputs)
          .pipe(
            ts.createProject(tsConfig, {
              declaration: true, // 需要生成声明文件
              strict: false, // 关闭严格模式
              module: config.module,
            })()
          )
          .pipe(dest(output));
      }),
      withTaskName(`copy:${dirname}`,() => {
          // 将打包好的文件放到 es=>utils 和 lib => utils
          // 将utils模块拷贝到dist目录下的es和lib目录
          return src(`${output}/**`).pipe(dest(path.resolve(outDir,config.output.name,name)))
      })
    );
  });

  return parallel(...tasks);
};

处理好后,测试一下,在utils下执行npm run build,结果如下:

image.png 可以看到在utils下生成了两个目录eslib,并且copy到了根目录下的dist目录中,目录如下:

image.png

现在处理好了theme-chalkutils,但是打包的时候我们需要分别到每个目录下执行build脚本,太麻烦了,我想在根目录下执行build后,就让它们各自打包,然后把结果输出到我根目录下的dist目录,所以在根目录下的gulpfile.ts中增加一个task,让它帮我分别执行每个目录下的build

build/gulpfile.ts中:增加

...
withTaskName("buildPackages",() => run("pnpm run --filter ./packages --parallel build"))
...

image.png 然后我们删除根目录下的dist,在根目录下执行npm run build,测试一下:

image.png

可以看到分别帮我们执行了每个目录下的build脚本,最终输出到根目录dist

image.png

到这里,我们的theme-chalkutils的打包就处理完成了,然后我们来处理组件的打包


后续组件打包,其余代码可去gitee查看