背景
早期写了一个很老的项目, 目前一直在迭代维护.
没有用到模块化的思想, 也没有用到目前流行的框架, 就是引入了一些简单的样式库.
目前遇到的问题有:
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 文档 http://nodejs.cn/api/fs.html path | Node.JS API 文档 http://nodejs.cn/api/path.html
另外需要注意的是, 不同操作系统的路径分隔符有所不同.
最后会得到类似这样的文件路径数组:["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/myBundler
来源: https://www.cnblogs.com/huangcy/p/11418791.html