微信原生小程序的一次gulp构建

发布于:2025-08-19 ⋅ 阅读:(12) ⋅ 点赞:(0)

看到标题大家应该很奇怪,原生小程序,直接放在微信开发者工具里面,开发就行了呗,做啥还要再构建一下呢?其实主要原因是想配置通过命令行直接切换环境,根据环境量,配置不同的配置文件,不同的页面入口,不同的tabbar等等,而不是每次手动切换手动修改。这里介绍了如何用gulp一次构建,直接用微信开发者工具就可以预览,不需要再手动切换配置,不需要手动安装依赖、手动npm构建的方式。

一、项目目录介绍

源码目录

├── build
│ ├── appConfig
│ ├── npm
│ ├── pageUrlConfig
│ └── env.config.json
├── dist
├── miniprogram
├── node_modules
├── typings
├── .gitignore
├── Gulpfile.js
├── package-lock.json
├── package.json
├── private.wx.config160dd2fa…
├── project.config.json
├── project.private.config.json
└── tsconfig.json

  • build:用于存放构建相关的配置文件,是前端工程化的重要部分,里面的子目录和文件会在项目构建过程中发挥作用,以生成符合小程序要求的代码或资源。
    • appConfig:存放微信小程序app.json文件,这里可以根据环境量选择不同的app.json。
    • npm:存放 微信小程序用到的package.json文件,里面不包含构建用的包,只包含小程序代码用到的文件。
    • pageUrlConfig:这个目录用于存放小程序路由map文件{key:路径},这个文件不是必须的,但有了它,便于管理页面跳转地址。
    • env.config.json:环境配置文件,可根据不同的运行环境(如开发环境、生产环境)配置不同的参数,像接口地址、调试开关等,实现项目在不同环境下的灵活切换。
  • dist:构建完成后生成的最终输出目录,里面包含了可直接用于微信小程序运行的代码、资源等文件,是项目发布或调试的目标产物存放地。
  • miniprogram:微信小程序的核心源码目录,小程序的页面、组件、逻辑等主要代码都放置在此目录下,是小程序功能实现的关键所在。
  • node_modules:通过 npm 安装的项目依赖包都存放在这里,这些依赖为项目提供了各种功能支持,比如第三方库、工具等。
  • typings:用于存放 TypeScript 的类型定义文件,当项目使用 TypeScript 开发时,这些文件能为代码提供类型检查和智能提示,提升开发体验和代码质量。
  • .gitignore:Git 版本控制的忽略文件配置,指定了哪些文件或目录不需要被 Git 跟踪和提交,通常包括编译生成的文件、依赖目录等,避免将不必要的文件纳入版本管理。
  • Gulpfile.js:Gulp 任务配置文件,Gulp 是前端自动化构建工具,通过此文件可以定义一系列构建任务,如代码压缩、文件复制、资源编译等,实现项目的自动化构建流程。
  • package.json:项目的配置文件,包含了项目的基本信息(如名称、版本)、依赖包列表、脚本命令等,是 npm 管理项目的核心文件。
  • private.wx.config160dd2fa…:微信小程序的私钥文件,这个文件不一定要在项目里,可以存放在你觉得安全的地方,文件来自于微信公众平台–开发管理–小程序代码上传密钥, 这个miniprogram初始化的时候必须有。
  • project.config.json:微信小程序项目的配置文件,用于配置小程序的项目名称、appid、编译设置等项目级别的信息,微信开发者工具会根据此文件来识别和管理项目。
  • project.private.config.json:微信小程序的私有项目配置文件,与 project.config.json 类似,但更侧重于存放一些不希望公开的私有配置内容。

构建目录

在这里插入图片描述

二、目录文件介绍

2.1 build/appConfig

  1. index.json 配置索引文件
{
    "customer1": "customer1_app.json" // 环境:配置文件名
}
  1. default_app.json 默认的小程序app.json文件配置
  2. customer1_app.json 环境量customer1对应的app.json配置文件,当环境量是customer1时,选取此文件作为app.json文件

