寫一個自己的打包工具-打包原生專案
背景
早期寫了一個很老的專案,目前一直在迭代維護。
沒有用到模組化的思想,也沒有用到目前流行的框架,就是引入了一些簡單的樣式庫。
目前遇到的問題有:
1、程式碼未做壓縮等處理,佔用空間比較大
2、每次更新版本都需要清一下快取才能讀取到最新的靜態資源
3、有一些新的ES6語法和老的語法混用之後相容性不佳
所以就想著自己寫一個簡單的打包工具吧
需求分析
1、迴圈讀取本地檔案,分離不同型別檔案
2、用babel對JS檔案做一次轉換後壓縮,壓縮CSS檔案
3、對JS、CSS檔案做hash計算,並在輸出的檔名後面新增hash小尾巴
4、修改HTML中的引用連結,壓縮HTML
功能實現
1、迴圈讀取本地檔案,分離不同型別檔案。
這裡用的最簡單的正則表示式來區分不同的檔案型別,主要用到了node中的fs模組和path模組。
const readRegDirSync = (filePath, reg, pathArray) => { const pa = fs.readdirSync(filePath); if (!pathArray) { pathArray = []; } pa.forEach(function (ele) { let info = fs.statSync(path.join(filePath, ele)); if (info.isDirectory()) { return pathArray.concat(readRegDirSync(path.join(filePath, ele), reg, pathArray)); } else { let newPath = path.join(filePath, ele); if (reg.test(ele)) { pathArray.push(newPath); } } }); return pathArray };
對於node我也還是個小白,就不分享自己的心得,具體使用說明最好看文件。
具體的文件點這裡:fs | Node.js API 文件 path | Node.js API 文件
另外需要注意的是,不同作業系統的路徑分隔符有所不同。
最後會得到類似這樣的檔案路徑陣列:["my_source\css\global.css", "my_source\css\index.css"]
2-3、對檔案進行處理
JS使用babel對語法進行處理,用babel-minify壓縮整個檔案,CSS用clean-css來進行壓縮
然後用crypto模組來計算檔案內容的hash值,給檔案新增一個hash的小尾巴。
最後把對應的檔名返回,建立一個依賴對應表,方面我們後續替換路徑使用。
const fileAnalyser = (filePath, dirPath, fileType) => { let fileContent = getScript(filePath); let writeCode; if (fileType === 'script') { fileContent = babel.transformSync(fileContent, { presets: ["@babel/preset-env"] }).code; if (mode === 'production') { const {code} = babelMinify(fileContent, { mangle: { keepClassName: true } }); writeCode = code; } else if (mode === 'development') { writeCode = fileContent; } } else if (fileType === 'css') { if (mode === 'production') { writeCode = new CleanCSS({}).minify(fileContent).styles; } else if (mode === 'development') { writeCode = fileContent; } } let dir = path.parse(filePath).dir; let sourceDir = dir.slice(dir.indexOf("\\") + 1); let nowName = path.parse(filePath).name + '.' + crypto.createHash('md5').update(writeCode).digest('hex') + (fileType === 'script' ? '.js' : '.css'); let fileDir = path.join(dirPath, sourceDir); let filename = path.join(fileDir, nowName); if (mkdirsSync(fileDir)) { fs.writeFileSync(filename, writeCode); return filename } };
我在這裡區分了不同的環境的打包模式,這個模式的值是命令列傳入可修改的,預設值為development
然後將得到的檔名和原始的檔名建立一個依賴對應表
cssDependencies[path.parse(cssArray[i]).base] = path.parse(cssPath).base;
我們就會得到類似這樣的對應關係
{global.css: "global.ac2aa87fad6ff0ced8abd4d300013047.css"
,index.css: "index.3261ae5739c390959d2105336d2b2733.css"}
4、修改HTML中的引用連結
用cheerio來解析HTML,然後根據剛才我們存下來的依賴對應表來修改其中的連結引用。
最後再用html-minifier壓縮一下HTML。
const htmlAnalyseScript = (filePath, scriptDependencies, cssDependencies, dirPath) => { const content = fs.readFileSync(filePath, 'utf-8'); const $ = cheerio.load(content); const getScript = $('script'); for (let i = 0; i < getScript.length; i++) { if (getScript[i].attribs.src) { let pathIndex = getScript[i].attribs.src.lastIndexOf("/") + 1; let prePath = getScript[i].attribs.src.slice(0, pathIndex); let scriptPath = getScript[i].attribs.src.slice(pathIndex); let scriptKey = Object.keys(scriptDependencies); let scriptIndex = scriptKey.indexOf(scriptPath); if (scriptIndex > -1) { $(getScript[i]).attr('src', prePath + scriptDependencies[scriptPath]) } } } const getCSS = $('link'); for (let i = 0; i < getCSS.length; i++) { if (getCSS[i].attribs.href) { let pathIndex = getCSS[i].attribs.href.lastIndexOf("/") + 1; let prePath = getCSS[i].attribs.href.slice(0, pathIndex); let cssPath = getCSS[i].attribs.href.slice(pathIndex); let cssKey = Object.keys(cssDependencies); let cssIndex = cssKey.indexOf(cssPath); if (cssIndex > -1) { $(getCSS[i]).attr('href', prePath + cssDependencies[cssPath]) } } } let dir = path.parse(filePath).dir; let sourceDir = dir.slice(dir.indexOf("\\") > -1 ? dir.indexOf("\\") + 1 : dir.length); let fileDir = path.join(dirPath, sourceDir); let filename = path.join(fileDir, path.parse(filePath).base); if (mkdirsSync(fileDir)) { fs.writeFileSync(filename, minifyHTML($.html(), { removeComments: true, collapseWhitespace: true, minifyCSS: true })); return filename } };
需要注意的是cheerio會將HTML中的中文轉換為unicode字元,因為對專案沒有影響,也就沒有再去做處理。
來看看最後的效果
對於我自己的專案來說,還是很有用的,每次更版本不用清一些快取,也不用擔心一些簡單的相容性問題,最主要的是整個專案的大小縮小了很多。
當然最好的情況自然是用模組化的方式來寫專案。
總結一下:node真滴好用!很多模組已經很成熟了,文件也很完整,想做什麼功能一搜就有!
目前做的功能還是很單薄的,希望能給想自己寫一個小工具的人,提供一些可行的思路。
這個小工具的不足之處:
1、只對JS、HTML、CSS檔案做了處理,下面應該還要對圖片等靜態資源做處理。
2、打包速度還是有點慢原因在於:讀取檔案的時候是每讀一種檔案都迴圈一次的,這個邏輯應該可以改成讀一個檔案識別為某個檔案型別就進行處理。
原始碼地址:https://github.com/Chellyyy/myBun