这次,研究一些稍微复杂一点的。
之前的操作,我们都是用ES5语法写的,打包直接进行合并/压缩就可以了。仅仅是让webpack
负责处理代码require
依赖而已。
代码合并例子可以体现,但是代码压缩之前的例子里面是没有的。
我们平时接触的项目,会远远比这复杂。
比如你写angular2,那么它使用typescript
,React呢,它用JSX
,或者,你更喜欢原生的代码,使用ES6来书写。
不仅如此,CSS也有很多“变种”,比如说LESS
、SASS
等。
再或者,你希望仅仅书写
1 | transition: all 2s; |
但是最终代码希望是
1 | -webkit-transition: all 2s; |
这样子,自动帮你补全兼容处理。
这时候,就需要对源代码进行加工,加工完之后,变成普通ES5代码或者CSS之后,在进行之前的打包工作。
这就是webpack
的loaders
。
所有例子的代码位置
可以访问 github 进行查阅。
DEMO4 体验一次loaders
首先创建项目工程,建立demo4
目录
建立一个./src/mian.js
文件:
1 | var hello = () => { |
我们写一句最简单的ES6语法——箭头函数。这样,打包之后,我们可以直接查看生成的JS就可以了,都不用镶嵌到html中在浏览器中查看效果~怎么样?岂不是很简单?
下面,我们要做的事情是,配置webpack.config.js
,越简单越好!
我们在原有的基础上,进行精简。之后,再试试我们的loaders
功能。
等等!我们要把我们写的./src/main.js
先转换为普通的ES5代码,才能使用我们之前的配置方案。
这时候,需要引入其他组件了,根据经验,将ES6转换为ES5,大家用的比较多的是 babel
。我们需要用 npm
安装它。如果你不会用npm
,请先查阅最基础的文章。
首先在我们的demo4
项目里,
- 创建npm配置文件:
npm init
,使用默认参数即可 - 安装babel插件:
npm install -save-dev babel-core babel-preset-es2015
- 安装webpack的babel适配器:
npm install -save-dev babel-loader
其中,babel-core
是babel的核心文件,它可以对JS进行转码。但是转成什么呢,我们需要babel-preset-es2015
,即ES5代码。
为什么还要安装babel-loader
呢?因为babel
自己是一个独立的工具,可以直接运行的。我们想在 webpack
中应用它,就需要对它做兼容处理,babel-loader
就是这个兼容器,相当于连接桥。webpack
下基本上所有的loaders(可以理解为工具/连接桥),都是xxx-loader
这种形式的。
恩,就这些,搞定了。检查下你的package.json
文件,是不是和下面的很类似,应该不缺少任何一行!当然,版本号可以和我的有出入。
1 | { |
接下来,我们来写webpack配置文件webpack.config.js
:
1 | module.exports = { |
loaders
是一个数组,因为它可以配置多种处理器。每一种处理器,是一个对象。
处理器对象要包括test
字段,用正则或者路径进行匹配要处理的文件,比如上面这个,匹配所有.js
结尾的文件。匹配成功后,会用loader
内的处理器进行处理。我们这里用babel-loader
。
loader
需要说明的是:
- 它可以是字符串,也可以直接是数组。如果是字符串,使用
!
进行分割。 - 它的插件默认都是
xxx-loader
形式,可以简写为xxx
。比如上面的例子简写是loader: 'babel'
。 - 如果是多个loader,那么处理顺序是从右往左。例如
loader: 'aaa!bbb!ccc'
,那么相当于先进行ccc-loader
处理,之后是bbb-loader
,最后是aaa-loader
。
babel
需要有自己的配置文件。我们可以在根目录下创建一个.babelrc
的json文件,或者在上面的文件内配置babel
字段。甚至还有其他的配置方法(比如在当前处理器对象中,加入query
字段),反正条条大路通罗马。
具体的插件如何配置,需要参考插件的文档。
好了,不需要在进行其他配置了,我们直接当前目录下执行webpack命令就可以了:webpack
程序自动生成了./build/app.js
。我们只需要打开查看最后一点点代码:
1 | /******/ ([ |
你看,babel已经帮我们把ES6转化为ES5了!多么神奇!
loaders的include和exclude参数
这个参数我也查过一些文档,就是说在处理时候包含/排除那些规则的文件,但是目前没有测试成功具体的用法。网络上也没查到有什么特殊的用意。
网上都是这么用的:
1 | { |
曾经测试过,加入exclude
后确实管用,可以让打出来的包更小(但是不会差别很大),但是原因还不详。
DEMO5 编写自己的loader
这次,我们自己简单的实现一个loader,来熟悉下loader的处理流程。
这里,我不做过多的扩展字段说明,可以参考例子代码中的注释。
新建一个目录,叫做demo5。入口文件 ./src/main.js
如下:由于我们不打算加入babel,所以处理不了ES6语法,我们这次试用ES5的语法。
1 | var someComponent = require('./some-component'); |
引用的文件./src/some-component.js
如下:
1 | module.exports = function(){ |
现在,我们做两个loader,这样可以更清楚的看到它的处理流程。第一个是针对当前处理的文件,通过注释的形式,给当前代码段加入文件名和版本号,第二个更简单,直接在当前代码段最前面加上 'ususe strict'
标志。
先要写好 webpack.config.js
文件:
1 | var path = require('path'); |
这里要注意的是:
- 引用的每个loader,需要使用绝对路径,这里我用
path.resolve
来解决。使用相对路径,require会找不到的! - loader如果是字符串或者数组,一定是从右往左写的。
commnetLoader
字段,我们稍后学习在loader里面进行读取使用。相当于是loader的参数。
给代码段加入文件名和版本号注释
我们就在根目录建立./comment-loader.js
文件:
1 | const path = require('path'); |
解释说明
所有的loader可以接收两个参数,分别为 source
map
,第一个是当前的源代码,第二个是sourceMap。第二个一般用不到,这里我们省略。
当前环境内的 this
,比较复杂,是webpack提供的一些方法。
通过打印 source
参数,我们可以看到内容如下:
1 | ====进入comment模块==== |
可以看到,访问了几个符合条件的文件,就要被执行几次。而参数 source
就是单纯的文件内容而已。
之后,我发现 this.options
内包含了整个webpack.config.js
内容,故直接采用 var param = this.options['commentLoader'] || {};
方式获取 commentLoader
字段内容,当然做了兼容处理,如果不存在则默认配置一个对象。
下文也是, var str = param.str || ''
,读取 param.str
字段,不存在则为空。
通过测试发现, this.resourcePath
可以获得当前正在处理的文件路径。我使用NodeJS的 path.parse
进行解析。下文就可以直接用 file.base
来获得文件名了。
最后,进行返回数据。
loader返回数据的三种方案:
- 代码是同步模式,可以最后直接返回,比如例子可以写成:
return ret;
。这个很明显有个缺陷,那就是:只能返回一个代码字符串!最上面说了,可以传入2个参数,那么第二个map参数呢?这个方法解决不了。 - 代码是同步模式,可以解决直接
return
的单参数问题:this.callback(null, ret, null)
。第一个参数不详,第二个为处理后的代码,第三个参数为map。 - 代码是异步模式,首先需要在同步代码中,调用
var cb = this.async()
。之后就可以在异步中调用cb()
了。参数和同步模式一样的。
上面的例子,为异步模式,意思为延时两秒后,进行回调。
这个看懂之后,就好办了。我们来写./strict-loader.js
文件:
1 | module.exports = function (source) { |
这个就不解释了。
最终,我们执行webpack,首先看看终端中的显示内容:
1 | ====进入comment模块==== |
可以很清晰的看到,从入口开始,遇到一个符合条件的文件,开始进入loader处理。首先将源码带入 comment-loader
,之后将输出的代码带入 strict-loader
。这样完成一个文件之后,在进行下一个文件。
再看看最终生成的代码:
1 | /******/ ([ |
也是非常符合预期的。每个代码模块标有文件名和版本号,最上面还有严格模式标签。
完成。