2.2 build/pageUrlConfig

  1. index.json配置索引文件
{
    "customer1": "customer1_pageUrl.ts" // 环境:配置文件名
}
  1. default_pageUrl.ts 默认的小程序pageUrl.ts文件配置
export default {
    HOME: '/pages/index/index',
    LOG: '/pages/logs/logs'
}
  1. customer1_pageUrl.ts环境量customer1对应的pageUrl.ts配置文件,当环境量是customer1时,选取此文件作为pageUrl.ts文件

2.3 build/env.config.json

环境量配置文件

{
    "PAGE_TITLE": "测试项目",
    "env": {
        "customer1": {
            "dev": {
                "API_URL": "http://test.com.cn/customer1"
            },
            "prod": {
                "API_URL": "http://prod.com.cn/customer1"
            }
        },
        "customer2": {
            "dev": {
                "API_URL": "http://test.com.cn/customer2"
            },
            "prod": {
                "API_URL": "http://prod.com.cn/customer2"
            }
        }
    }
}

环境量customer1对应的dev环境量就是

{
	"API_URL": "http://test.com.cn/customer1"
}

2.4 build/npm/package.json

{
  "name": "miniprogram-ts-less-quickstart",
  "version": "1.0.0",
  "description": "",
  "scripts": {},
  "keywords": [],
  "author": "",
  "license": "",
  "dependencies": {
    "dayjs": "^1.11.13" // 小程序需要的依赖
  },
  "devDependencies": {
    "miniprogram-api-typings": "^2.8.3-1"
  }
}

2.5 .gitignore

node_modules
dist

2.6 package.json

{
  "name": "miniprogram-ts-less-quickstart",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "clean": "gulp clean --env=dev --name",
    "dev": "gulp dev --env=dev --name", // 这里可以 npm run dev customer1
    "build": "gulp build --env=prod --name"
  },
  "keywords": [],
  "author": "",
  "license": "",
  "dependencies": {},
  "devDependencies": { // 构建需要内容
    "@babel/core": "^7.28.0",
    "@babel/preset-env": "^7.28.0",
    "chalk": "^4.1.2",
    "del": "^6.1.1",
    "eslint": "^8.57.1",
    "gulp": "^4.0.0",
    "gulp-babel": "^8.0.0",
    "gulp-changed": "^4.0.1",
    "gulp-clean-css": "^4.0.0",
    "gulp-cli": "^2.3.0",
    "gulp-data": "^1.3.1",
    "gulp-debug": "^4.0.0",
    "gulp-ignore": "^2.0.2",
    "gulp-imagemin": "^7.1.0",
    "gulp-rename": "^1.4.0",
    "gulp-sass": "^6.0.1",
    "gulp-strip-debug": "^4.0.0",
    "gulp-tap": "^2.0.0",
    "gulp-typescript": "^6.0.0-alpha.1",
    "gulp-wechat-weapp-src-alisa": "^1.0.4",
    "imagemin-mozjpeg": "^10.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^11.0.1",
    "miniprogram-api-typings": "^2.8.3-1",
    "miniprogram-ci": "^2.1.14",
    "sass": "^1.90.0",
    "typescript": "^5.9.2",
    "yargs": "^17.7.2"
  }
}

三、构建文件Gulpfile介绍

// 从 gulp 中解构出常用的任务流控制和文件操作方法
const { series, parallel, src, dest, watch } = require('gulp');
// 用于删除文件或目录的库
const del = require('del');
// 文件系统操作模块
const fs = require('fs');
// 路径处理模块
const path = require('path');
// 用于美化控制台输出的库
const chalk = require('chalk');
// 用于解析命令行参数的库
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
// sass 相关编译库
const sass = require('sass');
const gulpSass = require('gulp-sass')(sass);
// 用于压缩 CSS 的库
const cleanCss = require('gulp-clean-css');
// babel 相关,用于 JavaScript 代码转译
const babel = require('gulp-babel');
// 用于移除代码中的调试语句(如 console、debugger)
const stripDebug = require('gulp-strip-debug');
// 用于重命名文件
const rename = require('gulp-rename');
// 用于只处理变更文件,提高构建效率
const changed = require('gulp-changed');
// 微信小程序源码别名处理插件
const alias = require('gulp-wechat-weapp-src-alisa');
// 用于在流中对文件进行自定义操作
const tap = require('gulp-tap');
// 用于执行 shell 命令
const { execSync } = require('child_process');
// 用于图片压缩的库
const imagemin = require('gulp-imagemin');
// 声明图片压缩相关的 ES 模块变量,后续动态导入
let imageminMozjpeg;
let imageminOptipng;
let imageminSvgo;

