写一个本身的打包工具-打包原生项目

背景css

早期写了一个很老的项目,目前一直在迭代维护。html

没有用到模块化的思想,也没有用到目前流行的框架,就是引入了一些简单的样式库。node

目前遇到的问题有:git

一、代码未作压缩等处理,占用空间比较大github

二、每次更新版本都须要清一下缓存才能读取到最新的静态资源正则表达式

三、有一些新的ES6语法和老的语法混用以后兼容性不佳api

因此就想着本身写一个简单的打包工具吧数组

 

需求分析缓存

一、循环读取本地文件,分离不一样类型文件babel

二、用babel对JS文件作一次转换后压缩,压缩CSS文件

三、对JS、CSS文件作hash计算,并在输出的文件名后面添加hash小尾巴

四、修改HTML中的引用连接,压缩HTML

 

功能实现

一、循环读取本地文件,分离不一样类型文件。

这里用的最简单的正则表达式来区分不一样的文件类型,主要用到了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-三、对文件进行处理

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"}

 

四、修改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真滴好用!不少模块已经很成熟了,文档也很完整,想作什么功能一搜就有!

目前作的功能仍是很单薄的,但愿能给想本身写一个小工具的人,提供一些可行的思路。

 

这个小工具的不足之处:

一、只对JS、HTML、CSS文件作了处理,下面应该还要对图片等静态资源作处理。

二、打包速度仍是有点慢缘由在于:读取文件的时候是每读一种文件都循环一次的,这个逻辑应该能够改为读一个文件识别为某个文件类型就进行处理。

 

源码地址:https://github.com/Chellyyy/myBundler

相关文章
相关标签/搜索