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

資訊專(zhuān)欄INFORMATION COLUMN

minipack源碼解析以及擴(kuò)展

tangr206 / 2506人閱讀

摘要:的變化利用進(jìn)行前后端通知。例如的副作用,資源只有資源等等,仔細(xì)剖析還有很多有趣的點(diǎn)擴(kuò)展閱讀創(chuàng)建熱更新流程本文示例代碼聯(lián)系我

前置知識(shí)

首先可能你需要知道打包工具是什么存在

基本的模塊化演變進(jìn)程

對(duì)模塊化bundle有一定了解

了解babel的一些常識(shí)

對(duì)node有一定常識(shí)

常見(jiàn)的一些打包工具

如今最常見(jiàn)的模塊化構(gòu)建工具 應(yīng)該是webpack,rollup,fis,parcel等等各種各樣。

但是現(xiàn)在可謂是webpack社區(qū)較為龐大。

其實(shí)呢,模塊化開(kāi)發(fā)很大的一點(diǎn)是為了程序可維護(hù)性

那么其實(shí)我們是不是可以理解為打包工具是將我們一塊塊模塊化的代碼進(jìn)行智能拼湊。使得我們程序正常運(yùn)行。

基本的模塊化演變
// 1. 全局函數(shù)

function module1 () {
    // do somethings
}
function module2 () {
    // do somethings
}

// 2. 以對(duì)象做單個(gè)命名空間

var module = {}

module.addpath = function() {}

// 3. IIFE保護(hù)私有成員

var module1 = (function () {
    var test = function (){}
    var dosomething = function () {
        test();
    }
    return {
        dosomething: dosomething
    }
})();

// 4. 復(fù)用模塊

var module1 = (function (module) {
    module.moduledosomething = function() {}
    return module
})(modules2);

// 再到后來(lái)的COMMONJS、AMD、CMD

// node module是COMMONJS的典型

(function(exports, require, module, __filename, __dirname) {
    // 模塊的代碼實(shí)際上在這里
    function test() {
        // dosomethings
    }
    modules.exports = {
        test: test
    }
});

// AMD 異步加載 依賴(lài)前置

// requireJS示例

define("mymodule", ["module depes"], function () {
    function dosomethings() {}
    return {
        dosomethings: dosomethings
    }
})
require("mymodule", function (mymodule) {
    mymodule.dosomethings()
})

// CMD 依賴(lài)后置 
// seajs 示例
// mymodule.js
define(function(require, exports, module) {
    var module1 = require("module1")
    module.exports = {
        dosomethings: module1.dosomethings
    }
})

seajs.use(["mymodule.js"], function (mymodule) {
    mymodule.dosomethings();
})


// 還有現(xiàn)在流行的esModule

// mymodule 

export default {
    dosomething: function() {}
}

import mymodule from "./mymodule.js"
mymodule.dosomething()
minipack的打包流程
可以分成兩大部分

生成模塊依賴(lài)(循環(huán)引用等問(wèn)題沒(méi)有解決的~)

根據(jù)處理依賴(lài)進(jìn)行打包

模塊依賴(lài)生成

具體步驟

給定入口文件

根據(jù)入口文件分析依賴(lài)(借助bable獲取)

廣度遍歷依賴(lài)圖獲取依賴(lài)

根據(jù)依賴(lài)圖生成(模塊id)key:(數(shù)組)value的對(duì)象表示

建立require機(jī)制實(shí)現(xiàn)模塊加載運(yùn)行

源碼的分析
const fs = require("fs");
const path = require("path");
const babylon = require("babylon");//AST 解析器
const traverse = require("babel-traverse").default; //遍歷工具
const {transformFromAst} = require("babel-core"); // babel-core

let ID = 0;

function createAsset(filename) {
  const content = fs.readFileSync(filename, "utf-8");
  // 獲得文件內(nèi)容, 從而在下面做語(yǔ)法樹(shù)分析
  const ast = babylon.parse(content, {
    sourceType: "module",
  });
  
  // 解析內(nèi)容至AST
  // This array will hold the relative paths of modules this module depends on.
  const dependencies = [];
  // 初始化依賴(lài)集
  // 使用babel-traverse基礎(chǔ)知識(shí),需要找到一個(gè)statement然后定義進(jìn)去的方法。
  // 這里進(jìn)ImportDeclaration 這個(gè)statement內(nèi)。然后對(duì)節(jié)點(diǎn)import的依賴(lài)值進(jìn)行push進(jìn)依賴(lài)集
  traverse(ast, {
    ImportDeclaration: ({node}) => {
      // We push the value that we import into the dependencies array.
      dependencies.push(node.source.value);
    },
  });
  // id自增
  const id = ID++;

  const {code} = transformFromAst(ast, null, {
    presets: ["env"],
  });

  // 返回這么模塊的所有信息
  // 我們?cè)O(shè)置的id filename 依賴(lài)集 代碼
  return {
    id,
    filename,
    dependencies,
    code,
  };
}

