webpack

须要webpack

去搞清楚webpack作了什么以前,我以为首先要思考一下咱们为何须要webpack,它究竟解决了什么痛点。想一想咱们平常搬砖的场景:
1.开发的时候须要一个开发环境,要是咱们修改一下代码保存以后浏览器就自动展示最新的代码那就行了(热更新服务)
2.本地写代码的时候,要是调后端的接口不跨域就行了(代理服务)
3.为了跟上时代,要是能用上什么ES678N等等新东西就行了(翻译服务)
4.项目要上线了,要是能一键压缩代码啊图片什么的就行了(压缩打包服务)
5.咱们平时的静态资源都是放到CDN上的,要是能自动帮我把这些搞好的静态资源怼到CDN去就行了(自动上传服务)javascript

  • 若是与输入相关的需求,找entry(好比多页面就有多个入口)
  • 若是与输出相关的需求,找output(好比你须要定义输出文件的路径、名字等等)
  • 若是与模块寻址相关的需求,找resolve(好比定义别名alias)
  • 若是与转译相关的需求,找loader(好比处理sass处理es678N)
  • 若是与构建流程相关的需求,找plugin(好比我须要在打包完成后,将打包好的文件复制到某个目录,而后提交到git上)

webpack打包出来的什么

webpack搞了不少东西,但最终产出的无非就是通过重重服务处理过的代码,那么这些代码是怎样的呢?
首先咱们先来看看入口文件index.js:java

console.log('index') const one = require('./module/one.js') const two = require('./module/two.js') one() two()

嗯,很简单,没什么特别,引入了两个模块,最后执行了它们一下。其中one.js和two.js的代码也很简单,就是导出了个函数:node

// one.js module.exports = function () { console.log('one') }
// two.js module.exports = function () { console.log('two') }

好了,就是这么简单的代码,放到webpack打包出来的是什么呢?webpack

/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { console.log('index') const one = __webpack_require__(1) const two = __webpack_require__(2) one() two() /***/ }), /* 1 */ /***/ (function(module, exports) { module.exports = function () { console.log('one') } /***/ }), /* 2 */ /***/ (function(module, exports) { module.exports = function () { console.log('two') } /***/ }) /******/ ]);

简化webpack打包出来的代码

其实进过简化后就能够看到,这些代码意图十分明显,也是咱们十分熟悉的套路。git

(function (modules) { const require = function (moduleId) { const module = {} module.exports = null modules[moduleId].call(module, module, require) return module.exports } require(0) })([ function (module, require) { console.log('index') const one = require(1) const two = require(2) one() two() }, function (module, require) { module.exports = function () { console.log('one') } }, function (module, require) { module.exports = function () {


1.观察一下,咱们须要一个自执行函数,这里面须要控制的是这个自执行函数的传参,就是那个数组
2.这个数组是毋容置疑是根据依赖关系来造成的
3.咱们要找到全部的require而后将require的路径替换成对应数组的索引
4.将这个处理好的文件输出出来
ok,上代码:es6

const fs = require('fs') const path = require('path') const esprima = require('esprima') const estraverse = require('estraverse') // 定义上下文 即全部的寻址都按照这个基准进行 const context = path.resolve(__dirname, '../') // 处理路径 const pathResolve = (data) => path.resolve(context, data) // 定义全局数据格式 const dataInfo = { // 入口文件源码 source: '', // 分析入口文件源码得出的依赖信息 requireInfo: null, // 根据依赖信息得出的各个模块 modules: null } /** * 读取文件 * @param {String} path */ const readFile = (path) => { return new Promise((resolve, reject) => { fs.readFile(path, function (err, data) { if (err) { console.log(err) reject(err) return } resolve(data) }) }) } /** * 分析入口源码 */ const getRequireInfo = () => { // 各个依赖的id 从1开始是由于0是入口文件 let id = 1 const ret = [] // 使用esprima将入口源码解析成ast const ast = esprima.parse(dataInfo.source, {range: true}) // 使用estraverse遍历ast estraverse.traverse(ast, { enter (node) { // 筛选出require节点 if (node.type === 'CallExpression' && node.callee.name === 'require' && node.callee.type === 'Identifier') { // require路径,如require('./index.js'),则requirePath = './index.js' const requirePath = node.arguments[0] // 将require路径转为绝对路径 const requirePathValue = pathResolve(requirePath.value) // 如require('./index.js')中'./index.js'在源码的位置 const requirePathRange = requirePath.range ret.push({requirePathValue, requirePathRange, id}) id++ } } }) return ret } /** * 模块模板 * @param {String} content */ const moduleTemplate = (content) => `function (module, require) {\n${content}\n},` /** * 获取模块信息 */ const getModules = async () => { const requireInfo = dataInfo.requireInfo const modules = [] for (let i = 0, len = requireInfo.length; i < len; i++) { const file = await readFile(requireInfo[i].requirePathValue) const content = moduleTemplate(file.toString()) modules.push(content) } return modules } /** * 将入口文件如require('./module/one.js')等对应成require(1)模块id */ const replace = () => { const requireInfo = dataInfo.requireInfo // 须要倒序处理,由于好比第一个require('./module/one.js')中的路径是在源码字符串42-59这个区间 // 而第二个require('./module/two.js')中的路径是在源码字符串82-99这个区间,那么若是先替换位置较前的代码 // 则此时源码字符串已经少了一截(从'./module/one.js'变成1),那第二个require的位置就不对了 const sortRequireInfo = requireInfo.sort((item1, item2) => item1.requirePathRange[0] < item2.requirePathRange[0]) sortRequireInfo.forEach(({requirePathRange, id}) => { const start = requirePathRange[0] const end = requirePathRange[1] const headerS = dataInfo.source.substr(0, start) const endS = dataInfo.source.substr(end) dataInfo.source = `${headerS}${id}${endS}` }) } /** * 输出打包好的文件 */ const output = async () => { const data = await readFile(pathResolve('./template/indexTemplate.js')) const indexModule = moduleTemplate(dataInfo.source) const allModules = [indexModule, ...dataInfo.modules].join('') const result = `${data.toString()}([\n${allModules}\n])` fs.writeFile(pathResolve('./build/output.js'), result, function (err) { if (err) { throw err; } }) } const main = async () => { // 读取入口文件 const data = await readFile(pathResolve('./index.js')) dataInfo.source = data.toString() // 获取依赖信息 dataInfo.requireInfo = getRequireInfo() // 获取模块信息 dataInfo.modules = await getModules() // 将入口文件如require('./module/one.js')等对应成require(1)模块id replace() // 输出打包好的文件 output() console.log(JSON.stringify(dataInfo)) } main() 
相关文章
相关标签/搜索