在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
vue的pc端浏览器兼容性都有什么坑?我看官网上说的是支持es6的浏览器vue都支持,请问有实际开发中用vue的项目吗,各个浏览器表现如何?
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
VUE做PC端项目最适合的还是公司的OA系统,毕竟可以规定员工用的浏览器等,因为现在不排除有部分用户使用IE8-的可能性,如果排除这些不谈,VUE主要开始开发单页面应用,单页面对于SEO也是极其不友好的,因此最多还是用在了手机端SPA和后台OA上面。
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
官网上的两句话描述了其兼容性
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
我现在的项目(银行的系统)使用的是angular1.2.32版本,完美兼容ie8,这还是努力争取不兼容更低的ie情况下,当然,系统不考虑seo。好像在这提angular不合适
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
这些框架最终都会被转成es5,是可以在主流浏览器运行的。vue比较特殊,因为它使用的是对象的getter,setter。这些特性ie9以上才支持,其他非ie系浏览器基本都支持
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
IE9及以上浏览器,所以chrome,firefox更是没有问题,只要支持ES5特性的浏览器均可
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
Vue.js 不支持 IE8 及其以下版本,因为 Vue.js 使用了 IE8 不能模拟的 ECMAScript 5 特性。Vue.js 支持所有兼容 ECMAScript 5 的浏览器。
同步到新浪微博
分享到微博?
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。51CTO旗下网站
移动前端:移动端页面坑与排坑技巧
对于前端开发者来说移动端存在更多的挑战,移动端页面开发过程中会碰到各种各样千奇百怪的问题(我们俗称BUG或坑),那么今天我为大家分享移动端页面开发过程中的一些坑和排坑技巧。
作者:佚名来源:腾讯TGideas| 14:34
对于前端开发者来说移动端存在更多的挑战,移动端页面开发过程中会碰到各种各样千奇百怪的问题(我们俗称BUG或坑),那么今天我为大家分享移动端页面开发过程中的一些坑和排坑技巧。
移动端页面在不同设备、不同操作系统 、不同运行环境下都可能造成各种各样的没有碰到过的的坑,相比曾经的IE6坑多了。下面先介绍一下4类具体常见的坑:
A、页面高度渲染错误
在各移动端浏览器中经常会出现这种页面高度100%的渲染错误,页面低端和系统自带的导航条重合了,高度的不正确我们需要重置修正它,通过javascript代码重置掉:
document.documentElement.style.height&=&window.innerHeight&+&'px';&
B、叠加区高亮
在部分android机型中点击页面某一块区域可能会出现如图所示的黄色框秒闪,这是部分机型系统自身的默认定制样式,给该元素一个CSS样式重置掉:
-webkit-tap-highlight-color:rgba(0,0,0,0);&
A、事件无法被触发
在部分android机型的微信环境中会出现事件无法触发、表单无法输入的情况,我们针对需要输入或者触发事件的元素设置样式:-webkit-transform: translate3d(0,0,0) ,不过新版本的微信已经直接修复了该问题。
B、:active 效果不兼容
在android 4.0版本以下CSS :active伪状态效果无法兼容,我们给该元素的touch系列的事件(touchstart/touchend/touchmove)绑定一个空匿名方法:
var&element=document.getElementsById(&btnShare&);&element.addEventListener(&touchstart&,function(){},false);&
A、浏览器崩溃
var&act&=&function(){&&&&&window.removeEventListener('devicemotion',act);&};&window.addEventListener('devicemotion',act,false);&
解绑函数写在了事件处理中导致小米手机中的微信崩溃,那么我们不要将解绑时间写在事件处理中即可。
B、预加载、自动播放无效
如上表所示,经过简单的测试发现预加载、自动播放的有效性受操作系统、浏览器(webview)、版本等的影响,苹果官方规定必须由用户手动触发才会载入音频,那么我们捕捉一次用户输入后,让音频加载实现预加载:
&&document.addEventListener('touchstart',&function&()&{&&&document.getElementsByTagName('audio')[0].play();&&&document.getElementsByTagName('audio')[0].pause();&&});&
C、无法同时播放多音频
在android设备中,播放后一音频会打断前一音频,而不会同步播放,这个是目前系统资深决定的,我们只有采取优雅降权的方法让android选择不一样风格的音频前后切换播放而不是同时播放,达到与预期接近的音频效果。
D、不支持局部滚动
在android 4.0版本以下在body(html)元素之外的元素 overflow:scroll 样式设置滚动条无效,这里有两种解决方案:
1、巧用布局直接设置样式滚动条在body(html)上,其他元素&错觉滚动&。
2、利用iscroll、自写js控制translate、scrollTop模拟。
4、系统/硬件
A、怪异悬浮的表单
在部分android 机型中的输入框可能会出现如图怪异的多余的浮出表单,经过观察与测试发现只有input:password类型的输入框存在,那么我们只要使用input:text类型的输入框并通过样式-webkit-text-security: 隐藏输入密码从而解决。
B、错误出现滚动条
在游戏内嵌页中出现了不应该出现的滚动条,而且内容并没有超出内容区宽度,经过测试overflow:hidden 无效,通过一系列尝试使用古老的 &body scroll=&no&& 写法解决,多尝试一下不同的写法和属性会有不一样的惊喜哦!
C、链接打开系统浏览器
在游戏内webview的部分android机型中可能会出现点击链接调用系统浏览器的情况,这是一个非常不好的体验。那么我们尝试给这个元素添加 target=&_blank&' 属性有可能解决,如果还不能解决那么需要修改IOS或android原生系统函数了。
D、Flex box 不兼容
在游戏内嵌webview中碰到Flex box布局不兼容的情况,图中所示下面部分的导航错位了,虽然之前有仔细查看过Flex box的兼容性,但是在游戏内嵌页中无法确定其调用的系统浏览器版本及兼容,所以导致错误,所以我们写完整历史版本呢的3种Flex box 解决。那么我们思考在写页面过程中还是本着保守稳定的方式书写样式可以减少不不要的麻烦。
面对这么多坑,还有各种各样已经的和未知的坑,时间上也不可能一一讲完,更不可能都已经有解决方案,我们需要学会如何去解决坑:
解决坑首先自己需要有个强有力的执行力,通过不断去尝试来探索未知的问题,那么一般我们会通过哪些方式哪些步骤和技巧来尝试呢?
1、首先我们需要定位问题,我们可以使用下列方法:
A、DOM隔离定位法
不断由大范围到小范围隔离出DOM,从而找出、触发问题的DOM元素
B、样式、JS剔除法
不断剔除一些JS、CSS观察调试检查是否是由部分JS、CSS属性引起问题
C、多运行环境测试法
在多环境中测试,排查是否是由于特定环境引起
D、PC与手机联合调试法
联合PC与手机进行定位,如:
2、我们解决问题可以尝试如下的方式:
* 尝试、转思维、绕解决
* 优雅降权、区分处理、沟通
* 搜索与提问......
和以下的思维:
* 分割......
3、在解决问题的过程中我们需要学会利用搜索和沟通资源解决问题:
B、stackoverflow
C、同事、朋友、QQ群、论坛等
Google和stackoverflow让你更容易更精确的快速搜索出问题相关的资料,同时、朋友QQ群、论坛等让你可以直接的跟人沟通出别人所遇到的问题及解决方案。
在发现、解决问题后,更重要的是要学会如何总结问题:
A、总结记录问题产生条件、解决方法和解决过程
B、尽可能分析原理、产生的条件,避免重蹈覆辙
C、分享给更多的人
无穷无尽的坑,走的人多了,总结分享的多了,坑也就越来越平了
我们在总结记录问题的同时,整理了一个移动端小贴士,记录问题与一些坑,虽然目前还不完善但是希望能分享给更多的人也希望更多的人能参与完善。
【编辑推荐】
【责任编辑: TEL:(010)】
大家都在看猜你喜欢
热点头条热点热点热点
24H热文一周话题本月最赞
讲师:108833人学习过
讲师:27130人学习过
讲师:13709人学习过
精选博文论坛热帖下载排行
本书描述了怎样应用面向对象的概念来进行.NET应用程序的架构、设计和开发。作者将重点放在了面向业务的对象,即业务对象和怎样在包括Web和...
订阅51CTO邮刊2016年, Vue框架在社区中逐渐活跃了起来, 同时公司也有更多的产品线启动, 这时很多团队需要做运营类管理后台, 而此时前端人力稀缺. 为了不重复建设和解放前端人力,
团队就准备基于webpack+vue框架做一个针对运营后台的前端打包和工程的解决方案, 然后进行公司内推广和培训, 让后端同学参与进来. 前端同学负责工程的建设和组件的开发, 后端同学负责具体业务开发.
到目前为止, 有5个以上团队采用这套前端解决方案进行运营后台或者工具项目开发, 极大的释放了前端人力, 效果不错. 在不断的演进中, 整个前端的客户的技术栈也逐渐由scrat+zepto 转型webpack+vue的开发方式.
因为是针对运营后台的设计初衷, 对性能和seo没有要求以及其他语言(java)的支持,采用是vue前端渲染方式进行设计的. 但随着新业务的发展, 加上vue支持服务端渲染的特性, 我们想在服务端渲染技术做一下研究和实践.
如下就针对webpack+vue服务端渲染进行相关研究和实践. 目前公司所有的前端新项目都是采用egg框架进行开发, 接下来主要讲的是基于egg项目如何实现webpack+vue工程化构建和服务端客户端渲染技术落地.
我们要解决什么问题
针对背景里面提到的一些问题, 基于webpack + egg项目的工程化, 当初想到和后面实践中遇到问题, 主要有如下问题需要解决:
Vue服务端渲染性能如何?
webpack 客户端(browser)运行模式打包支持
webpack 服务端(node)运行模式打包支持
如何实现服务端和客户端代码修改webpack热更新功能
webpack打包配置太复杂(客户端,服务端), 如何简化和多项目复用
开发, 测试, 正式等多环境支持, css/js/image的压缩和hash, cdn等功能如何配置, 页面依赖的css和js如何加载
如何快速扩展出基于vue, react前端框架服务端和客户端渲染的解决方案
webpack工程设计
我们知道webpack是一个前端打包构建工具, 功能强大, 意味的配置也会复杂. 我们可以通过针对vue, react等前端框架,采用不同的配置构建不同的解决方案.
虽然这样能实现, 但持续维护的成本大, 多项目使用时就只能采用拷贝的方式, 另外还有一些优化和打包技巧都需要各自处理.
基于以上的一些问题和想法, 我希望基于webpack的前端工程方案大概是这个样子:
webpack太复杂, 项目可重复性和维护性低, 是不是可以把基础的配置固化, 然后基于基础的配置扩展出具体的解决方案(vue/react等打包方案).
webpack配置支持多环境配置, 根据环境很方便的设置是否开启source-map, hash, 压缩等特性.
webpack配置的普通做法是写配置, 是不是可以采用面向对象的方式来编写配置.
能够基于基础配置很简单的扩展出基于vue, react 服务端渲染的解决方案
针对egg + webpack内存编译和热更新功能与框架无关, 可以抽离出来, 做成通用的插件
webpack基础配置固化
在使用webpack对不同的前端框架进行打包和处理时, 有些配置是公共的, 有些特性是共性的, 我们把这些抽离出来, 并提供接口进行设置和扩展.
option: entry读取, output, extensions 等基础配置
loader: babel-loader, json-loader, url-loader, style-loader, css-loader, sass-loader, less-loader, postcss-loader, autoprefixer 等
plugin: webpack.DefinePlugin(process.env.NODE_ENV), CommonsChunkPlugin等
js/css/image 是否hash
js/css/image 是否压缩
js/css commonChunk处理
开发辅助特性
编译进度条插件 ProgressBarPlugin
资源依赖表
ManifestPlugin
热更新处理
HotModuleReplacementPlugin
以上一些公共特性是初步梳理出来的, 不与具体的前端框架耦合. 针对这些特性可以单独写成一个npm组件, 并提供扩展接口进行覆盖, 删除和扩展功能.
在具体实现时, 可以根据环境变量 process.env.NODE_ENV 默认开启或者关闭一些特性. 比如本地开发时, 关闭 js/css/image 的hash和压缩,
开启热更新功能.
webpack配置面向对象实现
针对上面梳理的公共基础配置, 可以把webpack配置分离成三部分: option, loader, plugin
针对客户端和服务端打包的差异性, 设计成三个类 WebpackBaseBuilder, WebpackClientBuilder, WebpackServerBuilder
基于以上的想法, 大概代码实现:
WebpackBaseBuilder.js 公共配置
class WebpackBaseBuilder {
constructor(config) {
this.config = config;
this.initConfig();
this.initOption();
this.initConfigLoader();
this.initConfigPlugin();
initConfig() {
this.prod = process.env.NODE_ENV === 'production';
this.options = {};
this.loaders = [];
this.plugins = [];
this.setUglifyJs(this.prod);
this.setFileNameHash(this.prod);
this.setImageHash(this.prod);
this.setCssHash(this.prod);
this.setCssExtract(false);
initOption() {
this.options = {
entry: Utils.getEntry(this.config.build.entry),
resolve: {
extensions: ['.js']
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
initConfigLoader() {
// default custom loader config list, call createWebpackLoader to webpack loader
this.configLoaders = [
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: require.resolve('url-loader'),
limit: 1024
dynamic: () =& {
name: this.imageName
initConfigPlugin() {
// default custom plugin config list, call createWebpackPlugin to webpack plugin
this.configPlugins = [
enable: () =& {
return this.isUglifyJS;
clazz: webpack.optimize.UglifyJsPlugin,
args: () =& {
compress: {
warnings: false,
dead_code: true,
drop_console: true,
drop_debugger: true
setOption(option) {
this.options = merge(this.options, option);
setPublicPath(publicPath) {
this.options = merge(this.options, { output: { publicPath } });
setDevTool(devtool, force) {
if (!this.prod || force) {
this.options = merge(this.options, { devtool });
setConfigLoader(loader, isHead) {
setConfigPlugin(plugin, isHead) {
setLoader(loader, isHead) {
setPlugin(plugin, isHead) {
createWebpackLoader() {
return this.loaders;
createWebpackPlugin() {
return this.plugins;
create() {
this.createWebpackLoader();
this.createWebpackPlugin();
return this.getWebpackConfig();
getWebpackConfig() {
return merge({
rules: this.loaders
plugins: this.plugins
this.options);
setMiniCss(isMiniCss) {
this.isMiniCss = isMiniCss;
setUglifyJs(isUglifyJS) {
this.isUglifyJS = isUglifyJS
setFileNameHash(isHash, len = 7) {
if (isHash) {
this.filename = Utils.assetsPath(this.config, `js/[name].[hash:${len}].js`);
this.chunkFilename = Utils.assetsPath(this.config, `js/[id].[chunkhash:${len}].js`);
this.filename = Utils.assetsPath(this.config, 'js/[name].js');
this.chunkFilename = Utils.assetsPath(this.config, 'js/[id].js');
setImageHash(isHash, len = 7) {
if (isHash) {
this.imageName = Utils.assetsPath(this.config, `img/[name].[hash:${len}].[ext]`);
this.imageName = Utils.assetsPath(this.config, `img/[name].[ext]`);
setCssHash(isHash, len = 7) {
if (isHash) {
this.cssName = Utils.assetsPath(this.config, `css/[name].[contenthash:${len}].css`);
this.cssName = Utils.assetsPath(this.config, `img/[name].css`);
setCssExtract(isExtract) {
this.config.extractCss = isExtract;
module.exports = WebpackBaseBuilder;
WebpackServerBuilder.js 客户端打包公共配置
class WebpackClientBuilder extends WebpackBaseBuilder {
constructor(config) {
super(config);
this.initClientOption();
this.initClientConfigPlugin();
this.initHotEntry();
this.setCssExtract(this.prod);
this.setMiniCss(this.prod);
initHotEntry() {
initClientOption() {
initClientConfigPlugin() {
this.configPlugins.push({
enable: () =& {
return !this.prod;
clazz: webpack.HotModuleReplacementPlugin
module.exports = WebpackClientBuilder;
WebpackClientBuilder.js 服务端打包公共配置
class WebpackServerBuilder extends WebpackBaseBuilder {
constructor(config) {
super(config);
this.initServerOption();
this.setCssExtract(false);
initServerOption() {
this.setOption({
target: 'node',
libraryTarget: 'commonjs2',
path: path.join(this.config.baseDir, 'app/view')
externals: Utils.loadNodeModules()
module.exports = WebpackServerBuilder;
以上是正对webpack公共基础配置进行初步的设计, 还需不断完善, 目前已经基于此设想开发了
针对具体前端框架vue的打包方案实现
上面已经采用面向对象的方式实现了webpack的基础配置插件, 接下来我们在easywebpack的基础上扩展出
webpack+vue前端构建解决方案.
vue构建里面与vue相关主要有vue-style-loader和vue-html-loader, extensions,
process.env.VUE_ENV 环境变量, 我们在easywebpack上面扩展此特性即可.
Vue客户端和服务端打包公共配置config.js
const EasyWebpack = require('easywebpack');
const webpack = EasyWebpack.webpack;
const merge = EasyWebpack.merge;
exports.getLoader = config =& {
test: /\.vue$/,
loader: require.resolve('vue-loader'),
dynamic: () =& {
options: EasyWebpack.Loader.getStyleLoaderOption(config)
test: /\.html$/,
loader: require.resolve('vue-html-loader')
exports.initBase = options =& {
return merge({
resolve: {
extensions: ['.vue']
}, options);
exports.initClient = options =& {
return merge(exports.initBase(options), {
resolve: {
vue: 'vue/dist/vue.common.js'
}, options);
exports.initServer = options =& {
return merge(exports.initBase(options), {
resolve: {
vue: 'vue/dist/vue.runtime.common.js'
plugins: [
// 配置 vue 的环境变量,告诉 vue 是服务端渲染,就不会做耗性能的 dom-diff 操作了
new webpack.DefinePlugin({
'process.env.VUE_ENV': '"server"'
}, options);
这些基础配置不复杂, 没有对所有的属性提供单独的方法进行设置, 直接通过setOption方法统一设置.
Vue客户端打包配置client.js
'use strict';
const EasyWebpack = require('easywebpack');
const baseConfig = require('./config');
class WebpackClientBuilder extends EasyWebpack.WebpackClientBuilder {
constructor(config) {
super(config);
this.setOption(baseConfig.initClient());
this.setConfigLoader(baseConfig.getLoader(this.config), true);
create() {
return super.create();
module.exports = WebpackClientBuilder;
Vue服务端打包配置server.js
'use strict';
const EasyWebpack = require('easywebpack');
const baseConfig = require('./config');
class WebpackServerBuilder extends EasyWebpack.WebpackServerBuilder {
constructor(config) {
super(config);
this.setOption(baseConfig.initServer());
this.setConfigLoader(baseConfig.getLoader(this.config), true);
module.exports = WebpackServerBuilder;
命令行运行编译 build.js
编译入口脚本
'use strict';
const EasyWebpack = require('easywebpack');
const clientConfig = require('./client')(config);
const serverConfig = require('./server')(config);
EasyWebpack.build([clientConfig, serverConfig]);
命令行运行
NODE_ENV=development && node build.js
NODE_ENV=production && node build.js
目前已经基于此实现开发了
egg + webpack内存编译和热更新
webpack基础配置和vue打包插件已完成, 那如何在本地开发实现修改代码自动编译功能呢?
webpack提供了很好的内存编译和热更新机制, 极大提高了编译效率和开发效率.
如果你仅仅是进行webpack编译客户端运行文件, 可以很方便的做到热更新机制: 修改代码, 不需要手动刷新界面, UI马上会自动更新.
这种更新不会刷新页面, 而是webpack通过监听文件修改, 通过socket建立客户端的连接, 把修改的内容通过socket发给浏览器,通过动态执行js达到页面更新的效果.
如果是修改Node.js服务端端代码想要项目自动重启和webpack编译内存继续存在而不是重新编译, 就需要借助egg框架的agent机制.
egg已经内置了worker和agent通信机制以及自动重启功能.
实现egg项目服务端代码修改项目自动重启的功能可以使用插件.
worker和agent通信机制: https://eggjs.org/zh-cn/core/cluster-and-ipc.html
当egg自动重启时, agent是不会销毁的, 这样就可以让webpack实例继续保持, 我们需要解决的是如何从webpack编译内存里面编译的文件内容.
内容包括webpack编译的服务端渲染文件和客户端用到js, css, image等静态资源. 这些内容我们都可以通过worker发现消息给agent, 然后读取内容,
再通过agent发送消息给worker. js, css, image等静态资源也这样处理的话就需要读取文件流,然后根据文件类型返回给对应的响应内容.
这里采用一种更简单的方式, 直接在agent里面启动一个koa的webpack编译服务, 这样就可以通过http的方式访问js, css, image等静态资源.
首先在agent中启动koa服务
针对webpack server 编译模式, 在egg agent里面单独启动koa编译服务, 同时开启跨域功能. agent.js 代码如下:
const webpack = require('webpack');
const koa = require('koa');
const cors = require('kcors');
const app = koa();
app.use(cors());
const Constant = require('./constant');
const Utils = require('./utils');
module.exports = agent =& {
const config = agent.config.webpack;
const webpackConfig = config.webpackConfig;
const compiler = webpack(webpackConfig);
const devMiddleware = require('koa-webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
watchOptions: {
ignored: /node_modules/,
app.use(devMiddleware);
app.listen(config.port + 1, err =& {
if (!err) {
agent.logger.info(`start webpack build service: http://127.0.0.1:${config.port}`);
koa服务启动以后, 就可以通过 http://127.0.0.1:port 方式访问js, css, image等静态资源.
agent中监听worker发送的消息
在agent.js 增加如下监听的代码:
通过agent.messenger.on 监听app worker发送过来的消息
agent.messenger.on(Constant.EVENT_WEBPACK_READ_FILE_MEMORY, data =& {
const fileContent = Utils.readWebpackMemoryFile(compiler, data.filePath);
if (fileContent) {
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, {
fileContent,
// agent.logger.error(`webpack server memory file[${data.filePath}] not exist!`);
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, {
fileContent: '',
在worker view 渲染文件读取里面调用如下方法, 获取webpack的编译内容
通过app.messenger.sendToAgent 向agent发送消息
通过app.messenger.on 监听agent发送过来的消息
function readFile(filePath){
return new Promise(resolve =& {
this.app.messenger.sendToAgent(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, {
this.app.messenger.on(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, data =& {
resolve(data.fileContent);
在app/extend/application.js 挂载webpack实例, 以便进行扩展.
const WEBPACK = Symbol('Application#webpack');
module.exports = {
get webpack() {
if (!this[WEBPACK]) {
this[WEBPACK] = new FileSystem(this);
return this[WEBPACK];
针对此方案实现基于了egg的开发webpack server编译插件
关于koa的实现热编译和重启, 可以参考
本地开发内存文件读取与线上运行文件读取分离实现
在egg-view插件开发规范中,我们会在ctx上面挂载render方法, render方法会根据文件名进行文件读取, 模板与数据编译, 从而实现模板的渲染.如下就是controller的调用方式:
exports.index = function* (ctx) {
yield ctx.render('index/index.js', Model.getPage(1, 10));
那我们如何处理从webpack内存读取还是从本地文件读取呢? 其中最关键的一步是根据文件名进行文件读取, 只要view插件设计时, 把文件读取的方法暴露出来,就可以实现本地开发webpack热更新内存存储读取.
vue view engine设计实现:
const Engine = require('../../lib/engine');
const VUE_ENGINE = Symbol('Application#vue');
module.exports = {
get vue() {
if (!this[VUE_ENGINE]) {
this[VUE_ENGINE] = new Engine(this);
return this[VUE_ENGINE];
class Engine {
constructor(app) {
this.app = app;
this.config = app.config.vue;
this.cache = LRU(this.config.cache);
this.fileLoader = new FileLoader(app, this.cache);
this.renderer = vueServerRenderer.createRenderer();
this.renderOptions = Object.assign({
cache: this.cache,
}, this.config.renderOptions);
createBundleRenderer(code, renderOptions) {
return vueServerRenderer.createBundleRenderer(code, Object.assign({}, this.renderOptions, renderOptions));
* readFile(name) {
return yield this.fileLoader.load(name);
render(code, data = {}, options = {}) {
return new Promise((resolve, reject) =& {
this.createBundleRenderer(code, options.renderOptions).renderToString(data, (err, html) =& {
if (err) {
reject(err);
resolve(html);
ctx.render 方法
class View {
constructor(ctx) {
this.app = ctx.app;
* render(name, locals, options = {}) {
// 我们通过覆写app.vue.readFile即可改变文件读取逻辑
const code = yield this.app.vue.readFile(name);
return this.app.vue.render(code, { state: locals }, options);
renderString(tpl, locals) {
return this.app.vue.renderString(tpl, locals);
module.exports = View;
服务器view渲染插件实现
通过webpack实例覆写app.vue.readFile 改变从webpack内存读取文件内容.
if (app.vue) {
app.vue.readFile = fileName =& {
const filePath = path.isAbsolute(fileName) ? fileName : path.join(app.config.view.root[0], fileName);
if (/\.js$/.test(fileName)) {
// webpack 实例是有[egg-webpack]插件挂载上去的.
return app.webpack.fileSystem.readServerFile(filePath, fileName);
return app.webpack.fileSystem.readClientFile(filePath, fileName);
app.messenger.on(app.webpack.Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, data =& {
if (data.state) {
const filepath = app.config.webpackvue.build.manifest;
const promise = app.webpack.fileSystem.readClientFile(filepath);
promise.then(content =& {
fs.writeFileSync(filepath, content, 'utf8');
webpack + vue 编译插件实现
说明: 在最开始的实现时,一直在摸索, 为了尽快满足项目, 整个功能是在一个插件中实现的.
随着业务稳定下来, 就对这边个插件进行了分离和重新实现, 才有了上面的三个插件, 目地是让webpack编写更简单, 扩展更容易.
egg+webpack+vue项目实战
用egg-init 初始化一个项目和安装依赖后, 我们添加 @hubcarl/egg-view-vue 和 egg-view-vue-ssr依赖, 添加egg-webpack 和 egg-webpack-vue 开发依赖
npm i @hubcarl/egg-view-vue --save
npm i @hubcarl/egg-view-vue --save
npm i egg-webpack --save-dev
npm i egg-webpack-vue --save-dev
config/plugin.js
exports.vue = {
enable: true,
package: '@hubcarl/egg-view-vue'
exports.vuessr = {
enable: true,
package: 'egg-view-vue-ssr'
exports.webpack = {
enable: true,
package: 'egg-webpack'
exports.webpackvue = {
enable: true,
package: 'egg-webpack-vue'
config.default.js
exports.view = {
cache: false
exports.static = {
router: '/public', // 请求进来的前缀,避免和应用的 router 冲突,默认是 `/public`
// maxAge: 3600 * 24 * 180, // maxAge 缓存,默认 1 年
dir: path.join(app.baseDir, 'public') // 静态文件目录,默认是 `${baseDir}/app/pulbic`
config.local.js
const webpackConfig = {
baseDir: path.resolve(__dirname, '../'),
port: 8090,
path: 'public',
publicPath: '/public/',
prefix: 'static',
entry: [path.join(baseDir, 'app/web/page')],
commonsChunk: ['vendor']
webpack: {
styleLoader: 'vue-style-loader',
loaderOption: {
includePaths: [path.join(baseDir, 'app/web/asset/style')]
exports.webpack = {
port: webpackConfig.webpackvue.build.port,
clientConfig: require(path.join(app.baseDir, 'build/client')),
serverConfig: require(path.join(app.baseDir, 'build/server'))
exports.webpackvue = webpackConfig.webpackvue;
package.json 增加如下命令
"scripts": {
"build-dev": "NODE_ENV=development node build",
"build-prod": "NODE_ENV=production node build",
"start": "WORKERS=1 NODE_ENV=development node index.js",
"start-prod": "WORKERS=1 && NODE_ENV=production node index.js",
编写webpack配置
在项目根目录下增加build文件夹, 然后分别编写client和server webpack配置文件
client/dev.js
const ClientBaseBuilder = require('./base');
class ClientDevBuilder extends ClientBaseBuilder {
constructor() {
this.setEggWebpackPublicPath();
this.setDevTool('eval-source-map');
this.setCssExtract(false);
module.exports = new ClientDevBuilder().create();
sever/dev.js
const ServerBaseBuilder = require('./base');
class ServerDevBuilder extends ServerBaseBuilder {
constructor() {
this.setEggWebpackPublicPath();
module.exports = new ServerDevBuilder().create();
命令行编译文件到磁盘
增加 build.js 文件
const EggWebpackVue = require('egg-webpack-vue');
const clientConfig = require('./client');
const serverConfig = require('./server');
EggWebpackVue.EasyWebpack.build([clientConfig, serverConfig]);
npm run build-dev
npm run build-prod
client 文件默认编译到public目录下,
server 编辑结果默认到 app/view` 目录下
npm start 会自动通过egg-webpack 启动编译流程, 无需单独运行npm run build-dev 或 npm run build-prod. 启动流程效果如下:
访问: http://127.0.0.1:7001
基于Vue多页面和单页面服务端渲染同构工程骨架项目
基于vue + axios 多页面服务器客户端同构入口: http://127.0.0.1:7001
基于vue + vuex + vue-router + axios 单页面服务器客户端同构入口: http://127.0.0.1:7001/app
关于性能和优化以及问题 (单独总结)
egg+webpack+vue 依赖
egg view plugin for vue
vue server side render solution for egg-view-vue
webpack dev server plugin for egg, support read file in memory and hot reload
egg webpack building solution for vue
programming instead of configuration, webpack is no longer complex