gulp插件编写指南(转)

你的插件不应该去做一些现有 node 模块已经能很容易做到的事情

比如:

  • 删除一个文件夹并不需要做成一个 gulp 插件,在 task 里使用一个类似 del 这样的插件即可。
  • 只是为了封装而封装一些的东西进去,这只会增加很多低质量的插件到生态中,这不符合 gulp 的期望。
  • gulp 插件都是以文件为基础操作的,如果你发现你正在把一些很复杂的操作塞进 stream 中去,那么,请直接写一个 node 模块就好。
  • 一个好的 gulp 插件例子像是 gulp-coffee,coffee-script 模块并不能直接和 vinyl 做很好的适配,因此,才去封装它来使用相应的功能,并且将一些比较痛苦的操作抽象出来,做成更简单的 gulp 插件来使用。

你的插件应该只做一件事,并且做好。

  • 避免使用配置选项,使得你的插件能胜任不同场合的任务。
  • 比如:一个 JS 压缩插件不应该有一个加头部的选项

你的插件不能去做一些其他插件做的事:

  • 不应该去拼接,用 gulp-concat 去做
  • 不应该去增加头部,用 gulp-header 去做
  • 不应该去增加尾部,用 gulp-footer 去做
  • 如果是一个常用的可选的操作,那么,请在文档中注明你的插件通常和其他某个插件一起使用
  • 在你的插件中使用其他的插件,这能大大减少你的代码量,并保证生态系统的稳定。

你的插件必须被测试过

  • 测试一个插件很简单,你甚至不需要 gulp 就能测试
  • 参考其他的插件是怎么做的

其他注意事项

  • 在 package.json 中增加一个名为 gulpplugin 的关键字,这可以让它能在我们的搜索中出现
  • 你应该以触发 error 事件来代替
  • 如果你在 stream 外面遇到错误,比如在创建 stream 时候发现错误的配置选项等,那么你应该抛出它。错误需要加上以你插件名字作为前缀,比如: gulp-replace: Cannot do regexp replace on a stream。使用 gulp-util 的 PluginError 类来完成它
  • file.contents 的类型需要总是在输入输出中保持一致;如果 file.contents 为空 (不可读) 请将他忽略,并传过去;如果 file.contents 是一个 stream,但是你不支持,那么请触发一个错误;不要把 stream 硬转成 buffer 来使你的插件支持 stream,这会引发很严重的问题。在你处理完成之前,不要将 file 传到下游去;使用 file.clone() 来复制一个文件或者创建另一个以此为基础的文件
  • 不要把 gulp 作为一个依赖,使用 gulp 来测试你的插件的工作流这的确很酷,但请务必确保你将它放到 devDependency 中
    在你的插件中依赖 gulp,这意味着安装你的插件的用户将会重新安装一遍 gulp 以及所有它所依赖的东西。没有任何理由说明你需要将 gulp 写到你的插件代码中去,如果你发现你必须这么做,那么请开一个 issue,我们会帮你解决。

为什么这些指导这么严格?

gulp 的目标是为了让用户觉得简单,通过提供一些严格的指导,我们就能提供一致并且高质量的生态系统给大家。不过,这确实给插件作者增加了一些需要考虑的东西,但是也确保了后面的问题会更少。

如果我不遵守这些,会发生什么?

npm 对每个人来说是免费的,你可以开发任何你想要开发的东西出来,并且不需要遵守这个规定。我们承诺测试机制将会很快建立起来,并且加入我们的插件搜索中。如果你坚持不遵守插件导览,那么这会反应在我们的打分/排名系统上,人们都会更加喜欢去使用一个 “更加 gulp” 的插件。

一个插件大概会是怎么样的?

// through2 是一个对 node 的 transform streams 简单封装
var through = require('through2');
var gutil = require('gulp-util');
var PluginError = gutil.PluginError;

// 常量
const PLUGIN_NAME = 'gulp-prefixer';

function prefixStream(prefixText) {
  var stream = through();
  stream.write(prefixText);
  return stream;
}