// 异步加载图片压缩相关的 ES 模块
async function loadImageMinPlugins() {
  imageminMozjpeg = (await import('imagemin-mozjpeg')).default;
  imageminOptipng = (await import('imagemin-optipng')).default;
  imageminSvgo = (await import('imagemin-svgo')).default;
}

// ============================ 1. 配置区(集中管理路径、别名等) ============================
const paths = {
  src: './miniprogram', // 小程序源码目录
  dist: './dist', // 构建输出根目录
  npmDist: './dist/miniprogram_npm', // 小程序 npm 构建输出目录
  distSrc: './dist/miniprogram', // 构建后小程序源码输出目录
  envConfig: './build/env.config.json', // 环境配置文件路径
  appJsonConfig: './build/appConfig', // 小程序 app.json 相关配置目录
  pageUrlConfig: './build/pageUrlConfig', // 页面 URL 配置目录
  configOutput: './dist/miniprogram/utils', // 配置文件输出目录
  imageIndexOutput: './dist/miniprogram/images/index.ts', // 图片映射文件输出路径
  buildNpmDir: './build/npm', // build/npm 目录,用于 npm 相关构建
  patterns: {
    npmPackage: './build/npm/package.json', // npm 包配置文件路径
    images: ['./miniprogram/**/*.{png,jpg,jpeg,gif,ico,svg}'], // 图片文件匹配模式
    wxml: ['./miniprogram/**/*.wxml'], // wxml 文件匹配模式
    wxss: ['./miniprogram/**/*.wxss'], // wxss 文件匹配模式
    scss: ['./miniprogram/**/*.scss'], // scss 文件匹配模式
    ts: ['./miniprogram/**/*.ts'], // TypeScript 文件匹配模式
    wxs: ['./miniprogram/**/*.wxs'], // wxs 文件匹配模式
    json: ['./miniprogram/**/*.json'], // json 文件匹配模式
    typings: ['./typings/', './typings/**'], // 类型定义文件匹配模式
    projectConfig: ['project.config.json', 'project.private.config.json', 'tsconfig.json'], // 项目配置文件列表
  },
};

// 小程序源码别名配置,用于简化模块引用路径
const aliasConfig = {
  '@Utils': path.join(paths.src, 'utils'),
  '@pages': path.join(paths.src, 'pages'),
  '@images': path.join(paths.src, 'images'),
  '@typings': './typings',
};

// ============================ 2. 工具函数区(可复用逻辑) ============================
/** 解析命令行参数(env、name),获取环境类型和环境标识 */
const getParams = () => {
  return yargs(hideBin(process.argv))
    .option('env', { alias: 'e', default: 'dev', describe: '环境类型(dev/prod)' })
    .option('name', { alias: 'n', required: true, describe: '环境标识(如customer1)' })
    .parse();
};

/** 日志工具(美化输出),封装不同类型的日志输出方法 */
const logger = {
  info: (msg) => console.log(chalk.blue(`[INFO] ${msg}`)),
  success: (msg) => console.log(chalk.green(`[SUCCESS] ${msg}`)),
  error: (msg) => console.log(chalk.red(`[ERROR] ${msg}`)),
  warn: (msg) => console.log(chalk.yellow(`[WARN] ${msg}`)),
  task: (name) => console.log(chalk.cyan(`[TASK] 开始执行:${name}`)),
};

