国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

模仿webpack實(shí)現(xiàn)簡(jiǎn)單的打包工具

linkFly / 2677人閱讀

摘要:模仿實(shí)現(xiàn)簡(jiǎn)單的打包工具是一款前端項(xiàng)目構(gòu)建工具,隨著現(xiàn)在前端生態(tài)的發(fā)展,已經(jīng)成為前端開(kāi)發(fā)人員必備的技能之一,很多開(kāi)發(fā)人員開(kāi)始使用和的時(shí)候,都會(huì)使用默認(rèn)的單頁(yè)應(yīng)該創(chuàng)建指令來(lái)創(chuàng)建一個(gè)工程化項(xiàng)目,實(shí)際上,這些工程化的項(xiàng)目都是基于來(lái)搭建的當(dāng)我們熟悉使

模仿webpack實(shí)現(xiàn)簡(jiǎn)單的打包工具

webpack是一款前端項(xiàng)目構(gòu)建工具,隨著現(xiàn)在前端生態(tài)的發(fā)展,webpack已經(jīng)成為前端開(kāi)發(fā)人員必備的技能之一,很多開(kāi)發(fā)人員開(kāi)始使用react和vue的時(shí)候,都會(huì)使用默認(rèn)的單頁(yè)應(yīng)該創(chuàng)建指令來(lái)創(chuàng)建一個(gè)工程化項(xiàng)目,實(shí)際上,這些工程化的項(xiàng)目都是基于webpack來(lái)搭建的;
當(dāng)我們熟悉使用這些工程話(huà)文件的時(shí)候,我們就會(huì)開(kāi)始思考,為什么我們寫(xiě)的代碼直接在瀏覽器運(yùn)行不了,經(jīng)過(guò)webpack打包以后就能在瀏覽器上運(yùn)行,打包的過(guò)程發(fā)生了什么?

實(shí)際上,webpack 是基于node實(shí)現(xiàn)的,打包的過(guò)程包括了讀取文件流進(jìn)行處理和模塊依賴(lài)的引入解析和導(dǎo)出等過(guò)程,下面來(lái)簡(jiǎn)單的實(shí)現(xiàn)這么一個(gè)過(guò)程。github源碼地址:https://github.com/wzd-front-...

項(xiàng)目初始化

首先,我們新建一個(gè)文件夾,可以命名為bundler,并在命令行工具(黑窗口)中使用npm init進(jìn)行初始化,初始化的過(guò)程中,會(huì)要求我們輸入項(xiàng)目相關(guān)的一些信息,如下

Press ^C at any time to quit.
package name: (bundler)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository: (https://github.com/wzd-front-end/bundler.git)
keywords:
author:
license: (ISC)

如果我們想跳過(guò)這一個(gè)環(huán)節(jié),可以使用npm init -y,加上-y后,自動(dòng)生成默認(rèn)配置,不會(huì)再詢(xún)問(wèn);

接下來(lái),在創(chuàng)建測(cè)試用例之前,我們先來(lái)構(gòu)建我們的項(xiàng)目,下面是我們的目錄結(jié)構(gòu),src文件夾下面的文件為我們的測(cè)試?yán)樱?/p>

--bundler
 --src
    index.js
    message.js
    word.js
 --node_modules
 --bundler.js
 --package.json
 --README.md

word.js代碼

export const word = "hello";

message.js代碼

import { word } from "./word.js";
const message = `say ${word}`;
export default message;

index.js代碼

import message from "./message.js";
console.log(message);

通過(guò)觀察上面簡(jiǎn)單的三個(gè)文件的代碼,我們會(huì)發(fā)現(xiàn),這幾段代碼的主要功能模塊的導(dǎo)入和導(dǎo)出解析,這也是打包工具的主要功能,那這些代碼是如何轉(zhuǎn)換為瀏覽器可識(shí)別代碼的,接下來(lái),我們來(lái)通過(guò)代碼演示實(shí)現(xiàn)這個(gè)過(guò)程;

模塊解析