// 插件级别函数 (处理文件)
function gulpPrefixer(prefixText) {

  if (!prefixText) {
    throw new PluginError(PLUGIN_NAME, 'Missing prefix text!');
  }
  prefixText = new Buffer(prefixText); // 预先分配

  // 创建一个让每个文件通过的 stream 通道
  return through.obj(function(file, enc, cb) {
    if (file.isNull()) {
      // 返回空文件
      cb(null, file);
    }
    if (file.isBuffer()) {
      file.contents = Buffer.concat([prefixText, file.contents]);
    }
    if (file.isStream()) {
      file.contents = file.contents.pipe(prefixStream(prefixText));
    }

    cb(null, file);

  });

};

// 暴露(export)插件主函数
module.exports = gulpPrefixer;

BAT——JS模板引擎哪家强?

JavaScript 模板引擎作为数据与界面分离工作中最重要一环,越来越受开发者关注和喜爱,那么国内的前端界是怎么样呢?我们不妨先看看BAT他们是怎么做的。

看介绍

百度:baiduTemplate

baiduTemplate希望创造一个用户觉得“简单好用”的JS模板引擎

淘宝:juicer,

Juicer 是一个高效、轻量的前端 (Javascript) 模板引擎,效率和易用是它追求的目标。 除此之外,它还可以运行在 Node.js 环境中。

腾讯:artTemplate

新一代 javascript 模板引擎

首先从名字上来说,我会记住百度和的腾讯的,国为baidu,art,template这三个单词我能记住,juicer我还得学一下:[‘dʒuːsə]榨汁器;照明技师;酒鬼(俚语)。

看用法

当然这不是最重要的,我们还是来看一下他们的用法。

var data = {

name : ‘world!’

};

//artTemplate

var str1 = template.render(‘<p>{{name}}</p>’)(data);

console.log(str1);

//juicer

var str2 = juicer(‘<p>${name}</p>’, data);

console.log(str2);

//baiduTemplate

var str3 = baidu.template(‘<p><%=name%></p>’, data);

console.log(str2);

最简单先过了一下,用法比较相似,都是传下模板和数据,而且都是模板字符串在前,数据在后,注意了template.render返回的是一个方法,个人认为有加分。

接下去我们再看一下循环

<script id=”tpl1″ type=”text/html”>

{{each list as value i}}

<p>索引 {{i + 1}} :{{value.name}}</p>

{{/each}}

</script>

<script id=”tpl2″ type=”text/html”>

{@each list as value,i}

<p>索引 ${Number(i) + 1} :${value.name}</p>

{@/each}

</script>

<script id=”tpl3″ type=”text/html”>

<% for(var i=0;i<list.length;i++){%>

<p>索引 <%=i+1%> :<%=list[i].name %></p>

<% } %>

</script>

<script>

//artTemplate

var html1 = template(‘tpl1′, list);

console.log(html1);

//juicer

var html2= juicer(document.getElementById(‘tpl2′).innerHTML, list);

console.log(html2);

////baiduTemplate

var html3= juicer(document.getElementById(‘tpl3′).innerHTML, list);

console.log(html3);

</script>

artTemplate又有一个小贴心了,只要传script标签就会自动取html了,好吧,再次给你加分。

看网站

过了两个简单的用法之后,我们来看看他们的网站。

artTemplate:

https://github.com/aui/artTemplate (3231 Star)

Latest commit b31492f  on 23 Oct 2015

BaiduTemplate:

http://baidufe.github.io/BaiduTemplate/

https://github.com/wangxiao/BaiduTemplate (292 Star)

Latest commit 324f9f5  on 15 Jan 2013

juicer:

http://juicer.name/

https://github.com/PaulGuo/Juicer (627 Star)

Latest commit 9709d18  on 1 Apr 2016

从Star数上来看artTemplate又一次加分了,不过本人还是很公平的,都给了star。

从最后提交代码来看,BaiduTemplate已经有多年不更新了。

artTemplate没有做自己的官网,而是把github当作了官网。

juicer自己的官网,没有链接可以链到github,感觉应该加一个。

