這篇主要就講一下【打包】(bundle是打包拒啰,bundler是打包器)
現(xiàn)有問題
上面三個(gè)文件的代碼都不能直接運(yùn)行在瀏覽器中,因?yàn)闉g覽器不支持直接運(yùn)行帶有import和export關(guān)鍵字的代碼市埋,所以就引出了下面的問題
問題一:很多瀏覽器不認(rèn)識(shí)import和export,只有現(xiàn)代瀏覽器(chrome恕刘、firefox缤谎、edge等),通過<script type="module" >
來支持import和export褐着;
問題二:雖然通過<script type="module" >
可以支持import和export坷澡,但是不兼容IE8~15,而且可能會(huì)導(dǎo)致文件請(qǐng)求過多含蓉;
平穩(wěn)的兼容策略:把關(guān)鍵字轉(zhuǎn)譯成普通代碼频敛;且把所有文件打包成一個(gè)文件项郊;
下面就來講一下如何來實(shí)現(xiàn)上面這個(gè)策略
編譯import和export關(guān)鍵字
項(xiàng)目中新增了bundler_1.ts,可以拿它和deps_4.ts做一下比較斟赚,看哪里做了改動(dòng)着降;
主要添加了下面幾行代碼,通過babel把code轉(zhuǎn)譯一下:
const { code: es5Code } = babel.transform(code, {
presets: ['@babel/preset-env']
})
運(yùn)行一下拗军,取其中的a.js的結(jié)果看一下:
可以看出區(qū)別:
①import關(guān)鍵字沒有了鹊碍,變成了require;
②export關(guān)鍵字也沒有了食绿,變成了exports['default']
注意:這時(shí)這里的code是字符串
把所有代碼打包成一個(gè)文件
那么這個(gè)文件應(yīng)該是什么樣的呢侈咕,首先它應(yīng)該包含所有的模塊,其次它還要可以執(zhí)行所有的模塊器紧;
這時(shí)就有三個(gè)問題我們要來解決一下:
1耀销、depRelation目前是對(duì)象,我們要把它變成數(shù)組(為什么變成數(shù)組呢铲汪,因?yàn)閿?shù)組的第一項(xiàng)就是入口呀熊尉,而對(duì)象沒有第一項(xiàng)這么一個(gè)概念);
2掌腰、code目前是字符串狰住,我們要把它變成函數(shù);
3齿梁、完善execude函數(shù)(execude函數(shù)就是用來執(zhí)行入口文件的)
把depRelation從對(duì)象變成數(shù)組
引入bundler_2.ts催植,把它與bundler_1.ts做一下對(duì)比;node -r ts-node/register ./bundler_2.ts
運(yùn)行bundler_2.ts勺择;
把code從字符串變成函數(shù)
步驟
1创南、把code字符串外面包一個(gè)function(require, modules, exports){....};
2省核、再把這個(gè)code寫到文件里稿辙,注意不要讓引號(hào)出現(xiàn)在文件中;
完善execude函數(shù)
function execute(key) {
// 如果已經(jīng) require 過气忠,就直接返回上次的結(jié)果
if (modules[key]) { return modules[key] }
// 找到要執(zhí)行的項(xiàng)目
var item = depRelation.find(i => i.key === key)
// 找不到就報(bào)錯(cuò)邻储,中斷執(zhí)行
if (!item) { throw new Error(`${item} is not found`) }
// 把相對(duì)路徑變成項(xiàng)目路徑
var pathToKey = (path) => {
var dirname = key.substring(0, key.lastIndexOf('/') + 1)
var projectPath = (dirname + path).replace(/\.\//g, '').replace(/\/\//, '/')
return projectPath
}
// 創(chuàng)建 require 函數(shù)
var require = (path) => {
return execute(pathToKey(path))
}
// 初始化當(dāng)前模塊
modules[key] = { __esModule: true }
// 初始化 module 方便 code 往 module.exports 上添加屬性
var module = { exports: modules[key] }
// 調(diào)用 code 函數(shù),往 module.exports 上添加導(dǎo)出屬性
// 第二個(gè)參數(shù) module 大部分時(shí)候是無用的旧噪,主要用于兼容舊代碼
item.code(require, module, module.exports)
// 返回當(dāng)前模塊
return modules[key]
}
手動(dòng)構(gòu)造dist.js文件
最后dist.js文件的主體結(jié)構(gòu)應(yīng)該如下
var depRelation = [
{key: 'index.js', deps: ['a.js', 'b.js'], code: function...},
{key: 'a.js', deps: ['b.js'], code: function...},
{key: 'b.js', deps: ['a.js'], code: function...},
]
var modules = {}
execute(depRelation[0].key)
function execute(key){
var require = ...
var module = ...
item.code(require, module, module.exports )
....
}
運(yùn)行dist.js吨娜,可以得到和index.js一樣的結(jié)果
如何得到最終文件dist.js
我們已經(jīng)知道了dist.js內(nèi)容是什么了,但是該怎么去得到它呢舌菜,也就是說怎么自動(dòng)生成dist.js文件呢萌壳?
答:拼湊出字符串,再把這些字符串寫入到文件中即可,如下:
var code = ''
code += content
writeFileSync('dist.js', code)
編寫bundler_3.ts袱瓮,把它與bundler_2.ts做一下對(duì)比看哪里做了改動(dòng)缤骨;bundler_3.ts就是打包器,可以自動(dòng)生成最終文件尺借,運(yùn)行bundler_3.ts绊起,得到dist_2.js(只是把名字變了下),node ./dist_2.js
燎斩,得到結(jié)果與index.js一樣虱歪;
并把我們通過打包器自動(dòng)生成的dist_2.js與我們手動(dòng)寫出來的dist.js作對(duì)比,發(fā)現(xiàn)內(nèi)容是一樣的栅表。
所以bundler_3.ts里的內(nèi)容就是一個(gè)簡(jiǎn)易打包器笋鄙,也就是webpack的核心內(nèi)容!怪瓶!
具體步驟
1萧落、需要得到一個(gè)depRelation來收集依賴,它是一個(gè)數(shù)組洗贰,具體內(nèi)容如下:
depRelation = [
{key: 'index.js', deps: ['a.js', 'b.js'], code: `index.js代碼內(nèi)容的字符串`},
{key: 'a.js', deps: ['b.js'], code: `a.js代碼內(nèi)容的字符串`},
{key: 'b.js', deps: ['a.js'], code: `b.js代碼內(nèi)容的字符串`},
]
2找岖、得到execude函數(shù),函數(shù)具體內(nèi)容可見上面敛滋,它接受一個(gè)參數(shù)许布,參數(shù)為depRelation[0].key,相當(dāng)于depRelation[0].key(其實(shí)就是index.js)就是一個(gè)入口文件绎晃;
3蜜唾、編寫generateCode函數(shù),它是打包器的核心箕昭,主要通過字符串拼接把我們手寫出的dist.js里的內(nèi)容灵妨,通過writeFileSync('dist2.js', generateCode())
輸出到指定文件里面去;這個(gè)過程中需要把depRelation中每一項(xiàng)中的code由字符串變成函數(shù)落竹;
4、最后生成的dist2.js就是打包出來的文件啦货抄;
不過目前這個(gè)簡(jiǎn)易打包器還有不少問題:
1述召、生成的代碼中有不少重復(fù)的函數(shù);
2蟹地、目前只能引入和運(yùn)行JS文件积暖;
3、只能理解import怪与,無法理解require夺刑;
4、不支持插件;
5遍愿、不支持配置入口文件和dist的文件名存淫;
但本篇文章主旨在于理解打包器是怎么打包的,所以這些問題后面一一再來梳理沼填;