/webpack-external-library

该文章来源于我的github文章全集,欢迎star,https://github.com/liangklfangl/react-article-bucket

Primary LanguageJavaScript

##1.webpack中的externals vs libraryTarget vs library

我们的目的只有一个,那就是编译script标签上的模块来使用。

下面的例子有两个条件:

1.我们的模块依赖于jQuery,但是我们不希望jQuery打包到最后的文件中去

2.我们的模块要存在于全局的变量Foo上面

webpack配置如下:

module.exports = {
  entry: 
  {
    main:process.cwd()+'/example1/main.js',
  },
  output: {
    path:process.cwd()+'/dest/example1',
    filename: '[name].js',
    // export itself to a global var
    libraryTarget: "var",
    // name of the global var: "Foo"
    library: "Foo"
  },
    externals: {
        // require("jquery") is external and available
        //  on the global var jQuery
        "jquery": "jQuery"
    },
  plugins: [
   new CommonsChunkPlugin({
       name:"chunk",
       minChunks:2
   }),
    new  HtmlWebpackPlugin()
  ]
};

1.1 externals参数

其中external配置表示我们的模块中的require('jquery')中的jquery来自于window.jQuery,也就是来自于全局对象jQuery,而不要单独打包到我们的入口文件的bundle中,在页面中我们通过script标签来引入!

 externals: {
        // require("jquery") is external and available
        //  on the global var jQuery
        "jquery": "jQuery"
    }

1.2 libraryTarget vs library

library:在output中配置,可以指定你的库的名称

libraryTarget:指定你的模块输出类型,可以是commonjs,AMD,script形式,UMD模式

例子1:

其中我们的libraryTarget设置为var,而library设置为'Foo'。也就是表示我们把入口文件打包的结果封装到遍历Foo上面(以下例子的external全部是一样的,见webpack.config.js文件)

output: {
    path:process.cwd()+'/dest/example1',
    filename: '[name].js',
    // export itself to a global var
    libraryTarget: "var",
    // name of the global var: "Foo"
    library: "Foo"
  }

我们看看打包的结果:

var Foo =
webpackJsonpFoo([0,1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
  var jQuery = __webpack_require__(1);
  var math = __webpack_require__(2);
  function Foo() {}
  // ...
  module.exports = Foo;
/***/ },
/* 1 */
/***/ function(module, exports) {
       module.exports = jQuery
/***/ },
/* 2 */
/***/ function(module, exports) {

       console.log('main1');

/***/ }
]);

从结果分析我们的目的,我们的入口文件的bunle被打包成为一个变量,变量名就是library指定的Foo。而且我们externals中指定的jQuery也被打包成为一个模块,但是这个模块是没有jQuery源码的,他的模块内容很简单,就是引用window.jQuery:

/* 1 */
/***/ function(module, exports) {
       module.exports = jQuery;
/***/ },

例子2:

修改webpack配置为:

    libraryTarget: "commonjs",
  // name of the global var: "Foo"
    library: "Foo"

根据library,libraryTarget定义,我们知道打包结果如下:

exports["Foo"] =
webpackJsonpFoo([0,1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {

  var jQuery = __webpack_require__(1);
  var math = __webpack_require__(2);
  function Foo() {}
     module.exports = Foo;
/***/ },
/* 1 */
/***/ function(module, exports) {
       module.exports = require("jQuery");
/***/ },
/* 2 */
/***/ function(module, exports) {
        console.log('main1');
/***/ }
]);

其中library指定的库的名称转化为exports["Foo"],!最后,很显然指定id为0的模块,也就是入口模块,最后得到的结果是符合commonjs规范的!

例子3:

修改webpack配置为:

  // export itself to a global var
    libraryTarget: "amd",
  // name of the global var: "Foo"
    library: "Foo"

编译结果为:

define("Foo", ["jQuery"], function(__WEBPACK_EXTERNAL_MODULE_1__) { 
  return webpackJsonpFoo([0,1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
  var jQuery = __webpack_require__(1);
  // var math = require(["./main1.js",'./main2.js']);
  var math = __webpack_require__(2);
  function Foo() {}
  // ...
  module.exports = Foo;
/***/ },
/* 1 */
/***/ function(module, exports) {
       //jQuery加载由amd完成,其返回的是require('jQuery')后得到的结果
        module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
/***/ },
/* 2 */
/***/ function(module, exports) {

  console.log('main1');

/***/ }
])});;

很显然是符合AMD规范的,第二个参数表示依赖的模块。jQuery被打包成为:

/***/ function(module, exports) {
       //jQuery加载由amd完成
        module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
/***/ }

而__WEBPACK_EXTERNAL_MODULE_1__指的就是jQuery,其加载有amd完成,直接require!

例子4:

webpack配置为:

  libraryTarget: "this",
  // name of the global var: "Foo"
    library: "Foo"

打包结果为:

this["Foo"] =
webpackJsonpFoo([0,1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
  var jQuery = __webpack_require__(1);
  // var math = require(["./main1.js",'./main2.js']);
  var math = __webpack_require__(2);
  function Foo() {}

  // ...

  module.exports = Foo;
/***/ },
/* 1 */
/***/ function(module, exports) {

  (function() { module.exports = this["jQuery"]; }());

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

  console.log('main1');

/***/ }
]);

此时我们的library指定为this,我们的external指定的模块编译如下:

/* 1 */
/***/ function(module, exports) {
     //this.jQuery和this.Foo
    (function() { module.exports = this["jQuery"]; }());
/***/ },

例子5:

webpack配置为:

 libraryTarget: "umd",
  // name of the global var: "Foo"
    library: "Foo"

打包结果为:

//接受到第一个参数为this(编译到this上),第二个参数就是我们的一个函数,函数参数为__WEBPACK_EXTERNAL_MODULE_1__表示我们的jQuery模块
(function webpackUniversalModuleDefinition(root, factory) {
  //commonjs直接require
  if(typeof exports === 'object' && typeof module === 'object')
    module.exports = factory(require("jQuery"));
    //amd让define函数加载
  else if(typeof define === 'function' && define.amd)
    define(["jQuery"], factory);
    //cmd(require,jQuery的处理由cmd处理),直接require
  else if(typeof exports === 'object')
    exports["Foo"] = factory(require("jQuery"));
  else
  //this
    root["Foo"] = factory(root["jQuery"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_1__) {
return webpackJsonpFoo([0,1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {

  var jQuery = __webpack_require__(1);
  // var math = require(["./main1.js",'./main2.js']);
  var math = __webpack_require__(2);
  function Foo() {}
  // ...
  module.exports = Foo;

/***/ },
/* 1 */
/***/ function(module, exports) {
        module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
/***/ },
/* 2 */
/***/ function(module, exports) {

  console.log('main1');

/***/ }
])
});
;

注意,从上面的分析可以知道,我们除了将入口文件打包成为特定的模块以外,其external模块(require('jQuery'))也要进行处理的,external的key是require时候模块名称,value是我们在页面中通过script引入的文件名!

官方实例如下:

{
    output: { libraryTarget: "commonjs" },
    externals: [
        {
            a: false, // a is not external
            b: true, // b is external `module.exports = b`
            "./c": "c", // "./c" is external `module.exports = c`
            "./d": "var d", // "./d" is external `module.exports = ./d`  <-- would be syntax error
                        "./f": "commonjs2 ./a/b", // "./f" is external `module.exports = require("./a/b")`
                        "./f": "commonjs ./a/b", // ...same as commonjs2
                        "./f": "this ./a/b" // "./f" is external `(function() { module.exports = this["./a/b"]; }())`
        },
        // Every non-relative module is external
        // abc -> require("abc")
        /^[a-z\-0-9]+$/,
        function(context, request, callback) {
            // Every module prefixed with "global-" becomes external
            // "global-abc" -> abc
            if(/^global-/.test(request))
                return callback(null, "var " + request.substr(7));
            callback();
        },
        "./e" // "./e" is external (require("./e"))
    ]
}

1.3 require数组情况

上图中我们还有很多种情况没有考虑到,如'abc.def',['abc','def'],我们按照上面的方式继续分析。

例子1:

其中我们的libraryTarget设置为var,而library设置为'Foo'。也就是表示我们把入口文件打包的结果封装到遍历Foo上面(以下例子的external全部是一样的,见webpack.config.js文件)

output: {
    path:process.cwd()+'/dest/example1',
    filename: '[name].js',
    // export itself to a global var
    libraryTarget: "var",
    // name of the global var: "Foo"
    library: "Foo"
  }

其中main.js修改为如下:

var jQuery = require("jquery");
var math = require(["./main1.js",'./main2.js']);
// var math = require("./main1.js");
function Foo() {}
// ...
module.exports = Foo;

我们看看打包的结果:

var Foo =
webpackJsonpFoo([0,2],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
  var jQuery = __webpack_require__(1);
  var math = __webpack_require__.e/* require */(1, function() {[__webpack_require__(2),__webpack_require__(3)];});
  // var math = require("./main1.js");
  function Foo() {}
  // ...
  module.exports = Foo;
/***/ },
/* 1 */
/***/ function(module, exports) {

  module.exports = jQuery;

/***/ }
]);