看大小

接下来看看下载下来的源码的大小

artTemplate:5.24k,有压缩

BaiduTemplate: 8.96k,未压缩

juicer:7.71k,有压缩

其实这几k大小影响不大,除非真的很在意。BaiduTemplate如果要用,建议自己再压缩一下。

看效率

其实大多时候,除了只要自己觉得用得爽就行了,效率还是很重要的。我把上面的代码循环1000次做了简单的测试。

console.time(‘a’);

for (var i = 0; i < 1001; i++) {

template.render(‘<p>{{name}}</p>’)(data);

};

console.timeEnd(‘a’);

// 148.759ms 146.308ms 153.022ms 151.757ms 146.887ms

console.time(‘b’);

for (var i = 0; i < 1001; i++) {

juicer(‘<p>${name}</p>’, data);

};

console.timeEnd(‘b’);

// 2.022ms 2.468ms 1.846ms 1.824ms 3.330ms

console.time(‘c’);

for (var i = 0; i < 1001; i++) {

baidu.template(‘<p><%=name%></p>’, data);

};

console.timeEnd(‘c’);

// 404.429ms 399.213ms 402.100ms 402.016ms 400.970ms

不比不知道,一比吓一跳,BaiduTemplate这回是得减分了。这回只做了一个简单的比较,如果有更详情的内容,后期再补上。

什么?全静态网站要做分页?

做为一个前端,我想听到这句话还是很崩溃的,要做一个全静态的网站。

我第一个考虑的问题是:头和尾可以单独出来做,然后引用吗?

于是我照着这样子的思路就去做了。

首先我确定使用gulp来构建这个项目,因为是这个比较简单。

我的目前结构是这样子的

build
node_modules
src
gulpfile.js
package.json

也就是在src下面进行开发,然后构后之后的网站就在build下面

下面说说需要用到的node模块:

一、gulp 这个是必须的,不多说了

二、gulp-watch 文件有变化需要知道

三、gulp-livereload 配合chrome浏览器来使用,不用按F5了

四、gulp-ejs 模板引擎

五、gulp-connect 简洁的本地服务器

六、gulp-less 把less转成css

七、gulp-cssmin 把css压缩

八、gulp-imagemin 图片压缩

九、gulp-uglify JS压缩

代码片段


<%- include('tpl/header.html', {mIndex : 1}) %>

<div class="header-wrap">
    <div class="header w1000">
        <a href="index.html"><img src="images/logo.png" alt="" /></a>
    </div>
    <div class="menu w1000 tar">
        <a class="<%= mIndex === 0 && 'current' %>" href="index.html">首页</a>
        <a class="<%= mIndex === 1 && 'current' %>" href="about.html">关于我们</a>
        <a class="<%= mIndex === 2 && 'current' %>" href="partner-a-1.html">合作伙伴</a>
        <a class="<%= mIndex === 3 && 'current' %>" href="join.html">合作加盟</a>
        <a class="<%= mIndex === 4 && 'current' %>" href="channel-a.html">推广渠道</a>
        <a class="<%= mIndex === 5 && 'current' %>" href="contact.html">联系我们</a>
    </div>
</div>

上面就是头部代码了,还有分页的代码

<div class="page-wrap">
    <%- include('tpl/page.html', {
        list : [1,2,3],
        name : 'news-a',
        curr : 1
    }) %>
</div>
<div class="page">
<% if(curr > 1) {%>
        <a href="<%=name%>-<%=curr-1%>.html" class="page-prev">&lt;</a>
<% } %>

<% list.forEach(function(v){ %>
    <a href="<%=name%>-<%=v%>.html" class="page-a <%= v == curr && 'current'%>"><%=v%></a>
<% }) %>


<% if(curr < list.length) {%>
        <a href="<%=name%>-<%=curr+1%>.html" class="page-next">&gt;</a>
<% } %>
</div>

差不多就这样子搞定了。

package.json里面devDependencies的对象

