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