gulp 是什么?
一个基于 node 的前端自动化工作构建工具,应用经典回调 + 链式调用的形式实现工作的自动化 (src.pipe(...).pipe)
,gulp 其实和 webpack 很类似,然而 gulp 侧重点不同,gulp 更偏重前端流程自动化、工作执行(通过工作使开发提效),就像一条流水线。而 webpack 则是更偏重用于打包前端资源,所有皆可打包成模块。
官网文档:https://www.gulpjs.com.cn/
gulp 的利用场景?为什么用 gulp?
1. 构建前端自动化流程比拟好的计划,一些反复的人工操作能够让 gulp 去做,并且代码量不大,晋升开发效率(自动化实现前端工作流工作:单元测试、优化、打包)。
2. 与其余构建工具相比开发简略,易上手。基于 nodejs 文件系统流,运行速度快。
3. 更适宜原生前端我的项目的打包。有助于了解前端工程化。
4. 公布通用型组件或者 npm 库的时候能够用 gulp 来进行打包。
gulp 的装置
在你的我的项目目录中执行
npm install -g gulp
在根目录下创立 gulp 的配置文件 gulpfile.js
,install 一下文件中的依赖,之后就能够间接在这个文件中定义咱们的 gulp 工作了
var gulp=require("gulp");// 引入 gulp 模块
gulp.task('default',function(){console.log('hello world');
});
而后间接终端中进入当前目录运行 gulp
命令就开始执行工作了。
gulp 前面也能够加上要执行的工作名,例如 gulp task1
,如果没有指定工作名,则会执行工作名为default
的默认工作。
gulp 罕用的几个 api:task, series, parallel, src, pipe, dest
- task: 创立一个工作
- series:程序执行多个工作
- prallel:并行执行多个工作
- src:读取数据源转换成 stream
- pipe:管道 - 能够在两头对数据流进行解决
- dest:输入数据流到指标门路
gulp 理论利用之原生前端我的项目打包:
以一个 jQuery 原生我的项目为例子,目录构造:
门路表:
因为我的项目构造有点非凡,资源比拟扩散,html 的 js、css 保留在对应模块文件夹中。而公共的 js、css 却在 html 的里面的文件中,所以就设置了 2 个入口(以 js 资源为例):一个根目录入口、一个 html 入口
scriptsjs: {// 根目录入口
src: [
"./temp/js/**/*.js",//temp/js/ 之下的所有文件夹下的 js
"!./temp/**/*.min.js", // 不匹配压缩过的 js,避免二次压缩
"!./temp/js/common.js",
"!./temp/mpcc/**/*.js",// 这个文件不须要压缩解决所以不匹配
],
dest: destDir + "/js",// 进口
},
scriptshtml: {//html 入口
src: [
"./temp/html/**/*.js",
"!./temp/html/BasicSet/RoleManage/js/*.js",// 不匹配
"!./temp/html/BasicSet/UserAuthorization/js/*.js",
],
dest: destDir + "/html",
},
你没看错,是不是有点像 webpack 中的 entry
和output
。* 通配符
是代表所有文件夹、!
是代表不匹配。这里能够本人选哪些文件不须要进行匹配
gulpfile.js 残缺代码:
var gulp = require("gulp"),
sourcemaps = require("gulp-sourcemaps");
var babel = require("gulp-babel");
var uglify = require("gulp-uglify");
var del = require("del");
var minifycss = require("gulp-minify-css");
var through = require("through2");
var path = require("path");
var fs = require("fs");
var crypto = require("crypto");
var ramdomHash = function (len) {
// 获取随机 hash 值
var random = Math.random().toString();
return crypto
.createHash("md5")
.update(new Date().valueOf().toString() + random) // 退出工夫戳和随机数保障 hash 的惟一
.digest("hex")
.substr(0, len);
};
var destDir = "workOrder"; // 生产包文件目录
// 包门路表
var paths = {
stylescss: {src: ["./temp/css/**/*.css", "!./temp/**/*.min.css"],
dest: destDir + "/css",
},
styleshtml: {src: ["./temp/html/**/*.css", "!./temp/**/*.min.css"],
dest: destDir + "/html",
},
scripts: {
src: "./temp/**/*.js",
dest: destDir + "/",
},
scriptsjs: {
src: [
"./temp/js/**/*.js",
"!./temp/**/*.min.js",
"!./temp/js/common.js",
"!./temp/mpcc/**/*.js",
],
dest: destDir + "/js",
},
scriptshtml: {
src: [
"./temp/html/**/*.js",
"!./temp/html/BasicSet/RoleManage/js/*.js",
"!./temp/html/BasicSet/UserAuthorization/js/*.js",
],
dest: destDir + "/html",
},
html: {
src: "./temp/**/*.html",
dest: destDir + "/",
}
};
// 删除生产包
function clean() {return del([destDir]);
}
// 革除 temp 文件夹
function revClean() {return del(["temp"]);
}
// 复制到 temp,防止净化 src
function revCopy() {
return gulp
.src("./workorder_dev/**/*", { base: "./workorder_dev"})
.pipe(gulp.dest("./temp/"));
}
//html 中资源门路加版本号,更改所有的文件里的资源门路,以便接下来的减少版本号工作.
function revHtmlPathReplace() {
var ASSET_REG = {
SCRIPT:
/("|')(.[^('|")]*((\.js)|(\.css)|(\.json)|(\.png)|(\.jpg)|(\.ttf)|(\.eot)|(\.gif)|(\.woff2)|(\.woff)))(\1)/gi,
};
return gulp
.src("./temp/html/**/*.html")
.pipe((function () { // 利用 through 读取 html 文件夹下的所有 html 文件
return through.obj(function (file, enc, cb) {if (file.isNull()) {this.push(file);
return cb();}
if (file.isStream()) {
this.emit(
"error",
new gutil.PluginError(PLUGIN_NAME, "Streaming not supported")
);
return cb();}
var content = file.contents.toString();
var filePath = path.dirname(file.path);
for (var type in ASSET_REG) { // 获取 html 文件内容间接应用 replace+ 正则进行替换
content = content.replace(ASSET_REG[type],
function (str, tag, src) {var _f = str[0];
src = src.replace(/(^['"]|['"]$)/g, "");
if (/\.min\./gi.test(src)) {
// 压缩文件不加版本号
return src;
}
var assetPath = path.join(filePath, src);
if (fs.existsSync(assetPath)) {var buf = fs.readFileSync(assetPath);
var md5 = ramdomHash(7); // 获取版本号 hash,只须要 7 位 hash 不须要太长
var verStr = "" + md5;
src = src + "?v=" + verStr;
}
src = _f + src + _f;
return src;
}
);
}
file.contents = new Buffer(content);
this.push(file);
cb();});
})())
.pipe(gulp.dest("./temp/html/"));
}
//css 中资源加版本号
function assetRev(options) {
var ASSET_REG = {SCRIPT: /(<script[^>]+src=)['"]([^'"]+)["']/gi,
STYLESHEET: /(<link[^>]+href=)['"]([^'"]+)["']/gi,
IMAGE: /(<img[^>]+src=)['"]([^'"]+)["']/gi,
BACKGROUND: /(url\()(?!data:|about:)([^)]*)/gi,
};
return through.obj(function (file, enc, cb) {options = options || {};
if (file.isNull()) {this.push(file);
return cb();}
if (file.isStream()) {
this.emit(
"error",
new gutil.PluginError(PLUGIN_NAME, "Streaming not supported")
);
return cb();}
var content = file.contents.toString();
var filePath = path.dirname(file.path);
for (var type in ASSET_REG) {if (type === "BACKGROUND" && !/\.(css|scss|less)$/.test(file.path)) { } else {content = content.replace(ASSET_REG[type], function (str, tag, src) {src = src.replace(/(^['"]|['"]$)/g, "");
if (!/\.[^\.]+$/.test(src)) {return str;}
if (options.verStr) {
src += options.verStr;
return tag + '"'+ src +'"';
}
// remote resource
if (/^https?:\/\//.test(src)) {return str;}
var assetPath = path.join(filePath, src);
if (src.indexOf("/") == 0) {
if (
options.resolvePath &&
typeof options.resolvePath === "function"
) {assetPath = options.resolvePath(src);
} else {assetPath = (options.rootPath || "") + src;
}
}
if (fs.existsSync(assetPath)) {var buf = fs.readFileSync(assetPath);
var md5 = ramdomHash(7);
var verStr = (options.verConnecter || "") + md5;
src = src + "?v=" + verStr; // 减少版本号
} else {return str;}
return tag + '"'+ src +'"';
});
}
}
file.contents = new Buffer(content);
this.push(file);
cb();});
}
// 为 css 中引入的图片 / 字体等增加 hash 编码
function revAssetCsscss() {
return gulp
.src(paths.stylescss.src) // 该工作针对的文件
.pipe(assetRev()) // 该工作调用的模块
.pipe(gulp.dest("./temp/css")); // 编译后的门路
}
function revAssetCsshtml() {
return gulp
.src(paths.styleshtml.src) // 该工作针对的文件
.pipe(assetRev()) // 该工作调用的模块
.pipe(gulp.dest("./temp/html")); // 编译后的门路
}
// 压缩 css,并增加 sourcemap
function stylesMinifyCss() {
return (
gulp
.src(paths.stylescss.src)
.pipe(sourcemaps.init())
// .pipe(less())
// .pipe(cleanCSS())
// // pass in options to the stream
// .pipe(rename({
// basename: 'main',
// suffix: '.min'
// }))
.pipe(minifycss())
.pipe(sourcemaps.write("./maps"))
.pipe(gulp.dest(paths.stylescss.dest))
);
}
// 把 html 文件夹下的 css 进行压缩
function stylesMinifyHtml() {
return (
gulp
.src(paths.styleshtml.src)
.pipe(sourcemaps.init())
.pipe(minifycss())
.pipe(sourcemaps.write("./maps"))
.pipe(gulp.dest(paths.styleshtml.dest))
);
}
// 压缩 js,并增加 sourcemap
function scriptsjs() {
return gulp
.src(paths.scriptsjs.src, { sourcemaps: true})
.pipe(sourcemaps.init()) // 源码映射便于调试
.pipe(sourcemaps.identityMap())
.pipe(babel()) //es6 转换
.pipe(uglify()) // 压缩
.pipe(sourcemaps.write("./maps"))
.pipe(gulp.dest(paths.scriptsjs.dest));
}
// 压缩 html 文件夹下的 js
function scriptshtml() {
return gulp
.src(paths.scriptshtml.src, { sourcemaps: true})
.pipe(sourcemaps.init())
.pipe(sourcemaps.identityMap())
.pipe(babel())
.pipe(uglify())
.pipe(sourcemaps.write("./maps"))
.pipe(gulp.dest(paths.scriptshtml.dest));
}
// 把临时文件拷贝到生产目录
function copy() {
return gulp
.src("./temp/**/*", { base: "./temp"})
.pipe(gulp.dest(destDir + "/"));
}
// 创立一个 json 文件保留标识用于辨认以后是否是线上环境
function updateEnv(done) {
fs.writeFile(
"./temp/env.json",
JSON.stringify({env: "prod"}),
function (err) {if (err) {console.error(err);
}
done();
console.log("--------------------updateEnv");
}
);
}
var build = gulp.series(// 串行工作
clean,// 革除上一次的生产包
revClean,// 删除 temp 文件夹
revCopy,// 拷贝开发目录到 temp
revHtmlPathReplace,//html 加版本号
revAssetCsscss,// 给 css 资源加版本号
revAssetCsshtml,
updateEnv,// 生成运行环境 json
copy, //copy 之后再压缩
gulp.parallel( // 对两个入口的资源压缩、优化的并行任务
stylesMinifyCss,
stylesMinifyHtml,
scriptsjs,
scriptshtml
),
revClean // 删除 temp
);
exports.clean = clean;
exports.stylesMinifyCss = stylesMinifyCss;
exports.stylesMinifyHtml = stylesMinifyHtml;
exports.updateEnv = updateEnv;
exports.scriptsjs = scriptsjs;
exports.scriptshtml = scriptshtml;
exports.default = build;
能够看出每个工作就是一个函数,在最初对定义的工作(函数)按程序进行执行一遍。
打包流程:
其实看最初的 gulp.series
就能看进去,串行工作外面含有一个并行任务,间接运行 gulp 命令就能间接看到打包的过程。
所有工作执行的先后顺序:
删除上次生产包 > 删除 temp 文件夹 > 把开发目录拷贝到 temp 文件夹 > html 内容加版本号 > 资源内容加版本号 > 创立 json > 把 temp 文件拷到生产目录 > 开始并行压缩 > 最初删除 temp 文件夹
gulp 实现防缓存
为什么要防缓存?
如果不防缓存,在原生我的项目上线后,浏览器会把前端的 css,js 资源缓存在本地,下次关上的时候如果资源不变就会间接应用本地的缓存来加载页面,这样会造成用户必须手动革除浏览器缓存能力应用新的性能,影响体验,所以就须要在页面引入资源的时候给文件加上版本号?v=xxxx,这样浏览器就能辨认到资源有变动就会从服务器上从新获取资源
咱们回看一下 gulpfile 文件中加版本号的工作revHtmlPathReplace
:
//html 中资源门路加版本号,更改所有的文件里的资源门路,以便接下来的减少版本号工作.
function revHtmlPathReplace() {
var ASSET_REG = {
SCRIPT:
/("|')(.[^('|")]*((\.js)|(\.css)|(\.json)|(\.png)|(\.jpg)|(\.ttf)|(\.eot)|(\.gif)|(\.woff2)|(\.woff)))(\1)/gi,
};
return gulp
.src("./temp/html/**/*.html")
.pipe((function () { // 利用 through 读取 html 文件夹下的所有 html 文件
return through.obj(function (file, enc, cb) {if (file.isNull()) {this.push(file);
return cb();}
if (file.isStream()) {
this.emit(
"error",
new gutil.PluginError(PLUGIN_NAME, "Streaming not supported")
);
return cb();}
var content = file.contents.toString();
var filePath = path.dirname(file.path);
for (var type in ASSET_REG) { // 获取 html 文件内容间接应用 replace+ 正则进行替换
content = content.replace(ASSET_REG[type],
function (str, tag, src) {var _f = str[0];
src = src.replace(/(^['"]|['"]$)/g, "");
if (/\.min\./gi.test(src)) {
// 压缩文件不加版本号
return src;
}
var assetPath = path.join(filePath, src);
if (fs.existsSync(assetPath)) {var buf = fs.readFileSync(assetPath);
var md5 = ramdomHash(7); // 获取版本号 hash,只须要 7 位 hash 不须要太长
var verStr = "" + md5;
src = src + "?v=" + verStr;
}
src = _f + src + _f;
return src;
}
);
}
file.contents = new Buffer(content);
this.push(file);
cb();});
})())
.pipe(gulp.dest("./temp/html/"));
}
通过遍历所有 html 文件时应用 through
获取到文件的文本内容,而后利用正则对文本内容中须要加版本的门路加上 hash 版本而后替换下来,最初再输入一个新的文件文件。同时 assetRev
工作也相似
这个工作的代码其实是借鉴了 gulp-asset-rev/index.js
中的源码,把原来源码中的 css 文件内容的资源门路加版本拿进去改成了给 html 文件内容加版本。
加版本后成果:
顺带说一下 gulp 另一种比拟麻烦的加版本防缓存计划就是应用 rev
模块,须要批改多处源码。这种计划有不好的中央,因为是生成 rev-manifest.json
对应关系来加版本,如果因为某些起因在这个 json 中没有对应文件对照,会导致某些非凡门路如 (../../xxx.js)
的文件加版本号没加上 就会漏掉某些文件没加版本。还有就是 node_modules 源码改变后重新安装就会被笼罩。
最初:
对打包流程进行一些优化
1. 因为每次打包后是都要手动进行压缩、命名一下再发给后端部署。解放双手,写一个打包后对生产包主动进行压缩的工作distZip:
这个 distZip
工作要放到最初执行,默认压缩当前目录下的 dist 文件夹
// 对生产包主动压缩成 zip
function distZip(done) {var archiver = require("archiver");
var now = new Date();
var filename = [
__dirname + "/dist",
now.getMonth(),
now.getDate(),
now.getHours(),
now.getMinutes(),
now.getSeconds(),
".zip",
].join(""); // 以后工夫拼接
var output = fs.createWriteStream(filename);
// 设置压缩格局为 zip
var archive = archiver("zip", {zlib: { level: 9}, // Sets the compression level.
}); archive.on("error", function (err) {throw err;});
archive.pipe(output);
archive.directory("./dist/");
archive.finalize();
done();}
2. 在原生我的项目打包实现时没有像 webpack 有 process.env
来辨别是否是生产环境,能够在打包阶段写一个生成以后运行环境配置文件的工作 updateEnv
,主动生成一个 json 文件,我的项目进入时提前加载这个 json 文件,在代码中就能够用这个 json 里的env
标识来判断以后是否在生产环境。
function updateEnv(done) {
fs.writeFile(
"./temp/env.json",
JSON.stringify({env: "prod"}),
function (err) {if (err) {console.error(err);
}
done();
console.log("--------------------updateEnv");
}
);
}
3. 以上打包的 js、css 资源文件没有做文件合并的工作,如果提前进行 js,css 文件合并就能够解决了文件扩散的问题,就不须要多设置入口了