function createGraph(entry) {
  // 從一個(gè)入口進(jìn)行解析依賴(lài)圖譜
  // Start by parsing the entry file.
  const mainAsset = createAsset(entry);

  // 最初的依賴(lài)集
  const queue = [mainAsset];

  // 一張圖常見(jiàn)的遍歷算法有廣度遍歷與深度遍歷
  // 這里采用的是廣度遍歷
  for (const asset of queue) {
    // 給當(dāng)前依賴(lài)做mapping記錄
    asset.mapping = {};
    // 獲得依賴(lài)模塊地址
    const dirname = path.dirname(asset.filename);
    // 剛開(kāi)始只有一個(gè)asset 但是dependencies可能多個(gè)
    asset.dependencies.forEach(relativePath => {
      // 這邊獲得絕對(duì)路徑
      const absolutePath = path.join(dirname, relativePath);
      // 這里做解析
      // 相當(dāng)于這層做的解析擴(kuò)散到下一層,從而遍歷整個(gè)圖
      const child = createAsset(absolutePath);

      // 相當(dāng)于當(dāng)前模塊與子模塊做關(guān)聯(lián)
      asset.mapping[relativePath] = child.id;
      // 廣度遍歷借助隊(duì)列
      queue.push(child);
    });
  }

  // 返回遍歷完依賴(lài)的隊(duì)列
  return queue;
}
function bundle(graph) {
  let modules = "";
  graph.forEach(mod => {
    modules += `${mod.id}: [
      function (require, module, exports) { ${mod.code} },
      ${JSON.stringify(mod.mapping)},
    ],`;
  });
  // CommonJS風(fēng)格
  const result = `
    (function(modules) {
      function require(id) {
        const [fn, mapping] = modules[id];
        function localRequire(name) {
          return require(mapping[name]);
        }
        const module = { exports : {} };
        fn(localRequire, module, module.exports);
        return module.exports;
      }

      require(0);
    })({${modules}})
  `;
  return result;
}
一個(gè)簡(jiǎn)單的實(shí)例
// doing.js 
import t from "./hahaha.js"

document.body.onclick = function (){
    console.log(t.name)
}

// hahaha.js

export default {
    name: "ZWkang"
}

const graph = createGraph("../example/doing.js");
const result = bundle(graph);
實(shí)例result 如下
// 打包出的代碼類(lèi)似
    (function(modules) {
      function require(id) {
        const [fn, mapping] = modules[id];
        function localRequire(name) {
          return require(mapping[name]);
        }
        const module = { exports : {} };
        fn(localRequire, module, module.exports);
        return module.exports;
      }

      require(0);
    })({0: [
      function (require, module, exports) { "use strict";
        
        var _hahaha = require("./hahaha.js");
        
        var _hahaha2 = _interopRequireDefault(_hahaha);
        
        function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
        
        document.body.onclick = function () {
          console.log(_hahaha2.default.name);
        }; },
      {"./hahaha.js":1},
    ],1: [
      function (require, module, exports) { "use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = {
  name: "ZWkang"
}; },
      {},
    ],})
依賴(lài)的圖生成的文件可以簡(jiǎn)化為
modules = {
    0: [function code , {deps} ],
    1: [function code , {deps} ]
}

而require則是模擬了一個(gè)很簡(jiǎn)單的COMMONJS模塊module的操作

function require(id) {
    const [fn, mapping] = modules[id];
    function localRequire(name) {
      return require(mapping[name]);
    }
    const module = { exports : {} };
    fn(localRequire, module, module.exports);
    return module.exports;
}

require(0);
分析得

我們模塊代碼會(huì)被執(zhí)行。并且執(zhí)行的結(jié)果會(huì)存儲(chǔ)在module.exports中

并接受三個(gè)參數(shù) require module module.exports

類(lèi)似COMMONJS module會(huì)在模塊閉包內(nèi)注入exports, require, module, __filename, __dirname

會(huì)在入口處對(duì)其代碼進(jìn)行require執(zhí)行一遍。

minipack源碼總結(jié)

通過(guò)上述分析,我們可以了解

minipack的基本構(gòu)造

打包工具的基本形態(tài)

模塊的一些問(wèn)題

擴(kuò)展

既然bundle都已經(jīng)實(shí)現(xiàn)了,我們可不可以基于minipack實(shí)現(xiàn)一個(gè)簡(jiǎn)單的HMR用于熱替換模塊內(nèi)容

可以簡(jiǎn)單的實(shí)現(xiàn)一下