/** 创建带别名+缓存的公共流,返回一个包含 src 方法的对象,方便后续文件处理流的创建 */
const createCommonPipe = (destPath, ext = '') => {
  return {
    src: (globPath, options) => {
      return src(globPath, options)
        .pipe(alias(aliasConfig)) // 应用别名配置
        .pipe(changed(destPath, ext ? { extension: ext } : {})); // 只处理变更过的文件
    }
  };
};

/** 检查文件是否存在(封装错误处理),返回文件是否存在的布尔值 */
const fileExists = (filePath) => {
  try {
    return fs.existsSync(filePath);
  } catch (err) {
    logger.error(`检查文件失败:${filePath},错误:${err.message}`);
    return false;
  }
};

// ============================ 3. 环境配置处理(独立逻辑块) ============================
/** 解析环境配置(支持多环境多客户),将配置转换为便于使用的格式并返回 */
const parseConfig = (config) => {
  const { env, name } = getParams();
  if (!config.env?.[name]?.[env]) {
    logger.error(`环境配置缺失:env.${name}.${env}`);
    process.exit(1);
  }
  return Object.entries(config).reduce((prev, [key, value]) => {
    if (key === 'env') {
      Object.entries(config[key][name][env]).forEach(([k, v]) => {
        prev[k.toUpperCase()] = v;
      });
    } else {
      prev[key.toUpperCase()] = value;
    }
    return prev;
  }, { ENV: env, CUSTOMER_ENV: name });
};

/** 生成环境配置文件(如 config.ts),从环境配置文件读取并处理后生成对应的 TypeScript 配置文件 */
const generateEnvConfig = () => {
  logger.task('generateEnvConfig');
  if (!fileExists(paths.envConfig)) {
    logger.error(`环境配置文件不存在:${paths.envConfig}`);
    process.exit(1);
  }
  return src(paths.envConfig)
    .pipe(tap((file) => {
      try {
        const conf = JSON.parse(file.contents.toString());
        const config = parseConfig(conf);
        file.contents = Buffer.from(
          '// 自动生成,请勿修改\n' +
          `export default ${JSON.stringify(config, null, 2)};`
        );
      } catch (err) {
        logger.error(`解析环境配置失败:${err.message}`);
        process.exit(1);
      }
    }))
    .pipe(rename({ basename: 'config', extname: '.ts' }))
    .pipe(dest(paths.configOutput))
    .on('end', () => logger.success('环境配置文件生成完成'));
};

/** 从配置文件提取目标路径(通用逻辑),异步获取指定配置对应的文件路径 */
const getFilePathByConfig = async (defaultFile, basePath, sourceFile) => {
  return new Promise((resolve, reject) => {
    const { name } = getParams();
    fs.readFile(`${basePath}/${sourceFile}`, 'utf8', (err, data) => {
      if (err) reject(new Error(`读取文件失败: ${err.message}`));
      else {
        const config = JSON.parse(data);
        const filePath = config[name] || defaultFile;
        resolve(`${basePath}/${filePath}`);
      }
    });
  });
};

// 生成 app.json(独立任务),根据配置生成小程序的 app.json 文件
const generateAppJson = async () => {
  logger.task('generateAppJson');
  if (!fileExists(`${paths.appJsonConfig}/index.json`)) {
    logger.error(`配置文件不存在:${paths.appJsonConfig}`);
    process.exit(1);
  }
  return src(await getFilePathByConfig('default_app.json', paths.appJsonConfig, 'index.json'))
    .pipe(rename({ basename: 'app', extname: '.json' }))
    .pipe(dest(paths.distSrc))
    .on('end', () => logger.success('app.json生成完成'));
};

// 生成 pageUrl.ts(独立任务),根据配置生成页面 URL 相关的 TypeScript 文件
const generatePageUrl = async () => {
  logger.task('generatePageUrl');
  if (!fileExists(`${paths.pageUrlConfig}/index.json`)) {
    logger.error(`配置文件不存在:${paths.pageUrlConfig}`);
    process.exit(1);
  }
  return src(await getFilePathByConfig('default_pageUrl.js', paths.pageUrlConfig, 'index.json'))
    .pipe(rename({ basename: 'pageUrl', extname: '.ts' }))
    .pipe(dest(paths.configOutput))
    .on('end', () => logger.success('pageUrl.ts生成完成'));
};

