目录
看到标题大家应该很奇怪,原生小程序,直接放在微信开发者工具里面,开发就行了呗,做啥还要再构建一下呢?其实主要原因是想配置通过命令行直接切换环境,根据环境量,配置不同的配置文件,不同的页面入口,不同的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
- index.json 配置索引文件
{
"customer1": "customer1_app.json" // 环境:配置文件名
}
- default_app.json 默认的小程序app.json文件配置
- customer1_app.json 环境量customer1对应的app.json配置文件,当环境量是customer1时,选取此文件作为app.json文件
2.2 build/pageUrlConfig
- index.json配置索引文件
{
"customer1": "customer1_pageUrl.ts" // 环境:配置文件名
}
- default_pageUrl.ts 默认的小程序pageUrl.ts文件配置
export default {
HOME: '/pages/index/index',
LOG: '/pages/logs/logs'
}
- 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)
注意事项:
- privateKeyPath 这个路径可以是你自定义的,不一定是项目根目录。虽然我们npm构建用不到这个,但是miniprogram-cli初始化的时候不能省。
- 要确保package.json和node_modules和小程序的app.json同级目录才能构建成功,也就是正确生成miniprogram_npm目录。
- generateImageIndex任务,是生成了图片的映射map, 访问的时候可以使用key值访问资源地址,这样我们在修改的时候只需要修改这个文件,不需要修改其他代码,但是这个文件放在主包,会增加主包大小,所以看具体情况使用。
- 此示例支持配置多个不同的tabbar数组,别当心主包页面多,增加体积,小程序在上传的时候,会自动过滤掉没有使用的文件,比如home页面只有环境量customer2是否用了,那么使用customer1的时候,home页面相关文件会被过滤调。
四、其他
- 小程序开发主要只放tabbar页面,其他页面放在子包。
- 只有子包使用的静态文件,放在子包。
- 页面、静态资源 目录命名要具体并保持一致,在readme中做好目录简介
- 做好工具方法封装,但自由子包用到的可以放在子包
- 业务弱相关的页面,可以单独放在一个子包,其他如果一个子包特别大,可以按业务再细分,多分出一个包,避免请求子包时,由于体积过大,请求慢。
- 做好页面跳转接口的封装,便于后面添加些拦截或者其他请求。
- 做好项目中页面的映射map,页面上所有跳转都是用key,这样路径有修改只需要修改映射文件,不需要修改其他代码。
- 公共常量尽量抽取,多处引入使用,便于修改。
- 图片可使用
https://tinypng.com/
压缩后使用,也可放置cdn - request\api拦截可借助wxapp-api-intercepters插件