zoukankan      html  css  js  c++  java
  • 一篇文章带你深入当下最流行的前端构建系统 Gulp

    Gulp基本介绍

    Gulp是当下最流行的前端自动化构建系统系统系统系统,特点高效、易用

    Gulp基本使用

    • 安装依赖
    yarn  add gulp --dev
    
    • 在根目录中添加gulpfile.js

    • 在gulpfile.js中添加构建任务

    构建任务即在gulpfile.js中导出函数成员

    exports.foo=done=>{
        console.log('foo task working')
        // 需要调用done函数来标记任务结束
        done()
    }
    

    执行foo任务:

    yarn gulp foo
    

    Gulp的default任务

    当任务名是default的时候,执行此任务不需要指定任务名,执行yarn gulp就默认执行的是default任务

    exports.default=done=>{
        console.log('defaulttask working')
        // 需要调用done函数来标记任务结束
        done()
    }
    

    gulp 的task方法

    gulp的task方法是4.0版本以前的一个注册任务的方法,现在依然保留,使用此方法需要引入gulp作为依赖,现在已经不推荐使用了

    const gulp=require('gulp')
    gulp.task('bar',done=>{
      console.log('bar working')
      done()
    })
    

    Gulp的组合任务:series和parallel的使用

    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.seriesTask=series(task1,task3,task2)
    exports.parallelTask=parallel(task1,task3,task2)
    

    执行seriesTask:

    可以看见任务是一个一个顺序执行的,总耗时为3.01s

    执行parallelTask:

    并行任务同时执行,总耗时1.01s

    实际工作中,比如编译js和css,它们是互不干扰的,可以使用parallel,而像部署任务这种,需要先编译,再打包,然后再发布,这样的任务则需要使用series

    Gulp的异步任务的三种方式

    1、 通过回调的方式来标记任务完成,

    const task1=done=>{
        setTimeout(() => {
            console.log('task1 working')
            // 调用回调来标记任务完成
            done()
        }, 1000);
    }
    

    当想要标记任务失败时,可以在回调中传入一个

    const task1=done=>{
        setTimeout(() => {
            console.log('task1 working')
            // 调用回调来标记任务完成
            done(new Error('task failed!'))
        }, 1000);
    }
    

    2、通过Promise来处理异步任务

    exports.promiseTask=()=>{
        console.log('promise task working..')
        // 这里并不需要传入什么,因为gulp会忽略掉这个值
        return Promise.resolve()
    }
    

    要标记任务失败的话就返回Promise.reject

    exports.promiseTask_error=()=>{
        console.log('promise task working..')
        return Promise.reject(new Error('task failed!'))
    }
    

    3、使用async/await(node8以上版本)

    const asyncTask=time=>{
        return new Promise(resolve=>{
            setTimeout(resolve,time)
        })
    }
    exports.asyncTask=async ()=>{
        await asyncTask(1000)
        console.log('asyncTask working..')
    }
    

    stream文件流

    const fs=require('fs')
    // 读取package.json的内容并写入到temp.txt文件中
    exports.copyfile=()=>{
        const readStream=fs.createReadStream('package.json')
        const writeStream=fs.createWriteStream('temp.txt')
        // 把读取流通过管道导入写入流
        readStream.pipe(writeStream)
        // 返回流,gulp会根据流的状态来判断任务是否完成
        return readStream
    }
    

    Gulp构建过程核心工作原理

    首先为看一个css文件的转换、压缩的构建过程

    const fs=require('fs');
    const { Transform } = require('stream');
    
    exports.copyfile=()=>{
        // 文件读取流
        const readStream=fs.createReadStream('package.json')
        // 文件写入流
        const writeStream=fs.createWriteStream('temp.txt')
        // 文件转换流
        const transform=new Transform({
            transform:(chunk,encoding,callback)=>{
                // 核心转换过程实现
                // chunk=> 读取流中读取到的内容(Buffer,字节数组)
                const input=chunk.toString()
                // 替换文件中的空白字符和注释
                const output=input.replace(/s+/g,'').replace(//*.?*//g,'')
                // callback 是一个错误优先的函数,第一个参数是错误信息,没有则传入null
                callback(null,output)
            }
        })
        // 把读取流通过管道导入写入流
        readStream
        .pipe(transform)
        .pipe(writeStream)
        return readStream
    }
    

    从上面代码表示的过程中包含三个概念,分别是读取流、转换流、写入流
    通过读取流把需要转换的文件读取出来,然后通过转换流进行转换,最后使用写入流来写入到指定的文件

    Gulp的官方定义是The streaming build system,基于流的构建系统

    至于在gulp构建过程中为什么使用文件流的方式,是因为gulp希望实现构建管道的概念,这样的话在后续制作扩展插件的时候可以有一种很统一的方式

    Gulp的文件操作API

    前面的例子中使用fs来读取和写入文件,其实gulp有提供文件读取方法src,和文件写入方法dest,另外一般文件内容的转换是通过gulp的插件来完成,看下面例子:

    const {src, dest} =require('gulp')
    const cleanCss =require('gulp-clean-css')
    const rename=require('gulp-rename')
    exports.default=()=>{
        // 读取流
        return src('src/normalize.css')
        // 压缩css代码
        .pipe(cleanCss())
        // 文件后缀重命名
        .pipe(rename({extname:'.min.css'}))
        // 写入流
        .pipe(dest('dist'))
    }
    

    上面的例子使用到了gulp的src来读取css文件
    并通过gulp-clean-css插件来压缩css文件
    然后通过gulp-rename插件来重命名文件,为文件添加min后缀
    最后通过gulp的dest方法写入到dist文件夹中
    插件需要安装

    yarn add gulp-clean-css gulp-rename --dev
    

    Gulp样式编译

    以sass为例,需要先安装gulp-sass插件

    yarn add gulp-sass --dev
    
    const {src, dest} =require('gulp')
    const sass=require('gulp-sass')
    exports.style=()=>{
        // 指定base可以保持文件目录结构
        return src('src/styles/*.scss',{base:
        'src'})
        // 使用sass来转换scss文件,并设置格式为完全展开,因为默认的会使每个样式结尾的“}”都根随代码结尾,而不是换行
        .pipe(sass({outputStyle:'expanded'}))
        .pipe(dest('dist'))
    }
    

    Gulp脚本编译

    编译脚本中的es6+语法,需要使用babel

    yarn add gulp-babel @babel/core @babel/preset-env --dev
    
    const {src, dest} =require('gulp')
    const sass=require('gulp-babel')
    exports.scripts=()=>{
        return src('src/scripts/*.js',{base:'src'})
        // 这里需要添加presets,不然转换不生效
        .pipe(babel({presets:['@babel/preset-env']}))
        .pipe(dest('dist'))
    }
    

    Gulp页面模板编译

    在页面模板中使用swig模板引擎

    yarn add gulp-swig --dev
    
    // 模板中需要的数据
    const data={
        name:'myweb',
        description:'hello gulp'
    }
    const {src, dest} =require('gulp')
    const swig=require('gulp-swig')
    exports.html=()=>{
        return src('src/tempaltes/*.html',{base:'src'})
        // 在swig插件中传入数据
        .pipe(swig({data}))
        .pipe(dest('dist'))
    }
    

    模板文件:

    编译后的文件:

    图片和字体文件的转换

    图片的转换需要使用imagemin插件,imagemin插件需要使用到github上的一些c++的二进制资源,安装可能不成功,可以使用cnpm来安装可能会好一点

    cnpm install gulp-imgagemin --dev
    
    const imagemin=require('gulp-imagemin')
    exports.image=()=>{
        return src('src/images/**',{base:'src'})
        .pipe(imagemin())
        .pipe(dest('dist'))
    }
    

    字体文件的处理和图片一样都可以用imagemin插件来完成

    exports.font=()=>{
        return src('src/fonts/**',{base:'src'})
        .pipe(imagemin())
        .pipe(dest('dist'))
    }
    

    其它文件和文件的清除

    把public的文件直接拷贝到dist目录中

    exports.extra=()=>{
        return src('public/**',{base:'public'})
        .pipe(dest('dist'))
    }
    

    清除文件任务需要使用del插件:

    yarn add del --dev
    

    组合任务

    前面已经讲过,gulp可以通过series和parallel来组合串行和并行任务,那么上面的编译样式、脚本、模板html、字体和图片的任务是各不相关的,可以使用parallel来进行组合,提高编译效率

    exports.compile=parallel(this.style,this.scripts,this.html,this.image,this.font)
    

    对于pulic目录的拷贝,其实也可以放到compile中,但是因为compile只是针对src目录,对于extra可以放到build任务中,build同时包含compile,这样显得更清晰一点,还有clean任务需要优先执行完成

    exports.build=series(this.clean,parallel(this.compile,this.extra)) 
    

    自动加载插件

    可以使用gulp-load-plugins插件来自动加载插件,而不再需要重复的手动引入 每一个插件了,需要安装此插件:

    yarn add gulp-load-plugins --dev
    

    引入插件:

    const plugins =loadPlugins()
    

    需要注意的是自动加载插件会把所有的插件都归属为 plugins 的成员属性,所有使用时插件需要加上"plugins."前缀比如前面的imagemin需要改为如下所示:

    // const imagemin=require('gulp-imagemin') // 使用loadPlugins之后 ,这句不需要了
    exports.image=()=>{
        return src('src/images/**',{base:'src'})
        .pipe(plugins.imagemin())
        .pipe(dest('dist'))
    }
    

    启用热更新开发服务器

    安装browser-sync模块

    yarn add browser-sync --dev
    
    exports.serve=()=>{
        bs.init({
            notify:false,// 关闭browser-sync的提示
            // open:false,// 是否自动访问
            // files:监听dist目录中的文件的变动
            files:'dist/**',
            server:{
                // 启动目录
                baseDir:'dist'
            }
        })
    }
    

    监视变化及构建优化

    上面的serve任务只是能够监听dist目录的文件变化,但我们需要的是在开发时的src目录的变动,还需要对serve添加watch选项来监听文件的变动

    const { watch } = require('browser-sync')
    exports.serve=()=>{
        //watch第一个参数是监听的路径,第二个参数是指定任务
        watch('src/styles/*.scss',this.style)
        watch('src/scripts/*.js',this.scripts)
        watch('src/*.html',this.html)
        // 对于图片和字体,压缩前后并不会有显示上的变化,压缩是无损的,并不需要对其进行监听
        // 还有public目录是静态目录,也不需要监听
        // 若你真的需要监听,可以像下面这样写,因为这几种文件需要执行的任务都是一样的,可以写到一个数组中,以减少任务执行次数
        // watch([
        //     'src/images/**',
        //     'src/fonts/**',
        //     'public/**'
        // ],bs.reload)
        bs.init({
            notify:false,
            // 指定端口
            prot:2080,
            //open:false,
            // 表示监听dist目录中的文件的变动
            files:'dist/**',
            server:{
                //  这里会按数组由左往右的顺序找文件,即优先找dist目录
                baseDir:['dist','src','public'],
            }
        })
    }
    

    另外,对于开发环境和生产环境执行的任务也是不一样的,生产环境需要首先清除dist目录,然后进行编译,同时还要对图片和字体文件进行压缩,对静态文件进行拷贝。
    开发环境则不需要清除dist,图片和字体也不需要压缩,因为browser-sync会根据配置先在dist目录中找文件,没有则在src中找,还没有再到public目录中找,开发环境还需要启动热更新服务器,所以组合任务更改如下:

    // 编译任务
    exports.compile=parallel(this.style,this.scripts,this.html)
    // 生产执行任务
    exports.build=series(this.clean,parallel(this.compile,this.image,this.font,this.extra)) 
    // 开发执行任务
    exports.dev=series(this.compile,this.serve)
    

    gulp-useref

    当项目文件中有对类似在node_modules中的文件的引入时,比如对/node_modules/bootstrap/dist/css/bootstrap.css的引入,这样的文件在开发时,可以通过在serve中配置路由映射来进行处理:

    bs.init({
        notify:false,
        prot:2080,
        //open:false,
        // 表示监听dist目录中的文件的变动
        files:'dist/**',
        server:{
            //  这里会按数组由左往右的顺序找文件,即优先找dist目录
            baseDir:['dist','src','public'],
            routes:{
                '/node_modules':'node_modules'
            }
        }
    })
    

    但这样并不能解决在生产环境时的情况,因为对应的文件并没有拷贝到dist目录
    这里可以使用useref插件来获取引用的文件并拷贝到指定路径,useref可以处理的引用标签外添加的构建注释,如下图所示,另外如果构建中包含多个引入,则会把这些引入打包到同一个文件中

    <!-- build:css styles/vender.css -->
    <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
    <!-- endbuild -->
    <!-- build:css styles/main.css -->
    <link rel="stylesheet" href="styles/main.css">
    <!-- endbuild -->
    

    useref处理过后的引入:

    分别压缩html、css、js

    上面的useref已经把引入的资源获取并打包进了dist目录,但是还存在问题就是这些文件不一定是被压缩的,现在来对html、css、js分别进行不同的压缩工作
    需要分别安装对应插件:gulp-htmlmin、gulp-clean-css、gulp-uglify
    这时候对三种不同类型的文件进行操作,需要对文件类型进行区分,需要使用插件gulp-if
    另外上面的usered输出到release目录中,而其它文件依然在dist目录中,这样是不符合我们预期的,我们需要把全部的文件都放到dist中,那么我们可以把html、css、js这些需要语法编译的内容先通过compile输出到temp目录中,再通过useref把这些文件从temp经处理再输出到dist中

    style、html、scirpts三个任务需要进行一些调整,即dest的输出路径改为temp

    exports.style=()=>{
        return src('src/styles/*.scss',{base:
        'src'})
        // 使用sass来转换scss文件,并设置格式为完全展开,因为默认的会使每个样式结尾的“}”都根随代码结尾,而不是换行
        .pipe(sass({outputStyle:'expanded'}))
        .pipe(dest('temp'))
    }
    
    exports.scripts=()=>{
        return src('src/scripts/*.js',{base:'src'})
        // 这里需要添加presets,不然转换不生效
        .pipe(babel({presets:['@babel/preset-env']}))
        .pipe(dest('temp'))
    }
    
    const data={
        name:'myweb',
        description:'hello gulp'
    }
    const swig=require('gulp-swig')
    exports.html=()=>{
        return src('src/*.html',{base:'src'})
        .pipe(swig({data}))
        .pipe(dest('temp'))
    }
    

    serve任务的baseDir也要调整:

    exports.serve=()=>{
        //watch第一个参数是监听的路径,第二个参数是指定任务
        watch('src/styles/*.scss',this.style)
        watch('src/scripts/*.js',this.scripts)
        watch('src/*.html',this.html)
        // 对于图片和字体,压缩前后并不会有显示上的变化,并不需要对其进行监听
        // 还有public目录是静态目录,也不需要监听
        // 若你真的需要监听,可以像下面这样写,因为这几种文件需要执行的任务都是一样的,可以写到一个数组中,以减少任务执行次数
        // watch([
        //     'src/images/**',
        //     'src/fonts/**',
        //     'public/**'
        // ],bs.reload)
        bs.init({
            notify:false,
            prot:2080,
            //open:false,
            // 表示监听dist目录中的文件的变动
            files:'dist/**',
            server:{
                //  这里会按数组由左往右的顺序找文件,即优先找dist目录
                baseDir:['temp','src','public'],
                routes:{
                    '/node_modules':'node_modules'
                }
            }
        })
    }
    
    yarn add gulp-if --dev
    

    useref 从temp中读取文件并经转换后输出到dist目录中

    const loadPlugins=require('gulp-load-plugins')
    const plugins =loadPlugins()
    exports.useref=()=>{
        return src('temp/*.html',{base:'temp'})
        .pipe(plugins.useref({searchPath:['temp','.']}))
        // 分别对html、css、js进行压缩
        .pipe(plugins.if(/.js$/,plugins.uglify({
            //mangle: false,//是否改变变量名
        })))
        .pipe(plugins.if(/.css$/,plugins.cleanCss()))
        .pipe(plugins.if(/.html$/,plugins.htmlmin({
            collapseWhitespace:true,
            minifyCSS:true,
            minifyJS:true
        })))
        .pipe(dest('dist'))
    }
    

    最后还需要调整一下组合任务,把useref添加到组合任务,useref只在build的时候需要使用,所以这里只需要再调整build任务:

    exports.build=series(this.clean,parallel(series(this.compile,this.useref) ,this.image,this.font,this.extra)) 
    

    到此,gulpfile.js的代码总体展示如下:

    const {series,parallel, src, dest} =require('gulp')
    const browserSync=require('browser-sync')
    const bs=browserSync.create()
    
    const loadPlugins=require('gulp-load-plugins')
    const plugins =loadPlugins()
    const data={
        name:'myweb',
        description:'hello gulp'
    }
    const del=require('del')
    const { watch } = require('browser-sync')
    const style=()=>{
        return src('src/styles/*.scss',{base:
        'src'})
        // 使用sass来转换scss文件,并设置格式为完全展开,因为默认的会使每个样式结尾的“}”都根随代码结尾,而不是换行
        .pipe(plugins.sass({outputStyle:'expanded'}))
        .pipe(dest('temp'))
    }
    
    const scripts=()=>{
        return src('src/scripts/*.js',{base:'src'})
        // 这里需要添加presets,不然转换不生效
        .pipe(plugins.babel({presets:['@babel/preset-env']}))
        .pipe(dest('temp'))
    }
    const html=()=>{
        return src('src/*.html',{base:'src'})
        .pipe(plugins.swig({data}))
        .pipe(dest('temp'))
    }
    const image=()=>{
        return src('src/images/**',{base:'src'})
        .pipe(plugins.imagemin())
        .pipe(dest('dist'))
    }
    const font=()=>{
        return src('src/fonts/**',{base:'src'})
        .pipe(plugins.imagemin())
        .pipe(dest('dist'))
    }
    const extra=()=>{
        return src('public/**',{base:'public'})
        .pipe(dest('dist'))
    }
    const clean=()=>{
        return del(['dist','temp'])
    }
    const serve=()=>{
        //watch第一个参数是监听的路径,第二个参数是指定任务
        watch('src/styles/*.scss',style)
        watch('src/scripts/*.js',scripts)
        watch('src/*.html',html)
        // 对于图片和字体,压缩前后并不会有显示上的变化,并不需要对其进行监听
        // 还有public目录是静态目录,也不需要监听
        // 若你真的需要监听,可以像下面这样写,因为这几种文件需要执行的任务都是一样的,可以写到一个数组中,以减少任务执行次数
        // watch([
        //     'src/images/**',
        //     'src/fonts/**',
        //     'public/**'
        // ],bs.reload)
        bs.init({
            notify:false,
            prot:2080,
            //open:false,
            // 表示监听dist目录中的文件的变动
            files:'dist/**',
            server:{
                //  这里会按数组由左往右的顺序找文件,即优先找dist目录
                baseDir:['temp','src','public'],
                routes:{
                    '/node_modules':'node_modules'
                }
            }
        })
    }
    const useref=()=>{
        return src('temp/*.html',{base:'temp'})
        .pipe(plugins.useref({searchPath:['temp','.']}))
        // 分别对html、css、js进行压缩
        .pipe(plugins.if(/.js$/,plugins.uglify({
            mangle: true,
        })))
        .pipe(plugins.if(/.css$/,plugins.cleanCss()))
        .pipe(plugins.if(/.html$/,plugins.htmlmin({
            collapseWhitespace:true,
            minifyCSS:true,
            minifyJS:true
        })))
        .pipe(dest('dist'))
    }
    // 编译任务
    const compile=parallel(style,scripts,html)
    // 打包执行任务
    const build=series(clean,parallel(series(compile,useref) ,image,font,extra)) 
    // 开发执行任务
    const dev=series(compile,serve)
    module.exports={
        clean,
        build,
        dev
    }
    

    上面的gulpfile.js中的内容进行了一点额外的处理,就是只导出了几个任务,而其它的任务作为了私有成员,因为平时使用时,只会用到这几个任务,
    接下来我们可以把这几个任务添加到package.json的scripts中,这样应该不会有人还不知道这几个任务是干什么用的吧。

    "scripts": {
        "clean":"gulp clean",
        "build":"gulp build",
        "dev":"gulp dev"
      },
    

    这样我们可以直接使用yarn cleanyarn buildyarn dev来使用

  • 相关阅读:
    Cassandra vs. HBase
    游戏留存率分析
    Writing a Discard Server
    t
    启动进程 派生 关闭而不关闭
    单页应用 SAP Vue
    TiDB 整体架构 结合yarn zookeeper分析架构
    Writing a Simple YARN Application 从hadoop生态抽出yarn ,单独使用yarn
    Phoenix put the sql back in NoSql
    事件序列化器 Flume 的无数据丢失保证,Channel 和事务
  • 原文地址:https://www.cnblogs.com/MissSage/p/14899477.html
Copyright © 2011-2022 走看看