摘要:模仿實(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
摘要:模仿實(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ā)人員必備的技能之...
摘要:模仿實(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ā)人員必備的技能之...
摘要:但高度封裝的帶來(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配置流程。但高度封...
摘要:一原理與環(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 。要想讓瀏覽器用上這些模塊,...
閱讀 1135·2023-04-26 03:02
閱讀 1176·2023-04-25 19:18
閱讀 2591·2021-11-23 09:51
閱讀 2573·2021-11-11 16:55
閱讀 2624·2021-10-21 09:39
閱讀 1704·2021-10-09 09:59
閱讀 1999·2021-09-26 09:55
閱讀 3527·2021-09-26 09:55