一個(gè)簡(jiǎn)單HMR實(shí)現(xiàn)
可以分為以下幾步

watch file change

emit update to front-end

front-end replace modules

當(dāng)然還有更多仔細(xì)的處理。

例如,模塊細(xì)分的hotload 處理,HMR的顆粒度等等

主要還是在設(shè)置module bundle時(shí)需要考慮。

基于minipack實(shí)現(xiàn)
我們可以設(shè)想一下需要做什么。

watch module asset的變化
利用ws進(jìn)行前后端update通知。
改變前端的modules[變化id]

// 建立一個(gè)文件夾目錄格式為

- test.js
- base.js
- bundle.js
- wsserver.js
- index.js
- temp.html
// temp.html




    
    
    
    Document


    
    <% script %> 
    

// base.js與test.js則是測(cè)試用的模塊
// base.js

var result = {
    name: "ZWKas"
}

export default result

// test.js

import t from "./base.js"

console.log(t, "1");
document.body.innerHTML = t.name
watch module asset的變化
// 首先是實(shí)現(xiàn)第一步
// watch asset file

function createGraph(entry) {
  // Start by parsing the entry file.
  const mainAsset = createAsset(entry);

  const queue = [mainAsset];

  for (const asset of queue) {
    asset.mapping = {};

    const dirname = path.dirname(asset.filename);

    fs.watch(path.join(__dirname,asset.filename), (event, filename) => {
        console.log("watch ",event, filename)
        const assetSource = createAsset(path.join(__dirname,asset.filename))
        wss.emitmessage(assetSource)
    })
    asset.dependencies.forEach(relativePath => {

      const absolutePath = path.join(dirname, relativePath);

      const child = createAsset(absolutePath);

      asset.mapping[relativePath] = child.id;
      queue.push(child);
    });
  }

  return queue;
}

簡(jiǎn)單改造了createGraphl 添加了fs.watch方法作為觸發(fā)點(diǎn)。

(根據(jù)操作系統(tǒng)觸發(fā)底層實(shí)現(xiàn)的不同,watch的事件可能觸發(fā)幾次)

創(chuàng)建資源圖的同時(shí)對(duì)資源進(jìn)行了watch操作。

這邊還有一點(diǎn)要補(bǔ)充的。當(dāng)我們使用creareAsset的時(shí)候,如果沒(méi)有對(duì)id與path做關(guān)聯(lián)的話(huà),那再次觸發(fā)獲得的id也會(huì)發(fā)生改動(dòng)。

可以直接將絕對(duì)地址module id關(guān)聯(lián)。從而復(fù)用了module的id

// createasset一些代碼的改動(dòng) 關(guān)鍵代碼
let mapWithPath = new Map()
if(!mapWithPath.has(path.resolve(__dirname, filename))) {
    mapWithPath.set(path.resolve(__dirname, filename), id)
}
const afterid = mapWithPath.get(path.resolve(__dirname, filename))
return {
    id: afterid,
    filename,
    dependencies,
    code,
};
利用websockt進(jìn)行交互提示update
 
// wsserver.js file 則是實(shí)現(xiàn)第二步。利用websocket與前端進(jìn)行交互,提示update


const EventEmitter = require("events").EventEmitter
const WebSocket = require("ws")

class wsServer extends EventEmitter {
    constructor(port) {
        super()
        this.wss = new WebSocket.Server({ port });
        this.wss.on("connection", function connection(ws) {
            ws.on("message", function incoming(message) {
              console.log("received: %s", message);
            });
        });
    }
    emitmessage(assetSource) {
        this.wss.clients.forEach(ws => {
            ws.send(JSON.stringify({
                type: "update",
                ...assetSource
            }))
        })
    }
}


const wsserver = new wsServer(8080)
module.exports = wsserver
// 簡(jiǎn)單地export一個(gè)帶對(duì)客戶(hù)端傳輸update信息的websocket實(shí)例

在fs.watch觸發(fā)點(diǎn)觸發(fā)

const assetSource = createAsset(path.join(__dirname,asset.filename))
wss.emitmessage(assetSource)

這里就是做這個(gè)操作。將資源圖進(jìn)行重新的創(chuàng)建。包括id,code等

bundle.js則是做我們的打包操作

const minipack = require("./index")
const fs = require("fs")

const makeEntry = (entryHtml, outputhtml ) => {
    const temp = fs.readFileSync(entryHtml).toString()
    // console.log(temp)caches.c
    const graph = minipack.createGraph("./add.js")

    const result = minipack.bundle(graph)

    const data = temp.replace("<% script %>", ``)
    fs.writeFileSync(outputhtml, data)
}

makeEntry("./temp.html", "./index.html")

