在我们的开发中,我们经常会使用打包工具,对css进行打包,在scss中,我们还经常把模块放在一个单独的文件夹内,这样就会有路径问题(这个问题本文暂时也不解决),比如我们把scss文件放在src/style目录中,但是编译打包完之后的css放在dist目录下,这样就会导致原本样式中的url路径在最终的css中不正确。本文就试图写一个插件来调整这个引用路径。
gulp的插件怎么写?
gulp的插件,其实是放在pipe中作为参数的函数,它处理的要么是stream要么是buffer,在我们这个场景里面,stream就是所有的整个流程里面的信息,buffer就是当前正在处理的这个文件的信息,所以我们要处理的,是buffer,可以简单(而不正确)的理解为,一个buffer就对应一个文件。
理解这点之后,在继续往下看。我们通过在pipe中截获文件信息,包括文件的内容,通过我们自己的代码,修改内容或文件信息(比如文件名),把修改过的buffer返回给pipe,这样就达到了我们gulp插件的目的。
基于这种思想,我写了一个gulp插件的模块,你如果以后要自己写,可以方便的使用这个模块:
// modifyStreamContent.js import through from "through2" import {log} from "../loader" export default function modifyStreamContent(modify) { return through.obj(function(file, endcoding, callback) { if(file.isNull()) { this.push(file) return callback() } if(file.isStream()) { log("streaming not supported", "error") return callback() } var content = file.contents.toString() content = modify(content, file.path) || content file.contents = new Buffer(content) this.push(file) callback() }) }
上面就是专门用来修改文件内容的一个模块,下面开始利用这个模块,撰写你自己的gulp插件:
// replaceName.js import modify from "./modifyStreamContent" export default function() { return modify((content, filePath) => { content = content.replace("{{name}}", "my name") return content }) }
上面就是最最简单的一个gulp插件了,它的作用是将文件内容里面的{{name}}替换为"my name"(第一个)。
接下来看下在gulpfile里面怎么使用这个插件:
import rename from "./replaceName" gulp.task("default", () => { gulp.src("my.md").pipe(rename()).pipe(gulp.dest(".")) })
如果在replaceName里面加入参数,你还可以实现更负责的选项。
gulp编译scss
写完scss之后,当然是想着把它转换为css,放到浏览器下面去看看效果。但是不要急,既然是我们用scss写的源码,所以不需要一个可以修改的css,所以我们就minify一下好了,而且生成一个sourcemap文件方便调试吧。
import sass from "gulp-sass" import cssmin from "gulp-cssmin" import sourcemaps from "gulp-sourcemaps" gulp.task("scss", () => { gulp.src("src/style/*.scss") .pipe(sorucemaps.init()) .pipe(sass()) .pipe(cssmin()) .pipe(sroucemaps.write("./")) .pipe(gulp.dest("dist")) })
OK,这样当运行scss任务的时候,机会把style目录下的scss文件进行编译合并和压缩,在dist目录下会生成一个css文件,当然是经过压缩的。注意,不要直接在style目录下相互引用。
调整css里面的url相对路径
为什么只调相对路径呢?因为绝对路径没有必要调,撰写的时候如果把http都加进去了,肯定是考虑从cdn使用图片,这种情况下就没必要考虑我们本文的这个问题。但是如果是我文章开头提到的那种情况,就有必要,举一个例子:
// in src/style/icons.scss .icon { background-image: url(../images/icons.png) }
当我们进行上面的编译任务之后
// in dist/icons.css .icon { background-image: url(../images/icons.png) }
这就有问题了,url里面的图片根本找不到,应该是
background-image: url(../src/images/icons.png)
才对。因为在scss编译的时候,并没有对里面的文件路径进行处理。我们现在要做的,就是写一个gulp插件,在scss编译之前,对图片路径进行修正。
import {path} from "../loader" import {modifyStreamContent} from "./index" function matchAll(str, reg) { var res = [] var match while(match = reg.exec(str)) { res.push(match) } return res } export default function AssetsRelativePath(distPath) { return modifyStreamContent((content, filePath) => { if(!distPath) { return content } var matches = matchAll(content, /url\((\S+?)\)/gi) var currentDir = path.dirname(filePath) if(matches instanceof Array) { matches.forEach(match => { let url = match[1] // only relative path supported if(url.toString().substr(0, 1) !== ".") { return } let file = url.replace("'", "").replace('"', "") let originalPath = path.resolve(currentDir, file) let relative = path.relative(distPath, originalPath) let res = relative.replace(/\\/g, "/") content = content.replace(url, res) }) } return content }) }
在gulpfile中这样去用:
import sass from "gulp-sass" import cssmin from "gulp-cssmin" import sourcemaps from "gulp-sourcemaps" import AssetsRelativePath from "./AssetsRelativePath" gulp.task("scss", () => { gulp.src("src/style/*.scss") .pipe(AssetsRelativePath("dist")) .pipe(sorucemaps.init()) .pipe(sass()) .pipe(cssmin()) .pipe(sroucemaps.write("./")) .pipe(gulp.dest("dist")) })
为什么要放在sass()前面呢?因为当sass()执行完毕之后,我们无法通过源文件的路径,找出与目标文件夹之间的相对路径。
这样就可以实现相对路径替换了。不过这个方案还是有很多欠缺,主要是:
- 在scss中写url时,考虑的是当前写的这个文件与图片之间的相对关系,而不是生成的css文件与图片之间的相对路径。这一点其实也要从两面看,如果编写的时候就从后者去考虑,那本文就没有必要去写了。但是如果考虑的是编写的文件与图片之间的相对关系的话,又要考虑生成文件与图片之间的路径合理性,比如上面我的例子中,dist里面的文件引用了src里面的图片,感觉不是很好,最好是在dist同级目录中放置静态文件夹,这样src和dist都是使用相同的一份静态文件,更有利于发布。
- 本文的方法,只能处理gulp.src中接收的文件,不在这个匹配列表中的文件中的url路径也无法处理,比如写一个_settings.scss放到settings目录下,因为没有被gulp.src到,所以里面的路径也没有办法替换到。
小结
这篇文件只是给出了一个方案,由于欠缺还是很大,所以也只能当做启发用的思考,它可以帮助你思考怎么来写gulp,怎么通过gulp的buffer去修改输出的内容。有了这些技巧,一旦你想到了新的方案,就可以更好的去实践。
-src\n -img\n -font\n -js\n -page\n -*.html\n -tpl\n -*.html\n打包出来是这样目录:\n-dist\n -img\n -font\n -js\n -*.html\n而不是指向原来的src目录,img里的文件也是要用插件打包优化的。
基于这个思想,我后来写了一个插件 https://github.com/tangshuang/gulp-css-copy-assets 你可以看下,它完全不考虑路径的问题,最终是要把图片等资源合理检出来。