大部分都是和上面一样的,只是这里多了一个__webpack_require__.e方法(见下面分析),其作用就是保证模块的加载顺序!因为这里的jQuery是external表示不要单独打包进来,而是直接使用script引入,所以这里的提前加载的id为1,其就是我们的jQuery!

//提前加载external指定的jQuery,因为他是通过script直接引入,在其他模块中require
var math = __webpack_require__.e/* require */(1, function() {[__webpack_require__(2),__webpack_require__(3)];});

1.4 当require的是external中指定的数组或者'abc.def格式'

官网说明:

如果external指定为数组 ["abc", "def"],那么我们会打包成如下的格式:

module.exports = require("abc").def;

也就相当于对于abc这个external文件,我们是通过script引入的,但是我们只会引入这个模块的def部分!

实际上:

main.js如下:

var main1=require('abc');
var main1=require('def');
function Foo() {}

// ...
module.exports = Foo;

我们的webpack配置为如下:

  externals:["abc", "def"],

我们require('abc')得到的结果是:

exports["Foo"] =
webpackJsonpFoo([0,1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
  var main1=__webpack_require__(1);
  var main1=__webpack_require__(2);
  function Foo() {}
  // ...

  module.exports = Foo;

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

  module.exports = require("abc");

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

  module.exports = require("def");

/***/ }
]);

此处结果暂时存疑,以后弄懂了会及时更新

##2. webpack_require.e方法分析

/******/  __webpack_require__.e = function requireEnsure(chunkId, callback) {
/******/    // "0" is the signal for "already loaded"
            //0表示已经加载了这个模块,直接回调加载另外一个模块
            //这样可以保证我们的第一个参数的chunk是提前加载好的!
/******/    if(installedChunks[chunkId] === 0)
/******/      return callback.call(null, __webpack_require__);
/******/    // an array means "currently loading".
            //数组表示模块在加载中,这时候只是添加回调而已,所以回调可以是一个数组,但是
            //模块直接加载一次
/******/    if(installedChunks[chunkId] !== undefined) {
/******/      installedChunks[chunkId].push(callback);
/******/    } else {
/******/      // start chunk loading
             //开始加载模块
/******/      installedChunks[chunkId] = [callback];
/******/      var head = document.getElementsByTagName('head')[0];
/******/      var script = document.createElement('script');
/******/      script.type = 'text/javascript';
/******/      script.charset = 'utf-8';
/******/      script.async = true;
              //__webpack_require__.p = "";
/******/      script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"main"}[chunkId]||chunkId) + ".js";
              //直接通过chunId加载模块,文件名chunkId + "." + ({"0":"main"}[chunkId]||chunkId) + ".js"
/******/      head.appendChild(script);
/******/    }
/******/  };

参考资料:

webpack官网

webpack中library和libraryTarget与externals的使用