了减少产品和前端开发人员之间的矛盾,不断降本提效,美团医药技术部构建了跨端一体化富文本管理平台Page-佩奇。本文系统介绍了该平台的定位、设计思路、实现原理以及取得的成效。希望这些实战经验与总结,能给大家带来一些启发或帮助。
在互联网圈,开发和产品经理之间相爱相杀的故事,相信大家都有所耳闻。归根结底,往往都是从简单的改需求开始,然后你来我往、互不相让,接着吵架斗嘴,最后导致矛盾不断升级,甚至带来比较严重的后果。
图1
在这种背景下,如果把一些功能相对简单的、需求变动比较频繁的页面,直接交给产品或者运营自己去通过平台实现,是不是就可以从一定程度上减少产品和开发人员之间的矛盾呢?
当然上述的情况,美团也不例外。近些年,美团到家事业群(包括美团外卖、美团配送、闪购、医药、团好货等)的各个业务稳步发展,业务前端对接的运营团队有近几十个,每个运营团队又有不同的运营规则,这些规则还存在一些细微的样式差别,同时规则内容还会随着运营季节、节日、地理位置等进行变化和更新。这些需求具体来说有以下几个特点:
基于以上特点,为了提高研发效率,美团医药技术部开始构建了一个跨端一体化富文本管理平台,希望提供解决这一大类问题的产研方案。不过,部门最初的目标是开发一套提效工具,解决大量诸如帮助文档、协议页、消息通知、规则说明等静态页面的生产与发布问题,让产品和运营同学能够以所见即所得的方式自主完成静态页面制作与发布,进而缩短沟通成本和研发成本。
但是,随着越来越多业务部门开始咨询并使用这个平台,我们后续不断完善并扩充了很多的功能。经过多次版本的设计和迭代开发后,将该平台命名为Page-佩奇,并且注册成为美团内部的公共服务,开始为美团内部更多同学提供更好的使用体验。
本文将系统地介绍Page-佩奇平台的定位、设计思路、实现原理及取得成效。我们也希望这些实战经验与总结,能给更多同学带来一些启发和思考。
我们希望将Page-佩奇打造成一款为产品、运营、开发等用户提供快速一站式发布网页的产研工作台,这是对该平台的一个定位。
一般来讲,传统开发流程是这样的:首先产品提出需求,然后召集研发评审,最后研发同学开发并且部署上线;当需求上线之后,如果有问题需要反馈,产品再找研发同学进行沟通并修复,这种开发流程也是目前互联网公司比较常见的开发流程。
图2 传统开发流程图
而美团Page-佩奇平台的开发流程是:首先产品同学提出需求,然后自己在Page平台进行编辑和发布上线,当需求上线之后有问题需要反馈,直接就能触达到产品同学,他们通常可自行进行修复。如果需求需要定制化,或者需要做一些复杂的逻辑处理,那么再让研发人员配合在平台上进行开发并发布上线。
图3 Page佩奇平台开发流程图
简单来说,对那些功能相对简单、需求变动比较频繁的页面,如果用传统的开发流程将会增加产研沟通和研发排期成本,因此传统方案主要适用于功能复杂型的需求。而Page佩奇平台开发流程,并不适合功能复杂型的需求,特别适用于功能相对简单、需求变动比较频繁的页面需求。
综上所述,可以看出这两种开发流程其实起到了一个互补的作用,如果一起使用,既可以减少工作量,又可以达到降本提效的目的。
我们最初设计Page-佩奇平台的初心其实很简单,为了给产品和运营提供一个通过富文本编辑器快速制作并发布网页的工具。但是,在使用的过程中,很多缺陷也就慢慢地开始暴露,大致有下面这些问题:
于是,我们针对这些问题进行了一些思考和调研:
实现一个功能很容易,但是想要实现一个相对完善的功能,就必须好好下功夫,多思考和多调研。于是,围绕着这些问题,我们不断挖掘和延伸出了一系列功能:
功能流程图如下所示:
图4 Page佩奇平台功能流程图
Page-佩奇平台的基础服务有四个部分,包括物料服务、编译服务、产品赋能、扩展服务。
图5 整体架构图
3.3.2 核心架构
图6 核心架构图
Page-佩奇平台核心架构主要包含页面基础配置层、页面组装层以及页面生成层。我们通过Vuex全局状态对数据进行维护。
图7 关键流程图
如上图7所示,平台的核心流程主要包含页面创建之后的页面预览、编译服务、生成页面。
Page-佩奇平台也可以作为一个完善的富文本编辑器供业务系统使用,支持内嵌到其他系统内。作为消息发布等功能承载,减少重复的开发工作,同时我们配备完善的SDK供大家选择使用。通过Page-SDK可以直接触发Page平台发布、管理等操作,具体的流程如下图所示:
图8 Page-SDK流程图
在使用Page-佩奇平台的时候,美团内部一些业务方提出想要通过Page-佩奇平台进行页面的发布,同时想要拿到发布的内容做一些自定义的处理。于是,我们提供了Open API开放能力,支持以HTTP和Thrift两种方式进行调用。下面主要讲一下Thrift API实现的思路,首先我们先了解下Thrift整体流程:
图9 Thrift整体流程图
Thrift的主要使用过程如下:
那么下面我们具体讲下,Node语言是如何实现和其他服务语言实现调用的。由于我们的服务使用的Node语言,因此我们的Node服务就充当了服务端的角色,而其他语言(Java等)调用就充当了客户端的角色。
图10 Thrift使用详细流程图
目前,美团内部已经有相对成熟的NPM包服务,已经帮我们实现了服务注册、数据传输、服务发现和获取流程。客户端如果想调用我们所提供的的Open API开放能力,首先申请AppKey,然后选择使用Thrift方式或者HTTP的方式,按照所要求的参数进行请求调用。
能力:富文本编辑。 描述:提供富文本可视化编辑,产品和运营无需前端就可以发布和二次编辑页面。 场景:文本协议,消息通知,产品FAQ。
具体案例:
图11 H5静态文本协议案例
能力:开放API(Thirft + HTTP)。 描述:提供开放API,支持业务自定义和样式渲染到业务系统,同时解决了iframe体验问题。 场景:客户端、后端、小程序的同学,可根据API渲染文案,实现动态化管理富文本信息。
具体案例:
小程序使用组件、Vue使用v-html指令实现动态化渲染商品选择说明。
{
"code": 0,
"data": {
"tag": "苹果,标准",
"title": "如何挑选苹果",
"html": "<h1>如何挑选苹果</h1>><p>以下标准可供消费者参考</p><ul><li>酸甜</li><li>硬度</li></ul>",
"css": "",
"js": "",
"file": {}
},
"msg": "success"
}
能力:WebIDE代码编辑。 描述:开发基于WebIDE代码开发工作,基于渠道和环境修改下载链接,能够做到分钟级支撑。 场景:根据产品创建静态页面进行逻辑和样式开发。
具体案例:
var ua=window.navigator.userAgent
var URL_MAP={
ios: 'https://apps.apple.com/cn/app/xxx',
android: 'xxx.apk',
ios_dpmerchant: 'itms-apps://itunes.apple.com/cn/app/xxx'
}
if (ua.match(/android/i)) location.href=URL_MAP.android
if (ua.match(/(ipad|iphone|ipod).*os\s([\d_]+)/i)) {
if (/xx\/com\.xxx\.xx\.mobile/.test(ua)) {
location.href=URL_MAP.ios_dpmerchant
} else {
location.href=URL_MAP.ios
}
}
能力:WebIDE代码编辑 + 物料平台。 描述:通过物料平台,引入公司客户端桥SDK,可以快速完成客户端通信需求。方便前端调试客户端基础桥功能。 场景:客户端跳转,通信中间页。
具体案例:
// 业务伪代码
XXX.ready(()=> {
XXX.sendMessage({
sign: true,
params: {
id: window.URL
}
}, ()=> {
console.error('通信成功')
}, ()=> {
console.error('通信失败')
})
})
能力:提供胶水层Page-SDK,连接业务系统和Page。 描述:业务系统与Page-佩奇平台可进行通信,业务系统可调用Page发布、预览、编辑等功能,Page可返回业务系统页面链接、内容、权限等信息。减少重复前后端工作,提升研发效率。 场景:前端富文本信息渲染,后端富文本信息管理后台。
具体案例:
图12 业务系统内嵌Page案例
截止目前数据统计,Page佩奇平台生成网页5000多个,编辑页面次数16000多次,累计页面访问PV超过8260万。现在,美团已经有十多个部门和三十多条业务线接入并使用了Page佩奇平台。
图13 Page平台每日生成页面统计
富文本编辑器和WebIDE不仅是复杂的系统,而且还是比较热门的研究方向。特别是在和美团的基建结合之后,能够解决团队内部很多效率和质量问题。这套系统还提供了语法智能提示、Diff对比、前置检测、命令行调试等功能,不仅要关注业务发布出去页面的稳定性和质量,更要有内置的一系列研发插件,主动帮助研发提高代码质量,降低不必要的错误。
经过长期的技术和业务演进,Page-佩奇平台已经能够有效地帮助研发人员大幅提升开发效率,具备初级的Design To Code能力,但是仍有许多业务场景值得去我们探索。我们也期待优秀的你参与进来,一起共同建设。
高瞻、宇立、肖莹、浩畅,来自美团医药终端团队。王咏、陈文,来自美团闪购终端团队。
美团医药长期招聘Android、iOS、FE前端工程师,坐标在北京和成都。感兴趣的同学可将简历发送至:sunyuli@meituan.com(邮件主题请注明:美团医药终端)。
| 本文系美团技术团队出品,著作权归属美团。欢迎出于分享和交流等非商业目的转载或使用本文内容,敬请注明“内容转载自美团技术团队”。本文未经许可,不得进行商业性转载或者使用。任何商用行为,请发送邮件至tech@meituan.com申请授权。
天来聊聊定制你的bootstrap文件,换句话说就是修改css文件,大家知道bootstrap文件是有less书写的,好吧,我们从源头开始吧!
1、下载bootstrap源文件:
在bootstrap中文网上下载源码,不要选错呀。
2、解压后看到less文件夹,打开,找到variables.less文件,这个文件基本上存的都是变量;用sublime打开。
3、看到了从第4行开始:
4、我们今天就说这一段,基本的less语法,等说完了,再说如何解析成css文件。
第1,2,3行是注释,在less中,注释有2中,一种是css的格式注释,一种是//注释;
区别1:/**/是多行注释 // 是单行注释
区别2:/**/编译的时候写入css文件 //注释的不写入css文件
@gray-base: #000; 定义一个变量@gray-base值为#000;
@gray-darker: lighten(@gray-base, 13.5%); // #222
定义一个变量@gray-darker 值是多少呢?
@gray-base 是#000;16进制是 0;
0的13.5%,黑色的就是 22呀!
所以 lighten默认的就是#222;
整个是什么意思呢? 就是@gray-darker的值为 lighten(@gray-base, 13.5%);,如果变量没有传参数,那么就是默认的222;如果传了参数,则为(@gray-base, 传入的参数)
我们在392行,可以看到“反色导航条”颜色定义的代码:
它的后面传了个参数为15%,值是多少你自己算算。
这样的好处是什么呢?
你想想,你在做一个项目的时候,客户今天说就定这个颜色了,过了几天,改了,换颜色了!
如果你用css写的,O(∩_∩)O~,你就挨个改吧。如果你用less写的,只需要修改一处,就可以了,分分钟的事!惬意吧!
今天就先说到这里,改天说说如何让less文件编译为css文件!
1.安装node: 官网
2.配置cnpm(非必须)
设置淘宝镜像:npm install -g cnpm --registry=https://registry.npm.taobao.org,10分钟同步官网一次。
或者可以使用npm config set registry https://registry.npm.taobao.org来改变npm默认下载地址
1.初始化工程目录
mkdir my_demo
cd my_demo
npm init
回车会生产一个npm目录,只包含一个package.json,创建src目录
mkdir src
2.配置工程
(1)首先安装webpack和webpack-cli
npm i -D webpack webpack-cli -g
(2)新建并配置webpack(entry, output, loader, plugin, mode)
const path=require('path');
4 module.exports={
5 // 项目入口,webpack从此处开始构建
6 entry: {
7 main: path.join(__dirname, 'src/index.js'), // 指定入口,可以指定多个。参考webpack文档
8 },
9 output: {
10 path: path.join(__dirname, "dist"), // bundle生成(emit)到哪里
11 filename: "bundle.js", // bundle生成文件的名称
12 },
13 }
(3)配置开发应用服务器
正常情况下,我们需要以应用服务器打开我们的网页,webpack-dev-server提供了一个简单的web服务器,并且能够实时重新加载。指南 首先需要安装webpack-dev-server
npm i -D webpack-dev-server
module.exports={
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
},
devServer: {
contentBase: './dist',
},
...
}
这段配置告诉webpack-dev-server,在默认host和port(8080)下建立服务,并将contentBase目录下的目录,作为可访问文件。
接下来让我们的服务器跑起来webpack --mode production --config ./webpack.config.js,在package.json配置命令脚本:
可能会有报错,webpack-cli @4的版本不兼容,降到@3就可以啦
"scripts" : {
2 "start": "webpack-dev-server --mode development --open",
3 "build: "webpack --mode production --config ./webpack.config.js"
4 }
其中mode为模式,webpack会有相应优化
3.安装react,babel
安装react全家桶,安装jsx转换工具babel
npm install react react-dom react-router-dom -S
npm install babel-core babel-loader babel-preset-env babel-preset-react -D
配置webpack使其生效
module.exports={
// ...省略
module: {
rules: [
{
test: /\.(js|jsx)$/, // babel7开始支持编译ts,tsx
exclude: /node_modules/,
enforce: 'pre',
use: [{
loader: 'babel-loader',
}, {
loader: 'eslint-loader', // 指定启用eslint-loader,需安装
options: {
formatter: require('eslint-friendly-formatter'), // 安装
emitWarning: false
}
}]
},
]
}
babel-loader的配置可以在rules中loader option配置,也可以单独一个.bablerc或babel.config.js的文件放在根目录下,babel会自动读取配置
{
"presets": ["env","react"],
// antd按需引入
// "plugins": ["react-hot-loader/babel", ["import", { "libraryName": "antd", "libraryDirectory": "es","style": "css" }], "transform-runtime"]
}
扩展:babel 7.x 以上开始支持两种类型的配置文件, 分别是.babelrc 和 babel.config.js ,在官方的描述里:
babel.config.js 当前项目维度 (Project Wide)的配置文件,相当于一份全局配置,如果 babel 决定应用这个配置文件,则一定会应用到所有文件的转换。
.babelrc 相对文件(File Relative)的配置文件, babel 决定一个 js 文件应用哪些配置文件时,会执行如下策略: 如果要转换的这个 js 文件在当前项目内,则会先递归向上搜索最近的一个 .babelrc 文件(直到遇到package.json目录),将其与全局配置合并。如果这个 js 文件不在当前项目内,则只应用全局配置,忽略与这个文件相关的 .babelrc 。
这两个我更愿意将其称为全局配置 (babel.config.js) 和局部配置 (.babelrc) 便于理解一些。
参考链接:https://zhuanlan.zhihu.com/p/367724302
4.添加常用loader
(1)样式loader:
前置安装style-loader、less-loader/scss-loader、css-loader、postcss-loader。
test: /\.(css|less)$/,
exclude: /node_modules/,
include: /src/,
use: [
{loader: "style-loader"},
{
loader: 'css-loader',
options: {
minimize: process.env.NODE_ENV==='production',
importLoaders: 2,
localIdentName: '[name]-[local]-[hash:base64:5]',
modules:true
}
}, {
loader: 'postcss-loader',
options: { // 如果没有options这个选项将会报错 No PostCSS Config found
plugins: (loader)=> [
require('autoprefixer')(), //CSS浏览器兼容
]
}
},{
loader: 'less-loader',
options: {
javascriptEnabled: true,
}
}],
(2)图片资源 file-loader/url-loader
url-loader里的limit参数可以指定小于配置参数的文件转换成base64编码的文件,其余的生成一个可配置hash的单独文件(file-loader的作用),这里也指出了两者的不同之处。
1.单独打包样式文件
# for webpack 4
npm install --save-dev mini-css-extract-plugin
# for webpack 3
npm install --save-dev extract-text-webpack-plugin
# for webpack 2
npm install --save-dev extract-text-webpack-plugin@2.1.2
# for webpack 1
npm install --save-dev extract-text-webpack-plugin@1.0.1
extract-text-webpack-plugin用法
const ExtractTextPlugin=require("extract-text-webpack-plugin");
module.exports={
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new ExtractTextPlugin("styles.css"),
]
}
mini-css-extract-plugin用法
const MiniCssExtractPlugin=require('mini-css-extract-plugin');
module.exports={
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
2.为打包后的文件增加hash
如果浏览器加载发现远端文件没有发生变化时,将会启用缓存,导致新修改的页面并没有同步,这时候为了避免缓存,我们就需要让每次打包后的文件有不同的文件名,以减少缓存。
// webpack.config.js -> output
output: {
path: path.join(__dirname, "dist"),
publicPath: '/',
filename: "js/[name]-[hash]" + ".js",
chunkFilename: "js/[name]-[hash]" + ".js",
},
同样抽取的css文件也可以类似命名hash
3.html-webpack-plugin
生成一个自动引入我们生成的文件的新模板。 当我们在生成有 hash 标识符的 css,js时非常方便
4.提取公共代码
splitChunks:提取公共代码,防止代码被重复打包,拆分过大的js文件,合并零散的js文件。
具体使用翻看专题
*请认真填写需求信息,我们会在24小时内与您取得联系。