274 lines
8.9 KiB
JavaScript
274 lines
8.9 KiB
JavaScript
import gulp from "gulp";
|
||
import gulpIf from "gulp-if";
|
||
import MemoryFS from "memory-fs";
|
||
import syncBrowser from "browser-sync";
|
||
const browserSync = syncBrowser.create();
|
||
const __dirname = process.cwd();
|
||
|
||
// 创建内存文件系统实例
|
||
const mfs = new MemoryFS();
|
||
import htmlmin from "gulp-htmlmin"; // 压缩html文件
|
||
import uglify from "gulp-uglify"; // 压缩js文件
|
||
import babel from "gulp-babel"; // ES6转ES5
|
||
import sass from "gulp-sass"; // sass编译
|
||
import cleanCss from "gulp-clean-css"; // 压缩css文件
|
||
import replace from "gulp-replace"; // 替换文件内容
|
||
import ignore from "gulp-ignore"; // 排除文件不进入打包结果
|
||
import { deleteAsync } from "del"; // 清除打包目标目录
|
||
import fs from "fs/promises";
|
||
import path from "path";
|
||
import map from "map-stream";
|
||
import { createProxyMiddleware } from "http-proxy-middleware";
|
||
|
||
const devAssetsDir = "src";
|
||
const htmlGlob = `${devAssetsDir}/**/*.html`;
|
||
const cssGlob = `${devAssetsDir}/**/*.css`;
|
||
const jsGlob = `${devAssetsDir}/**/*.js`;
|
||
const staticGlob = `${devAssetsDir}/static/**/*`;
|
||
const modulesGlob = "modules/**/*";
|
||
const allFileGlob = `${devAssetsDir}/**/*`;
|
||
|
||
/**
|
||
* @type {"dev" | "prod"} 环境变量
|
||
*/
|
||
let env = "dev";
|
||
function setEnv(currEnv) {
|
||
return async () => {
|
||
env = currEnv;
|
||
};
|
||
}
|
||
import config from "./config.js";
|
||
let { moduleMap, assetsDir, devServer } = config;
|
||
let { browsersync: browsersyncOptions, httpProxy } = devServer && typeof devServer === "object" ? devServer : {};
|
||
moduleMap = parseModuleMap(moduleMap);
|
||
// 异步读取文件内容
|
||
async function getCode(path) {
|
||
return await fs.readFile(path, "utf8");
|
||
}
|
||
|
||
// 清除dist目录的任务
|
||
async function cleanTask() {
|
||
return deleteAsync([assetsDir]); // 使用 del 删除 dist 目录及其内容
|
||
}
|
||
|
||
export const clean = cleanTask;
|
||
|
||
/**
|
||
* @description 注入模块
|
||
* @param {NodeJS.ReadWriteStream} ctx
|
||
* @returns {NodeJS.ReadWriteStream}
|
||
*/
|
||
async function injectModules(ctx) {
|
||
// 遍历模块文件,将每个模块文件的内容替换到对应的模块标签中
|
||
for (const key in moduleMap) {
|
||
const filePath = path.join(__dirname, moduleMap[key]);
|
||
const code = await getCode(filePath);
|
||
const replaceStream = replace(key, code);
|
||
ctx = ctx.pipe(replaceStream);
|
||
}
|
||
return ctx;
|
||
}
|
||
|
||
// 制定html任务
|
||
async function htmlTask() {
|
||
const condition = env === "prod";
|
||
let ctx = gulp.src(htmlGlob, {
|
||
ignore: [staticGlob],
|
||
});
|
||
ctx = await injectModules(ctx);
|
||
return (
|
||
ctx
|
||
.pipe(
|
||
htmlmin({
|
||
removeComments: condition, // 生产环境下移除注释
|
||
collapseWhitespace: condition, // //压缩HTML
|
||
collapseBooleanAttributes: condition, //省略布尔属性的值 <input checked="true"/> ==> <input />
|
||
removeEmptyAttributes: env === "prod", //删除所有空格作属性值 <input id="" /> ==> <input />
|
||
removeScriptTypeAttributes: false, //删除<script>的type="text/javascript"
|
||
removeStyleLinkTypeAttributes: condition, //删除<style>和<link>的type="text/css"
|
||
minifyJS: condition, //压缩页面JS
|
||
minifyCSS: condition, //压缩页面CSS
|
||
})
|
||
)
|
||
.pipe(ignore.exclude(modulesGlob)) // 忽略模块文件
|
||
.pipe(gulpIf(condition, writeFileToFileSystem(assetsDir)))
|
||
// 将处理后的文件写入内存文件系统
|
||
.pipe(gulpIf(!condition, writeFileToMemory()))
|
||
);
|
||
}
|
||
|
||
// 定义Gulp任务
|
||
export const html = htmlTask;
|
||
|
||
// 制定js任务:ES6转ES5,再压缩js
|
||
async function jsTask() {
|
||
const ctx = gulp.src(jsGlob, {
|
||
ignore: [staticGlob],
|
||
});
|
||
const condition = env === "prod";
|
||
return ctx
|
||
.pipe(
|
||
gulpIf(
|
||
condition,
|
||
babel({
|
||
presets: ["@babel/preset-env"],
|
||
})
|
||
)
|
||
)
|
||
.pipe(
|
||
gulpIf(
|
||
condition,
|
||
uglify({
|
||
compress: {
|
||
drop_console: false, // 移除console语句
|
||
drop_debugger: true, // 移除debugger语句
|
||
},
|
||
})
|
||
)
|
||
)
|
||
.pipe(gulpIf(condition, writeFileToFileSystem(path.join(assetsDir))))
|
||
.pipe(gulpIf(!condition, writeFileToMemory()));
|
||
}
|
||
|
||
export const js = jsTask;
|
||
|
||
// css任务:压缩css
|
||
async function cssTask() {
|
||
const ctx = gulp.src(cssGlob, {
|
||
ignore: [staticGlob],
|
||
});
|
||
const condition = env === "prod";
|
||
return ctx
|
||
.pipe(gulpIf(condition, cleanCss())) // css样式压缩
|
||
.pipe(gulpIf(condition, writeFileToFileSystem(path.join(assetsDir))))
|
||
.pipe(gulpIf(!condition, writeFileToMemory()));
|
||
}
|
||
|
||
export const css = cssTask;
|
||
|
||
// 静态文件目录,直接复制
|
||
async function staticFile() {
|
||
const condition = env === "prod";
|
||
const staticDir = "/static";
|
||
return gulp
|
||
.src(staticGlob)
|
||
.pipe(gulpIf(condition, writeFileToFileSystem(assetsDir)))
|
||
.pipe(gulpIf(!condition, writeFileToMemory(staticDir)));
|
||
}
|
||
|
||
export const staticFileTask = staticFile;
|
||
|
||
// 其他文件:直接复制(除了html, css,js文件和static目录)
|
||
async function otherFile() {
|
||
const condition = env === "prod";
|
||
return gulp
|
||
.src(allFileGlob, {
|
||
ignore: [htmlGlob, cssGlob, jsGlob, staticGlob],
|
||
})
|
||
.pipe(gulpIf(condition, writeFileToFileSystem(assetsDir)))
|
||
.pipe(gulpIf(!condition, writeFileToMemory()));
|
||
}
|
||
|
||
export const otherFileTask = otherFile;
|
||
|
||
// 打包任务:清除dist目录,然后并行执行html、js、css、其他文件、static等任务
|
||
export const build = gulp.series(setEnv("prod"), cleanTask, gulp.parallel(html, js, css, otherFileTask, staticFileTask));
|
||
// server任务:开启一个本地服务器
|
||
async function serverTask() {
|
||
browsersyncOptions = browsersyncOptions && typeof browsersyncOptions === "object" ? browsersyncOptions : {};
|
||
|
||
const httpProxyList = Object.keys(httpProxy).map((prefix) => createProxyMiddleware(prefix, httpProxy[prefix]));
|
||
return browserSync.init({
|
||
...browsersyncOptions,
|
||
middleware: [
|
||
...httpProxyList,
|
||
// 自定义中间件来从内存文件系统中提供文件
|
||
function (req, res, next) {
|
||
let fileLogicPath = req.url.split("?")[0];
|
||
let url = fileLogicPath === "/" ? "/index.html" : fileLogicPath;
|
||
let filePath = path.join(__dirname, "/", devAssetsDir, ...url.split("/"));
|
||
mfs.readFile(decodeURIComponent(filePath), (err, data) => {
|
||
if (err) {
|
||
const map = {
|
||
ENOENT: () => console.error(`\x1b[31m找不到文件或目录:${err.path}\x1b[0m`),
|
||
};
|
||
map[err.code] ? map[err.code]() : console.error(err);
|
||
res.statusCode = 404;
|
||
res.end(`not found ${req.url}`);
|
||
} else {
|
||
res.end(data);
|
||
}
|
||
});
|
||
},
|
||
],
|
||
server: true,
|
||
});
|
||
}
|
||
|
||
export const server = serverTask;
|
||
|
||
// 监听文件的修改,执行对应的任务
|
||
async function watchTask() {
|
||
gulp.watch(htmlGlob, gulp.series(html)).on("change", browserSync.reload);
|
||
gulp.watch(jsGlob, gulp.series(js)).on("change", browserSync.reload);
|
||
gulp.watch(cssGlob, gulp.series(css)).on("change", browserSync.reload);
|
||
gulp.watch(staticGlob, gulp.series(staticFileTask)).on("change", browserSync.reload);
|
||
gulp.watch(allFileGlob, gulp.series(otherFileTask)).on("change", browserSync.reload);
|
||
}
|
||
|
||
export const watch = watchTask;
|
||
|
||
// 开发服务
|
||
export const dev = gulp.series(setEnv("dev"), gulp.parallel(html, js, css, staticFileTask, otherFileTask), (cb) => setTimeout(() => cb(), 3000), server, watch);
|
||
|
||
/**
|
||
* 解析模块映射
|
||
* @param {Object} moduleMap
|
||
* @returns
|
||
*/
|
||
function parseModuleMap(moduleMap) {
|
||
const newModuleMap = {};
|
||
Object.keys(moduleMap).forEach((fileId) => {
|
||
const newId = `#include(${fileId})`;
|
||
newModuleMap[newId] = moduleMap[fileId];
|
||
});
|
||
return newModuleMap;
|
||
}
|
||
/**
|
||
* @description 把文件流写入到内存中
|
||
* @returns
|
||
*/
|
||
function writeFileToMemory() {
|
||
return map(function mapFile(file, cb) {
|
||
const filepath = path.resolve(file.path);
|
||
const dir = path.dirname(filepath);
|
||
if (!mfs.existsSync(dir)) {
|
||
mfs.mkdirpSync(dir);
|
||
}
|
||
file.contents && mfs.writeFileSync(filepath, file.contents);
|
||
cb(null, file);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @description 把文件流写入到硬盘中
|
||
* @param {String} assetsDir 写入到哪个目录,如根目录的dist,则assetsDir位'dist'
|
||
* @returns
|
||
*/
|
||
function writeFileToFileSystem(assetsDir) {
|
||
return map(async function mapFile(file, cb) {
|
||
const relativePath = path.relative(path.join(process.cwd(), devAssetsDir), file.path);
|
||
const destFilePath = path.join(process.cwd(), assetsDir, relativePath);
|
||
const dirPath = path.dirname(destFilePath);
|
||
try {
|
||
if (file.contents) {
|
||
await fs.access(dirPath).catch(() => fs.mkdir(dirPath, { recursive: true }));
|
||
await fs.writeFile(destFilePath, file.contents);
|
||
}
|
||
} catch (error) {
|
||
console.log(error);
|
||
}
|
||
cb(null, file);
|
||
});
|
||
}
|