// ============================ 4. 构建任务区(按类型拆分) ============================
/** 清理构建目录,删除之前的构建产物 */
const clean = () => {
  logger.task('clean');
  return del([`${paths.dist}/**/*`, `!${paths.dist}`], { force: true })
    .then(() => logger.success('清理完成'));
};

// WXML 编译,将小程序的 WXML 文件复制到构建目录
const compileWxml = () => {
  logger.task('compileWxml');
  return src(paths.patterns.wxml, { base: paths.src })
    .pipe(changed(paths.distSrc))
    .pipe(dest(paths.distSrc))
    .on('end', () => logger.success('WXML编译完成'));
};

// SCSS 编译(含错误处理),将 SCSS 文件编译为 WXSS 并压缩
const compileScss = () => {
  logger.task('compileScss');
  return createCommonPipe(paths.distSrc, '.wxss')
    .src(paths.patterns.scss, { base: paths.src })
    .pipe(gulpSass())
    .pipe(cleanCss())
    .pipe(rename({ extname: '.wxss' }))
    .pipe(dest(paths.distSrc))
    .on('end', () => logger.success('SCSS编译完成'));
};

// WXSS 编译,将 WXSS 文件复制到构建目录
const compileWxss = () => {
  logger.task('compileWxss');
  return src(paths.patterns.wxss, { base: paths.src })
    .pipe(changed(paths.distSrc))
    .pipe(dest(paths.distSrc))
    .on('end', () => logger.success('WXSS编译完成'));
};

// TS/JS 编译(区分环境),根据环境决定是否转译和移除调试语句
const compileTs = () => {
  logger.task('compileTs');
  const { env } = getParams();
  let stream = createCommonPipe(paths.distSrc).src(paths.patterns.ts, {});
  if (env === 'prod') {
    stream = stream.pipe(babel({ presets: ['@babel/preset-env'] }))
      .pipe(stripDebug());
  }
  return stream.pipe(dest(paths.distSrc))
    .on('end', () => logger.success('JS编译完成'));
};

// WXS 编译,将 WXS 文件转译后复制到构建目录
const compileWxs = () => {
  logger.task('compileWxs');
  return createCommonPipe(paths.distSrc)
    .src(paths.patterns.wxs, { base: paths.src })
    .pipe(babel({ presets: ['@babel/preset-env'] }))
    .pipe(rename({ extname: '.wxs' }))
    .pipe(dest(paths.distSrc))
    .on('end', () => logger.success('WXS编译完成'));
};

// JSON 编译,将 JSON 文件复制到构建目录
const compileJson = () => {
  logger.task('compileJson');
  return src(paths.patterns.json)
    .pipe(changed(paths.distSrc))
    .pipe(dest(paths.distSrc))
    .on('end', () => logger.success('JSON编译完成'));
};

// 图片处理(含压缩和映射生成),压缩图片并生成图片映射对象
let imagesMap = {};
const compileImage = async () => {
  logger.task('compileImage');
  imagesMap = {};
  // 确保插件已加载
  if (!imageminMozjpeg) {
    await loadImageMinPlugins();
  }
  return src(paths.patterns.images)
    .pipe(tap((file) => {
      const relativePath = file.relative.replace(/\\/g, '/');
      const pathParts = relativePath.split('/');
      const fileName = pathParts.pop();
      const nameMatch = fileName.match(/^([\w-]+)(@\d+x)?\.\w+$/);
      if (!nameMatch) return;
      const imageName = nameMatch[1];
      pathParts.reduce((obj, part, index) => {
        if (index === pathParts.length - 1) {
          obj[part] = obj[part] || {};
          obj[part][imageName] = `/${relativePath}`;
        } else {
          obj[part] = obj[part] || {};
        }
        return obj[part];
      }, imagesMap);
    }))
    .pipe(imagemin([
      imageminMozjpeg({ quality: 80, progressive: true }),
      imageminOptipng({ optimizationLevel: 5 }),
      imageminSvgo({
        plugins: [
          { name: 'removeViewBox', active: false },
          { name: 'cleanupIDs', active: false }
        ]
      })
    ]))
    .pipe(dest(paths.distSrc))
    .on('end', () => logger.success('图片压缩和处理完成'));
};