{
    "gulp": "^3.9.0",
    "gulp-ejs": "~2.1.2",
    "gulp-watch": "~4.3.9",
    "gulp-connect": "~5.0.0",
    "gulp-livereload": "~3.8.1",
    "gulp-less": "~3.1.0",
    "gulp-imagemin": "~3.0.2",
    "gulp-htmlmin": "~2.0.0",
    "gulp-cssmin": "~0.1.7",
    "gulp-uglify": "1.5.4"
  }

最后就是gulp的配置文件了

var gulp = require("gulp");
var watch = require('gulp-watch');
var livereload = require('gulp-livereload');
var ejs = require("gulp-ejs");
var connect = require('gulp-connect');
var less = require('gulp-less');
var imagemin = require('gulp-imagemin');
var htmlmin = require('gulp-htmlmin');
var cssmin = require('gulp-cssmin');
var uglify = require('gulp-uglify');



gulp.task('js', function() {
    return gulp.src('src/js/**/*.js')
        .pipe(uglify())
        .pipe(gulp.dest('build/js'));
});


gulp.task('img', function() {
    return gulp.src('src/images/**/*')
        .pipe(imagemin())
        .pipe(gulp.dest('build/images'));
});


gulp.task('css', function() {
    return gulp.src('./src/css/**/*.less')
        .pipe(less())
        .pipe(cssmin())
        .pipe(connect.reload())
        .pipe(gulp.dest('build/css'));
});

gulp.task('html', function() {
    return gulp.src('src/*.html')
        .pipe(ejs())
        .pipe(htmlmin({
            collapseWhitespace: true
        }))
        .pipe(connect.reload())
        .pipe(gulp.dest('build'));
});



gulp.task('watch', function() {
    gulp.watch('src/**/*', ['html', 'css', 'js', 'img']);
});


gulp.task('connect', function() {
    connect.server({
        root: 'build',
        livereload: true
    });
});


gulp.task('default', ['connect', 'watch']);

什么是 “use strict”; ? 使用它的好处和坏处分别是什么?

ECMAscript 5添加了第二种运行模式:”严格模式”(strict mode)。这种模式使得Javascript在更严格的条件下运行。

优点:
– 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
– 消除代码运行的一些不安全之处,保证代码运行的安全;
– 提高编译器效率,增加运行速度;
– 为未来新版本的Javascript做好铺垫。

缺点:
同样的代码,在”严格模式”中,可能会有不一样的运行结果;
一些在”正常模式”下可以运行的语句,在”严格模式”下将不能运行。

参考链接
– MDN, Strict mode
– Dr. Axel Rauschmayer,JavaScript’s strict mode: a summary
– Douglas Crockford, Strict Mode Is Coming To Town
– http://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html

apicloud上传图片到数据云

前提条件:
1、创建了APP, 创建了APP后就有appId和appKey
2、开启数据云