首先,我們?cè)赽undler文件下創(chuàng)建bundler.js文件,作為我們打包過(guò)程的執(zhí)行文件,然后我們?nèi)?zhí)行node bundler.js來(lái)執(zhí)行打包的過(guò)程;我們先創(chuàng)建一個(gè)名為moduleAnalyser的函數(shù)來(lái)解析模塊,該函數(shù)接收一個(gè)filename地址字符串,獲取到對(duì)應(yīng)地址的文件,并通過(guò)
@babel/parser模塊的parser方法將對(duì)應(yīng)的文件字符串轉(zhuǎn)化為抽象節(jié)點(diǎn)樹(shù),不清楚抽象節(jié)點(diǎn)樹(shù)的小伙伴可以通過(guò)把下面代碼中的ast在控制臺(tái)中打印出來(lái),觀察其結(jié)構(gòu);在我們生成節(jié)點(diǎn)樹(shù)后,我們需要獲取其中的import節(jié)點(diǎn),很多人可以想著,那通過(guò)字符串截取出import字符不就u可以嗎?
當(dāng)只有一個(gè)import的時(shí)候,確實(shí)可以,但多個(gè)的時(shí)候,我們通過(guò)截取來(lái)實(shí)現(xiàn)就比較復(fù)雜了,這個(gè)時(shí)候,我們可以借助
@babel/traverse來(lái)幫我們實(shí)現(xiàn),具體實(shí)現(xiàn)可以查看babel官網(wǎng),引入該模塊后,我們可以將parser獲取到ast作為參數(shù)傳入;通過(guò)前面輸出的節(jié)點(diǎn)樹(shù)我們可以發(fā)現(xiàn),import 節(jié)點(diǎn)的type類(lèi)型為ImportDeclaration,我們可以在traverse()的第二個(gè)參數(shù)中傳入一個(gè)對(duì)象,以節(jié)點(diǎn)的type類(lèi)型作為名稱(chēng),可以幫我們獲取到對(duì)應(yīng)的節(jié)點(diǎn),最后我們?cè)賹⑻幚砗蟮腶st重新轉(zhuǎn)化為代碼字符串返回,具體實(shí)現(xiàn)如下:

const fs = require("fs")
const path = require("path")
const parser = require("@babel/parser")
const traverse = require("@babel/traverse").default;
const babel = require("@babel/core");

const moduleAnalyser = (filename) => {
    // 通過(guò)fs模塊的異步讀取文件api獲取傳入路徑的文件,編碼格式為"utf-8"
    const content = fs.readFileSync(filename, "utf-8");
    // 通過(guò)parser.parse方法將讀取到的代碼轉(zhuǎn)化為抽象節(jié)點(diǎn)樹(shù),其中sourceType類(lèi)型是指定導(dǎo)入文件的方式
    const ast = parser.parse(content, {
        sourceType: "module"
    });
    const dependencies = {}
    // 通過(guò)traverse獲取節(jié)點(diǎn)樹(shù)中類(lèi)型為ImportDeclaration的節(jié)點(diǎn),并將其映射關(guān)系保存到dependencies對(duì)象中
    traverse(ast, {
        ImportDeclaration({ node }) {
            // 獲取傳入路勁的根路徑
            const dirname = path.dirname(filename)
            // 拼接文件中實(shí)際引入文件的路徑

            const newFile = dirname + node.source.value
            // 將映射關(guān)系存入dependencies對(duì)象中
           dependencies[node.source.value] = newFile
        }
    })
    // 利用presets將ast轉(zhuǎn)化為對(duì)應(yīng)的es5代碼,第一個(gè)參數(shù)是抽象節(jié)點(diǎn)樹(shù),第二個(gè)參數(shù)是源碼,第三個(gè)參數(shù)是配置
    const { code } = babel.transformFromAst(ast, null, {
        presets: ["@babel/preset-env"]
    })
    return {
        filename,
        dependencies,
        code
    }
}
console.log(moduleAnalyser("./src/index.js"))

通過(guò)上面代碼,我們可以得到一個(gè)模塊入口文件的分析,包括模塊的名稱(chēng),依賴(lài)以及代碼,但我們只是得到一個(gè)入口文件的解析,入口模塊里面有自己的依賴(lài),依賴(lài)?yán)锩嬗钟凶约旱囊蕾?lài),因此,我們需要去對(duì)每一個(gè)模塊進(jìn)行深度分析;

