整合jade輸出到webpack
目前來説,由於seo需要,前端需要配合中間層使用後端渲染。當前的構建中,我們的構建會全量將jade移入相應的dest文件,修改後會對對應的文件進行重新複製移動,但當我們使用webpack的時候,後端模板的處理變成了一件頭疼的事。
如果要用webpack,我們首先要處理的便是模板文件中引用的靜態資源的問題。我們應該做的事情是想如何將jade資源通過自己的手段分析,達到webpack一同輸出的目的。
由於我們的jade模板是通過後端渲染輸出的,產生的模板必然需要是jade模板。此時模塊市場上常用的html-webpack-plugin,html-webpack-plugin-pug,
pug-webpack-html-plugin等等插件並不能滿足我們的需求。於是只好按着自己的思路造個方輪子了。。。
思路
入口區分
在我們的項目中,jade用作內容輸入,佈局,公共模塊,混入方法等。但一個項目的入口一定在該項目下,一般叫index.jade。由於目前某些頁面的入口模板命名並非index.jade,我們可以通過給文件加入入口前綴或者放入一個標誌文件夾進行入口區分。
靜態資源解析
在模板中,我們有時候會引用如下靜態資源
// 通用js靜態資源
+JS([
'common/xxx/xxx.js'
], 'common/xxx/xxx.js')
// 通用css靜態資源
+CSS([
'common/xxx/xxxx.css'
], [
'common/xxx/xxxx.css'
])
// jade模板中使用的靜態資源
img(src="aaaa/bbbb/ccc.png")
div(style="background:url(aaaa/bbbb/cccc.png)")
// jade擴展的佈局模板
extends xxxxx.jade
// jade包涵模塊
include xxxxx.jade
先説js和css的處理。這個直接以vendor.app.js的入口形式進行打包,把這部分資源分離出來。
其次是其他資源,由於是jade模板引用,所以不會通過webpack走file-loader將靜態資源移動到相應位置。此時我們就必須正則出來這些資源進行相應的移動處理了。
var jadeSource = fs.readFileSync('xxxx.jade', 'utf-8');
jadeSource.replace(/extends\s*(\S+)|include\s*(\S+)|src\s*\=\s*"(\S+)"|src\s*\=\s*'(\S+)'/g, function (matched, catched) {
// 獲取到catched Source,依據此文件進行路徑分析和文件讀取,繼續進行文件分析,完成整個文件依賴讀取
})
我們獲得了這些資源,並不會去無窮無盡的一遍一遍讀寫,畢竟重複讀寫明顯蛋疼。這個時候我們需要稍微做一個緩存系統,讓多個共同資源不會重複讀寫。思路很簡單,用map,可控參數cache,以及文件的最後修改時間來判斷即可。
function JadeStat(path, cache) {
this.path = path;
this.cache = cache;
this.stat = fs.statSync(path);
if (this.cache) {
this.file = fs.readFileSync(path, 'utf-8');
}
this.deps = this.getDeps();
}
JadeStat.prototype.update = function () {
this.stat = this.getNowStat();
if (this.cache) {
this.file = fs.readFileSync(this.path, 'utf-8');
}
this.deps = this.getDeps();
}
JadeStat.prototype.getName = function () {
return path.relative(app.staticFolder, this.path);
}
JadeStat.prototype.getNowStat = function () {
return fs.statSync(this.path);
}
JadeStat.prototype.getContent = function () {
if (this.cache) {
return this.file;
}
return fs.readFileSync(this.path, 'utf-8');
}
JadeStat.prototype.getDeps = function () {
var content = this.getContent();
var deps = [];
content.replace(/extends\s*(\S+)|include\s*(\S+)|src\s*\=\s*"(\S+)"|src\s*\=\s*'(\S+)'/g, function (a, cachedSource) {
deps.push(path.resolve(this.path, cachedSource))
})
return deps;
}
var cache = {};
function MyExampleWebpackPlugin(options = {}) {
this.who = options.who;
this.cache = options.cache === undefined ? true : options.cache;
};
MyExampleWebpackPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', (compilation, callback) => {
// 遞歸處理所有模塊以及依賴,並將其扁平化,這樣我們在cache中就獲取到了一個扁平的assetsMap,然後一一做處理即可
// 這裏沒有進行遞歸,僅僅是展示一下。
this.who.forEach((p) => {
var firstInit = false;
if (!cache[p]) {
cache[p] = new JadeStat(p, this.cache);
firstInit = true;
}
let jadestat = map[p];
if (!firstInit && jadestat.stat.mtime !== jadestat.getNowStat().stat.mtime) {
jadestat.update();
}
})
for (var stat of cache) {
var source = stat.getContent();
compilation.assets[stat.getName()] = {
source: function() {
return source;
},
size: function() {
return source.length;
}
};
}
callback();
});
};
後續問題
在我們的項目中,publicPath是指向www/${version}的,所以必須處理好output...