操作則是獲取temp.html 將依賴(lài)圖打包注入script到temp.html中

并且建立了ws鏈接。以獲取數(shù)據(jù)

在前端進(jìn)行模塊替換
const [fn,mapping] = modules[parseData.id]
modules[parseData.id] = [
    new Function("require", "module", "exports", parseData.code),
    mapping
] // 這里是刷新對(duì)應(yīng)module的內(nèi)容
require(0) // 從入口從新運(yùn)行一次

當(dāng)然一些細(xì)致操作可能replace只會(huì)對(duì)引用的模塊parent進(jìn)行replace,但是這里簡(jiǎn)化版可以先不做吧

這時(shí)候我們?nèi)un bundle.js的file我們會(huì)發(fā)現(xiàn)watch模式開(kāi)啟了。此時(shí)
訪問(wèn)生成的index.html文件

當(dāng)我們改動(dòng)base.js的內(nèi)容時(shí)




就這樣 一個(gè)簡(jiǎn)單的基于minipack的HMR就完成了。

不過(guò)顯然易見(jiàn),存在的問(wèn)題很多。純當(dāng)拋磚引玉。

(例如module的副作用,資源只有js資源等等,仔細(xì)剖析還有很多有趣的點(diǎn))

擴(kuò)展閱讀

github minipack

what-aspect-of-hot-module-replacement-is-this-article-for

node 創(chuàng)建websocket

browserify-hmr

webpack熱更新流程

本文示例代碼

minipack hmr

聯(lián)系我

kangkangblog/zwkang

zwkang github

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

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

相關(guān)文章

  • 打包工具的配置教程見(jiàn)的多了,但它們的運(yùn)行原理你知道嗎?

    摘要:前端模塊化成為了主流的今天,離不開(kāi)各種打包工具的貢獻(xiàn)。與此同時(shí),打包工具也會(huì)處理好模塊之間的依賴(lài)關(guān)系,最終這個(gè)大模塊將可以被運(yùn)行在合適的平臺(tái)中。至此,整一個(gè)打包工具已經(jīng)完成。明白了當(dāng)中每一步的目的,便能夠明白一個(gè)打包工具的運(yùn)行原理。 showImg(https://segmentfault.com/img/bVbckjY?w=900&h=565); 前端模塊化成為了主流的今天,離不開(kāi)各...

    MoAir 評(píng)論0 收藏0
  • 【前端語(yǔ)言學(xué)習(xí)】學(xué)習(xí)minipack源碼,了解打包工具的工作原理

    摘要:作者王聰學(xué)習(xí)目標(biāo)本質(zhì)上,是一個(gè)現(xiàn)代應(yīng)用程序的靜態(tài)模塊打包器。為此,我們檢查中的每個(gè)導(dǎo)入聲明。將導(dǎo)入的值推送到依賴(lài)項(xiàng)數(shù)組中。為此,定義了一個(gè)只包含入口模塊的數(shù)組。當(dāng)隊(duì)列為空時(shí),此循環(huán)將終止。 作者:王聰 學(xué)習(xí)目標(biāo) 本質(zhì)上,webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴(lài)關(guān)...

    shery 評(píng)論0 收藏0
  • 【前端語(yǔ)言學(xué)習(xí)】學(xué)習(xí)minipack源碼,了解打包工具的工作原理

    摘要:作者王聰學(xué)習(xí)目標(biāo)本質(zhì)上,是一個(gè)現(xiàn)代應(yīng)用程序的靜態(tài)模塊打包器。為此,我們檢查中的每個(gè)導(dǎo)入聲明。將導(dǎo)入的值推送到依賴(lài)項(xiàng)數(shù)組中。為此,定義了一個(gè)只包含入口模塊的數(shù)組。當(dāng)隊(duì)列為空時(shí),此循環(huán)將終止。 作者:王聰 學(xué)習(xí)目標(biāo) 本質(zhì)上,webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴(lài)關(guān)...

    toddmark 評(píng)論0 收藏0
  • dayjs 源碼解析(五)(dayjs 插件詳解)

    摘要:前言上一篇源碼解析四類(lèi)介紹了的源碼目錄結(jié)構(gòu)。接下來(lái),本篇將分析一下中插件功能的用法源碼以及如何編寫(xiě)自己的插件。并且,可以通過(guò)插件選項(xiàng),來(lái)對(duì)插件進(jìn)行配置。 前言 上一篇 dayjs 源碼解析(四)(Dayjs 類(lèi))介紹了 dayjs 的源碼目錄結(jié)構(gòu)。接下來(lái),本篇將分析一下 dayjs 中插件功能的用法、源碼以及如何編寫(xiě)自己的 dayjs 插件。 dayjs 插件用法 dayjs 的插件,...

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

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

0條評(píng)論

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