别被标题吓到,哈哈,即使现在vite
横空出世,社区光芒四射,两个字很快,但是webpack
依旧宝刀未老,依然扛起前端工程化的大梁,但是今天我为啥说要拥抱gulp
,因为我们经常常吃一道菜,所以要换个口味,这样才营养均衡。
gulp
定义是:用自动化构建工具
增强你的工作流程
,是一种基于任务文件流
方式,你可以在前端写一些自动化脚本,或者升级历史传统项目,解放你重复打包,压缩,解压之类的操作。
个人理解gulp
是一种命令式编程
的体验,更注重构建过程,所有的任务需要你自己手动创建,你会对构建流程会非常清楚,这点不像webpack
,webpack
就是一个开箱即用的声明式
方式,webpack
是一个模块化打包工具,内部细节隐藏非常之深,你也不需关注细节,你只需要照着提供的API
以及引入对应的loader
和plugin
使用就行。
言归正传,为了饮食均衡
,今天一起学习下gulpjs
正文开始...
搭建一个简单的前端应用
相比较webpack
,其实gulp
的项目结构更偏向传统的应用,只是我们借助gulp
工具解放我们的一些代码压缩
、es6编译
、打包
以及在传统项目中都可以使用less
体验。
在gulp
目录下新建01-simple-demo
根目录下生成默认package.json
npm init -y
然后在public
目录下新建images
、css
、js
、index.html
文件结构,大概就这样
然后在安装gulp
npm i gulp --save-dev
在根目录下新建gulpfile.js
我们先在gulpfile.js
中写入一点内容,测试一下
const defaultTask = (cb) => {
console.log('hello gulp');
cb();
};
exports.default = defaultTask;
然后我们在命令行执行
npx gulp
当我们执行npx gulp
时会默认运行gulpfile.js
导出的default
,在gulpfile.js
导出的任务会注册到gulp
任务中
在gulp
中任务主要分两种,一种是公开任务
、另一种是私有任务
公开任务
可以直接在命令执行npx gulp xxx
调用执行,比如下面的defaultTask
就是一个公开任务,只要被导出就是一个公开任务,没有被导出就是一个私有任务。
...
exports.default = defaultTask;
公有任务taskJS
// gulpfile.js
const { src, dest } = require('gulp');
const pathDir = (dir) => {
return path.resolve(__dirname, dir);
};
// todo 执行ts任务,将js目录下的js打包到dist/js目录下
const taskJS = () => {
return src(pathDir('public/**/*.js'), { sourcemaps: true }).pipe(dest(pathDir('dist/js')));
};
exports.taskJS = taskJS;
然后你在命令行执行
npx gulp taskJS
至此你会发现dist
目录下就有生成的js
了
安装 less
npm i less gulp-less --save-dev
在css/index.less
中写入测试 css 的代码
@bgcolor: yellow;
@defaultsize: 20px;
body {
background-color: @bgcolor;
}
h1 {
font-size: @defaultsize;
}
在gulpfile.js
中写入编译less
的任务,需要gulp-less
const { src, dest } = require('gulp');
const less = require('gulp-less');
const pathDir = (dir) => {
return path.resolve(__dirname, dir);
}
...
// todo less任务
const taskLess = () => {
// css目录洗的所有.less文件,dest输出到dist/css目录下
return src(pathDir('public/css/*.less')).pipe(less()).pipe(dest(pathDir('dist/css')))
}
exports.taskLess = taskLess;
命令行运行npx gulp taskLess
,结果如下
图片资源
使用一个gulp-image
插件对图片进行无损压缩处理
// gulpfile.js
const { src, dest } = require('gulp');
const image = require('gulp-image');
const path = require('path');
const pathDir = (dir) => {
return path.resolve(__dirname, dir);
}
...
// todo 图片资源
const taskImage = () => {
return src(pathDir('public/images/*.*')).pipe(image()).pipe(dest(pathDir('dist/images')))
}
exports.taskImage = taskImage;
一顿操作发现,最新版本不支持esm
,所以还是降低版本版本,这里降低到6.2.1
版本
然后运行npx gulp taskImage
图片压缩得不小
在这之前,我们分别定义了三个不同的任务,gulp
导出的任务有 公开任务和私有任务,多个公开任务可以串行组合
使用
组合任务 series 与 parallel
因此我可以将之前的介个任务组合在一起
// gulpfile.js
const { src, dest, series } = require('gulp');
const less = require('gulp-less');
const image = require('gulp-image');
const path = require('path');
const pathDir = (dir) => {
return path.resolve(__dirname, dir);
}
// todo js任务
const taskJS = () => {
return src(pathDir('public/**/*.js'), { sourcemaps: true }).pipe(dest(pathDir('dist/js')))
}
...
// series组合多个任务
const seriseTask = series(taskJS, taskLess, taskLess, taskImage)
exports.seriseTask = seriseTask;
当我在命令行npx gulp seriseTask
时 已经在dist
生成对应的文件了
编译转换 es6
在我们index.js
,很多时候是写的es6
,在gulp
中我们需要一些借助一些插件gulp-babel
,另外我们需要安装另外两个babel
核心插件@babel/core
,@babel/preset-env
npm i gulp-babel @babel/core @babel/preset-env
在gulpfile.js
中我们需要修改下
...
const babel = require('gulp-babel');
// todo js任务
// 用babel转换es6语法糖
const taskJS = () => {
return src(pathDir('public/**/*.js'), { sourcemaps: true }).pipe(babel({
presets: ['@babel/preset-env']
})).pipe(dest(pathDir('dist/js')))
}
当我们在js/index.js
写入一段测试代码
js / index.js;
const appDom = document.getElementById('app');
appDom.innerHTML = 'hello gulp';
const fn = () => {
console.log('公众号:Web技术学苑,好好学习,天天向上');
};
fn();
运行npx gulp seriseTask
箭头函数和const
申明的变量就变成了es5
了
通常情况下,一般打包后的dist
下的css
或者js
都会被压缩,在gulp
中也是需要借助插件来完成
压缩 js 与 css
压缩js
...
const teser = require('gulp-terser');
// todo js任务
const taskJS = () => {
return src(pathDir('public/**/*.js'), { sourcemaps: true }).pipe(babel({
presets: ['@babel/preset-env']
})).pipe(teser({
mangle: {
toplevel: true // 混淆代码
}
})).pipe(dest(pathDir('dist/js')))
}
...
压缩css
...
const uglifycss = require('gulp-uglifycss');
// todo less任务
const taskLess = () => {
return src(pathDir('public/css/*.less')).pipe(less()).pipe(uglifycss()).pipe(dest(pathDir('dist/css')))
}
...
在这之前我们在输出dest
时候我们都执向了一个具体的文件目录,在src
这个api
中是创建流,从文件中读取vunyl
对象,本身也提供了一个base
属性,因此你可以像下面这样写
const { src, dest, series } = require('gulp');
const less = require('gulp-less');
const image = require('gulp-image');
const babel = require('gulp-babel');
const teser = require('gulp-terser');
const uglifycss = require('gulp-uglifycss');
const path = require('path');
const pathDir = (dir) => {
return path.resolve(__dirname, dir);
};
// 设置base,当输出文件目标dist文件时,会自动拷贝当前文件夹到目标目录
const basePath = {
base: './public'
};
// todo js任务
const taskJS = () => {
return src(pathDir('public/**/*.js', basePath))
.pipe(
babel({
presets: ['@babel/preset-env']
})
)
.pipe(
teser({
mangle: {
toplevel: true // 混淆代码
}
})
)
.pipe(dest(pathDir('dist')));
};
// todo less任务
const taskLess = () => {
return src(pathDir('public/css/*.less'), basePath)
.pipe(less())
.pipe(uglifycss())
.pipe(dest(pathDir('dist')));
};
// todo 图片资源,有压缩,并输出到对应的dist/images文件夹下
const taskImage = () => {
return src(pathDir('public/images/*.*'), basePath)
.pipe(image())
.pipe(dest(pathDir('dist')));
};
// todo html
const taskHtml = () => {
return src(pathDir('public/index.html'), basePath).pipe(dest(pathDir('dist')));
};
const defaultTask = (cb) => {
console.log('hello gulp');
cb();
};
// series组合多个任务
const seriseTask = series(taskHtml, taskJS, taskLess, taskLess, taskImage);
exports.default = defaultTask;
exports.taskJS = taskJS;
exports.taskLess = taskLess;
exports.taskImage = taskImage;
exports.seriseTask = seriseTask;
将资源注入 html 中
在gulp
中,任务之间的依赖关系需要我们自己手动写一些执行任务流,现在一些打包后的dist
的文件并不会自动注入html
中。
...
const inject = require('gulp-inject');
...
// 将css,js插入html中
const injectHtml = () => {
// 目标资源
const targetSources = src(['./dist/**/*.js', './dist/**/*.css'], { read: false });
// 目标html
const targetHtml = src('./dist/*.html')
// 把目标资源插入目标html中,同时输出到dist文件下
const result = targetHtml.pipe(inject(targetSources)).pipe(dest('dist'));
return result
}
// series串行组合多个任务
const seriseTask = series(taskHtml, taskJS, taskLess, taskLess, taskImage, injectHtml)
exports.seriseTask = seriseTask;
注意一个执行顺序,必须是等前面任务执行完了,再注入,所以在series
任务的最后才执行injectHtml
操作
并且在public/index.html
下,还需要加入一段注释
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>gulp</title>
<!-- inject:css -->
<!-- endinject -->
</head>
<body>
<div id="app"></div>
<!-- inject:js -->
<!-- endinject -->
</body>
</html>
当我们运行npx gulp seriseTask
时
创建本地服务
const { src, dest, series, watch } = require('gulp');
const browserSync = require('browser-sync');
...
const taskBuild = seriseTask;
// 本地服务
const taskDevServer = () => {
// 监听public所有目录下,只要文件发生改变,就重新加载
watch(pathDir('public'), taskBuild);
// 创建服务
const server = browserSync.create();
// 调用init开启端口访问
server.init({
port: '8081', //设置端口
open: true, // 自动打开浏览器
files: './dist/*', // dist文件
server: {
baseDir: './dist'
}
})
}
exports.taskDevServer = taskDevServer;
当我们运行npx gulp taskDevServer
时,浏览器会默认打开http://localhost:8081
我们使用了一个watch
监听public
目录下的所有文件,如果文件有变化时,会执行taskBuild
任务会在dist
目录下生成对应的文件,然后会启动一个本地服务,打开一个8081
的端口就可以访问应用了。
至此一个一个用gulp
搭建的前端应用终于可以了。
重新组织 gulpfile
最后我们可以再重新组织一下gulpfile.js
,因为多个任务写在一个文件里貌似不太那么好维护,随着业务迭代,会越来越多,因此,有必要将任务分解一下
在根目录新建task
,我们把所有的任务如下
common.js
// task/common.js
const path = require('path');
const pathDir = (dir) => {
return path.join(__dirname, '../', dir);
};
const rootDir = path.resolve(__dirname, '../');
const basePath = {
base: './public'
};
const targetDest = 'dist';
module.exports = {
rootDir,
pathDir,
basePath,
targetDest
};
injectHtml.js
// task/injectHtml.js
const { src, dest } = require('gulp');
const inject = require('gulp-inject');
const { targetDest, rootDir } = require('./common.js');
// 将css,js插入html中
const injectHtml = () => {
// 目标资源
const targetSources = src([`${rootDir}/${targetDest}/**/*.js`, `${rootDir}/${targetDest}/**/*.css`]);
// 目标html
const targetHtml = src(`${rootDir}/${targetDest}/*.html`);
// 把目标资源插入目标html中,同时输出到dist文件下
const result = targetHtml.pipe(inject(targetSources, { relative: true })).pipe(dest(targetDest));
return result;
};
module.exports = injectHtml;
taskDevServer.js
const { watch } = require('gulp');
const path = require('path');
const browserSync = require('browser-sync');
const { pathDir, targetDest, rootDir } = require('./common.js');
const taskDevServer = (taskBuild) => {
return (options = {}) => {
const defaultOption = {
port: '8081', //设置端口
open: true, // 自动打开浏览器
files: `${rootDir}/${targetDest}/*`, // 当dist文件下有改动时,会自动刷新页面
server: {
baseDir: `${rootDir}/${targetDest}` // 基于当前dist目录
},
...options
};
// 监听public所有目录下,只要文件发生改变,就重新加载
watch(pathDir('public'), taskBuild);
const server = browserSync.create();
server.init(defaultOption);
};
};
module.exports = taskDevServer;
...
task/index.js
const injectHtml = require('./injectHtml.js');
const taskDevServer = require('./taskDevServer.js');
const taskHtml = require('./taskHtml.js');
const taskImage = require('./taskImage.js');
const taskJS = require('./taskJS.js');
const taskLess = require('./taskLess.js');
module.exports = {
injectHtml,
taskDevServer,
taskHtml,
taskImage,
taskJS,
taskLess
};
在gulpfile.js
中,我们修改下
// gulpfile.js
const { series } = require('gulp');
const { injectHtml, taskDevServer, taskHtml, taskImage, taskJS, taskLess } = require('./task/index.js');
// series组合多个任务
const seriseTask = series(taskHtml, taskJS, taskLess, taskLess, taskImage, injectHtml);
// 本地服务
const devServer = taskDevServer(seriseTask);
// 启动服务
const server = () => {
devServer({
port: 9000
});
};
const taskBuild = seriseTask;
const defaultTask = (cb) => {
console.log('hello gulp');
cb();
};
exports.default = defaultTask;
exports.server = server;
exports.build = taskBuild;
我们在package.json
中新增命令
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "gulp server",
"build": "gulp build"
},
npm run build
在启动server
之前,我们先执行npm run build
,然后再执行下面命令,保证browserSync
创建的服务文件夹存在,不然页面打开就404
错误
npm run server
至此gulp
搭建一个简单的应该就已经完全 ok 了
页面背景貌似有点黄
总结
gulpjs
开发是一个任务流的开发方式,它的核心思想就是用自动化构建工具增强你的工作流
,所有的自动化工作流操作都牢牢的掌握在自己手上,你可以用gulp
写一些自动化脚本,比如,文件上传,打包,压缩,或者改造传统的前端应用。用
gulp
写了一个简单的应用,但是发现中途需要找好多gulp
插件,gulp
的生态还算可以,3w
多个 star,生态相对丰富,但是有些插件常年不更新,或者版本更新不支持,比如gulp-image
,当你按照官方文档使用最新的包时,不支持 esm,你必须降低版本6.2.1
,改用cjs
才行使用
gulp
的一些常用的 api,比如src
、dest
、series
,以及browser-sync
实现本地服务,更多api参考官方文档。即使项目时间再多,也不要用
gulp
搭建前端应用,因为webpack
生态很强大了,看gulp
的最近更新还是 2 年前,但是写个自动化脚本,还算可以,毕竟gulp
的理念就是用自动化构建工具增强你工作流程
,也许当你接盘传统项目时,一些打包,拷贝,压缩文件之类的,可以尝试用用这个。本文示例code-example