什么是webpack
当开发一个具有规模的程序,将会遇到非常多的非业务问题,这些问题包括:执行效率、兼容性、代码的可维护性、可扩展性、团队协作、测试等等、我们将这些问题称之为工程问题。工程问题与业务无关,但它深刻影响哦开发难度,如果没有一个好的工具解决这些问题,将使得开发难度变得极其缓慢,同时也让开发者陷入技术的泥潭。
在浏览器端,开发时态和运行时态的侧重点不一样。
开发时态:
- 模块划分越细越好
- 支持多种模块化标准
- 支持npm或者其他包管理器下载的模块
- 能够解决其他工程化的问题
运行时态:
- 文件越少越好
- 文件体积越小越好
- 代码内容越乱越好
- 所有浏览器都要兼容
- 能够解决其他运行时的问题,主要是执行效率的问题
这种差异在小项目中表现的并不明显,可是一旦项目形成规模,就越来越明显,如果不解决这些问题,前端项目形成只能是空谈。
解决办法
既然开发时态和运行时态面临的局面有巨大差异,因此,我们需要有一个工具,这个工具能够让开发者专心的在开发时态写代码,然后利用这个工具将开发时态编写的代码转换为运行时态需要的东西。这样的工具叫做构建工具
这样一来开发者就可以专注于开发时态的代码结构,而不用担心运行时态遇到问题了。
而webpack就是众多构建工具中最流行的一种。
webpack
webpack是基于模块化的打包(构建)工具,它把一切视为模块,它通过一个开发时态的入口模块为起点,分析出所有的依赖关系,然后经过一系列的过程(压缩、合并),最终生成运行时态的文件。
至于它的一些特点在这就不多说了,官网上都有,下面进入正题
webpack打包
建立一个文件夹,有a文件和入口文件index,这两个文件中的内容如下:
// a文件里的代码
console.log('module a')
module.exports = 'a'
// index文件里的代码
console.log('index module')
var a = require('./a')
console.log(a)
写好代码之后用weboack打包之后会生成一个dist文件夹,里面有一个main.js文件,这个文件就是打包生成后的文件,我们执行的时候,就是执行的这个js文件。不知道大家有没有看过main文件里面代码长什么样,希望大家看完这篇文章去研究一下。
打包之后就是这种代码(部分):
下面我们就手动实现以下一样的结果:
建立一个my-main.js文件,这个里面就是我们的代码
1 建立一个module对象,里面的键名就是模块的路径,键值是一个函数,函数体就是这个模块里的代码。
var module = { // 该对象保存了所有的模块,以及模块对应的代码
"./src/a.js": function (module, exports) {
console.log('module a')
module.exports = 'a'
},
"./src/index.js": function (module, exports, require) {
console.log('index module')
var a = require("./src/a.js")
console.log(a)
}
}
- 这样写有一个问题,那就是会造成变量污染,所以我们把所有的代码都写在一个立即执行函数中,把module对象传到立即执行函数中,如果只是将对象名传进去还是有变量污染,干脆就直接在立即执行函数的括号里写这个对象:
(function (modules) {
//require相当于是运行一个模块,得到模块导出结果
return require('./src/index.js') // 执行入口模块
})({ // 该对象保存了所有的模块,以及模块对应的代码
"./src/a.js": function (module, exports) {
console.log('module a')
module.exports = 'a'
},
"./src/index.js": function (module, exports, require) {
console.log('index module')
var a = require("./src/a.js")
console.log(a)
}
})
- 接下来就是写出这个require函数就大功告成了:
function require(moduleId){ // moduleId就是模块的路径
var modulesExports = {} // 导出结果的缓存
if(modulesExports[moduleId]){
// 如果已经存在结果,直接导出,不再运行后面代码
return modulesExports[moduleId]
}
// 得到模块相对应的函数
var func = modules[moduleId]
var module = {
exports: {}
}
// 运行模块
func(module, module.exports, require)
// 得到模块导出结果
var result = module.exports
// 将结果存入缓存
modulesExports[moduleId] = result
// 导出结果返回
return result
}
在main.js里require就是**webpack_require**,我们替换一下就行了,这时候大家可能看到源码之后发现每个模块的函数体并不是我写的这种形式,是用的eval,其实是一样的,我们也可以改造成eval的形式,webpack用这种形式主要是为了报错的时候方便调试,使用eval的话,报错的时候就只显示报错的这几行代码,而不会显示所有,并且也可以显示出路径。
下面附上完整代码:
(function (modules) {
// require 函数相当于是运行一个模块,得到模块的导出结果
function __webpack_require__(moduleId){ // moduleId就是模块的路径
var modulesExports = {} // 导出结果的缓存
if(modulesExports[moduleId]){
// 如果已经存在结果,直接导出,不再运行后面代码
return modulesExports[moduleId]
}
// 得到模块相对应的函数
var func = modules[moduleId]
var module = {
exports: {}
}
// 运行模块
func(module, module.exports, __webpack_require__)
// 得到模块导出结果
var result = module.exports
// 将结果存入缓存
modulesExports[moduleId] = result
// 导出结果返回
return result
}
//__webpack_require__相当于是运行一个模块,得到模块导出结果
return __webpack_require__('./src/index.js') // 执行入口模块
})({ // 该对象保存了所有的模块,以及模块对应的代码
"./src/a.js": function (module, exports) {
console.log('module a')
module.exports = 'a'
},
"./src/index.js": function (module, exports, __webpack_require__) {
console.log('index module')
var a = __webpack_require__("./src/a.js")
console.log(a)
}
})
只要肯多花一点时间,我们就不再只是会一些皮毛,而是掌握真正的原理。