....
// 用于循環(huán)調(diào)用多個(gè)模塊
const makeDependenciesGraph = (entry) => {
  // 首先獲取入口模塊的分析對(duì)象
  const entryModule = moduleAnalyser(entry)
  // 保存全部模塊的分析對(duì)象
  const graphArray = [entryModule]
  // 對(duì)graphArray 中的每一項(xiàng)進(jìn)行分析,分析每一項(xiàng)中的dependencies,如果存在,我們就把新的依賴(lài)模塊進(jìn)行分析,直到全部查找完為止
  for (let i = 0; i < graphArray.length; i++) {
    const item = graphArray[i]
    const {dependencies} = item
    // 如果dependencies不為空對(duì)象,就利用for..in枚舉對(duì)象中每個(gè)依賴(lài)模塊,將依賴(lài)模塊的路徑存入,分析生成新的分析結(jié)果對(duì)象,存入到graphArray數(shù)組中
    if (JSON.stringify(dependencies) !== "{}") {
      for (let j in dependencies) {
        graphArray.push(moduleAnalyser(dependencies[j]))
      }
    }
  }
  // 我們把最后的結(jié)果通過(guò)每個(gè)分析結(jié)果對(duì)象的filename作為key值,存入graph對(duì)象中,目的是為了方便后續(xù)通過(guò)模塊路徑進(jìn)行取值
  const graph = {}
  graphArray.forEach(item => {
    graph[item.filename] = {
      dependencies: item.dependencies,
      code: item.code
    }
  })
  return graph
}
console.log(makeDependenciesGraph("./src/index.js"))

執(zhí)行完上面的操作后,我們通過(guò)入口文件進(jìn)入后所有相關(guān)的模塊已經(jīng)全部解析完畢,接下來(lái),我們需要把這些模塊,轉(zhuǎn)化為瀏覽器可以執(zhí)行的代碼,轉(zhuǎn)化后生成的代碼中,我們會(huì)發(fā)現(xiàn),包含了require方法和export對(duì)象,這都是我們?yōu)g覽器不具備的,我們需要進(jìn)一步聲明對(duì)應(yīng)的方法,讓瀏覽器能找到對(duì)應(yīng)的方法去執(zhí)行,接下來(lái)我們執(zhí)行最后一步的生成代碼操作

....
const generateCode = (entry) => {
  // 因?yàn)槲覀冃枰祷貙?duì)應(yīng)的可執(zhí)行字符串,所以我們需要把對(duì)象先轉(zhuǎn)化為字符串,不然會(huì)出現(xiàn)"[object, object]"
  const graph = JSON.stringify(makeDependenciesGraph(entry));
  // 返回字符串使用模板字符串,且使用到閉包,防止污染全局
  return `
        (function(graph){
            function require(module) { 
                function localRequire(relativePath) {
                    return require(graph[module].dependencies[relativePath]);
                }
                var exports = {};
                (function(require, exports, code){
                    eval(code)
                })(localRequire, exports, graph[module].code);
                return exports;
            };
            require("${entry}")
        })(${graph});
    `;
}
const code = generateCode("./src/index.js")
console.log(code)

最后我們?cè)诳刂婆_(tái)輸出的代碼,復(fù)制到瀏覽器的控制抬中執(zhí)行,按照預(yù)定的結(jié)果運(yùn)行打印出結(jié)果,運(yùn)行代碼如下:

(function(graph){
  function require(module) {
    function localRequire(relativePath) {
      return require(graph[module].dependencies[relativePath]);
    }
    var exports = {};
    (function(require, exports, code){
      eval(code)
    })(localRequire, exports, graph[module].code);
    return exports;
  };
  require("./src/index.js")
})({"./src/index.js":{"dependencies":{"./message.js":"./srcmessage.js"},"code":""use strict";

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

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

console.log(_message["default"]);"},"./srcmessage.js":{"dependencies":{"./word.js":"./srcword.js"},"code":""use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports["default"] = void 0;

var _word = require("./word.js");

var message = "say ".concat(_word.word);
var _default = message;
exports["default"] = _default;"},"./srcword.js":{"dependencies":{},"code":""use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.word = void 0;
var word = "hello";
exports.word = word;"}});

以上的代碼就是我們打包后的代碼,我們會(huì)發(fā)現(xiàn),在我們打包后,需要用到其他的模塊的時(shí)候,會(huì)調(diào)用require 方法,require方法又會(huì)通過(guò)傳入的地址路徑參數(shù)去查詢(xún)我們生成的以filename為key值的對(duì)象,找到對(duì)應(yīng)的code,利用eval()方法去執(zhí)行,這就是打包工具的一個(gè)基本原理。

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/105962.html

相關(guān)文章

  • 模仿webpack實(shí)現(xiàn)簡(jiǎn)單打包工具

    摘要:模仿實(shí)現(xiàn)簡(jiǎn)單的打包工具是一款前端項(xiàng)目構(gòu)建工具,隨著現(xiàn)在前端生態(tài)的發(fā)展,已經(jīng)成為前端開(kāi)發(fā)人員必備的技能之一,很多開(kāi)發(fā)人員開(kāi)始使用和的時(shí)候,都會(huì)使用默認(rèn)的單頁(yè)應(yīng)該創(chuàng)建指令來(lái)創(chuàng)建一個(gè)工程化項(xiàng)目,實(shí)際上,這些工程化的項(xiàng)目都是基于來(lái)搭建的當(dāng)我們熟悉使 模仿webpack實(shí)現(xiàn)簡(jiǎn)單的打包工具 webpack是一款前端項(xiàng)目構(gòu)建工具,隨著現(xiàn)在前端生態(tài)的發(fā)展,webpack已經(jīng)成為前端開(kāi)發(fā)人員必備的技能之...

    wow_worktile 評(píng)論0 收藏0
  • 模仿webpack實(shí)現(xiàn)簡(jiǎn)單打包工具

    摘要:模仿實(shí)現(xiàn)簡(jiǎn)單的打包工具是一款前端項(xiàng)目構(gòu)建工具,隨著現(xiàn)在前端生態(tài)的發(fā)展,已經(jīng)成為前端開(kāi)發(fā)人員必備的技能之一,很多開(kāi)發(fā)人員開(kāi)始使用和的時(shí)候,都會(huì)使用默認(rèn)的單頁(yè)應(yīng)該創(chuàng)建指令來(lái)創(chuàng)建一個(gè)工程化項(xiàng)目,實(shí)際上,這些工程化的項(xiàng)目都是基于來(lái)搭建的當(dāng)我們熟悉使 模仿webpack實(shí)現(xiàn)簡(jiǎn)單的打包工具 webpack是一款前端項(xiàng)目構(gòu)建工具,隨著現(xiàn)在前端生態(tài)的發(fā)展,webpack已經(jīng)成為前端開(kāi)發(fā)人員必備的技能之...

    Simon 評(píng)論0 收藏0
  • 基于webpack模仿vue-cli(簡(jiǎn)略版)工程化

    摘要:但高度封裝的帶來(lái)方便的同時(shí),很多人卻很少去關(guān)注輪子的內(nèi)部結(jié)構(gòu),以至于當(dāng)使用需要手動(dòng)配置一些東西如編譯實(shí)現(xiàn)代碼壓縮,移動(dòng)端適配等配置的時(shí)候往往無(wú)從下手。廢話(huà)不多說(shuō),下面我們來(lái)看看如何基于模仿實(shí)現(xiàn)項(xiàng)目工程化。 從零搭建vue-cli 原創(chuàng)不易,如需轉(zhuǎn)載請(qǐng)聯(lián)系作者并注明出處 vue-cli的出現(xiàn)為vue工程化前端開(kāi)發(fā)工作流提供了開(kāi)箱即用的構(gòu)建配置,減輕了煩人的webpack配置流程。但高度封...

    GitCafe 評(píng)論0 收藏0
  • 手寫(xiě)一個(gè)CommonJS打包工具(一)

    摘要:一原理與環(huán)境不同,瀏覽器中不支持的主要原因是缺少了以下幾個(gè)環(huán)境變量換句話(huà)說(shuō),打包器的原理就是模擬這四個(gè)變量的行為。 本文首發(fā)于知乎專(zhuān)欄:http://zhuanlan.zhihu.com/starkwang CommonJS 是一個(gè)流行的前端模塊化規(guī)范,也是目前 NodeJS 以及其模塊托管倉(cāng)庫(kù) npm 使用的規(guī)范,但目前暫無(wú)瀏覽器支持 CommonJS 。要想讓瀏覽器用上這些模塊,...

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

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

0條評(píng)論

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