// 生成图片映射文件,将图片映射对象写入到指定的 TypeScript 文件中
const generateImageIndex = (cb) => {
  logger.task('generateImageIndex');
  try {
    fs.writeFileSync(paths.imageIndexOutput, 
      '// 自动生成,请勿修改\n' +
      `export default ${JSON.stringify(imagesMap, null, 2)};`
    );
    logger.success('图片映射生成完成');
    cb();
  } catch (err) {
    logger.error(`生成映射失败:${err.message}`);
    cb(err);
  }
};

// 复制项目配置,将项目配置文件复制到构建目录
const copyProjectConfig = () => {
  logger.task('copyProjectConfig');
  return src(paths.patterns.projectConfig)
    .pipe(changed(paths.dist))
    .pipe(dest(paths.dist))
    .on('end', () => logger.success('项目配置复制完成'));
};

// 复制类型定义,将类型定义文件复制到构建目录
const copyTypings = () => {
  logger.task('copyTypings');
  return src(paths.patterns.typings, { base: './' })
    .pipe(changed(paths.dist))
    .pipe(dest(paths.dist))
    .on('end', () => logger.success('类型定义复制完成'));
};

// 复制 npm 依赖,将 npm 包配置文件复制到构建目录
const copyNpmPackage = () => {
  logger.task('copyNpmPackage');
  return src(paths.patterns.npmPackage)
    .pipe(changed(paths.distSrc))
    .pipe(dest(paths.distSrc))
    .on('end', () => logger.success('package.json复制完成'));
};
// 安装依赖的任务函数,用于在构建目录中安装npm依赖
// cb是Gulp的回调函数,用于通知任务完成或失败
const installNpm = (cb) => {
  // 记录任务开始日志
  logger.task('installNpm');

  // 根据操作系统生成不同的命令:
  // Windows系统使用cd /d切换目录(/d参数用于跨盘符切换),然后执行npm install
  // 其他系统(如macOS、Linux)直接使用cd切换目录,然后执行npm install
  const cmd = process.platform === 'win32' 
    ? `cd /d "${paths.distSrc}" && npm install` 
    : `cd ${paths.distSrc} && npm install`
  
  try {
    // 同步执行shell命令,stdio: 'pipe'表示捕获输出流
    const stdout = execSync(cmd, { stdio: 'pipe' });
    // 将输出流转为字符串(npm的警告信息通常输出到stdout)
    const stderr = stdout.toString('utf-8');
    // 如果有警告信息,输出错误日志
    if (stderr) {
      logger.error(`npm警告:${stderr}`);
    }
    // 安装成功,输出成功日志并调用回调
    logger.success('依赖安装完成');
    cb();
  } catch (error) {
    // 安装失败,输出错误信息并将错误传递给回调
    logger.error(`npm安装失败:${error.message}`);
    cb(error);
  }
};

