参考gulp官方 文档:快速入门 · gulp.js 中文文档
1.gulp是一个用于开发者在开发阶段用于自动化构建的一个工具 特点就是 简单、高效。
简单:
1.在项目中安装gulp 2.在根目录下新建一个gulpfi.js 用于编写我们需要gulp需要自动执行的构建任务。之后就可以在命令行终端使用gulp-cli 用于运行这个任务。
高效: 就是基于node的流(stream)能力,不写入磁盘,提高构建速度。
项目使用Gulp
1.安装:
项目使用gulp之前 电脑要有先决条件:
电脑要有node.js 和npm 或者 yarn (我使用yarn,后续介绍yarn)
node安装官网:Node.js
yarn 安装 官网:首页 | Yarn - JavaScript 软件包管理器 | Yarn 中文文档 - Yarn 中文网
ps:node npm cnpm yarn npx 的含义
node:
Node.js是一个Javascript运行环境(runtime)。Node.js 是基于 Chrome 的 V8 JavaScript 引擎的 JavaScript 运行时。Node.js 采用事件驱动、非堵塞 I/O 模型,使其轻量化和高效。
npm:
NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题。用于node插件管理(包括安装、卸载、管理依赖等.
常见场景:
允许用户从NPM服务器下载别人编写的第三方包到本地使用。
允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。
cnpm:
使用npm是国外的站点 所以国内就有cnpm 关于淘宝镜像的网站 当你npm安装依赖时 网不好容易报错时 就使用cnpm。淘宝团队干了这事。来自官网:“这是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读),同步频率目前为 10分钟 一次以保证尽量与官方服务同步。”
//全局安装cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm支持所有的npm命令
有两种方法:第一种就是下载cnpm 像上述命令 第二种是将npm仓库地址替换为淘宝镜像地址
npm config set registry https://registry.npm.taobao.org
查看是否更改成功
npm config get registry
以后安装时,依然用npm命令,但是实际是从淘宝国内服务器下载的
yarn:
yarn 功能定位和npm是相同的定位 都是包管理工具。是facebook发布的一款取代npm的包管理工具。
npm install -g yarn
Yarn的特性
离线模式:如果你以前安装过某个包,再次安装时可以在没有任何互联网连接的情况下进行。
确定性:不管安装顺序如何,相同的依赖关系将在每台机器上以相同的方式安装。
网络性能:Yarn 有效地将请求排序,避免请求堆积,以最大限度地提高网络利用率。
多个注册表:无论从 npm 或 Bower 安装任何包,能保持包工作流程相同。
网络恢复:单个请求失败不会导致安装失败,请求在失败时会自动重试。
平面模式:将不兼容版本的依赖项解析为单个版本,以避免创建重复项
ps再ps:最近又出现了一个pnpm 和npm yarn是一样的定位 看大家的喜好喽
pnpm :
npm i pnpm -g //查看版本 pnpm -v
pnpm创建非平铺的node_modules目录
当使用npm或yarn时,如果你有100个项目,并且所有项目都有一个相同的依赖包,那么,你在硬盘上就需要保存100份该相同依赖包的副本。
如果是使用pnpm,依赖包将被存放在一个统一的位置,因此:
如果你对同一依赖包使用相同的版本,那么磁盘上只有这个依赖包的一份文件;
如果你对同一依赖包需要使用不同的版本,则仅有版本之间不同的文件会被存储起来;
所有文件都保存在硬盘的统一的位置:
当安装软件包时,其包含的所有文件都会硬链接到此位置,而不会占用而外的硬盘空间;
这让你可以在项目之间方便地共享相同版本的依赖包。
使用pnpm安装依赖包时,只有安装的那个包会在node_modules的根目录下,并且以软链接(符号链接)的方式存在;
在node_modules的根目录下同时还会有一个.pnpm文件,里面保存的是所有包的硬链接;
其结果是,源码不可以访问本不属于当前项目所设定的依赖包
我们在这个使用gulp的项目下 所需要的就是: node npm 即可 因为作者训练使用的是yarn所以作者使用的就是 node npm yarn
npx:
ps:Node安装后自带npm模块,可以直接使用npx命令。如果不能使用,就要手动安装一下。
npm install -g npx
npx 作用是:调用项目内部安装的模块
npx 的原理,就是运行的时候,会到node_modules/.bin路径和环境变量$PATH里面,检查命令是否存在。
例如: 项目中安装了webpack
yarn add webpack -D // npm install webpack -D 这里我使用的yarn 之后就不再提示 我会把yarn和npm两个的命令行都敲出来
如果我们使用webpack,只能在项目脚本的 package.json 中的scripts字段里面, 如果想在命令行下调用,必须像下面这样。
node-modules/.bin/webpack -v
npx 可以让项目内部安装的模块用起来更方便,只要像下面这样调用就行了。
npx webpack -v
好,接下来进入正题:
1.1检查版本: 检查node npm npx的版本
node --version npm --version npx --version
1.2 安装gulp和gulp-cli(在此之前 node有了 npm有了 或者你使用yarn yarn也有了 )
项目打开 ,检查版本结束之后 在命令终端安装其他依赖
//1. 初始化项目 得到package.json文件 npm init //或者yarn 初始化项目 yarn init
//2.npm 安装 gulp命令行 npm install gulp-cli --dev //dev 本地项目使用 全局安装是 npm install --global gulp-cli //或者yarn 安装 gulp-cli yarn add gulp-cli --dev //2.安装gulp npm install gulp --dev //或者yarn安装gulp yarn add gulp --dev //检查gulp 的版本 是否安装成功 gulp --version
这时候项目中有了gulp和gulp-cli 这里说明一下为什么安装了gup还要安装gulp-cli
gulp是一个工具包,单纯的关于gulp的各种任务。
gulp-cli 就是可以在命令行运行gulp命令.而gulp只是纯粹的一个代码构建的工具包。只能被require,引用.然后执行你规定的任务.
gulp-cli才能帮助你在命令行操作gulp,并且还能看到gulp的运行状况。
gulp-cli代表你的电脑拥有了命令行执行gulp任务的能力,并不表示你可以马上运行gulp命令。要运行命令还得你在自己的项目目录中,安装了gulp模块,通过gulpfile.js创建了gulp任务,才能在这个项目目录里运行gulp命令。
现在我们是一个新建的项目 使用了init创建了package.json。安装了gulp和gulp-cli 所以我们现在就可以在gulpfile.js中敲写我们的任务,然后使用。
目前安装gulp模块,安装gulp的同时会自动安装cli模块。
2.gulpfile.js 实例
gulpfile.js
2.1 注册任务
gulp4.0之前使用task:
const gulp = require('gulp') gulp.task('bar',done=>{ console.log("bar working...") done() })
使用命令gulp bar 就会运行这个任务
但是最新的gulp :
task 不再是api推荐模式了 使用直接暴露的方式 如下:
exports.foo = done()=>{ consloe.log("foo task working...") done() //使用回调函数 标识任务完成 如果不用这个代码 就会报错 }
在命令行使用 gulp foo 命令 就会执行这个任务。
为什么使用 done() 这个代码//使用回调函数 标识任务完成 如果不用这个代码 就会报错
最新的gulp取消了同步代码模式 约定每一个任务都是异步 任务完成之后要调用回调函数或者其它方式 标识任务完成 我们用手动调用回调函数
2.2组合任务
series 将任务函数组合操作 parallel将任务函数同时执行
串型函数
const {series,parallel}=require("gulp") const task1= done=>{ setTimeout(()=>{ console.log("task1 working") done() },1000) } const task2= done=>{ setTimeout(()=>{ console.log("task2 working") done() },1000) } const task3= done=>{ setTimeout(()=>{ console.log("task3 working") done() },1000) } exports.foo=series(task1,task2,task3)//串行 exports.bar=parallel(task1,task2,task3) //并行
使用gulp foo 会依次运行task1 task2 task3 任务
使用gulp bar 会同时运行task1 task2 task3 任务
这就是series和parallel的api的区别 串行和并行任务
3.gulp的异步任务
如何通知gulp完成了异步任务
任务(task)完成通知
当从任务(task)中返回 stream、promise、event emitter、child process 或 observable 时,成功或错误值将通知 gulp 是否继续执行或结束。如果任务(task)出错,gulp 将立即结束执行并显示该错误。 (摘抄官网)
我们这里选择三种方式
3.1.回调方式解决
exports.callBack= done =>{ console.log("callBack task .."); done()//标识任务完成 } //回调函数是错误优先的执行方式 exports.callBack_error= done =>{ console.log("callBack task .."); done(new Error("fail task"))//标识任务完成 }
使用命令 yarn gulp callBack 执行任务 当任务报错时,任务执行会报错
3.2 还可以用promise的回调
exports.promise=()=>{ console.log("promise task"); return Promise.resolve()//不用加返回值 gulp会忽略 } exports.promise_error=()=>{ console.log("promise task"); return Promise.reject(new Error("fail promise"))//不用加返回值 gulp会忽略 }
3.3 async await promise的语法糖
const timeout = time=>{ return new Promise(resolve=>{ setTimeout(resolve,time) }) } exports.async= async()=>{ await timeout(1000) console.log("async await task"); }
2 通过stream方式 通过流的方式
const fs = require("fs"); //node的内置模块fs exports.stream=()=>{ const readStream=fs.createReadStream('package.json') const writeStream=fs.createWriteStream("team.txt") readStream.pipe(writeStream) return readStream } exports.stream1=(done)=>{ const readStream=fs.createReadStream('package.json') const writeStream=fs.createWriteStream("team.txt") readStream.pipe(writeStream) readStream.on(end,()=>{ done() }) return readStream } //结束实际是readStream end的时候 触发end事件 结束了任务结束
5gulp的构建过程
gulp的pipe方法是来自nodejs stream API的。
pipe方法传入方法的是一个function,这个function作用无非是接受上一个流(stream)的结果,并返回一个处理后流的结果(返回值应该是一个stream对象)。 注意的是这个函数只是一个包装,并不是会直接操作文件的。
//gulp处理文件 const fs = require("fs"); const {Transform}=require("stream") //Transform - 操作被写入数据,然后读出结果。 exports.default=()=>{ // 文件读取流 const readStream=fs.createReadStream('html.css') // 文件写入流 const writeStream=fs.createWriteStream("html.min.css") // 文件转换流 const transform = new Transform({ transform:(chunk,encoding,callback)=>{ // 核心转换过程实现 // chunk => 读取流中读取到的内容(buffer)字节流 const input = chunk.toString()//转换为字符串 const output = input.replace(/\s+/g,"").replace(/\/\*.*?\*\//g,"")//空白字符替换 替换注释 callback(null,output) } }) readStream .pipe(transform)//转换流 .pipe(writeStream)//写入流 return readStream }
构建核心工作原理就是 输入=》加工=》输出 等同于这个任务中的过程 读取文件=》压缩文件=》写入文件
6.gulp文件操作API和插件的使用
src方法创建读取流 src()
接受 glob 参数,并从文件系统中读取文件然后生成一个 Node 流(stream)。它将所有匹配的文件读取到内存中并通过流(stream)进行处理
pipe连接流 流(stream)所提供的主要的 API 是 .pipe()
方法,用于连接转换流(Transform streams)或可写流(Writable streams)。
dest方法写入流 dest()
接受一个输出目录作为参数,并且它还会产生一个 Node 流(stream),通常作为终止流(terminator stream)。当它接收到通过管道(pipeline)传输的文件时,它会将文件内容及文件属性写入到指定的目录中。
6.1 gulp样式编译 脚本编译 html编译
样式编译
yarn add gulp-clean-css --dev //样式编译的gulp插件依赖
const {src,dest}=require("gulp") const cleanCss = require("gulp-clean-css") exports.default=()=>{ return src('src/*.css') .pipe(cleanCss()) .pipe(dest("dist")) }
脚本编译
yarn add gulp-sass --dev //样式编译的gulp插件依赖 yarn add gulp-babel --dev //es语法编译babel插件 需要同时下载下面连个 yarn add @babel/core --dev //es语法编译核心插件 yarn add @babel/preset-env --dev //预设集 //babel只是帮babel/core过程 不会自动安装 需要自己安装babel/core 还需要安装@babel/preset-env" 只是一个预设集 会把es6的新特性都会做转换的一个预设集
const {src,dest}=require("gulp") //const sass = require("gulp-sass")(require("node-sass")) 使用这个是因为yarn的时候安装依赖报错了没有下载下来node-sass 下面那个是gulp-sass正常使用的 const sass = require("gulp-sass") const babel = require("gulp-babel") const style= ()=>{ return src('src/assets/styles/*.scss',{base:"src"}) .pipe(sass({outputStyle:"expanded"})) .pipe(dest("dist")) } const script = ()=>{ return src('src/assets/script/*.js',{base:"src"}) .pipe(babel({presets:["@babel/preset-env"]})) .pipe(dest('dist')) } module.exports={ style, script }
页面模版编译
yarn add gulp-swig --dev //页面模版编译的gulp插件依赖
const swig = require("gulp-swig") const page = ()=>{ return src('src/*.html',{base:"src"}) .pipe(swig()) .pipe(dest('dist')) } module.exports={ page }
6.2图片压缩以及字体转换 任务
yarn add gulp-imagemin --dev //图片压缩以及字体转换的gulp插件依赖
const imagemin = require("gulp-imagemin") const image = ()=>{ return src('src/assets/images/*',{base:"src"}) .pipe(imagemin()) .pipe(dest('dist')) } const font = ()=>{//转换字体 return src('src/assets/fonts/**',{base:"src"}) .pipe(imagemin()) .pipe(dest('dist')) } module.exports={ image, font }
6.3文件拷贝
const extra= ()=>{ return src('public/**',{base:"public"}) .pipe(dest("dist")) } module.exports={ extra } //其他文件拷贝
6.4自动加载插件
当gulp的插件过多 一个一个引用非常繁琐 就可以使用gulp-loade-plugins插件
const loadPlugins= require("gulp-load-plugins") const plugins =loadPlugins() const del = require('del') const {src,dest,parallel,series}=require("gulp") // const plugins.sass = require("gulp-sass")(require("node-sass")) // const plugins.babel = require("gulp-babel") // const plugins.swig = require("gulp-swig") // const plugins.imagemin = require("gulp-imagemin") const clean = ()=>{ //清除文件夹里的文件 return del(['dist']) } const style= ()=>{ return src('src/assets/styles/*.scss',{base:"src"}) .pipe(plugins.sass({outputStyle:"expanded"})) .pipe(dest("dist")) } const script = ()=>{ return src('src/assets/script/*.js',{base:"src"}) .pipe(plugins.babel({presets:["@babel/preset-env"]})) .pipe(dest('dist')) } const page = ()=>{ return src('src/*.html',{base:"src"}) .pipe(plugins.swig()) .pipe(dest('dist')) } const image = ()=>{ return src('src/assets/images/*',{base:"src"}) .pipe(plugins.imagemin()) .pipe(dest('dist')) } const extra= ()=>{ return src('public/**',{base:"public"}) .pipe(dest("dist")) } //module.exports={ // style, // script, // page, // image, // font, // extra //} //当暴露的任务过多时,可以根据功能使用串行和并行任务 const compile = parallel(style,script,page,image,font) const build =parallel(compile,extra) module.exports={ compile, build } //之后就可以直接使用compile 和build 命令
6.5文件清除
yarn add del --dev
const del = require('del') const loadPlugins= require("gulp-load-plugins") const plugins =loadPlugins() const del = require('del') const {src,dest,parallel,series}=require("gulp") // const plugins.sass = require("gulp-sass")(require("node-sass")) // const plugins.babel = require("gulp-babel") // const plugins.swig = require("gulp-swig") // const plugins.imagemin = require("gulp-imagemin") const clean = ()=>{ //清除文件夹里的文件 return del(['dist']) } const style= ()=>{ return src('src/assets/styles/*.scss',{base:"src"}) .pipe(plugins.sass({outputStyle:"expanded"})) .pipe(dest("dist")) } const script = ()=>{ return src('src/assets/script/*.js',{base:"src"}) .pipe(plugins.babel({presets:["@babel/preset-env"]})) .pipe(dest('dist')) } const page = ()=>{ return src('src/*.html',{base:"src"}) .pipe(plugins.swig()) .pipe(dest('dist')) } const image = ()=>{ return src('src/assets/images/*',{base:"src"}) .pipe(plugins.imagemin()) .pipe(dest('dist')) } const extra= ()=>{ return src('public/**',{base:"public"}) .pipe(dest("dist")) } //module.exports={ // style, // script, // page, // image, // font, // extra //} //当暴露的任务过多时,可以根据功能使用串行和并行任务 const compile = parallel(style,script,page,image,font) //const build =parallel(compile,extra) const build =series( clean, //清除文件放在其他关于文件插件之前 parallel( compile, extra, )) module.exports={ compile, build } //之后就可以直接使用compile 和build 命令
6.5开发服务器 热更新
yarn add browser-sync --dev
const browserSync = require("browser-sync") const bs = browserSync.create() const serve = ()=>{ bs.init({ server:{ baseDir:"dist" } }) }
监视变化以及构建优化
const serve = ()=>{ watch('src/assets/styles/*.scss',style) watch('ssrc/assets/script/*.js',script) // watch('src/assets/images/*',image) // watch('public/**',extra) bs.init({ notify:false, port:2000, server:{ baseDir:['dist','src','pubic'], routes:{ '/node_modules':'node_modules' } } }) }
useref 插件 自动构建注释
yarn add gulp-useref --dev
const useref =()=>{ return src("dist/*.html",{base:"dist"}) .pipe(plugins.useref({searchPath:['dist','.']})) .pipe(dest('dist')) }
文件压缩
yarn add gulp-uglify --dev gulp-if --dev gulp-htmlmin --dev gulp-clean-css --dev
gulp-uglify gulp-if gulp-htmlmin gulp-clean-css const useref =()=>{ return src("dist/*.html",{base:"dist"}) .pipe(plugins.useref({searchPath:['dist','.']})) // html js css 压缩 .pipe(plugins.if(/\.js$/,plugins.uglify())) .pipe(plugins.if(/\.css$/,plugins.cleanCss())) .pipe(plugins.if(/\.js$/,plugins.htmlmin())) .pipe(dest('release')) }
重新规划构建过程
那些受到useref文件压缩影响的 放到temp临时文件中 图片类的不需要 开发过程中不需要 构建的时候使用一次就行
"dependencies": { "@babel/core": "^7.5.5", "@babel/preset-env": "^7.5.5", "browser-sync": "^2.26.7", "del":"^5.1.0", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", "gulp-clean-css": "^4.2.0", "gulp-htmlmin": "^5.0.1", "gulp-if": "^3.0.0", "gulp-imagemin": "^6.1.0", "gulp-load-plugins": "^2.0.1", "gulp-sass": "^4.0.2", "gulp-swig": "^0.9.1", "gulp-uglify": "^3.0.2", "gulp-useref": "^3.1.6" }, const {src,dest,parallel,watch,series}=require("gulp") //gulp自带api const del = require("del") //删除 const browserSync = require("browser-sync") //热更新 const bs = browserSync.create() const loadPlugins= require("gulp-load-plugins") //自动加载插件直接使用plugins.方法 用gulp-if babel等 const plugins =loadPlugins() // const sass = require("gulp-sass")(require("node-sass")) // const plugins.babel = require("gulp-babel") // const plugins.swig = require("gulp-swig") // const imagemin = require("gulp-imagemin") const data = { menus:[ { name:'Home', icon:'aperture', link:"index.html" }, { name:'Features', link:"features.html" }, { name:'About', link:"about.html" }, { name:'Contact', link:"#", children:[ { name:"Twitter", link:"https://twitter.com/ddd" } ] }, ], pkg:require("./package.json"), date:new Date() } const clean = ()=>{ //清楚文件夹里的文件 return del(['dist',"temp"]) } const style= ()=>{ // 转换css return src('src/assets/styles/*.scss',{base:"src"}) .pipe(plugins.sass({outputStyle:"expanded"})) .pipe(dest("temp")) //转到临时文件 .pipe(bs.reload({stream:true})) } const script = ()=>{ //转换 js return src('src/assets/script/*.js',{base:"src"}) .pipe(plugins.babel({presets:["@babel/preset-env"]})) .pipe(dest('temp')) .pipe(bs.reload({stream:true})) } const page = ()=>{ //转换页面模版 return src('src/*.html',{base:"src"}) .pipe(plugins.swig([data])) .pipe(dest('temp')) .pipe(bs.reload({stream:true})) } const image = ()=>{ //压缩图片 return src('src/assets/images/**',{base:"src"}) .pipe(plugins.imagemin()) .pipe(dest('dist')) .pipe(bs.reload({stream:true})) } const font = ()=>{//转换字体 return src('src/assets/fonts/**',{base:"src"}) .pipe(plugins.imagemin()) .pipe(dest('dist')) } const extra= ()=>{ //其他文件 return src('public/**',{base:"public"}) .pipe(dest("dist")) } const serve = ()=>{ watch('src/assets/styles/*.scss',style) //热更新样式 watch('ssrc/assets/script/*.js',script)//热更新js watch('src/*.html',page)//热更新页面模版 watch([ 'src/assets/images/**', //图片以及其他不需要热更新的不用更新 会在开发过程中很慢 'public/**', 'src/assets/fonts/**', 'src/*.html' ],bs.reload) //不需要实时的 用reload方法 变化之后就用这个 bs.init({ notify:false, port:2000, // files:'dist/**', //不使用这个files 使用reload 把文件以流的方式推到浏览器 server:{ baseDir:['temp','src','pubic'], //css js 请求dist 图片的 请求src和public中的 routes:{ '/node_modules':'node_modules' } } }) } const useref =()=>{ //要先执行compile 再执行useref return src("temp/*.html",{base:"dist"}) .pipe(plugins.useref({searchPath:['dist','.']})) // .为跟目录 .pipe(plugins.if(/\.js$/,plugins.uglify())) // 压缩js .pipe(plugins.if(/\.css$/,plugins.cleanCss()))// 压缩css .pipe(plugins.if(/\.js$/,plugins.htmlmin({ collapseWhitespace:true,// 压缩html 去除空白字符 minifyCSS:true, //行内css压缩 minifyJS:true, //行内js压缩 }))) .pipe(dest('dist')) } // const compile = parallel(style,script,page,image) // const build = parallel(compile,extra) // 图片 文件监视 只更新一次 热更新去掉 const compile = parallel(style,script,page) //更新一次 需要启动一次 开发时候用到 const build =series( clean, parallel( series(compile,useref), extra, image, font )) //至最后一次打包的时候需要 其他开发的时候不需要转换或者压缩 上线前用到的 const devlop = parallel(compile,serve) module.exports={ clean, build, devlop, }
以上是关于gulp的一些任务实例