成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

實(shí)現(xiàn)一個(gè)簡(jiǎn)易的webpack

darcrand / 2408人閱讀

摘要:首先一段代碼轉(zhuǎn)化成的抽象語(yǔ)法樹是一個(gè)對(duì)象,該對(duì)象會(huì)有一個(gè)頂級(jí)的屬性第二個(gè)屬性是是一個(gè)數(shù)組。最終完成整個(gè)文件依賴的處理。參考文章抽象語(yǔ)法樹一看就懂的抽象語(yǔ)法樹源碼所有的源碼已經(jīng)上傳

背景

隨著前端復(fù)雜度的不斷提升,誕生出很多打包工具,比如最先的grunt,gulp。到后來(lái)的webpack Parcel。但是目前很多腳手架工具,比如vue-cli已經(jīng)幫我們集成了一些構(gòu)建工具的使用。有的時(shí)候我們可能并不知道其內(nèi)部的實(shí)現(xiàn)原理。其實(shí)了解這些工具的工作方式可以幫助我們更好理解和使用這些工具,也方便我們?cè)陧?xiàng)目開發(fā)中應(yīng)用。

一些知識(shí)點(diǎn)

在我們開始造輪子前,我們需要對(duì)一些知識(shí)點(diǎn)做一些儲(chǔ)備工作。

模塊化知識(shí)

首先是模塊的相關(guān)知識(shí),主要的是 es6 modulescommonJS模塊化的規(guī)范。更詳細(xì)的介紹可以參考這里 CommonJS、AMD/CMD、ES6 Modules 以及 webpack 原理淺析?,F(xiàn)在我們只需要了解:

es6 modules 是一個(gè)編譯時(shí)就會(huì)確定模塊依賴關(guān)系的方式。

CommonJS的模塊規(guī)范中,Node 在對(duì) JS 文件進(jìn)行編譯的過(guò)程中,會(huì)對(duì)文件中的內(nèi)容進(jìn)行頭尾包裝,在頭部添加(function (export, require, modules, __filename, __dirname){ 在尾部添加了 };。這樣我們?cè)趩蝹€(gè)JS文件內(nèi)部可以使用這些參數(shù)。

AST 基礎(chǔ)知識(shí)

什么是抽象語(yǔ)法樹?

在計(jì)算機(jī)科學(xué)中,抽象語(yǔ)法樹(abstract syntax tree 或者縮寫為 AST),或者語(yǔ)法樹(syntax tree),是源代碼的抽象語(yǔ)法結(jié)構(gòu)的樹狀表現(xiàn)形式,這里特指編程語(yǔ)言的源代碼。樹上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。之所以說(shuō)語(yǔ)法是「抽象」的,是因?yàn)檫@里的語(yǔ)法并不會(huì)表示出真實(shí)語(yǔ)法中出現(xiàn)的每個(gè)細(xì)節(jié)。

大家可以通過(guò)Esprima 這個(gè)網(wǎng)站來(lái)將代碼轉(zhuǎn)化成 ast。首先一段代碼轉(zhuǎn)化成的抽象語(yǔ)法樹是一個(gè)對(duì)象,該對(duì)象會(huì)有一個(gè)頂級(jí)的type屬性Program,第二個(gè)屬性是body是一個(gè)數(shù)組。body數(shù)組中存放的每一項(xiàng)都是一個(gè)對(duì)象,里面包含了所有的對(duì)于該語(yǔ)句的描述信息:

type:描述該語(yǔ)句的類型 --變量聲明語(yǔ)句
kind:變量聲明的關(guān)鍵字 -- var
declaration: 聲明的內(nèi)容數(shù)組,里面的每一項(xiàng)也是一個(gè)對(duì)象
    type: 描述該語(yǔ)句的類型 
    id: 描述變量名稱的對(duì)象
        type:定義
        name: 是變量的名字
        init: 初始化變量值得對(duì)象
        type: 類型
        value: 值 "is tree" 不帶引號(hào)
        row: ""is tree"" 帶引號(hào)
進(jìn)入正題 webpack 簡(jiǎn)易打包

有了上面這些基礎(chǔ)的知識(shí),我們先來(lái)看一下一個(gè)簡(jiǎn)單的webpack打包的過(guò)程,首先我們定義3個(gè)文件:

// index.js
import a from "./test"

console.log(a)

// test.js
import b from "./message"

const a = "hello" + b

export default a

// message.js
const b = "world"

export default b

方式很簡(jiǎn)單,定義了一個(gè)index.js引用test.js;test.js內(nèi)部引用message.js??匆幌麓虬蟮拇a:

(function (modules) {
  var installedModules = {};

  function __webpack_require__(moduleId) {
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }

    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    };

    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, {enumerable: true, get: getter});
    }
  };
  // define __esModule on exports
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, {value: "Module"});
    }
    Object.defineProperty(exports, "__esModule", {value: true});
  };
  // create a fake namespace object
  // mode & 1: value is a module id, require it
  // mode & 2: merge all properties of value into the ns
  // mode & 4: return value when already ns object
  // mode & 8|1: behave like require
  __webpack_require__.t = function (value, mode) {
    /******/
    if (mode & 1) value = __webpack_require__(value);
    if (mode & 8) return value;
    if ((mode & 4) && typeof value === "object" && value && value.__esModule) return value;
    var ns = Object.create(null);
    __webpack_require__.r(ns);
    Object.defineProperty(ns, "default", {enumerable: true, value: value});
    if (mode & 2 && typeof value != "string") for (var key in value) __webpack_require__.d(ns, key, function (key) {
      return value[key];
    }.bind(null, key));
    return ns;
  };
  // 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 = "./src/index.js");
})({
  "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {

    "use strict";
    eval("__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test */ "./src/test.js");


console.log(_test__WEBPACK_IMPORTED_MODULE_0__["default"])


//# sourceURL=webpack:///./src/index.js?");

  }),
  "./src/message.js": (function (module, __webpack_exports__, __webpack_require__) {
    // ...
  }),
  "./src/test.js": (function (module, __webpack_exports__, __webpack_require__) {
    // ...
  })
});

看起來(lái)很亂?沒關(guān)系,我們來(lái)屢一下。一眼看過(guò)去我們看到的是這樣的形式:

(function(modules) {
  // ...
})({
 // ...
})

這樣好理解了吧,就是一個(gè)自執(zhí)行函數(shù),傳入了一個(gè)modules對(duì)象,modules 對(duì)象是什么樣的格式呢?上面的代碼已經(jīng)給了我們答案:

{
  "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
    // ...
  }),
  "./src/message.js": (function (module, __webpack_exports__, __webpack_require__) {
    // ...
  }),
  "./src/test.js": (function (module, __webpack_exports__, __webpack_require__) {
    // ...
  })
}

是這樣的一個(gè) 路徑 --> 函數(shù) 這樣的 key,value 鍵值對(duì)。而函數(shù)內(nèi)部是我們定義的文件轉(zhuǎn)移成 ES5 之后的代碼:

"use strict";
eval("__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test */ "./src/test.js");


console.log(_test__WEBPACK_IMPORTED_MODULE_0__["default"])


//# sourceURL=webpack:///./src/index.js?");

到這里基本上結(jié)構(gòu)是分析完了,接著我們看看他的執(zhí)行,自執(zhí)行函數(shù)一開始執(zhí)行的代碼是:

__webpack_require__(__webpack_require__.s = "./src/index.js");

調(diào)用了__webpack_require_函數(shù),并傳入了一個(gè)moduleId參數(shù)是"./src/index.js"。再看看函數(shù)內(nèi)部的主要實(shí)現(xiàn):

// 定義 module 格式   
var module = installedModules[moduleId] = {
      i: moduleId, // moduleId
      l: false, // 是否已經(jīng)緩存
      exports: {} // 導(dǎo)出對(duì)象,提供掛載
};

modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

這里調(diào)用了我們modules中的函數(shù),并傳入了 __webpack_require__函數(shù)作為函數(shù)內(nèi)部的調(diào)用。module.exports參數(shù)作為函數(shù)內(nèi)部的導(dǎo)出。因?yàn)?b>index.js里面引用了test.js,所以又會(huì)通過(guò) __webpack_require__來(lái)執(zhí)行對(duì)test.js的加載:

var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/test.js");

test.js內(nèi)又使用了message.js所以,test.js內(nèi)部又會(huì)執(zhí)行對(duì)message.js的加載。message.js執(zhí)行完成之后,因?yàn)闆]有依賴項(xiàng),所以直接返回了結(jié)果:

var b = "world"
__webpack_exports__["default"] = (b)

執(zhí)行完成之后,再一級(jí)一級(jí)返回到根文件index.js。最終完成整個(gè)文件依賴的處理。
整個(gè)過(guò)程中,我們像是通過(guò)一個(gè)依賴關(guān)系樹的形式,不斷地向數(shù)的內(nèi)部進(jìn)入,等返回結(jié)果,又開始回溯到根。

開發(fā)一個(gè)簡(jiǎn)單的 tinypack

