在前端开发中,我们经常需要开发自己的 工具函数库Hooks 库 或者 组件库,并发布到 npm。这样的库通常有几个常见的需求:

  1. 提供 ES Module(ESM)版本,方便现代项目 import 使用;
  2. 提供 CommonJS(CJS)版本,兼容 Node.js 或老项目 require 使用;
  3. 提供 类型声明文件(.d.ts),方便 TypeScript 用户;
  4. 额外提供一个 UMD 包,可以让用户直接通过 <script> 从 CDN 引入,全局访问;

本文将带你基于 Gulp + TypeScript + Babel + Webpack 打造一条完整的构建流水线 🚦。


🔧 为什么选择 Gulp + Webpack

  • Gulp:擅长文件处理和任务流,适合执行「清理 → 编译 → 转换 → 输出」一系列步骤(或更多自定义复杂步骤)。
  • Webpack:擅长打包成单个文件,特别是 UMD 格式,方便浏览器直接使用。

两者结合的好处是:
👉 Gulp 做 模块构建(ESM/CJS/d.ts),保证 npm 使用;
👉 Webpack 做 UMD 打包,保证浏览器/CDN 使用。

这样,我们的库就能“一鱼多吃”,覆盖所有使用场景。


📂 项目构建需求

  1. 清理旧的构建产物(es/lib/dist/
  2. 输出 ESM 版本 → 放在 es/
  3. 输出 CJS 版本 → 放在 lib/
  4. 生成 类型声明文件 d.ts
  5. 复制 README.md 到子包目录
  6. 调用 Webpack 输出 UMD 包 → 放在 dist/

最终目录结构如下:

packages/hooks/
├── es/              # ESM 构建结果
├── lib/             # CJS 构建结果
├── dist/            # UMD 构建结果
│   └── index.umd.js 
├── README.md        # 子包说明文档
└── package.json

🛠️ 构建脚本

1. Gulp 脚本(gulpfile.js)

const gulp = require('gulp');
const del = require('del');
const babel = require('gulp-babel');
const ts = require('gulp-typescript');
const webpack = require('webpack');
const webpackConfig = require('./webpack.config.js');

// 清理目录
gulp.task('clean', async () => {
  await del(['lib/**', 'es/**', 'dist/**']);
});

// 编译 ESM
gulp.task('es', () => {
  const tsProject = ts.createProject('tsconfig.pro.json', { module: 'ESNext' });
  return tsProject.src().pipe(tsProject()).pipe(babel()).pipe(gulp.dest('es/'));
});

// 编译 CJS
gulp.task('cjs', () => {
  return gulp
    .src('es/**/*.js')
    .pipe(babel({ configFile: '../../.babelrc' }))
    .pipe(gulp.dest('lib/'));
});

// 类型声明
gulp.task('declaration', () => {
  const tsProject = ts.createProject('tsconfig.pro.json', {
    declaration: true,
    emitDeclarationOnly: true,
  });
  return tsProject.src().pipe(tsProject()).pipe(gulp.dest('es/')).pipe(gulp.dest('lib/'));
});

// 复制 README
gulp.task('copyReadme', async () => {
  await gulp.src('../../README.md').pipe(gulp.dest('../../packages/hooks'));
});

// 调用 webpack 打包 UMD
gulp.task('umd', (done) => {
  webpack(webpackConfig, (err, stats) => {
    if (err) console.error(err);
    console.log(stats?.toString({ colors: true }));
    done();
  });
});

// 默认构建任务
exports.default = gulp.series('clean', 'es', 'cjs', 'declaration', 'copyReadme', 'umd');

2. Webpack 配置(webpack.config.js)

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'index.umd.js',
    path: path.resolve(__dirname, 'dist'),
    libraryTarget: 'umd',   // 输出UMD格式
    globalObject: 'this',   // 兼容node和浏览器
    library: 'MyLibrary',   // 挂载到全局对象 window.MyLibrary
  },
  mode: 'production',
  resolve: {
    extensions: ['.ts', '.js', '.json'],
  },
  module: {
    rules: [
      {
        test: /\.(ts|js)$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
    ],
  },
  externals: {
    react: 'React', // react 作为外部依赖,不打包进来
  },
};

📦 构建产物 & 使用方式

构建产物包括三种:

  1. ESM(现代前端项目)

    import { useMyHook } from 'my-hooks/es';
    
  2. CJS(Node.js 或老项目)

    const { useMyHook } = require('my-hooks/lib');
    
  3. UMD(CDN / 浏览器脚本)

    <script src="https://cdn.xxx.com/my-hooks/dist/index.umd.js"></script>
    <script>
      const { useMyHook } = window.MyLibrary;
    </script>
    

⚡ 整体构建命令

# 编译 ESM/CJS/d.ts + 打包 UMD
gulp

这样一条命令,就完成了从 源码 → npm 包 → 浏览器包 的全流程。


✨ 总结

  • Gulp 控制任务流,适用于多产物输出(ESM / CJS / d.ts);
  • Webpack 打包成一个独立的 UMD 文件,方便 CDN 与浏览器使用;
  • 两者结合,覆盖所有使用场景,构建过程既灵活又全面。

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏或留言交流 😊

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