如何开发一个 Webpack Loader

 我来答
育知同创教育
2017-12-07 · 百度知道合伙人官方认证企业
育知同创教育
1【专注:Python+人工智能|Java大数据|HTML5培训】 2【免费提供名师直播课堂、公开课及视频教程】 3【地址:北京市昌平区三旗百汇物美大卖场2层,微信公众号:yuzhitc】
向TA提问
展开全部

有什么样的场景需要写一个loader呢?

有一个就是在js里面写html的模板,会有点不太方便,一个是语法不是高亮的,第二个是需要双引号把每一行包起来,或者用数组push之类的,所以如果能够require一个html文件,然后转化成相应的js字符串,那就比较方便了。(如果你用react开发的,用jsx是比较自然的事情)。我在网上找了下,没有找到能够比较符合上面两点需求的loader,所以就自己尝试写一个吧。

js模板通常有三种场景:

  • 纯html,一个文件就是一个模板,直接转成字符串即可;

  • 需要有变量,一个文件有几个模板,每个模板对应一个变量

  • 再复杂一点,模板是require了其它的工厂模块生成的字符串

  • 现在开始来写loader

    loader的基本框架

    首先在工程的node_modules下面新建一个文件夹html-template-loader,在里面创建一个index.js做为这个loader的js文件。这样就可以在webpack.config.js里面添加一个loader了:

    添加loader配置

    JavaScript

    {

    test:/\.tpl\.html$/,

    loader:'html-template-loader'

    }

  •    
  • 以.tpl.html作为后缀名,也就是说在逻辑代码里面引入一个.tpl.html结尾的文件,就归这个loader处理

    require tpl html

    JavaScript

  •    
  • vartpl=require("./dialog.tpl.html");

  •    
  • 重点就在于,这个loader怎么写呢?

    loader也需要写成一个模块,一个基本的loader的写法:

    html-template-loader/index.js

    JavaScript

  •    
  • /**

    * source为原文件的字符串格式

    */

    module.exports=function(source,map){

    //对source进行解析

    varexports=process(source);

    return"module.exports = "+exports;

    }

  •    
  • 是的,你没看错,一个loader就是这么简单。关键在于理解loader的机制——其实loader最后会创建一个模块,当我们require一个需要让loader的解析的文件之后,通过上面第7行的return——return里面的内容就是自动创建的模块的内容,跟平时自已写的模块的区别就在于这个要自己拼成一个js语法合法的字符串。

    如上面生成的exports为:

    JavaScript

  •    
  • var exports="var tpl = {email: '<div>1</div>', hello: '<p>2</p>'}";

    return"var tpl = "+tpl+";\n module.exports = tpl;";

  •    
  • 那么webpack自动创建这样一个模块:

    webpack自动创建的模块

    JavaScript

  •    
  • /***/109:

    /***/function(module,exports){

    var tpl={email:'<div>1</div>',hello:'<p>2</p>'};

    module.exports=tpl;

    /***/}

  •    
  • 在我这个工程,这个模块的id为109,然后require的返回结果就是这个tpl的object。

    另外一方面,初始化的时候可以拿到source,这个source就是webpack以utf-8读的整个文件的内容,以字符串的形式作为一个参数传进来。

    所以现在目标就很明确了,我们需要处理这个source的字符串,然后再返回一个可以eval的字符串。需要定义loader的输入和输出的格式,这是作为loader开发者的权利,同时中间的处理过程对用户屏蔽。

    loader的具体实现

    把source这整一个文件,拆成一行一行处理:

    把source拆成一行一行

    JavaScript

  •    
  • var tpl="";

    source.split(/\r?\n/).forEach(function(line){

    line=line.trim();

    if(!line.length){

    return;

    }

    //对line进行处理...

    tpl+=process(line);

    }

    return "module.exports = ...";

  •    
  • 难点就在于第8行的process函数怎么写,怎样拼成一个合法的tpl字符串。

    如果是上面提到的第一种场景,则很简单,直接加就好了;

    如果是第二种场景需要用变量区分的,那么需要自定义一个语法,由于我们用的是html,因为有高亮的功能。所以可考虑用html的注释的标签:

    用<!--%变量名%-->的格式定义变量名

  •    
  • <!--%email%-->

    <div>

    <p>hello,world</p>

    </div>

    <!--%alert%-->

    <p>hi</p>

    <span>man</span>

  •    
  • 我们的语法就是<!–%变量名%–>,像上面定义了两个变量email和alert,接下来在process函数里面就可以进行识别、拼一个object的字符串。

    如果是第三种场景需要引入第三方模块的,则需要在最后一行return的时候加上require这个函数的代码,即:

    JavaScript

  •    
  • return "var widget = require(../widgetFactory");\n" +

    "module.exports=widget.make()";

  •    
  • 同样地,需要定义一个语法,require的代码用script的方式:

    需要依赖的代码格式

    JavaScript

  •    
  • <script generate>varSELECT=require("js/select");</script>

  •    
  • 以一个generate的属性标志,这块是需要依赖的代码,也就是说要把它拼在前面。

    正常的调用就用一个script,不带属性:

  •    
  • <!--%email%-->

    <div>

    <p>hello,world</p>

    </div>

    <script>

    widget.make()

    </script>

  •    
  • 最后把它转成一个object.

    经过合适的处理之后,最后生成的模块是这样的:

    JavaScript

  •    
  • /***/110:

    /***/function(module,exports,__webpack_require__){

    var widget=__webpack_require__(111);

    var tpl={

    email:'<div>'+

    '<p>\'tpl\'</p>'+

    '<p>hello, world</p>'+

    '</div>'+

    widget.make()+

    '<p>good</p>'+

    '',

    alert:'<p>hi</p>'+

    '<span>man</span>'+

    ''

    }

    module.exports=tpl

    /***/},

  •    
  • 具体处理代码略,详见github

    loader的高阶话题

    1.  loader支持链式,上一个loader的处理结果可以给下一个loader,像sass的loader是这样写的:

    sass loader

    JavaScript

  •    
  • require("!style!css!sass!./file.scss");

  •    
  • 或者是写在配置文件里面:

    webpack.config.js

    JavaScript

  •    
  • {

    test:/\.scss$/,

    loaders:["style","css","sass"]

    }

  •    
  • sass处理完之后给css的loader,css的loader处理完后给style的loader,loader间的数据传递通过loader的定义的callback函数,例如上面的loader可以在return之前再加一行:

    JavaScript

    this.callback(null,source,map);

  •    
  • 这样就可以传给下一个loader,最后一个loader一定要有return的内容;

    2. loader可以缓存,可以加快速度

    JavaScript

  •    
  • this.cacheable();

  •    
  • 3. 其它:loader支持异步,loader的加载可以用异步的方式,loader可以传参数等等,详见官方文档:HOW TO WRITE A LOADER,官方文档比较绕,不是从0开始,也没有给一个完整的demo,具体可以再另外查查。

    写loader关键在于怎么编译这个文件,上面是用的把它拆成一行一行的方式,然后做了个简单的处理,一般编译是要用到语法树。

推荐律师服务: 若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询

为你推荐:

下载百度知道APP,抢鲜体验
使用百度知道APP,立即抢鲜体验。你的手机镜头里或许有别人想知道的答案。
扫描二维码下载
×

类别

我们会通过消息、邮箱等方式尽快将举报结果通知您。

说明

0/200

提交
取消

辅 助

模 式