webpack构造模块的道理 - 打包Library

之前一篇文章剖析了Webpack打包JS模块的基本原理,所引见的案例是最罕见的一种状况,即多个JS模块和一个进口模块,打包成一个bundle文件,可以直接被浏览器或许别的JavaScript引擎实行,相当于直接编译天生一个完全的可实行的文件。不过另有一种很罕见的状况,就是我们要构建宣布一个JavaScript的库,比方你在npm社区宣布本身的库,这时候Webpack就须要响应的设置,编译天生的代码也会略有差别。

和之前一篇文章一样,本文主要剖析的是Webpack的天生代码,并连系它来申明编译库时Webpack那些关于library的设置选项的详细作用,响应的官方文档在这里

编写JS的库

我们照样从简朴的案例最先,我们随意编写一个简朴的库util.js

import $ from 'jquery'

function sayHello() {
  console.log("Hello");
}

function hideImages() {
  $('img').hide();
}

export default {
  sayHello: sayHello,
  hideImages: hideImages
}

供应了两个函数,固然它们之间毫无关系,也实际上没有任何卵用,纯粹是仅供教授教养参考。。。

接下来写Webpack的设置:

// 进口文件
entry: {
  util: './util.js',
}

// 输出文件
output: {
  path: './dist',
  filename: '[name].dist.js'
}

但仅仅如许是不够的,如许输出的文件就是一个马上实行的函数,末了会返回util.js的exports,参照上一篇文章中剖析,末了天生的bundle代码构造大抵是如许的:

(function(modules) {
  var installedModules = {};
  
  function webpack_require(moduleId) {
     // ...
  }

  return webpack_require('./util.js');
}) ({
  './util.js': generated_util,
  '/path/to/jquery.js': generated_jquery
});

它假如实行完,那就完毕了,将util.js的export部份返回罢了,而我们须要的是将这个返回值交给编译后的文件的module.export,如许编译后的文件就成了一个可以被他人import的库。所以我们愿望获得的编译文件应该是如许的:

module.exports = (function(modules) {
  var installedModules = {};
  function webpack_require(moduleId) {
     // ...
  }
  return webpack_require('./util.js');
}) ({
  './util.js': generated_util,
  '/path/to/jquery.js': generated_jquery
});

要获得如许的效果,Webpack设置output部份须要到场library信息:

// 进口文件
output: {
  path: './dist',
  filename: '[name].dist.js',

  library: 'util',
  libraryTarget: commonjs2
}

这里最主要的就是libraryTarget,我们如今采纳commonjs2的花样,就会获得上面的编译效果,也就是说Webpack会library把末了的输出以CommonJS的情势export出来,如许就完成了一个库的宣布。

别的宣布花样

除了commonjs2,libraryTarget另有别的选项:

var (默认值,宣布为全局变量)
commonjs
commonjs2
amd
umd

运用差别的选项,编译出来的文件就可以在差别JavaScript实行环境中运用。在这里我们直接看万金油umd花样的输出是怎样的:

(function webpackUniversalModuleDefinition(root, factory) {
  if(typeof exports === 'object' && typeof module === 'object')  // commonjs2
    module.exports = factory();
  else if(typeof define === 'function' && define.amd)
    define("util", [], factory);  // amd
  else if(typeof exports === 'object')
    exports["util"] = factory();  // commonjs
  else
    root["util"] = factory();  // var
}) (window, function() {
  return (function(modules) {
    var installedModules = {};
    function webpack_require(moduleId) {
       // ...
    }
    return webpack_require('./util.js');
  }) ({
    './util.js': generated_util,
    '/path/to/jquery.js': generated_jquery
  });
}

比之前的commonjs2的状况要庞杂很多,由于它须要处置惩罚种种差别的case,但实在背面的部份都是一样的,最主要的就是最前面的几行,这是umd模块的规范写法。它运转传入的factory函数,实际上就是加载模块的函数,然后把返回的效果依据差别的运转环境交给响应的对象。比方var,那就会把效果设置为一个全局变量,这用于浏览器经由过程<script>标签直接导入该JS文件;假如是CommonJS,它则交给exports对象;假如是AMD环境,它也是用规范的AMD的写法。如许这个宣布出来的JS库就可以在恣意的环境中都能被其他人运用。

targetExport掌握输出内容

假如用umd花样打包,能够会有一个坑须要注重,假如你的库的源代码是用ES6花样export default来输出的,正如上面的例子util.js,你直接把编译后的JS库文件放到浏览器中运用,可所以<script>,或许RequireJS,能够得不到你想要的效果。这是由于你的JS文件返回给你的对象是如许的:

{
  'default': {
    sayHello: sayHello,
    hideImages: hideImages
  }
}

而不是你所希冀的:

{
  sayHello: sayHello,
  hideImages: hideImages
}

不仅是浏览器,在不支持ES6的模块体系中一样会出这个题目,就是由于它们并不熟悉default。所以你编译后的JS文件实际上应该只输出default,这就须要在Webpack设置里用targetExport来掌握:

library: 'util',
libraryTarget: umd,
targetExport: 'default'

如许上面的模块加载函数factory会在返回值背面加一个['default'],如许就只返回exports的default部份。

这个坑在umd花样下实在照样挺轻易踩到的,比方你宣布一个Vue组件,.vue文件中的JavaScript部份平常都是把Component对象以export default的花样导出的,就像如许:

export default {
  name: 'xxx',
  data() {
    return // ...
  },
  props: {
    // ...
  }
  methods: {
    // ...
  }
}

假如你把编译后的JS文件直接放在浏览器里运转,而且用CDN的体式格局加载Vue,你会发明Vue没法辨认这个Component,由于你获得的这个对象多了一层不必要的default

你能够会问假如我把输出内容改成了default,会不会影响这个模块在ES6环境下的运用?平常来说是不会的。之前一篇文章里已谈到,Webpack的天生代码在引入一个模块时,会经由过程一个叫__esModule的值来设置和推断它是不是ES6花样的export,如今假如只导出default部份,那末这个对象是被视为非ES6的,由于它不含__esModule。如许别的模块经由过程import来引入这个模块时,会引入悉数对象,这实际上变相地就等价于只引入原模块的export default部份。

固然以上议论的条件是,你一切须要export的内容悉数都在export default里。假如你既有default,又有一般的export,那编译后的文件只导出default部份显然是不可的。

    原文作者:hyuan
    原文地址: https://segmentfault.com/a/1190000013632880
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