亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

手把手教你擼一個簡易的 webpack

發布時間:2020-07-09 16:22:12 來源:網絡 閱讀:404 作者:wx5d61fdc401976 欄目:web開發

背景

隨著前端復雜度的不斷提升,誕生出很多打包工具,比如最先的grunt,gulp。到后來的webpack和Parcel。但是目前很多腳手架工具,比如vue-cli已經幫我們集成了一些構建工具的使用。有的時候我們可能并不知道其內部的實現原理。其實了解這些工具的工作方式可以幫助我們更好理解和使用這些工具,也方便我們在項目開發中應用。

一些知識點

在我們開始造輪子前,我們需要對一些知識點做一些儲備工作。

模塊化知識

es6 modules 是一個編譯時就會確定模塊依賴關系的方式。
CommonJS的模塊規范中,Node 在對 JS 文件進行編譯的過程中,會對文件中的內容進行頭尾包裝 ,在頭部添加(function (export, require, modules, filename, dirname){\n 在尾部添加了\n};。這樣我們在單個JS文件內部可以使用這些參數。
AST 基礎知識

什么是抽象語法樹?

在計算機科學中,抽象語法樹(abstract syntax tree 或者縮寫為 AST),或者語法樹(syntax tree),是源代碼的抽象語法結構的樹狀表現形式,這里特指編程語言的源代碼。樹上的每個節點都表示源代碼中的一種結構。之所以說語法是「抽象」的,是因為這里的語法并不會表示出真實語法中出現的每個細節。

手把手教你擼一個簡易的 webpack
大家可以通過Esprima 這個網站來將代碼轉化成 ast。首先一段代碼轉化成的抽象語法樹是一個對象,該對象會有一個頂級的type屬性Program,第二個屬性是body是一個數組。body數組中存放的每一項都是一個對象,里面包含了所有的對于該語句的描述信息:

type:描述該語句的類型 --變量聲明語句
kind:變量聲明的關鍵字 -- var
declaration: 聲明的內容數組,里面的每一項也是一個對象
type: 描述該語句的類型
id: 描述變量名稱的對象
type:定義
name: 是變量的名字
init: 初始化變量值得對象
type: 類型
value: 值 "is tree" 不帶引號
row: "\"is tree"\" 帶引號
進入正題

webpack 簡易打包

有了上面這些基礎的知識,我們先來看一下一個簡單的webpack打包的過程,首先我們定義3個文件:

// 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
方式很簡單,定義了一個index.js引用test.js;test.js內部引用message.js。看一下打包后的代碼:

(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);\n/ harmony import / var _testWEBPACK_IMPORTED_MODULE_0 = webpack_require(/! ./test / \"./src/test.js\");\n\n\nconsole.log(_testWEBPACK_IMPORTED_MODULE_0[\"default\"])\n\n\n//# sourceURL=webpack:///./src/index.js?");
}),
"./src/message.js": (function (module,
webpack_exports
, webpack_require) {
// ...
}),
"./src/test.js": (function (module,
webpack_exports
, __webpack_require__) {
// ...
})
});
看起來很亂?沒關系,我們來屢一下。一眼看過去我們看到的是這樣的形式:

(function(modules) {
// ...
})({
// ...
})
這樣好理解了吧,就是一個自執行函數,傳入了一個modules對象,modules 對象是什么樣的格式呢?上面的代碼已經給了我們答案:

{
"./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) {
// ...
})
}
是這樣的一個 路徑 --> 函數 這樣的 key,value 鍵值對。而函數內部是我們定義的文件轉移成 ES5 之后的代碼:

"use strict";
eval("webpack_require.r(webpack_exports);\n/ harmony import / var _testWEBPACK_IMPORTED_MODULE_0 = webpack_require(/! ./test / \"./src/test.js\");\n\n\nconsole.log(_test__WEBPACK_IMPORTED_MODULE_0__[\"default\"])\n\n\n//# sourceURL=webpack:///./src/index.js?");
到這里基本上結構是分析完了,接著我們看看他的執行,自執行函數一開始執行的代碼是:

webpack_require(webpack_require.s = "./src/index.js");
調用了__webpack_require_函數,并傳入了一個moduleId參數是"./src/index.js"。再看看函數內部的主要實現:

// 定義 module 格式
var module = installedModules[moduleId] = {
i: moduleId, // moduleId
l: false, // 是否已經緩存
exports: {} // 導出對象,提供掛載
};
modules[moduleId].call(module.exports, module, module.exports, webpack_require);
這里調用了我們modules中的函數,并傳入了
webpack_require
函數作為函數內部的調用。module.exports參數作為函數內部的導出。因為index.js里面引用了test.js,所以又會通過__webpack_require__來執行對test.js的加載:

var _testWEBPACK_IMPORTED_MODULE_0 = webpack_require("./src/test.js");
test.js內又使用了message.js所以,test.js內部又會執行對message.js的加載。message.js執行完成之后,因為沒有依賴項,所以直接返回了結果:

var b = 'world'
__webpack_exports__["default"] = (b)
執行完成之后,再一級一級返回到根文件index.js。最終完成整個文件依賴的處理。 整個過程中,我們像是通過一個依賴關系樹的形式,不斷地向數的內部進入,等返回結果,又開始回溯到根。

開發一個簡單的 tinypack

通過上面的這些調研,我們先考慮一下一個基礎的打包編譯工具可以做什么?

轉換ES6語法成ES5
處理模塊加載依賴
生成一個可以在瀏覽器加載執行的 js 文件
第一個問題,轉換語法,其實我們可以通過babel來做。核心步驟也就是:

通過babylon生成AST
通過babel-core將AST重新生成源碼
/**

  • 獲取文件,解析成ast語法
  • @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
    }
    接著我們需要處理模塊依賴的關系,那就需要得到一個依賴關系視圖。好在babel-traverse提供了一個可以遍歷AST視圖并做處理的功能,通過 ImportDeclaration 可以得到依賴屬性:

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

  • 生成完整的文件依賴關系映射
  • @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),
    };
    }
    到目前為止,我們也只是得到根文件的依賴關系和編譯后的代碼,比如我們的index.js依賴了test.js但是我們并不知道test.js還需要依賴message.js,他們的源碼也是沒有編譯過。所以此時我們還需要做深度遍歷,得到完成的深度依賴關系:

/**

  • 獲取深度隊列依賴關系
  • @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
    }
    那么進行到這一步我們已經完成了所有文件的編譯解析。最后一步,就是需要我們按照webpack的思想對源碼進行一些包裝。第一步,先是要生成一個modules對象:

function bundle(queue) {
let modules = ''
queue.forEach(function (mod) {
modules += '${mod.fileName}': function (require, module, exports) { ${mod.code} },
})
// ...
}
得到 modules 對象后,接下來便是對整體文件的外部包裝,注冊require,module.exports:

(function(modules) {
function require(fileName) {
// ...
}
require('${config.entry}');
})({${modules}})
而函數內部,也只是循環執行每個依賴文件的 JS 代碼而已,完成代碼:

function bundle(queue) {
let modules = ''
queue.forEach(function (mod) {
modules += '${mod.fileName}': function (require, module, exports) { ${mod.code} },
})
const result = <br/>(function(modules) {<br/>function require(fileName) {<br/>const fn = modules[fileName];<br/>const module = { exports : {} };<br/>fn(require, module, module.exports);<br/>return module.exports;<br/>}<br/>require('${config.entry}');<br/>})({${modules}})<br/>;
return result;
}
到這里基本上也就介紹完了,我們來打包試一下:

(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;
},
})
再測試一下:

手把手教你擼一個簡易的 webpack
恩,基本上已經完成一個簡易的 tinypack。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

资溪县| 洪湖市| 华亭县| 乌苏市| 新巴尔虎右旗| 尖扎县| 洪湖市| 留坝县| 阿合奇县| 家居| 云浮市| 天峻县| 勃利县| 遂溪县| 广州市| 大渡口区| 略阳县| 麻栗坡县| 黎平县| 桑日县| 鲁甸县| 安国市| 义马市| 商丘市| 黎平县| 巴林左旗| 江阴市| 全椒县| 苏尼特左旗| 仁怀市| 邛崃市| 布拖县| 全州县| 关岭| 巴东县| 松原市| 昭苏县| 佛学| 荆门市| 顺平县| 久治县|