注:本文的重点不是如何配置webpack.config.js并实现相应的功能,而是通过对比预编译来探究webpack打包后的文件是如何加载和执行的以及webpack的编译文件。本文在讨论commonJS模块化方案的同时讨论webpack的打包工作。为了便于讨论,我们准备了一个非常简单的示例,涉及三个文件,即文件a.jsmodule.exports={say:function(){console.log('Aissaying.');}}fileb.jsmodule.exports={say:function(){console.log('Bissaying.');}}文件index.jsvara=require('./a');varb=require('./b');a.say();b.say();依赖关系很简单,就是index.js文件依赖了a.js和b.js两个文件。并且被commonJS引用。我还发布了我的配置文件。varhtmlPlugin=require('html-webpack-plugin');module.exports={entry:{index:'./src/index.js'},output:{path:'builds',filename:'[name].js',chunkFilename:'chunk.[name].js'},plugins:[newhtmlPlugin({filename:__dirname+'/builds/index.html',template:'./index.html'})],devServer:{contentBase:'./builds',inline:true}}还有一个非常简单的index.html文件。
commonJS测试另外,本题的示例代码已经放在Github上,请点击这里查看。让我们在目录中运行webpack命令。builds文件夹下的文件是webpack处理后的文件,这也是我们讨论的重点。我们看一下文件目录:在builds中是编译后的文件,在src中是我们的原始文件。a.js和b.js呢?打包到builds/index.js文件中。打包实际上就是这样做的,将多个文件合并成一个或几个文件。我们点击builds/index.js发现我们的代码已经被改得面目全非了。您可以点击这里查看完整代码,以下是部分片段。(function(modules){varinstalledModules={};function__webpack_require__(moduleId){/*omit*/}/*omit*/return__webpack_require__(0);})([//这部分是index.jsfunction(module,exports,__webpack_require__){vara=__webpack_require__(1);varb=__webpack_require__(2);a.say();b.say();},//这部分是a.jsfunction(module,exports){module.exports={say:function(){console.log('Aissaying.');}}},//这部分是b.jsfunction(module,exports){module.exports={say:function(){console.log('Bissaying.');}}}])在上面的代码中,我们的index.js、a.js和b.js被放到了一个数组中。入口文件(index.js)位于数组的位置0。入口文件(a.js和b.js)的依赖关系会按照依赖关系倒序排列。该数组作为IIFE中函数的参数模块传递给函数。注意返回__webpack_require__(0);在匿名函数中,这个调用将执行数组中的第一个函数。第一个函数:function(module,exports,__webpack_require__){vara=__webpack_require__(1);varb=__webpack_require__(2);a.说();b.say();}__webpack_require__(1)是执行数组中的第二个函数。第二个函数:function(module,exports){module.exports={say:function(){console.log('Aissaying.');}}}第二个函数的执行结果是把module.exports中的内容赋值给变量a。至此,大家对代码的结构有了一定的了解,但是关键的__webpack_require__方法是如何工作的呢?让我们分析一下代码:function__webpack_require__(moduleId){//检查模块是否在缓存中//创建一个新模块(并将其放入缓存)varmodule=installedModules[moduleId]={exports:{},id:moduleId,loaded:false};//执行模块函数modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);//将模块标记为已加载module.loaded=true;//返回模块的导出returnmodule.exports;}这里,注意代码行modules[moduleId].call(module.exports,module,module.exports,__webpack_require__)。里面的模块就是我们刚才说的数组(模块不是模块)。module.exports是模块的上下文,也就是this。所以,如果我们在index.js中添加this.name='jack',那么这相当于module.exports.name='jack';此外,我们还可以访问module.id属性。这没有任何实际意义,只是加深了我们的理解。比如我在a.js中:module.exports={say:function(){console.log('Aissaying.');控制台日志(模块.id);}}是可以导出的模块的id。还有一点,如果你使用webpack-dev-server,它会打包其他文件,id会变大(我测试了75)。有一个module.loaded属性可能有用。当我们的模块第一次执行时,module.loaded还是false,执行完后设置为true。例如下面的代码:index.jsif(module.loaded){vara=require('./a');a.say();}else{varb=require('./b');b.say();}在我们的例子中,每次执行的结果都是B在说。因为每次执行index.js模块中的代码都是第一次执行。在实际开发中,可能需要判断当前代码是否是第一次执行。还有一个重要的变量installedModules。我们加载的模块中的module.exports对象会保存在installedModules[moduleId]中。这样下次调用就可以直接返回module.exports了。OK,这次就这些了,希望对同学们学习和理解webpack有所帮助。