通過(guò)上面的這些調(diào)研,我們先考慮一下一個(gè)基礎(chǔ)的打包編譯工具可以做什么?

轉(zhuǎn)換ES6語(yǔ)法成ES5

處理模塊加載依賴

生成一個(gè)可以在瀏覽器加載執(zhí)行的 js 文件

第一個(gè)問題,轉(zhuǎn)換語(yǔ)法,其實(shí)我們可以通過(guò)babel來(lái)做。核心步驟也就是:

通過(guò)babylon生成AST

通過(guò)babel-core將AST重新生成源碼

/**
 * 獲取文件,解析成ast語(yǔ)法
 * @param filename // 入口文件
 * @returns {*}
 */
function getAst (filename) {
  const content = fs.readFileSync(filename, "utf-8")

  return babylon.parse(content, {
    sourceType: "module",
  });
}

/**
 * 編譯
 * @param ast
 * @returns {*}
 */
function getTranslateCode(ast) {
  const {code} = transformFromAst(ast, null, {
    presets: ["env"]
  });
  return code
}

接著我們需要處理模塊依賴的關(guān)系,那就需要得到一個(gè)依賴關(guān)系視圖。好在babel-traverse提供了一個(gè)可以遍歷AST視圖并做處理的功能,通過(guò) ImportDeclaration 可以得到依賴屬性:

function getDependence (ast) {
  let dependencies = []
  traverse(ast, {
    ImportDeclaration: ({node}) => {
      dependencies.push(node.source.value);
    },
  })
  return dependencies
}

/**
 * 生成完整的文件依賴關(guān)系映射
 * @param fileName
 * @param entry
 * @returns {{fileName: *, dependence, code: *}}
 */
function parse(fileName, entry) {
  let filePath = fileName.indexOf(".js") === -1 ? fileName + ".js" : fileName
  let dirName = entry ? "" : path.dirname(config.entry)
  let absolutePath = path.join(dirName, filePath)
  const ast = getAst(absolutePath)
  return {
    fileName,
    dependence: getDependence(ast),
    code: getTranslateCode(ast),
  };
}

到目前為止,我們也只是得到根文件的依賴關(guān)系和編譯后的代碼,比如我們的index.js依賴了test.js但是我們并不知道test.js還需要依賴message.js,他們的源碼也是沒有編譯過(guò)。所以此時(shí)我們還需要做深度遍歷,得到完成的深度依賴關(guān)系:

/**
 * 獲取深度隊(duì)列依賴關(guān)系
 * @param main
 * @returns {*[]}
 */
function getQueue(main) {
  let queue = [main]
  for (let asset of queue) {
    asset.dependence.forEach(function (dep) {
      let child = parse(dep)
      queue.push(child)
    })
  }
  return queue
}

那么進(jìn)行到這一步我們已經(jīng)完成了所有文件的編譯解析。最后一步,就是需要我們按照webpack的思想對(duì)源碼進(jìn)行一些包裝。第一步,先是要生成一個(gè)modules對(duì)象:

function bundle(queue) {
  let modules = ""
  queue.forEach(function (mod) {
    modules += `"${mod.fileName}": function (require, module, exports) { ${mod.code} },`
  })
  // ...
}

得到 modules 對(duì)象后,接下來(lái)便是對(duì)整體文件的外部包裝,注冊(cè)require,module.exports

(function(modules) {
      function require(fileName) {
          // ...
      }
     require("${config.entry}");
 })({${modules}})

而函數(shù)內(nèi)部,也只是循環(huán)執(zhí)行每個(gè)依賴文件的 JS 代碼而已,完成代碼:

function bundle(queue) {
  let modules = ""
  queue.forEach(function (mod) {
    modules += `"${mod.fileName}": function (require, module, exports) { ${mod.code} },`
  })

  const result = `
    (function(modules) {
      function require(fileName) {
        const fn = modules[fileName];

        const module = { exports : {} };

        fn(require, module, module.exports);

        return module.exports;
      }

      require("${config.entry}");
    })({${modules}})
  `;

  // We simply return the result, hurray! :)
  return result;
}

到這里基本上也就介紹完了,接下來(lái)就是輸出編譯好的文件了,這里我們?yōu)榱丝梢匀质褂?b>tinypack包,我們還需要為其添加到全局命令(這里直接參考我的源碼吧,不再贅述了)。我們來(lái)測(cè)試一下:

npm i [email protected] -g

cd examples

tinypack

看一下輸出的文件:

(function (modules) {
  function require(fileName) {
    const fn = modules[fileName];

    const module = {exports: {}};

    fn(require, module, module.exports);

    return module.exports;
  }

  require("./src/index.js");
})({
  "./src/index.js": function (require, module, exports) {
    "use strict";

    var _test = require("./test");

    var _test2 = _interopRequireDefault(_test);

    function _interopRequireDefault(obj) {
      return obj && obj.__esModule ? obj : {default: obj};
    }

    console.log(_test2.default);
  }, "./test": function (require, module, exports) {
    "use strict";

    Object.defineProperty(exports, "__esModule", {
      value: true
    });

    var _message = require("./message");

    var _message2 = _interopRequireDefault(_message);

    function _interopRequireDefault(obj) {
      return obj && obj.__esModule ? obj : {default: obj};
    }

    var a = "hello" + _message2.default;
    exports.default = a;
  }, "./message": function (require, module, exports) {
    "use strict";

    Object.defineProperty(exports, "__esModule", {
      value: true
    });
    var b = "world";

    exports.default = b;
  },
})

再測(cè)試一下:

恩,基本上已經(jīng)完成一個(gè)建議的 tinypack。

參考文章

抽象語(yǔ)法樹 Abstract syntax tree

一看就懂的JS抽象語(yǔ)法樹

源碼

tinypack 所有的源碼已經(jīng)上傳 github

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/108042.html

相關(guān)文章

  • 一個(gè)簡(jiǎn)易DIY場(chǎng)景H5框架

    摘要:筆者也用操作的形式做了一個(gè)類似項(xiàng)目,現(xiàn)在整合成一個(gè)簡(jiǎn)易的框架分享給大家,只需要根據(jù)自己的需要完成布局與樣式的編寫,以及按下面指示操作,就可以生成一個(gè)自己的空間定制。 第一次發(fā)項(xiàng)目,以供大家學(xué)習(xí)參考,可能有些簡(jiǎn)陋,若有不合理處,請(qǐng)大神們指教。 最近在朋友圈出現(xiàn)了很多DIY類的H5游戲,通過(guò)添加拖拉擺放和組合元素,來(lái)DIY自定義一個(gè)場(chǎng)景并合成圖片分享出去的新H5展示形式。筆者也用操作DOM...

    dongxiawu 評(píng)論0 收藏0
  • Webpack系列——手把手教你使用Webpack搭建簡(jiǎn)易React開發(fā)環(huán)境

    摘要:在這篇文章中我們開始利用我們之前所學(xué)搭建一個(gè)簡(jiǎn)易的開發(fā)環(huán)境,用以鞏固我們之前學(xué)習(xí)的知識(shí)。 文章首發(fā)于我的github及個(gè)人博客,github請(qǐng)看https://github.com/huruji/blo...,轉(zhuǎn)載請(qǐng)注明出處。 在這篇文章中我們開始利用我們之前所學(xué)搭建一個(gè)簡(jiǎn)易的React開發(fā)環(huán)境,用以鞏固我們之前學(xué)習(xí)的Webpack知識(shí)。首先我們需要明確這次開發(fā)環(huán)境需要達(dá)到的效果:1、...

    cucumber 評(píng)論0 收藏0
  • 隨我來(lái)基于webpack構(gòu)建一個(gè)簡(jiǎn)易vue腳手架 (webpack系列二)

    摘要:構(gòu)建文件清理構(gòu)建目錄下的文件打包工具編譯文件模板函數(shù)編譯與配合使用入口處理項(xiàng)目中的不同類型的模塊。 前言 之前有寫了一篇webpack的文章(認(rèn)識(shí)篇) 猛戳,大家對(duì)于webpack基本概念(entry,output,loader,plugin,mode...)應(yīng)該是有了較模糊的認(rèn)識(shí).今天希望在通過(guò)(對(duì)于vue-cli的效仿)搭建一個(gè)自己的腳手架的途中對(duì)于概念會(huì)有更深刻的認(rèn)識(shí). 目錄 1...

    tomorrowwu 評(píng)論0 收藏0
  • webpack簡(jiǎn)易教程之loader

    摘要:一組鏈?zhǔn)降膶凑障群箜樞蜻M(jìn)行編譯。在最后一個(gè),返回所預(yù)期的。運(yùn)行在中,并且能夠執(zhí)行任何可能的操作。用于對(duì)傳遞配置。分開的每個(gè)部分都相對(duì)于當(dāng)前目錄解析。 webpack自稱能夠打包任何文件,這句話咋聽一下好像在吹牛逼,因?yàn)閣ebpack本身只能理解JavaScript。但是由于webpack中有l(wèi)oader的存在,可以將所有類型的文件轉(zhuǎn)換為webpack能夠處理的有效模塊,然后利用we...

    MobService 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<