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() 
相關文章
相關標籤/搜索