// 构建npm的任务函数,使用miniprogram-ci工具处理小程序npm依赖
const buildNpm = async (cb) => {
  // 记录任务开始日志
  logger.task('buildNpm');

  try {
    // 引入微信小程序CI工具(用于构建npm、上传代码等)
    const ci = require('miniprogram-ci');

    // 尝试从project.config.json读取小程序appid
    let appid = 'wxsomeappid'; // 默认appid(占位符)
    try {
      const projectConfig = require('./project.config.json');
      if (projectConfig.appid) {
        appid = projectConfig.appid;
        logger.info(`从project.config.json读取appid: ${appid}`);
      }
    } catch (err) {
      // 读取失败时使用默认值并警告
      logger.warn(`读取project.config.json失败,使用默认appid: ${appid}`);
    }

    // 定义私钥文件路径(用于小程序代码上传等操作的安全验证)
    const privateKeyPath = './private.wx0f4160dd2fa7d379.key';
    // 检查私钥文件是否存在
    if (!fs.existsSync(privateKeyPath)) {
      logger.error(`私钥文件不存在: ${privateKeyPath}`);
      cb(new Error('私钥文件不存在'));
      return;
    }

    // 创建项目实例,配置小程序基本信息
    const project = new ci.Project({
      appid: appid,                 // 小程序appid
      type: 'miniProgram',          // 项目类型(小程序)
      projectPath: paths.distSrc,   // 项目源码路径(构建后的目录)
      privateKeyPath: privateKeyPath, // 私钥文件路径
      ignores: ['node_modules/**/*'], // 构建时忽略的文件
    });

    // 使用miniprogram-ci的packNpm方法构建npm依赖
    logger.info('开始构建npm...');
    const warnings = await ci.packNpm(project, {
      ignores: ['pack_npm_ignore_list'], // 构建时忽略的列表
      outputPath: paths.npmDist,         // 构建结果输出目录
      reporter: (infos) => {             // 构建过程中的信息回调
        infos.forEach(info => {
          logger.info(`构建信息: ${info.msg}`);
        });
      }
    });

    // 处理构建过程中产生的警告信息
    if (warnings && warnings.length > 0) {
      logger.warn('npm构建存在警告:');
      // 格式化警告信息,显示序号、内容、错误码和位置
      const formattedWarnings = warnings.map((it, index) => {
        return `${index + 1}. ${it.msg}
	> code: ${it.code}
	@ ${it.jsPath}:${it.startLine}-${it.endLine}`;
      }).join('---------------\n'); // 用分隔符拼接多个警告
      logger.warn(formattedWarnings);
    }

    // 构建成功,输出日志并调用回调
    logger.success('npm构建完成');
    cb();
  } catch (error) {
    // 构建失败,输出错误信息并传递错误
    logger.error(`npm构建失败:${error.message}`);
    cb(error);
  }
};

// 监控文件变化的任务函数,当指定文件修改时自动执行对应构建任务
const watchFiles = () => {
  logger.info('开始监控文件变化...');
  
  // 监控WXML文件变化,变化时执行compileWxml任务
  watch(paths.patterns.wxml, compileWxml);
  // 监控SCSS文件变化,变化时执行compileScss任务
  watch(paths.patterns.scss, compileScss);
  // 监控TS文件变化,变化时执行compileTs任务
  watch(paths.patterns.ts, compileTs);
  // 监控WXS文件变化,变化时执行compileWxs任务
  watch(paths.patterns.wxs, compileWxs);
  // 监控JSON文件变化,变化时执行compileJson任务
  watch(paths.patterns.json, compileJson);
  // 监控图片文件变化,变化时按顺序执行compileImage和generateImageIndex任务
  watch(paths.patterns.images, series(compileImage, generateImageIndex));
  
  logger.success('文件监控已启动,等待文件变化...');
  
  // 监控环境配置文件变化,变化时执行generateEnvConfig任务
  watch(paths.envConfig, generateEnvConfig);
  // 监控npm包配置文件变化,变化时按顺序执行复制、安装、构建npm任务
  watch(paths.patterns.npmPackage, series(copyNpmPackage, installNpm, buildNpm));
  // 监控项目配置文件变化,变化时执行copyProjectConfig任务
  watch(paths.patterns.projectConfig, copyProjectConfig);
  // 监控appJson配置目录变化,变化时执行generateAppJson任务
  watch(paths.appJsonConfig, generateAppJson);
  // 监控pageUrl配置目录变化,变化时执行generatePageUrl任务
  watch(paths.pageUrlConfig, generatePageUrl);
};

