做为近段时间风头正盛的打包工具,webpack基本占领了前端圈。相信你都很差意思说不知道webpack。
有兴趣的同窗能够参考下我很早以前的webpack简介 .
确实webpack万事万物皆模块的思路真是极大的方便了咱们的开发,将css,图片等文件都能打包的功能离不开形形色色的loader。
对于一个事情要知其然更要知其因此然,抱着这个心态咱们一块儿来看下loader的相关知识及如何开发。css
对于一个新事物最好的学习方法,我认为是其官方文档。对于loader,将其官方文档看一遍,就知道如何开发最简单的loader了。
只是其官方文档是英文的,我就顺手翻译了一下,一方面加深本身理解。另外一方面为其余同窗提供个参考。
我相信看完文档你就知道如何开发一个loader了。html
loader是一个对面暴露一个方法的node包.当遇到某些资源须要被转换时调用该方法。前端
只有一个loader来处理某个文件时,该loader被调用时只有一个参数,这个参数是该文件的内容转化以后的字符串。node
loader在function执行时能够经过this context来访问laoder API 以便更高效的开发。webpack
一个仅仅须要一个值的同步loader能够简单的return 本身。其余状况下,loader能够经过this.callback(err, values...)返回一系列的值。error一样传递给this.callback或者在loader中抛出。git
loader指望返回1-2个值,第一个是处理以后做为string或者buffer返回的js代码。第二个是SourceMap或者js 对象github
当多个loader被链式调用时,只有最后一个loader得到资源文件。
同时只有第一个loader被指望返回1-2个值(即上面提到的JavaScript和SourceMap)。
其余loader接收值由上一个loader传递。web
换句话说,链式loader执行顺序从右至左或者自下而上。
举个栗子:下面这段代码的执行顺序就是自下而上 foo-loader==>bar-loadernpm
module: { loaders: [ { test: /\.js/, loaders: [ 'bar-loader', 'foo-loader' ] } ] }
若是你的文件夹不在该目录下须要在config下面增长一项配置:
即默认访问node_modules,你的文件夹不在的话就须要手动在配置文件里加上了。编程
resolveLoader: { modules: ['node_modules', path.resolve(__dirname, 'loaders')] }
ps:通过自身实践发现这样写是错的,不须要经过path去解析,直接将文件目录写入便可。
通常来讲loader都会发布到npm上进行管理,这种情况不用担忧,可是开发阶段若是要自行测试,就面对这种状况了。
例如,我手写的myloader在loaders下面,例子以下。
resolveLoader:{ modules: ['node_modules','loader'] }
就这么简单就是个普通的loader
module.exports = function(source,map){ this.cacheable && this.cacheable() this.value = source return '/*copy@ xiaoxiangdaiyu*/'+JSON.stringify(source) }
loader须要遵循如下事项。
如下事项按优先级排列,第一条具备最高优先级。
loaders能够被链式调用,为每一步建立一个loader而非一个loader作全部事情
也就是说,在非必要的情况下没有必要将他们转换为js。
例如:经过查询字符串将一个字符串模板转化为html。
若是你写了个loader作了全部事情那么你违背了loader的第一条要求。
你应该为每个task建立一个loader而且经过管道来使用它们
loader产出的module应该和遵循和普通的module同样的设计原则。
举个例子,下面这样设计是很差的,没有模块化,依赖全局状态
require("any-template-language-loader!./xyz.atl"); var html = anyTemplateLanguage.render("xyz");
大部分loaders是cacheable,因此应该标明是否cacheable。
只须要在loader里面调用便可
// Cacheable identity loader module.exports = function(source) { this.cacheable(); return source; };
若是该loader引用了其余资源(例如文件系统), 必须声明它们。这些信息用来是缓存的loader失效而且从新编译它们
var path = require("path"); module.exports = function(source) { this.cacheable(); var callback = this.async(); var headerPath = path.resolve("header.js"); this.addDependency(headerPath); fs.readFile(headerPath, "utf-8", function(err, header) { if(err) return callback(err); callback(null, header + "\n" + source); }); };
不少语言都提供了一些规范来声明依赖,例如css中的 @import 和 url(...)。这些依赖应该被模块系统所解析。
若是语言仅仅接受相对urls(如css中url(file) 老是表明./file),使用~来讲明成模块依赖.
url(file) -> require("./file") url(~module) -> require("module")
extract common code 我感受仍是翻译成上面的标题比较好。其实全部语言都遵循该思想,即封装
不要写出来不少每一个模块都在使用的代码,在loader中建立一个runtime文件,将公共代码放在其中
不要把绝对路径写入到模块代码中。它们将会破坏hash的过程当项目的根目录发生改变的时候。应该使用loader-utils的 stringifyRequest方法来绝对路径转化为相对路径。
例子:
var loaderUtils = require("loader-utils"); return "var runtime = require(" + loaderUtils.stringifyRequest(this, "!" + require.resolve("module/runtime")) + ");";
使用peerDependency容许应用开发者去在package.json里说明依赖的具体版本。这些依赖应该是相对开放的容许工具库升级而不须要从新发布loader版本。简而言之,对于peerDependency依赖的库应该是松耦合的,当工具库版本变化的时候不须要从新变动loader版本。
有些状况下,loader须要某些可编程的对象可是不能做为序列化的query参数被方法解析。例如less-loader经过具体的less-plugin提供了这种可能。这种状况下,loader应该容许扩展webpack的options对象去得到具体的option。为了不名字冲突,基于loader的命名空间来命名是很必要的。
// webpack.config.js module.exports = { ... lessLoader: { lessPlugins: [ new LessPluginCleanCSS({advanced: true}) ] } };
至此,如何开发一个webpack loader 我相信你们已经知道了,若是还不太清楚的话,能够移步w-loader查看。
另外,对于我这种英语渣渣来讲,翻译起来确实难度蛮大的。此处抛砖引玉,但愿你们共同探讨学习。