上传图片的几个注意点:
1.我们需要一个图片,可以是一个图片地址,但是这个图片地址是手机上的地址。(widget://这种是不行的)
3.”X-APICloud-AppKey”: “{{加密后的key}}”
4.加密需要sha1.js 下载地址是:(https://github.com/apicloudcom/mcm-js-sdk/blob/master/SHA1.js)

接下去就接上示例代码了:

document.getElementById('btn').onclick = function() {
    api.getPicture({
        sourceType: 'album',
        encodingType: 'jpg',
        mediaValue: 'pic',
        destinationType: 'url',
        allowEdit: false,
        quality: 80,
        targetWidth: 150,
        saveToPhotoAlbum: false
    }, function(ret, err) {
        if (ret) {

            var appId = "123"; //填写自己的appId
            var appKey = "xxx"; //填写自己的appKey
            var now = Date.now();

            appKey = SHA1(appId + "UZ" + appKey + "UZ" + now) + "." + now; //进行加密

            api.ajax({
                url: "http://d.apicloud.com/mcm/api/file",
                method: "POST",
                data: {
                    files: {
                        file: ret.data
                    }
                },
                headers: {
                    "X-APICloud-AppId": appId,
                    "X-APICloud-AppKey": appKey
                }
            }, function(ret, err) {
                if (ret) {
                    api.alert(JSON.stringify(ret));
                }
            });

        } else {
            alert(JSON.stringify(err));
        }
    });
}

什么是三元表达式 (Ternary expression)?“三元 (Ternary)” 表示什么意思?

想要说明白这个问题。

首先要知道什么表达式。
表达式即是javascript的一个短语,javascript解释器会将其计算出一个结果。
换一种方式理解,把表达式在chrome的控制台运行之后,就会有一个结果,比如1,回车之后就得出1

再则需要知道什么是三元
三元指的是三个操作数:即条件,结果1,结果2

那么三元表达式:
就是含有三个操作数,组成的表达式,比如:1 ? true : false;
条件1: 三个操作数。
条件2: 表达式,能计算出一个结果。

其他:
三元表达式用的运算符是三目运算符?:

注:(个人理解,仅供参考,不代表正确回答。)

如何取得一个安全的全局对象

javascript已经学了好久了,可是我总觉的了解的不够深入。奇奇怪怪的问题也很多。今天就发现奇怪的事了,分享给大家。

一、全局的window是不能被改变的

在浏览器里面的,你可以在任何地方,都可以使用window来获取全局对象。

//全局的window值是不能被改变的
window = 1;
console.log(window);

var window = 2;
console.log(window);


//来源:http://f00sun.com/271.html

二、方法里面的window是可以被改变的。

可是你不知道,在局部的方法里面,如果window被当作变量名之后,window就取不到全局变量了。

function test(){
    var window = 1;
    console.log(window);
}
test(1);



//来源:http://f00sun.com/271.html

三、取得一个安全的全局对象

function test(){
    var window = 1;
    console.log(this);
    console.log(window);

    //var g = (eval)('this');  //这样子是不行的
    var g = (1, eval)('this');
    console.log(g);
}

test.call(1, 1);



//来源:http://f00sun.com/271.html

四、也可以这样子来取得

function test(){
    var window = 1;
    console.log(this);
    console.log(window);

    var g =(function () { 
       return this;
    }());
    console.log(g);
}

test.call(1, 1);

node采集爬取gb2312,gbk出现乱码

在用node在采集的时候,如果页面是gb2312,很可能是乱码的。这时候就需要自己去解码。

如果是Windows下的用户,请先装两个包
1. npm i iconv-lite
2. npm i bufferhelper

装好两个包之后。改下代码就可以解决了,示例如下:

var http = require('http'), 
var url = require('url').parse('http://www.baidu.com/');
var iconv = require('iconv-lite'); 
var BufferHelper = require('bufferhelper');
 
http.get(url,function(res){
  var bufferHelper = new BufferHelper();
  res.on('data', function (chunk) {
    bufferHelper.concat(chunk);
  });
  res.on('end',function(){ 
    console.log(iconv.decode(bufferHelper.toBuffer(),'GBK'));
  });
})

其他提示:
1.node原生只能解码 utf8, ucs2 / utf16-le, ascii, binary, base64, hex.
2. windows 用户无法安装iconv,所以用iconv-lite

如何实现链式调用?每次return this

var $ = function(n) {
    return new $.prototype.init(n);
};

$.prototype = {
    init: function(n) {
        this.n = n;
        return this; //返回this
    },
    double: function() {
        this.n *= 2;
        return this; //返回this
    },
    negative: function() {
        this.n = -this.n;
        return this; //返回this
    },
    val: function() {
        return this.n;
    }
};

$.prototype.init.prototype = $.prototype;


//测一下
var res = $(2).double().negative().val();
console.log(res);

jquery为什么不用new一下?因为用了无new构造函数

var $ = function() {
    //每次都构建新的init实例对象,来分隔this,避免交互混淆
    return new $.prototype.init();
};

//定义原型链
$.prototype = {
    init: function() {
        return this;
    },
    test: function() {
        console.log('test');
    }
};

//原型传递,$的原型对象覆盖了init构造器的原型对象
$.prototype.init.prototype = $.prototype;


//测一下
$().test();