webpack學習(一)從一個打包文件開始

準備一個簡單的項目

webpack.config.js:

const path = require('path')

module.exports = {
    mode: 'development',
    entry: './src/home/index.js',
    output: {
        filename: 'js/[name].js',
        path: path.resolve(__dirname, `./dist`),
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    }
}

package.json

  "scripts": {
    "build": "webpack --progress"
  },

src/home/index.js

import initialNum from './a.js'
export default function add(a, b) {
    return a + b + initialNum()
}

src/home/a.js

export default function initialNum() {
    return 1
}

執行npm run build, 看看打包生成的文件

(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;
    }
    //...此處省略其余代碼
    return __webpack_require__(__webpack_require__.s = "./src/home/index.js");
})
({
 "./src/home/a.js":(function(module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return initialNum; });\n/**\n * Created by dengxuelian on 2020/3/7\n */\nfunction initialNum() {\n  return 1;\n}\n\n//# sourceURL=webpack:///./src/home/a.js?");
}),

"./src/home/index.js":(function(module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return add; });\n/* harmony import */ var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a.js */ \"./src/home/a.js\");\n/**\n * Created by dengxuelian on 2020/2/29\n */\n\nfunction add(a, b) {\n  return a + b + Object(_a_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])();\n}\n\n//# sourceURL=webpack:///./src/home/index.js?");
})
});

手把手分析代碼結構

  1. 整體結構:一個立即執行函數,接收一個參數
(function (modules) {
    //函數體
})(/*實參*/)
  1. 函數參數modules:一個對象,key是文件相對路徑,value是一個函數
(function (modules) {
    //函數體
})({
    "./src/home/a.js": function(){}
    "./src/home/index.js":  function () {},
})

想必你能猜到modules對象是什么了,它就是你項目中所有被引用的文件,準確地說:modules對象是從入口文件(webpack.entry)開始,所有被引用的文件列表,而webpack如何獲取到這些文件列表,我們暫時不關注。
現在我們有了一份js清單,接下來我們要做的就是加載并執行這些js文件。可以猜測,在立即執行函數體內,一定會調用參數modules傳入的函數,而其唯一的標識是key,其調用會像這樣:modules[moduleId]()

  1. 立即執行函數里調用modules對象上的某個key上的某個方法
(function (modules) {
    function __webpack_require__(moduleId) {
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); //moduleId就是modules的key,也就是文件的相對路徑
    }
})({
    "./src/home/a.js": function(){}
    "./src/home/index.js":  function () {},
})

我們接著看函數__webpack_require__的調用地方

  1. 函數__webpack_require__的執行時機
(function (modules) {
    function __webpack_require__(moduleId) {
        modules[moduleId].call(); //moduleId就是modules的key,也就是文件的相對路徑
    }
    return __webpack_require__("./src/home/index.js");
})({
    "./src/home/a.js": function(){}
    "./src/home/index.js":  function () {},
})

這個立即函數又執行了__webpack_require__(),那么就等于執行了modules對象傳進來的某個鍵上的方法,具體是哪個鍵(文件)呢?這里是./src/home/index.js,也就是webpack.entry配置的入口文件。

  1. 現在看看"./src/home/index.js": function () {}這個函數里面寫了什么
{
  "./src/home/index.js": (function(module, __webpack_exports__, __webpack_require__) {
      eval(/*我把這段字符格式化成js寫在下面了*/);
    __webpack_require__.r(__webpack_exports__)
    __webpack_require__.d(__webpack_exports__, "default", function() { return add; });
    var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/home/a.js")
    function add(a, b) {return a + b + Object(_a_js__WEBPACK_IMPORTED_MODULE_0__["default"])();}
  })
}

這個函數內部是一個eval()方法加上一堆字符串,我把字符串格式調整了一下,方便分析,首先關注的是這個函數確實加載了./src/home/index.js這個文件,其次是注意import initialNum from './a.js'變成了

var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/home/a.js")

__webpack_require__("./src/home/a.js")等價于執行"./src/home/a.js": function(){}。假如./src/home/a.js里面又引用了其它文件,那么可以猜測該函數內部也會有形如var XXX = __webpack_require__(XXX)這種定義,以此循環,直到到達從入口文件開始的所有js文件調用樹的結尾。

__webpack_require__上還掛載了一些輔助方法或值,比如判斷該模塊是否加載過,定義exports屬性等等。這里不再展開。

多個entry

entrywebpack分析文件依賴關系的入口,多個entry就對應多個打包文件。我們可以看看多個entry的打包結果
webpack.config.js:

const path = require('path')

const pages = ['home', 'login'];

let entries = {}

pages.forEach((page) => {
    entries[page] = path.resolve(__dirname, `./src/${page}/index.js`);
})

module.exports = {
    mode: 'development',
    entry: entries,
    output: {
        filename: 'js/[name].js',
        path: path.resolve(__dirname, `./dist`),
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    }
}

./src/login/index.js

import initialNum from '../home/a.js'

export default function desc(value) {
    return value - initialNum()
}

打包生成的文件:

//src/login.js
(function(modules) { // webpackBootstrap
    return __webpack_require__(__webpack_require__.s = "./src/login/index.js");
})
 ({
     "./src/home/a.js": (function(module, __webpack_exports__, __webpack_require__) {}),
     "./src/login/index.js": (function(module, __webpack_exports__, __webpack_require__) {})
});

//src/home.js
(function(modules) { // webpackBootstrap
    return __webpack_require__(__webpack_require__.s = "./src/home/index.js");
})
 ({
     "./src/home/a.js": (function(module, __webpack_exports__, __webpack_require__) {}),
     "./src/home/index.js": (function(module, __webpack_exports__, __webpack_require__) {})
});

細心的同學發現,兩個入口文件都引用了同一個js,打包后的代碼里也有一份相同的代碼,在沒有增加任何額外的配置之前,webpack只是老實本分地完成了自己的本職工作--找依賴、打包文件,拆包這種事,你不“下命令”,它可是不會做的。

當然,拆包是很簡單的,加一個splitChunks配置就可以解決,不過webpack的世界里配置實在太多,我們一步一步來吧。

下期預告

webpack構建依賴圖(dependency graph

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 寫在開頭 先說說為什么要寫這篇文章, 最初的原因是組里的小朋友們看了webpack文檔后, 表情都是這樣的: (摘...
    Lefter閱讀 5,322評論 4 31
  • 原文首發于:Webpack 3,從入門到放棄 Update (2017.8.27) : 關于 output.pub...
    昵稱都被用完了衰閱讀 1,914評論 4 19
  • 記得2004年的時候,互聯網開發就是做網頁,那時也沒有前端和后端的區分,有時一個網站就是一些純靜態的html,通過...
    陽陽陽一堆陽閱讀 3,330評論 0 5
  • 我喜歡來自北方的你,我們喜歡待在一起。我看星星,你看太陽。我們說著對方不懂的語言,沉醉其中,仿佛那是世間最美的音律...
    北方有琴南閱讀 218評論 0 0
  • 在 2019年的最后一個月里,從3號的腸胃炎到智齒發炎,又到門牙發炎,這一周吃的藥量快趕上比一年的了。 早上起來門...
    過海_f025閱讀 183評論 0 1