再谈多多猫插件开发

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载自夜明的孤行灯

本文链接地址: https://www.huangyunkun.com/2018/01/13/sited-plugin-gulp-configuration/

今天找漫画的时候又想到多多猫了,之前用过一下,当时写插件还是用的记事本在xml中直接编写js,那个体验很差,而且代码测试也很复杂。

今天又需要插件了,自然想换点花样来做,首先考虑的就是使用构建工具来处理大部分问题。这里用的是Gulp。

模板替换和文件生成

我们的目标文件只有一个,那就是index.sited.xml。而这个文件又分了两个部分,首先是xml的声明,包含了一些插件信息,其次是我们的js代码。

首先做的是将这两者分开,分别创建index.xml和index.js文件。

<?xml version="1.0" encoding="utf-8"?>
<sited ver="1" debug="1" engine="32" schema="1">
    <meta guid="<%=app.guid%>">
        <title><%= app.title %></title>
        <intro><%= app.intro %></intro>
        <author><%= app.author %></author>
        <url><%= app.url %></url>
        <encode>utf-8</encode>
        <expr><%= app.expr%></expr>
    </meta>
    <main dtype="1" durl="<%= app.url %>">
        <home>
            <hots cache="1d" title="首页" method="get" parse="hots_parse" url="<%= app.url %>"/>
        </home>
        <book cache="1d" method="get" parse="book_parse" />
        <section cache="1"  method="get" parse="section_parse" />
    </main>

    <script>
        <require>
            <item url="http://sited.noear.org/addin/js/cheerio.js" lib="cheerio"/>
        </require>
        <code>
            <![CDATA[
                <%-js%>
            ]]>
        </code>
    </script>
</sited>

这个文件使用了ejs语法,等会儿我们用ejs模板引擎来生成最终文件

function hots_parse(url, html) {
    return JSON.stringify([]);
}

function book_parse(url, html) {
    return JSON.stringify([]);
}

function section_parse(url, html) {
    return JSON.stringify([]);
}

这里面的三个方法名称和xml中的对应。

然后准备一份config.js文件来保存插件信息

const app = {
    "title": "",
    "intro": "",
    "author": "",
    "url": "http://",
    "expr": "\\.domain\\.com",
    "guid": "xxxx"
};

module.exports = app;

然后在gulp配置中添加gulp-ejs

gulp.task('build', function () {
    var jsContent = fs.readFileSync("./src/index.js", "utf8");
    var app = require('./config.js');
    gulp.src("index.xml")
        .pipe(ejs({
            app: app,
            js: uglifyJS.minify(jsContent).code
        }))
        .pipe(rename("index.sited.xml"))
        .pipe(gulp.dest("./dist"));
});

js文件我是压缩了一下再放进去,如果压缩影响了你的代码功能,你可以考虑把压缩取消掉。

这样当我们运行npm build的时候就能生成目标文件了。

开发服务器

当目标文件生成好了,我们就需要把它放到app中测试一下,这里本地搭建一个简单的文件服务器,然后通过扫描二维码直接将文件弄到手机上。

还是在gulp中,我们使用gulp-serve和qrcode-terminal,另外需要internal-ip来获取一个对外可用的IP地址。调试用的手机和电脑要在一个网络中。

gulp.task('qr', function () {
    qrcode.generate("http://" + internalIp.v4.sync() + ":" + port + "/index.sited.xml", function (qrcode) {
        console.log(qrcode);
    });
});

gulp.task('server', ['qr'], serve({
    root: ['dist'],
    port: port,
    hostname: internalIp.v4.sync()
}));

为了方便起见,当我们修改了js代码以后,最好能够自动刷新,这里用gulp-watch监控文件变动。

gulp.task('watch', ['server'], function () {
    return watch(['src/index.js', 'config.js', 'index.xml'], function () {
        gulp.start('build');
    });
});

现在运行npm run watch

拿出app扫描二维码就可以快速运行我们的新插件了。

自动化测试

多多猫插件写的时候还有一个问题就是调试很麻烦,有时候错了需要仔细脑补一下,虽然可以看日志,但是有时候日志并不能帮上多大忙。

除了开发中的问题外,还有一个问题就是插件完成后可能会失效(原因各种各样),有时候并不能第一时间知道问题,需要一个有效的机制来控制,尽可能早的知道问题。

这里就需要引入单元测试和集成测试了。

单元测试用在开发中,方便快速获得反馈,避免不停的在app上调试。集成测试会和目标网站实际通讯,以便快速验证插件是否还有效。

先来看看单元测试,首先随便抓几个网页下来,保持在项目中,测试的时候直接用这些数据就行。我这里用的mocha

var expect = require('chai').expect;
var cheerio = require('cheerio');
var fs = require('fs');

eval(fs.readFileSync('./src/index.js') + '');

describe('index.js', function () {
    it('should parse hots', function () {
        const html = fs.readFileSync('./test/data/hots_response.html');
        const response = JSON.parse(hots_parse(null, html));

        expect(response.length).to.equal(??);
        expect(response[0].name).to.equal('???');
        expect(response[0].url).to.equal('???');
        expect(response[0].logo).to.equal('???');
    });

    it('should parse book', function () {
        const html = fs.readFileSync('./test/data/book_response.html');
        const response = JSON.parse(book_parse(null, html));

        expect(response.name).to.equal('???');
        expect(response.sections.length).to.equal(??);
        expect(response.sections[0].name).to.equal('???');
        expect(response.sections[0].url).to.equal('???');
    });

    it('should parse section', function () {
        const html = fs.readFileSync('./test/data/section_response.html');
        const response = JSON.parse(section_parse(null, html));

        expect(response.length).to.equal(??);
        expect(response[0]).to.equal('http://???');
    });
});

原则上讲只要这几个测试通过,那么基本功能就没有大问题。测试中的???请替换成自己真实需要的数据。

这里面最关键的就是eval(fs.readFileSync('./src/index.js') + '');

因为我们的index.js并没有exports出任何东西,只是单纯写了三个方法,所以只有通过eval把方法放到上下文中。

集成测试就是真实请求目标网站,然后判断每个步骤都有返回且不为空。

var expect = require('chai').expect;
var cheerio = require('cheerio');
var fs = require('fs');
var request = require('request');
var prettyjson = require('prettyjson');

eval(fs.readFileSync('./src/index.js') + '');

describe('integration', function () {
    it('should able to parse all', function (done) {
        this.timeout(0);

        request('???', function (error, response, body) {
            if (error) done(error);
            var parsedResult = JSON.parse(hots_parse(null, body));
            console.log(prettyjson.render(parsedResult));
            expect(parsedResult.length > 0).to.equal(true);

            request(parsedResult[0].url, function (error, response, body) {
                if (error) done(error);
                parsedResult = JSON.parse(book_parse(null, body));
                console.log(prettyjson.render(parsedResult));
                expect(parsedResult.sections.length > 0).to.equal(true);

                request(parsedResult.sections[0].url, function (error, response, body) {
                    if (error) done(error);
                    parsedResult = JSON.parse(section_parse(null, body));
                    console.log(prettyjson.render(parsedResult));
                    expect(parsedResult.length > 0).to.equal(true);
                    done();
                });
            });
        });
    });
});

这个是一层层嵌套的,回调可以用别的库简化一下,但是这里整体比较简单,真的没有必要。

最后配置一下自己的CI服务,我用的bitbucket

运行效果如下

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载自夜明的孤行灯

本文链接地址: https://www.huangyunkun.com/2018/01/13/sited-plugin-gulp-configuration/

发表评论