• gulp思考


    gulp思考

    Gulp,一个基于流的构建工具。

    这是自己写的一个构建的demo,只是一个纯演示的示例,并没有完成什么项目工作。下面根据这个demo介绍一下Gulp。

    上代码:

    gulpfile.js

    复制代码
    'use strict';
    
    /**
     *  环境
     */
    
    const env = process.argv.slice(6)[0] || 'development';
    process.env.NODE_ENV = env;
    const isDev = env == 'development';
    
    /**
     *  依赖
     */
    const browserSync = require('browser-sync');
    const reload = browserSync.reload;
    
    const watchify = require('watchify');
    const browserify = require('browserify');
    const babelify = require('babelify');
    const envify = require('envify/custom');
    
    const del = require('del');
    const source = require('vinyl-source-stream');
    const buffer = require('vinyl-buffer');
    const gulp = require('gulp');
    const $ = require('gulp-load-plugins')();
    
    /**
     * path
     */
    const pathConf =  require('./path.js');
    const path = pathConf.path;
    const filePath = pathConf.filePath;
    
    
    /**
     * browserify & watchfy
     */
    // browserify conf
    const customOpts = {
        entries: ['./src/main.js'],
        debug: true
    };
    
    // watchfy conf
    const opts = Object.assign({}, watchify.args, customOpts);
    // init watchfy
    const browserify_watch = watchify(browserify(opts));
    browserify_watch.transform(babelify, {
        presets: ["es2015", "stage-2"],
        plugins: ["transform-runtime"]
    });
    browserify_watch.transform(envify({
        NODE_ENV: env
    }));
    
    function jsBundler (ids){
        return browserify_watch
            .bundle()
            .on('error', $.util.log)
            .pipe(source("build.js"))
            .pipe(buffer())
            .pipe($.if(isDev, $.sourcemaps.init({loadMaps: true})))
            .pipe($.uglify())
            .pipe($.if(isDev, $.sourcemaps.write(".")))
            .pipe(gulp.dest(path.dist))
    }
    
    browserify_watch.on('update', jsBundler);
    browserify_watch.on('log', $.util.log);
    
    /**
     * gulp task
     */
    gulp.task('cleanAll', () => {
        del([
            path.temp + '**/.{html.js.css}', 
            path.dist + '**/.{html.js.css}', 
            '!' + path.dist + 'video/**'
        ]);
    });
    
    gulp.task('serve', () => {
        browserSync({
            server: {
                // proxy: 'xx.xx.xx.xx',
                baseDir: __dirname
            }
        });
    });
    
    gulp.task('jsBundle', jsBundler);
    
    gulp.task('less', () => {
        return gulp.src(filePath.src_less)
            .pipe($.less())
            .on("error", function (error){
                $.util.log(error);
                this.emit("end");
            })
            .pipe(gulp.dest(path.temp + 'css/'));
    });
    
    gulp.task('cssBundle', ['less'], () => {
        return gulp.src([filePath.temp_css])
            .pipe($.if(isDev, $.sourcemaps.init({loadMaps: true})))
            .pipe($.concat('build.css'))
            .pipe($.minifyCss())
            .pipe($.if(isDev, $.sourcemaps.write('.')))
            .pipe(gulp.dest(path.dist));
    });
    
    gulp.task('watchLess', () => {
        gulp.watch([filePath.src_less], ['cssBundle']);
    });
    
    gulp.task('watchDist', () => {
        gulp.watch(['index.html', path.dist + '*.*'], reload);
    });
    
    /**
     * cli
     * '$ gulp build' for development
     * '$ npm run dev' for development
     * '$ npm run pro' for production
     */
    gulp.task('build', $.sequence(
          'cleanAll'
        , ['jsBundle', 'cssBundle']
        , ['watchLess', 'watchDist']
        , 'serve'
    ));
    复制代码

    package.json

    复制代码
    {
      "name": "gulp",
      "description": "gulp",
      "author": "james",
      "scripts": {
        "dev": "gulp build --gulpfile gulpfile.js --env development",
        "pro": "gulp build --gulpfile gulpfile.js --env production"
      },
      "dependencies": {
        "jquery": "^3.1.1"
      },
      "devDependencies": {
        "babel-core": "^6.17.0",
        "babel-plugin-transform-runtime": "^6.15.0",
        "babel-preset-es2015": "^6.16.0",
        "babel-preset-stage-2": "^6.17.0",
        "babel-preset-stage-3": "^6.17.0",
        "babel-runtime": "^6.11.6",
        "babelify": "^7.3.0",
        "browser-sync": "^2.17.3",
        "browserify": "^13.1.0",
        "del": "^2.2.2",
        "envify": "^3.4.1",
        "gulp": "^3.9.1",
        "gulp-changed": "^1.3.2",
        "gulp-concat": "^2.6.0",
        "gulp-debug": "^2.1.2",
        "gulp-filter": "^4.0.0",
        "gulp-if": "^2.0.1",
        "gulp-inject": "^4.1.0",
        "gulp-jshint": "^2.0.1",
        "gulp-less": "^3.1.0",
        "gulp-load-plugins": "^1.3.0",
        "gulp-minify-css": "^1.2.4",
        "gulp-minify-html": "^1.0.6",
        "gulp-notify": "^2.2.0",
        "gulp-rename": "^1.2.2",
        "gulp-rev-all": "^0.9.7",
        "gulp-sequence": "^0.4.6",
        "gulp-sourcemaps": "^2.0.1",
        "gulp-uglify": "^2.0.0",
        "gulp-util": "^3.0.7",
        "vinyl-buffer": "^1.0.0",
        "vinyl-source-stream": "^1.1.0",
        "watchify": "^3.7.0"
      }
    }
    复制代码

    文件结构如下:

     结构说明:

    main.js 为入口文件

    path.js 定义了一些路径变量

    src 文件夹存放原始代码

    dis 文件夹存放打包后的代码

    temp 文件夹存放零食文件

    通过browserify转译commjs和es6语法,通过gulp-less转译less语法,把转译后的文件输出到dist文件夹。

    下面通过一些主要代码介绍一下Gulp的工作方式:

    先看这段代码:

    复制代码
    function jsBundler (ids){
        return browserify_watch
            .bundle()
            .on('error', $.util.log)
            .pipe(source("build.js"))
            .pipe(buffer())
            .pipe($.if(isDev, $.sourcemaps.init({loadMaps: true})))
            .pipe($.uglify())
            .pipe($.if(isDev, $.sourcemaps.write(".")))
            .pipe(gulp.dest(path.dist))
    }
    复制代码

    它主要是在watchify监听到依赖的js文件有变动之后,会自动调用的一个回调函数。函数内部实现步骤为:

    1.通过browserify以main.js为入口文件,打包依赖的所有js,并通过browserify的es2015预设插件来转译es6语法。最后将生成的流通过管道传输给vinyl-source-stream这个插件

    2.普通流通过vinyl-source-stream处理以后再传输给vinyl-buffer

    3.vinyl-buffer处理以后的流再传输给gulp-sourcemaps插件(如果环境条件判断通过的话),生成原始文件和打包后的文件映射关系(便于调试)

    4.再传输给gulp-uglify插件进行压缩和混淆

    5.输出soucemap

    6.输出打包后的js文件到dist文件夹

    看到这个流程不免有些疑问:为啥browserify传输出流不能直接给gulp插件使用呢,要先通过vinyl插件的系列处理才可以?

    原因是:browserify处理后输出的流只是普通的node.js流,gulp的插件需要的是经过处理的vinyl流。vinyl可以把普通的node.js的流转换为Vinyl File Object Stream。这样,相当于把普通的流接入到了Gulp的处理体系内。如果需要完整流,可以通过vinyl-buffer插件接收完整个流转成二进制数据以后再进行处理,通常gulp-soucemaps、gulp-uglify这些需要完整文件进行映射或者替换的插件都需要buffer一次。否则在任务运行的时候会报流不支持的错误:

     

    vinyl流的特点是它是Object风格的,除了流的内容content,还有path等属性。记录了文件的内容和文件名、文件路径的信息。

    一个很好的例子可以解释为什么需要这样:

    gulp.task("css", () => {
        gulp.src("./src/**/*.css")
            .pipe(gulp.dest("./dist/css/"));
    });

    这个Gulp任务很简单,就是把src文件夹下面所有的css(无论子文件夹层级)都'搬运"到./dist/css/这个路径下。任务执行结果当然表明这是一次成功的搬运。

    单仔细看看不难发现一个蹊跷的事情:./src/下的文件夹是不定的,可以是很多层,也可以只有一层,每个css文件可以是任何名字,那么在dest到./dist/css/还能对应上文件夹层级和文件名,这是怎么做到的呢?

    其实就是通过vinyl流(对象)的path那些属性来知道文件夹层级和文件名的。普通的node.js流只传输String或Buffer类型,也就是只关注content。

    Gulp不只用到了文件的内容,而且还用到了这个文件的相关信息。因此,vinyl流就会有contents、path这样的多个属性了。

    但是Gulp本身并不能直接生成vinyl流对象,而是依赖了一个叫做vinyl-fs的node模块,这是一个类似于文件适配器的模块。它提供三个方法:.src()、.dest()和.watch(),其中.src()将生成vinyl流对象,而.dest()将使用vinyl流,进行写入操作。

    在Gulp的包中可以看到源码:

    复制代码
    var vfs = require('vinyl-fs');
    
    // ...
    
    Gulp.prototype.src = vfs.src;
    Gulp.prototype.dest = vfs.dest;
    
    // ...
    复制代码

    可以看到gulp的src和dest方法其实就是vinyl-fs的src和dest方法。

    深入探究之后可以发现这么一条依赖关系:gulp -> vinyl-fs ->vinyl -> glob-stream -> node-glob

    从gulp.src开始到gulp.dest结束这一系列的工作流程大概是这样的:

      首先通过node-glob来匹配路径和文件,然后glob-stream把匹配到的文件流式读取, 再通过vinyl输出Gulp体系需要用到的vinyl流对象,src完成工作后 流被pipe下去,直到gulp.dest时候,通过先前已知的文件路径和文件名进行写入操作。

    如果是从webpack/browserify的普通流开始,会有它们自己的一套文件流失读取方式和输出,只要通过vinyl改造后输出vinyl流对象就能接入进Gulp体系。

    值得注意的是:写入是一个异步的操作。任务结束以后,不一定表示文件已经全部写入!

    Gulp比起Grunt更具的node.js风格, 而且其依赖的orchestrator的特性就是最大并发的执行任务。在提供了大并发和node.js异步操作特性的同时也带来了不好的一点:链式任务不好控制。

    这也是为什么build这个task是通过gulp-sequence来严格按照既定顺序执行的(通过返回流和其他异步处理的方式也可以达到效果):

    复制代码
    gulp.task('build', $.sequence(
          'cleanAll'
        , ['jsBundle', 'cssBundle']
        , ['watchLess', 'watchDist']
        , 'serve'
    ));
    复制代码

    我们一定不会希望这类事情发生:一边写入文件一边又在删除;下一个任务需要上个任务提供必要文件,但在执行的时候文件还没有写入完;等等....

    Gulp常被用于和老前辈Grunt相比较,一般都说Gulp的速度快,这其实只是Gulp基于管道流的处理速度相对于Grunt不停地生成临时文件要快。读取和写入(I/O)操作还要看机器自身情况。

    npm原生的那种类似于unix的管道符构建方式也是不错的选择。

    -转载
  • 相关阅读:
    [转]Angular2-组件间数据传递的两种方式
    [转]Angular4---部署---将Angular项目部署到IIS上
    [转]Angular开发(十八)-路由的基本认识
    [转]【Angular4】基础(一):脚手架 Angular CLI
    [转]【Angular4】基础(二):创建组件 Component
    [转]Angular项目目录结构详解
    [转]Ionic国际化解决方案
    [转]Angular CLI 安装和使用
    [转]nodejs之cordova 跨平台开发
    [转]Windows下配置Node.js和Cordova
  • 原文地址:https://www.cnblogs.com/wenJiaQi/p/6271074.html
Copyright © 2020-2023  润新知