摘要:模仿實現簡單的打包工具是一款前端項目構建工具,隨著現在前端生態的發展,已經成為前端開發人員必備的技能之一,很多開發人員開始使用和的時候,都會使用默認的單頁應該創建指令來創建一個工程化項目,實際上,這些工程化的項目都是基于來搭建的當我們熟悉使
模仿webpack實現簡單的打包工具
webpack是一款前端項目構建工具,隨著現在前端生態的發展,webpack已經成為前端開發人員必備的技能之一,很多開發人員開始使用react和vue的時候,都會使用默認的單頁應該創建指令來創建一個工程化項目,實際上,這些工程化的項目都是基于webpack來搭建的;
當我們熟悉使用這些工程話文件的時候,我們就會開始思考,為什么我們寫的代碼直接在瀏覽器運行不了,經過webpack打包以后就能在瀏覽器上運行,打包的過程發生了什么?
實際上,webpack 是基于node實現的,打包的過程包括了讀取文件流進行處理和模塊依賴的引入解析和導出等過程,下面來簡單的實現這么一個過程。github源碼地址:https://github.com/wzd-front-...
項目初始化首先,我們新建一個文件夾,可以命名為bundler,并在命令行工具(黑窗口)中使用npm init進行初始化,初始化的過程中,會要求我們輸入項目相關的一些信息,如下
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)
如果我們想跳過這一個環節,可以使用npm init -y,加上-y后,自動生成默認配置,不會再詢問;
接下來,在創建測試用例之前,我們先來構建我們的項目,下面是我們的目錄結構,src文件夾下面的文件為我們的測試例子:
--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);
通過觀察上面簡單的三個文件的代碼,我們會發現,這幾段代碼的主要功能模塊的導入和導出解析,這也是打包工具的主要功能,那這些代碼是如何轉換為瀏覽器可識別代碼的,接下來,我們來通過代碼演示實現這個過程;
模塊解析首先,我們在bundler文件下創建bundler.js文件,作為我們打包過程的執行文件,然后我們去執行node bundler.js來執行打包的過程;我們先創建一個名為moduleAnalyser的函數來解析模塊,該函數接收一個filename地址字符串,獲取到對應地址的文件,并通過
@babel/parser模塊的parser方法將對應的文件字符串轉化為抽象節點樹,不清楚抽象節點樹的小伙伴可以通過把下面代碼中的ast在控制臺中打印出來,觀察其結構;在我們生成節點樹后,我們需要獲取其中的import節點,很多人可以想著,那通過字符串截取出import字符不就u可以嗎?
當只有一個import的時候,確實可以,但多個的時候,我們通過截取來實現就比較復雜了,這個時候,我們可以借助
@babel/traverse來幫我們實現,具體實現可以查看babel官網,引入該模塊后,我們可以將parser獲取到ast作為參數傳入;通過前面輸出的節點樹我們可以發現,import 節點的type類型為ImportDeclaration,我們可以在traverse()的第二個參數中傳入一個對象,以節點的type類型作為名稱,可以幫我們獲取到對應的節點,最后我們再將處理后的ast重新轉化為代碼字符串返回,具體實現如下:
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) => { // 通過fs模塊的異步讀取文件api獲取傳入路徑的文件,編碼格式為"utf-8" const content = fs.readFileSync(filename, "utf-8"); // 通過parser.parse方法將讀取到的代碼轉化為抽象節點樹,其中sourceType類型是指定導入文件的方式 const ast = parser.parse(content, { sourceType: "module" }); const dependencies = {} // 通過traverse獲取節點樹中類型為ImportDeclaration的節點,并將其映射關系保存到dependencies對象中 traverse(ast, { ImportDeclaration({ node }) { // 獲取傳入路勁的根路徑 const dirname = path.dirname(filename) // 拼接文件中實際引入文件的路徑 const newFile = dirname + node.source.value // 將映射關系存入dependencies對象中 dependencies[node.source.value] = newFile } }) // 利用presets將ast轉化為對應的es5代碼,第一個參數是抽象節點樹,第二個參數是源碼,第三個參數是配置 const { code } = babel.transformFromAst(ast, null, { presets: ["@babel/preset-env"] }) return { filename, dependencies, code } } console.log(moduleAnalyser("./src/index.js"))
通過上面代碼,我們可以得到一個模塊入口文件的分析,包括模塊的名稱,依賴以及代碼,但我們只是得到一個入口文件的解析,入口模塊里面有自己的依賴,依賴里面又有自己的依賴,因此,我們需要去對每一個模塊進行深度分析;
.... // 用于循環調用多個模塊 const makeDependenciesGraph = (entry) => { // 首先獲取入口模塊的分析對象 const entryModule = moduleAnalyser(entry) // 保存全部模塊的分析對象 const graphArray = [entryModule] // 對graphArray 中的每一項進行分析,分析每一項中的dependencies,如果存在,我們就把新的依賴模塊進行分析,直到全部查找完為止 for (let i = 0; i < graphArray.length; i++) { const item = graphArray[i] const {dependencies} = item // 如果dependencies不為空對象,就利用for..in枚舉對象中每個依賴模塊,將依賴模塊的路徑存入,分析生成新的分析結果對象,存入到graphArray數組中 if (JSON.stringify(dependencies) !== "{}") { for (let j in dependencies) { graphArray.push(moduleAnalyser(dependencies[j])) } } } // 我們把最后的結果通過每個分析結果對象的filename作為key值,存入graph對象中,目的是為了方便后續通過模塊路徑進行取值 const graph = {} graphArray.forEach(item => { graph[item.filename] = { dependencies: item.dependencies, code: item.code } }) return graph } console.log(makeDependenciesGraph("./src/index.js"))
執行完上面的操作后,我們通過入口文件進入后所有相關的模塊已經全部解析完畢,接下來,我們需要把這些模塊,轉化為瀏覽器可以執行的代碼,轉化后生成的代碼中,我們會發現,包含了require方法和export對象,這都是我們瀏覽器不具備的,我們需要進一步聲明對應的方法,讓瀏覽器能找到對應的方法去執行,接下來我們執行最后一步的生成代碼操作
.... const generateCode = (entry) => { // 因為我們需要返回對應的可執行字符串,所以我們需要把對象先轉化為字符串,不然會出現"[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)
最后我們在控制臺輸出的代碼,復制到瀏覽器的控制抬中執行,按照預定的結果運行打印出結果,運行代碼如下:
(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;"}});
以上的代碼就是我們打包后的代碼,我們會發現,在我們打包后,需要用到其他的模塊的時候,會調用require 方法,require方法又會通過傳入的地址路徑參數去查詢我們生成的以filename為key值的對象,找到對應的code,利用eval()方法去執行,這就是打包工具的一個基本原理。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/116115.html
摘要:模仿實現簡單的打包工具是一款前端項目構建工具,隨著現在前端生態的發展,已經成為前端開發人員必備的技能之一,很多開發人員開始使用和的時候,都會使用默認的單頁應該創建指令來創建一個工程化項目,實際上,這些工程化的項目都是基于來搭建的當我們熟悉使 模仿webpack實現簡單的打包工具 webpack是一款前端項目構建工具,隨著現在前端生態的發展,webpack已經成為前端開發人員必備的技能之...
摘要:模仿實現簡單的打包工具是一款前端項目構建工具,隨著現在前端生態的發展,已經成為前端開發人員必備的技能之一,很多開發人員開始使用和的時候,都會使用默認的單頁應該創建指令來創建一個工程化項目,實際上,這些工程化的項目都是基于來搭建的當我們熟悉使 模仿webpack實現簡單的打包工具 webpack是一款前端項目構建工具,隨著現在前端生態的發展,webpack已經成為前端開發人員必備的技能之...
摘要:但高度封裝的帶來方便的同時,很多人卻很少去關注輪子的內部結構,以至于當使用需要手動配置一些東西如編譯實現代碼壓縮,移動端適配等配置的時候往往無從下手。廢話不多說,下面我們來看看如何基于模仿實現項目工程化。 從零搭建vue-cli 原創不易,如需轉載請聯系作者并注明出處 vue-cli的出現為vue工程化前端開發工作流提供了開箱即用的構建配置,減輕了煩人的webpack配置流程。但高度封...
摘要:一原理與環境不同,瀏覽器中不支持的主要原因是缺少了以下幾個環境變量換句話說,打包器的原理就是模擬這四個變量的行為。 本文首發于知乎專欄:http://zhuanlan.zhihu.com/starkwang CommonJS 是一個流行的前端模塊化規范,也是目前 NodeJS 以及其模塊托管倉庫 npm 使用的規范,但目前暫無瀏覽器支持 CommonJS 。要想讓瀏覽器用上這些模塊,...
閱讀 2492·2021-09-28 09:36
閱讀 1486·2021-09-22 15:33
閱讀 3636·2019-08-30 15:44
閱讀 1743·2019-08-29 13:14
閱讀 3132·2019-08-29 11:17
閱讀 1441·2019-08-29 11:03
閱讀 2905·2019-08-26 17:10
閱讀 681·2019-08-26 12:13