使用webpack对原有web工程进行改造,遇到各种问题,适当地使用各种loader加以解决。感觉有些问题具备普遍性,还是有必要记录一下的。故有此文!

在webpack中对javascript的管理也是模块化管理,所以需要使用requireexports或者define来对js模块进行引用或者导出。

当你的前端web工程没有采用js模块化编码,那么就会遇到一些问题。问题的根源分为两种:js的调用者,没有require;js的提供者,没有exports

此时,你一个个文件去修改,按规范添加requireexports,当然可以解决这些问题。你也可以使用webpack提供的各种loader去解决这些问题。

下面,我们分情况讨论解决。

一、和jQuery相关的那些事儿

jQuery本身并没有问题,自身代码里兼容了exportsdefine方法。

只是,很多依赖jQuery模块的js文件,都没有require('jquery'),导致文件内的jQuery对象不可识别。

比如Bootstrap v3.3.2,走上来,就是如下代码:

if (typeof jQuery === 'undefined') {
  throw new Error('Bootstrap\'s JavaScript requires jQuery')
}

作为模块化调用,当然无法识别jQuery了。

解决方法有三个:

1. 方法一:采用import-loader

1.1 解决方法

在加载bootstrap.js时,使用imports-loader, 代码如下:

require('imports?jQuery=jquery!bootstrap/dist/js/bootstrap');

好了,可以正常使用bootstrap.js了!

1.2 原理

imports-loader会在bootstrap的源码前面,注入如下代码:

/*** IMPORTS FROM imports-loader ***/
var jQuery = require("jquery");

1.3 使用说明:

查询参数 等效代码
angular var angular = require('angular');
$=jquery var $ = require('jquery');
config=>{size:50} var config = {size:50};
this=>window (function(){...}).call(window);

支持一次多个查询参数,之间通过逗号分隔,比如:

require("imports?$=jquery,jQuery=jquery,angular,config=>{size:50}!./file.js");

2. 方法二:采用expose-loader

expose-loader的思路是将某个对象暴露成一个全局变量。

具体到这个问题中,就是把jQuery对象暴露成全局变量。这样,那些bootstrap.js之类的文件就都能访问这个变量了。

具体做法,修改webpack.config.js文件:

module: {
  loaders: [
    { test: require.resolve("jquery"), loader: "expose?$!expose?jQuery" },
  ]
}

3. 方法三:使用webpack.ProvidePlugin

具体做法,修改webpack.config.js文件:

plugins: [   
    new webpack.ProvidePlugin({
        $: "jquery",
        jQuery: "jquery",
        "window.jQuery": "jquery"
    })
]

4. 小节

从上面的介绍可以看出,三种解决方法的思路分两类:方法一,是修改调用者;方法二、三是修改提供者。

二、自定义的js文件

这里问题,属于模块提供方没有调用export导出模块,或者没有define定义模块。

好办,使用exports-loader!这个作用和前面提到的imports-loader类似,他是在文件的最后添加一行,类似如下的代码:

/*** EXPORTS FROM exports-loader ***/
module.exports = YouModule

同样,如果需要一次导出多个对象,如同imports-loader调用,多个对象间也是逗号分隔。比如:

require("exports?file,parse=helpers.parse!./file.js");

那么,./file.js文件的最后会添加如下代码:

/*** EXPORTS FROM exports-loader ***/
exports["file"] = (file);
exports["parse"] = (helpers.parse);

三、稍微复杂一点的情况

自定义的多个js模块直接存在相互调用的情况。

比如,b.js里面调用了a.js提供的对象,但是都没有exports或者require。见示例:

文件: a.js

var A = { ... };

文件: b.js

A.exec(....)

解决办法:

require([
	'expose?A!exports?A!./a.js',
	'./b.js'
], ...);

如上,把a.js中的A定义成全景对象公布出去,这样b.js里面就可以正常使用A对象了。

除此之外,还有一个解决方案(不推荐):使用script-loader,它可以在全局环境下执行一次指定的脚本,比如:

require([
	'script!./a.js',
	'./b.js'
], ...);

这里,会把./a.js直接作为脚本执行一次。在node.js环境下,script-loader什么都不做。

四、其他

  1. file-loader : 修改文件名,放在输出目录下,并返其对应的 url .

    默认修改后的文件名,是文件内容的MD5哈希串。你也可以自定义文件名。比如:

     require("file?name=js/[hash].script.[ext]!./javascript.js");
     // => js/0dcbbaa701328a3c262cfd45869e351f.script.js
    
     require("file?name=html-[hash:6].html!./page.html");
     // => html-109fa8.html
    
     require("file?name=[hash]!./flash.txt");
     // => c31e9820c001c9c4a86bce33ce43b679
    
     require("file?name=[sha512:hash:base64:7].[ext]!./image.png");
     // => gdyb21L.png
     // use sha512 hash instead of md5 and with only 7 chars of base64
    
     require("file?name=img-[sha512:hash:base64:7].[ext]!./image.jpg");
     // => img-VqzT5ZC.jpg
     // use custom name, sha512 hash instead of md5 and with only 7 chars of base64
    
     require("file?name=picture.png!./myself.png");
     // => picture.png
    
     require("file?name=[path][name].[ext]?[hash]!./dir/file.png")
     // => dir/file.png?e43b20c069c4a01867c31e98cbce33c9
    
  2. url-loader : 这个加载器的工作方式很像file-loader。只是当文件大小小于限制值时,它可以返回一个Data Url。限制值可以作为查询参数传入。默认不限制。比如:

     require("url?limit=10000!./file.png");
     // => 如果"file.png"小于10kb,则转成一个DataUrl
    	
     require("url?mimetype=image/png!./file.png");
     // => 指定文件的mimetype (不指定,则根据文件后缀推测.)
    
  3. style-loadercss-loader: 这个很常用了,不解释。推荐用法是:require("style!css!./file.css");,类似的还有less-loader,推荐使用:require("style!css!less!./file.less");;

  4. raw-loader: 把文件内容作为字符串返回。

     var fileContent = require('raw!./file.txt');
    

    这里,把./file.txt的内容作为字符串返回。

  5. html-loader: 把Html文件输出成字符串。与raw-loader不同的是,它默认处理html中的<img src="image.png">require("./image.png"),你同时需要在你的配置中指定image文件的加载器,比如:url-loader或者file-loader

    • 你可以通过加载器的查询参数attrs来指定哪些html标签可以被处理。比如:attrs=img:srchtml-loader默认只处理图片,你也可以通过attrs,告诉他也处理javascript文件,比如:

        require("html?attrs=attrs=script:src img:src!./file.html");
      

      或者

        require("html?attrs[]=script:src&attrs[]=img:src!./file.html");
      

      多个标签用空格分隔或者使用数组设定。

    • 你也可以告诉加载器,什么都不转换,包括默认处理的image。如下设置:

        require("html?attrs=false!./file.html");
      

      或者

        require("html?-attrs!./file.html");
      
    • 当使用webpack --optimize-minimize时,html-loader会对加载的html内容最小化。

  6. source-map-loader: 方便调试,不解释。

     module.exports = {
       module: {
         preLoaders: [
           {
             test: /[\.-]min\.js$/,
             loader: "source-map-loader"
           }
         ]
       }
     };
    

    上例,是对后缀为.min.js或者-min.js结尾的文件,做source-map操作。不过,你要确保min.js文件中按规范指定了//# sourceMappingURL=xxxx.map才行!

五、总结

我们讨论了常用的loader,包括:imports-loaderexports-loaderexpose-loaderscript-loaderfile-loaderurl-loadercss-loaderstyle-loaderraw-loaderhtml-loadersource-map-loader

官网还提供了一些其他的loader,你也可以根据个性化需要,自定义一个loader。这里就不再继续讨论了。

更多webpack的知识可以从官网获取。网上也有一些不错的第三方入门的博文,比如下面两个: