微站点前后端体系建设

标签:
  • 前端工程

微站点之前基于前后端完全分离的策略,采用了angular框架作前端的基本架构,利用yeoman前端工具快速搭出脚手架。在顺利完成了几个版本的开发 后,出现了一些意想不到的变化:

  • 我接手了后台接口的开发,采用nodejs后端,基本部署规参考express项目部署。
  • angular的学习门槛比较高,初中级的前端不懂,也无法独立完成工作; 蛋疼的是,官方的文档还被墙封了(万恶的gfw ),所以一直是以来是我在开发与扩展。
  • 无法平滑升级。
  • 需求变动频繁,需要接入第三方的业务接口。
  • SEO不友好,后期推广顾虑。
  • 微信上的自定义分享比较麻烦,后期采用了微信的api,上了中控服务器,算是解决了。

所以迫切需要采用全新的架构来解决现有的问题。 需要解决的问题有:

  • 直接在服务端直接输出模板。
  • 实现依赖声明,按需加载。
  • 方便协作开发。
  • 手机端友好。

参考百度前端工具框架,张云龙的前端体系建设。【语言能力扩展】、【基于表的静态资源管理系统】与【前端资源聚合】是fis的一大亮点,前端领域语言只要扩展了资源定位、内容嵌入和依赖声明三种能力,就可以实现绝大多数前端开发需求。

后端还是采用sails,但要剥离自带的grunt前端任务管理。

  • 使用 sails new microsite.vzhen –no-frontend 生成一个新的工程。
  • 干掉Gruntfile.js文件,创建.sailsrc 文件编辑
"generators": { "modules": {} }, "hooks" : { "grunt": false },status: "develop"}

这样一来,就剥离了自带的grunt前端任务管理。

基于sails 的特点来定制目录规范,根据sails官方文档说明,静态资源管理的目录是 .tmp/public;模板不做发布,还是在views目录下。

目录结构

  • pages目录存放页面资源。
  • components 存放模块化资源。

发布后,components的所有资源,pages的除了ejs模板外,都会发布到.tmp/public/ 目录,pages的ejs模板会发布到views。

  • 编辑.gitignore 加上views目录
  • fis-conf.js 的配置如下:
fis.config.set('project.include', ['common/**','components/**','pages/**']);
fis.config.set('roadmap.path', [
	{
    reg: /^\/pages\/(.*(?:\.(js|less|png|jpg|jpeg)))$/i,
    release: '/.tmp/public/$1',
		isPage: true,
    url: '/$1',
		id: '$1'
	} ,
	{
    reg: /^\/pages\/(.*(?:\.ejs))$/i,
		isHtmlLike: true,
		useMap: true,
    release: '/views/$1',
		id: '$1'
	} ,
	{
    reg: /^\/components\/(.*)$/i,
    release: '/.tmp/public/components/$1',
    url: '/components/$1',
		isComponents: true
	} ,
	{
    reg: '**',
    useStandard: false,
    useOptimizer: false
  }
]);
fis.config.set('project.fileType.text', 'ejs');
fis.config.set('roadmap.ext.less', 'css');
fis.config.set('modules.parser.less','less');

fis.config.set('modules.postprocessor.js', function(content, file){
	//只对模块化js文件进行包装
	if(file.isPage ){
		content = '(function(){' + content + '})(window)';
	}
	if (file.isComponents && file.filename !== "mod") {
		content = 'define("' + file.id + 
			'", function(require,exports,module){' +
			content + '});';
	}
	return content;
});

var createFrameworkConfig = function(ret, conf, settings, opt){
	var map = {
		res : {},
		pkg : {}
	};
	fis.util.map(ret.map.res, function(id, res){
		var r = map.res[id] = {};
		if(res.deps) r.deps = res.deps;
		//有打包的话就不要加url了,以减少map.js的体积
		if(res.pkg) {
			r.pkg = res.pkg;
		} else {
			r.url = res.uri;
		}
	});
	fis.util.map(ret.map.pkg, function(id, res){
		var r = map.pkg[id] = {};
		r.url = res.uri;
		if(res.deps) r.deps = res.deps;
	});

	fis.util.map(ret.src, function(subpath, file){
		if(file.filename == 'layout'){
			var content = file.getContent();
			content = content.replace(/\b__FRAMEWORK_CONFIG__\b/g, JSON.stringify(map));
			file.setContent(content);
		}
	});
}

fis.config.set('modules.postpackager', [createFrameworkConfig]);

接下来后台需要实现一个根据编译生成的map.json 文件实现收集依赖声明,从而实现按需引用的组件函数。

var has = {}, uris = { "css": [], "js": [] };
var _slice = Array.prototype.slice;

var fs = require('fs');
var map = JSON.parse(fs.readFileSync("map.json", "utf-8"));

function load_widget(id) {
  has = {}, uris = { "css": [], "js": [] };

	function _process(id) {
		if (has[id]) { return; }
		var resource = map["res"][id];
		var pkg = map["pkg"];
		if (!!resource) {
			has[id] = true;
			var type = resource["type"];
			var uri = resource["uri"];
			var pkg_key = resource["pkg"];

			if (type == "css" || type == "js") {
				if (!!pkg_key) {
					uris[type].push(pkg[pkg_key]["uri"]);
					pkg[pkg_key]["has"].forEach(function(val,index) {
						has[val] = true;
					});
				}else {
					uris[type].push(uri);
				}
			}
			var deps = resource["deps"];
			if (deps) {
				for (var i = 0; i < deps.length; i++) {
					_process(deps[i]);
				}
			}
		}
	}

	_process(id);
}

module.exports = {
	load_widget: load_widget,
	getDeps: function() {
		return {
			css: _slice.call(uris["css"], 0).reverse(),
			js: _slice.call(uris["js"], 0).reverse()
		};
	}
};

来看一下父版布局模板,css,js 都是动态输出的。

<!DOCTYPE html>
<html>

<head>
  <title>医生微站点</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

	<% if(css && css.length > 0) { %>
		<% for(var i = 0; i < css.length ;i++) {  %>
			<% var c = css[i] %>
			<link rel="stylesheet" href="<%= c %>" type="text/css" media="screen" charset="utf-8">
		<% } %>
  <% } %>

</head>

<body>
  <%- body %>
</body>
<script src="../components/modjs/mod.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
   require.resourceMap(__FRAMEWORK_CONFIG__);
</script>

	<% if(js && js.length > 0) { %>
		<% for(var i = 0; i < js.length ;i++) {  %>
			<% var j = js[i] %>
			<script src="<%= j %>" type="text/javascript" charset="utf-8"></script>
		<% } %>
  <% } %>

</html>

部署时,ssh到服务器,定位到相关目录。执行 sh deploy.sh 就可以了。 开发时:

  • nodemon ./app.js localhost 1341 启动sails 服务器
返回首页 30 March 2015