1. 程式人生 > >寫一個自己的打包工具-打包原生專案

寫一個自己的打包工具-打包原生專案

背景

早期寫了一個很老的專案,目前一直在迭代維護。

沒有用到模組化的思想,也沒有用到目前流行的框架,就是引入了一些簡單的樣式庫。

目前遇到的問題有:

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