// ============================ 5. 任务入口(清晰分层) ============================
/** 完整构建流程:按顺序执行一系列任务 */
const build = series(
  generateEnvConfig, // 第一步:生成环境配置文件
  
  // 第二步:并行执行多个构建任务(提高效率)
  parallel(
    compileWxml,      // 编译WXML文件
    compileTs,        // 编译TS文件
    compileWxs,       // 编译WXS文件
    compileJson,      // 处理JSON文件
    compileWxss,      // 处理WXSS文件
    compileScss,      // 编译SCSS文件
    compileImage,     // 处理图片文件
    copyNpmPackage,   // 复制npm配置文件
    copyProjectConfig,// 复制项目配置文件
    copyTypings       // 复制类型定义文件
  ),
  
  generateImageIndex, // 第三步:生成图片映射文件
  
  // 第四步:并行生成应用配置文件
  parallel(
    generateAppJson,  // 生成app.json
    generatePageUrl   // 生成pageUrl.ts
  ),
  
  installNpm,        // 第五步:安装npm依赖
  buildNpm           // 第六步:构建npm依赖
);

/** 开发模式任务:包含清理、完整构建、计时和文件监控 */
const dev = series(
  (cb) => { // 构建计时:记录开始时间
    startTime = process.hrtime(); // 使用高精度计时
    cb();
  },
  clean,        // 第一步:清理构建目录
  build,        // 第二步:执行完整构建流程
  (cb) => {     // 输出构建耗时
    const [s, ns] = process.hrtime(startTime); // 计算耗时(秒+纳秒)
    const totalMs = (s * 1000 + ns / 1e6).toFixed(2); // 转换为毫秒并保留两位小数
    logger.success(`构建完成,耗时:${totalMs}ms`);
    cb();
  },
  watchFiles,   // 第三步:启动文件监控
);

// 导出Gulp任务,允许通过命令行调用
exports.clean = clean;                  // 导出clean任务:gulp clean
exports.build = series(clean, build);   // 导出build任务(先清理再构建):gulp build
exports.watch = watchFiles;             // 导出watch任务:gulp watch
exports.dev = dev;                      // 导出dev任务:gulp dev
exports.default = dev;                  // 默认任务(执行gulp时默认运行dev)

注意事项:

  1. privateKeyPath 这个路径可以是你自定义的,不一定是项目根目录。虽然我们npm构建用不到这个,但是miniprogram-cli初始化的时候不能省。
  2. 要确保package.json和node_modules和小程序的app.json同级目录才能构建成功,也就是正确生成miniprogram_npm目录。
  3. generateImageIndex任务,是生成了图片的映射map, 访问的时候可以使用key值访问资源地址,这样我们在修改的时候只需要修改这个文件,不需要修改其他代码,但是这个文件放在主包,会增加主包大小,所以看具体情况使用。
  4. 此示例支持配置多个不同的tabbar数组,别当心主包页面多,增加体积,小程序在上传的时候,会自动过滤掉没有使用的文件,比如home页面只有环境量customer2是否用了,那么使用customer1的时候,home页面相关文件会被过滤调。

四、其他

  1. 小程序开发主要只放tabbar页面,其他页面放在子包。
  2. 只有子包使用的静态文件,放在子包。
  3. 页面、静态资源 目录命名要具体并保持一致,在readme中做好目录简介
  4. 做好工具方法封装,但自由子包用到的可以放在子包
  5. 业务弱相关的页面,可以单独放在一个子包,其他如果一个子包特别大,可以按业务再细分,多分出一个包,避免请求子包时,由于体积过大,请求慢。
  6. 做好页面跳转接口的封装,便于后面添加些拦截或者其他请求。
  7. 做好项目中页面的映射map,页面上所有跳转都是用key,这样路径有修改只需要修改映射文件,不需要修改其他代码。
  8. 公共常量尽量抽取,多处引入使用,便于修改。
  9. 图片可使用https://tinypng.com/压缩后使用,也可放置cdn
  10. request\api拦截可借助wxapp-api-intercepters插件

网站公告

今日签到

点亮在社区的每一天
去签到