jade05 / jade.github.io Goto Github PK
View Code? Open in Web Editor NEW所有的文章都在 issues 里面
Home Page: https://github.com/Jade05/jade.github.io/issues
所有的文章都在 issues 里面
Home Page: https://github.com/Jade05/jade.github.io/issues
接了一个「常规」需求:开发一个本地上传图片控件,需要支持三种上传方式:
我们先从工程角度来看一下用户上传图片的流程是怎样子的:
通过type为file的input标签选择本地图片上传,每次选择不同图片的时候会触发onChange事件。为什么我要强调不同图片,因为当两次选择的图片是一样的情况下,onChange事件无法触发。解决方法:每次处理完后手动置空input的value。
<input id='img-input' type='file' accept='image/*' onChange={this.bindChooseEvents} />
// 置空input value
clearImgInputValue () {
document.getElementById('img-input').value = ''
}
bindChooseEvents = (e) => {
console.log('choose a image')
// 获取File对象
const file = e.target.files[0]
let size = file.size
if (!file.type.match('image.*')) {
AntMessage.warning('File\'s type is not supported. Images only.')
this.clearImgInputValue()
return;
}
if (size > maxSize) {
AntMessage.warning('The size of the image is too large')
this.clearImgInputValue()
return;
}
/* eslint-disable */
const reader = new FileReader() // FileReader
/* eslint-disable */
// 将图片转换为base64
reader.readAsDataURL(file)
reader.onload = (arg) => {
// 获取到base64图片内容
const fileStream = arg.target.result
/**
* overwrite do something
* */
this.clearImgInputValue()
}
}
监听拖曳事件,通过拖曳相关的DataTransfer对象获取图片信息。
<div id='drop-zone' style={{width: '100px', height: '100px'}}>Drop Zone</div>
bindDragEvents = (e) => {
const handleDragOver = (event) => {
event.stopPropagation()
event.preventDefault()
event.dataTransfer.dropEffect = 'copy'
}
// 必须阻止dragenter和dragover事件的默认行为,这样才能触发 drop 事件
const handleFileSelect = (event) => {
event.stopPropagation()
event.preventDefault()
const files = event.dataTransfer.files // 文件对象
const file = files[0]
const size = file.size
const type = file.type
if (!type.match('image.*')) {
AntMessage.warning('File\'s type is not supported. Images only.')
return;
}
if (size > maxSize) {
AntMessage.warning('The size of the image is too large')
return;
}
/* eslint-disable */
const reader = new FileReader()
/* eslint-disable */
// 将图片转换为base64
reader.readAsDataURL(file)
reader.onload = (arg) => {
// 获取到base64图片内容
const fileStream = arg.target.result
/**
* overwrite do something
* */
}
}
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', handleDragOver, false);
dropZone.addEventListener('drop', handleFileSelect, false);
}
监听paste事件,通过剪贴板对象clipboardData获取图片信息。
bindClipEvents() {
document.addEventListener('paste', (e) => {
console.log('paste a image')
const clipboard = e.clipboardData
// 有无内容
if (!clipboard.items || !clipboard.items.length) {
AntMessage.warning('No content in the clipboard')
return;
}
let item = clipboard.items[0]
if (item.kind === 'file' && item.type.match('image.*')) {
// 获取图片文件
let imgFile = item.getAsFile()
if (imgFile.size > maxSize) {
AntMessage.warning('The size of the image is too large')
return;
}
const reader = new FileReader()
// 将图片转换为base64
reader.readAsDataURL(imgFile)
reader.onload = (arg) => {
// 获取到base64图片内容
const fileStream = arg.target.result
/**
* overwrite do something
* */
}
} else {
AntMessage.warning('File\'s type is not supported. Images only.')
}
}, false)
}
图片保存一般都采用独立图片独立域名服务器,不会傻乎乎地把图片保存在web服务器上也不会直接存在项目表的数据库中。这样做有什么好处呢?
项目实践中我们虽然采用了独立图片服务器,下载过程只是通过Web服务器去数据库拿到图片地址,但是我们的上传操作仍旧经过了Web服务器,需要Web服务器上的应用程序来处理,所以上传过程仍旧对Web服务器造成压力。所幸的是,我们对图片上传大小进行了1M的大小限制,同时作为内部系统没啥访问压力,而且图片上传功能也并不是很高发的行为,所以这么做基本也不会有啥问题。但是最好的方案还是不管下载上传都直接走独立图片服务器,避免对Web服务器造成额外的压力。
简介:本项目以express搭建服务端,本地开发配合webpack-dev-middlerware、wepack-hot-middleware、webpack.HotModuleReplacementPlugin实现热加载刷新。生产打包在dist目录下,开发打包热加载替换目录为/examples。
现象:本地代码通过生产配置文件(webpack-build.config.js)打包生成的js包能够被页面引用到并且入口正常,通过开发配置文件(webpack-develop.config.js)打包生成的js包虽然也能够被页面引用到但是入口不正常。
具体的例子:
入口文件 - examples/test/index.js
const Test = {
text: 'hello world!'
}
module.exports = Test
html - example/test/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>test</title>
</head>
<body>
Test
<!-- <script src="../../dist/components/test.js"></script> -->
<script src="/examples/test.js"></script>
<script>
console.log(test)
console.log(test.text)
</script>
</body>
</html>
webpack-develop.config.js:
entry: {
test: [path.resolve(__dirname, 'examples/test/index.js'), 'webpack-hot-middleware/client?path=/examples/__what&reload=true'],
},
output: {
filename: '[name].js',
publicPath: '/examples/',
library: '[name]',
libraryTarget: 'umd',
},
本地起服务,在浏览器中打开/examples/test/index.html,按照预期结果,控制台应该输出{text: "hello world!"} hello world!。但是控制台却输出了如下结果:
第一反应是:js文件没有加载到。但是通过浏览器面板查看后可以看到test.js已经被正确加载到,而且通过http://localhost:3000/examples/test.js可以看到,examples/test/index.js也已经打包进来:
懵,就是取不到,明明就在那里-.-。更神奇的是,如果我不用webpack-develop.config.js打包,而是用webpack-build.config.js直接打包到物理磁盘(注:因本地开发是基于webpack-dev-middleware和hot-middleware,开发打包的结果并不存在于物理磁盘,只存在于内存中),然后更换examples/test/index.html的引用入路径为 ‘../../dist/components/test.js’,即生产打包后的物理文件路径,就一切正常,不会出现加载但是无法引用的问题。
webpack-develop.config.js和webpack-build.config.js有啥区别?区别还是有的,但是其他入口的页面(注:本项目可理解为是多入口页面)也是基于这两个配置文件的,本地开发和生产打包后皆正常。仔细排查,会发现可以通过以下两个角度解决问题:
- webpack-develop.config.js和webpack-build.config.js是有区别的,build配置里头使用ExtractTextPlugin、UglifyJSPlugin,而develop没有使用但有热加载插件;
- 其它入口的页面都是正常的,唯独examples/test/index.js这种情况有问题,比较后会发现引入的使用方式不同,其他入口文件都是引用了模块文件后自执行代码片段输出dom,而examples/test/index.js作为打包入口最后通过module.exports = Test,输出Test,然后example/test/index.html通过 script 标签引入后直接去取全局变量里头的Test引用,取引用失败。而事实上我们知道,通过umd打包后,如果页面通过script标签直接引入,入口文件module.exports输出对象实际就是挂在window上的。(图片里头的this在浏览器全局环境中就是window)
从第1个入口处进行排查,除去热加载功能后,就一切正常了。结合以上两点,我们有理由猜测导致上述问题的原因很可能是热加载过程中导致入口文件module.exports输出的对象无法被正确挂载到window上。而至于真实情况是就是没有挂载还是挂载后被冲掉(复写)了,则需要分析源码才能知道。
去掉热更新,本地开发一切正常。
后记:直接去掉实在太残暴,而且热更新作为提高开发效率的利器就因为这一问题就直接去掉实在可惜,所以为了不影响其他入口的热更新,本地开发模式可以只对指定入口禁用热更新,webpack-develop.config.js 在指定entry去掉 'webpack-hot-middleware/client?path=/examples/__what&reload=true'即可。
Google 前沿的 AMP 「 Accelerated Mobile Pages 」技术,能使用户从搜索引擎当中进入我们页面的体验得到一个极大的提升。确切地说,AMP并不是一门新的技术,它只是一种能让页面打开得更快的解决方案。你只要会HTML、CSS、略懂JS,就可以开发你自己的AMP页面。
有关AMP更多内容可以参看官网。
看看AMP能给我们带来什么:
所以,作为携程的海外业务部门,我们率先试水了AMP项目。
AMP项目开发和普通站点的开发模式几乎一样,但是为了最大限度提升性能,AMP项目页面有不少要求。比如:
基于以上几点,页面上所有交互逻辑都必须通过css实现,无法依赖JS。对于实现复杂的交互,AMP会显得力不从心。但是这其实是和AMP原则相一致的,JS丰富了页面,但JS也是页面优化噩梦的开始。What Google AMP means for the JavaScript community这篇文章将阐述JavaScript对性能的影响。
当开发完成后,必须保证页面是符合AMP规范的,只有符合AMP规范的页面才会被搜索引擎收录。在Chrome中安装AMP Validator插件,当你的页面是完全符合AMP规范的时候,Chrome Validator AMP按钮会呈现绿色。如下图:
当页面通过验证以后,我们还需要在link中配置amphtml和canonical,让Google搜索发现我们的页面。
如果同一个页面同时具有非AMP版本和AMP版本:
为非AMP页面添加以下标记:
<link rel="amphtml" href="https://www.example.com/url/to/amp/document.html">
为AMP页面添加以下标记:
<link rel="canonical" href="https://www.example.com/url/to/full/document.html">
只有一个版本的网页:
如果只有一个版本的网页,并且该版本是 AMP 网页,则仍要为其添加规范链接,该链接会指向其自身:
<link rel="canonical" href="https://www.example.com/url/to/amp/document.html">
至此,AMP页面开发工作基本完成,可以发布了。
AMP有不少限制要求,开发中难免碰到不好解决的问题。以下对我们碰到过的问题及解决方法进行分享。
<a href='<%- searchData.defaultSearchUrl %>'
[href]='"/m/hotels/" + location.concatResult
+ "/?checkin=" + date.selectedStartDate
+ "&checkout=" + date.selectedEndDate
+ "&starID=" + activeStarKeys.keys
+ "&adult=" + guestsSelectResult.adultsNum
+ "&children=" + guestsSelectResult.childNum
+ "&ages=" + guestsSelectResult.childAges'>
对于此问题,除非AMP放开复杂度限制,否则我们能做的只能是尽量提前运算,当需要某个计算结果的时候,可以直接使用。比如例子中,location.concatResult就是在location组件内部先进行concat,再将concat的结果拼接到href。此外,可以尽量简化交互,减少参数。
此问题在AMP开发中势必会碰到,详细讨论可以看ISSUES-11434
AMP限制编写JavaScript,也就不允许我们读写Cookie、LocalStorage,但是记录用户行为是很重要的事情,比如一些表单信息,用户的选择,等用户下次打开我们的页面时,能够显示用户上一次的状态。我们的解决方法是通过http set-cookie方式解决前端无法记录cookie的问题。
amp-form只能提交ajax post请求,无法做到以post表单形式跳转。所以在开发过程中尽量避免出现post表单形式的请求,一般改用ajax post加页面跳转的形式来提交。在迫不得已的情况下,可以考虑通过增加非amp的中间路由,在中间页中构造表单并自动提交数据。
AMP CROS:用户最终访问的是AMP Cache,在AMP launch新版本之前,命中AMP Cache,页面地址并非是真实地址而是Google AMP Cache地址,如果页面上有额外的异步请求,就会有跨域限制,所以我们要在服务端开启跨域,返回头设置AMP-Access-Control-Allow-Source-Origin。
amp-iframe有sandbox属性,用来指定iframe内部的站点权限,默认值为空。如果希望iframe内部可以执行js脚本,则需要设置成"allow-scripts";如果需要添加内部发送同域请求的权限,则需要设置成"allow-scripts allow-same-origin";但如果amp-iframe的src是同域站点,那么sandbox属性必须不能包含allow-same-origin,这样做杜绝了脱离amp控件发送请求的可能性。
AMP下统计页面埋点必须基于amp自带的统计控件,目前amp封装了市面上大部分的第三方统计系统。但是由于公司内部的统计工具没有amp对应的控件,所以无法接入。对于页面埋点统计,开发者要评估自家的统计工具。
通过对非SEO数据异步化加载,让AMP更快。
AMP页面作为搜索排名优化页面的同时,还兼具引流功能。虽然AMP页面能让用户从搜索结果中最快速地到达我们的landing页,但是只有用户最终从landing页又回到原始页面走完必要的业务流程,才是有效的转化。例如,在我们的业务中,用户可以通过搜索引擎快速到达酒店列表、酒店详情的AMP页面,但只有从酒店详情AMP页面跳转到支付页(非AMP),并完成支付,才算转化成功。如果原始页面体验不好,用户依旧可能中途流失。这似乎不是AMP的"错",但AMP确实还能再做些什么。
通过amp-install-serviceworker安装原始站点的sw.js,提前加载好原始页面的资源,当用户从AMP页面跳出,进入原始主站的时候,让主站体验更好,从而提高实际转化。
实践后,我们发现AMP有以下局限性,希望对于打算了解AMP这一技术,或者有计划接入AMP技术的团队,可以起到借鉴作用。
AMP官方给我们的解释是:因为不是所有的浏览器都支持passive event,为了最大化浏览器的支持和效能,所以touch事件只开放给AMP内部组件使用,比如amp-carousel。
因为第三方会缓存AMP页面,出于安全考虑,AMP页面上不开放cookie。不过最新消息是,AMP官方回应后续可能会考虑开放cookie。
虽然AMP提供了不少组件(并且在持续新增中),但是开发者无法自己书写JS去实现交互,不适合开发复杂的交互。
eamp是从我们AMP SEO项目中提取出来的简化版框架,能够让我们快速开启AMP Node项目,使用者无需从0开始搭建,更能专注amp页面开发。
特点:
此部分可参考此文VirtualBox虚拟机安装Ubuntu详细教程
在Ubuntu Terminal中通过apt-get install可快速安装Node.js。安装好后,node -V 发现Node.js版本竟然是v4.2.6,虽然有点诧异,但是想着可以升级,也可通过版本管理器安装多个Node.js,就没多想,就继续安装npm,但是当Node.js和npm都安装好后,使用npm命令却报如下错误:
ERROR: npm is known not to run on Node.js v4.2.6 Node.js 4 is supported but the specific version you're running has a bug known to break npm.
问题很明朗,就是Node.js版本太低,那之前想通过npm安装Node.js版本管理器,再通过Node.js版本管理安装多个版本的Node.js的方法是行不通了。二话不说先卸载。
卸载 nodejs & npm
sudo apt remove nodejs npm
尝试第二种方法:wget获取指定版本的Node.js进行安装
sudo wget -qO- https://deb.nodesource.com/setup_8.x | sudo bash
看错误是apt-get update获取最新软件包的时候,需要操作 /var/cache/apt/lists下的文件但是没有权限导致失败。通过chmod修改文件权限即可。
sudo chmod -R 777 /var/lib/apt/lists/
修改权限后再执行
sudo wget -qO- https://deb.nodesource.com/setup_8.x | sudo bash
即可获取到Node.js版本包,通过install安装
sudo apt-get install nodejs
gnutls_handshake() failed: Error in the pull function.但最后提示我们:maybe run apt-get update or try with --fix-missing.通过运行apt-get update即可解决。
sudo apt-get update
另外,也可编译Node.js源码包安装。
# wget http://nodejs.org/dist/v9.3.0/node-v8.9.3.tar.gz
# tar xvf node-v8.9.3.tar.gz
# cd node-v8.9.3.tar.gz
# ./configure
# make
# make install
FYI
在前一篇文章最后,我们已经锁定问题——热更新过程中导致入口文件module.exports输出的对象无法被正确挂载到window上。而至于真实情况是就是没有挂载还是挂载后被冲掉(复写)了,这个就是这篇文章中需要探索的。
探索前务必要先看一下Webpack热更新的源码知道其原理。有关Webpack热更新原理在此将不再赘述,推荐两篇相关文章:
在此,我再用通俗的语言来阐述一下Webpack热更新的原理。
不得不再提一下我们的项目架构:本项目以express搭建服务端,本地开发配合webpack-dev-middleware、wepack-hot-middleware、webpack.HotModuleReplacementPlugin实现热加载更新。我们看到有webpack-dev-middlerware、webpack-hot-middleware、webpack.HotModuleReplacementPlugin各种工具,但是我们要明白一点,打包这个活儿本质上还是webpack干的,并不是加了热更新相关的插件或者中间件后,打包这项活就是插件或者中间件干了。插件和中间件的作用仅仅是在webpack编译打包核心的外围做了一些事儿(手脚)。所以在看待这几个插件的时候,我们只要抓住热更新这一个关键功能就可以了。
以下这个图来自文章-Webpack HMR 原理解析
这样理解起来就简化不少,热更新的机制本质也是client-server的机制,client就在我们的浏览器端,它需要知道文件变化了并且做出一系列的动作然后把最终结果通过浏览器让我们看到,那server端的职责就很明确了,它需要监控模块文件的变化,如果变化了就要让webpack(webpack对外暴露的API)重新打包,期间还要和client保持通信,把自己这边的状况告诉client,client根据信息采取动作。
以上就是进入问题分析前的准备工作。
为了便于看清问题,实验的例子越简单越好。所以我们仍旧采用上一篇文章中使用的例子。
入口文件 - examples/test/index.js
const Test = {
text: 'hello world!'
}
module.exports = Test
html - example/test/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>test</title>
</head>
<body>
Test
<!-- <script src="../../dist/components/test.js"></script> -->
<script src="/examples/test.js"></script>
<script>
console.log(test)
console.log(test.text)
</script>
</body>
</html>
然后用生产的webpack打包配置和本地开发的打包配置分别打包出test_s.js(这个是直接打到物理磁盘的,即为发布包)和test_f.js(开发包是打在内存的,但是浏览器能访问到,我们直接右键另存为即可),test_s.js能够按照预期运行,test_f.js无法拿到预期入口。
我们看下test_s.js和test_f.js文件内容,我们可以在注释中找到这么一句话『Load entry module and return exports』,没错,这就是最终打包后暴露出来的方法。看截图:
不管是test_s.js还是test_f.js,入口都是一个叫做341的模块,这没毛病,毕竟是同样的一份代码嘛。继续看。
test_s.js是生产包,摒除了热加载,本身test.js也没啥代码,所以打包后的代码也很少,一眼就能看到 341是入口,341里头_webpack_require_(342),而342就是我们test.js的代码内容,成功找到了Test。对于_webpack_require_有疑问的同学可以查看(这篇文章)[https://github.com/ShowJoy-com/showjoy-blog/issues/39],在这里只要了解 在入口模块中调用了 webpack_require(342),就会得到342这个模块返回的 module.exports,所以就能将Test找到,并且赋给module.exports,所以341作为入口文件,最终暴露出来的就是Test。
341作为入口模块,341中_webpack_require_(342),342模块就是我们的test.js,出乎意料的是341中同时引入了_webpack_require_(32),而且module.exports上挂载的不是342是32!!!。至此问题彻底明了,热加载过程中导致入口文件module.exports输出的对象无法被正确挂载到window上。事实情况是只引入了我们的入口文件并没有对输出进行挂载(不是冲掉)挂载的是32模块。
那这是不是bug呢?是bug我就去提issue啦,看来还是太天真:)。
如果你认真看过最前面推荐的文章——Webpack 热更新实现原理分析,并且浏览过HRM源码,就会有深刻印象。这是HRM的client模块代码,核心功能是通过EventSource,将与HRM server建立连接,进行模块更新通信。看一下代码注释/* WEBPACK VAR INFECTION */,没错,就是这段注入代码,成了真正的入口函数。
再回头看一下 webpack-develop.config.js:
entry: {
test: [path.resolve(__dirname, 'examples/test/index.js'), 'webpack-hot-middleware/client?path=/examples/__what&reload=true'],
},
entry test的数组依次是index.js,然后是 webpack-hot-middleware。所以index.js的包引到了,但是入口确是后者 webpack-hot-middleware。
webpack-develop.config.js:
entry: {
// test: [path.resolve(__dirname, 'examples/test/index.js'), 'webpack-hot-middleware/client?path=/examples/__what&reload=true'],
// 更换顺序,正确书写entry
test: ['webpack-hot-middleware/client?path=/examples/__what&reload=true', path.resolve(__dirname, 'examples/test/index.js')],
},
output: {
filename: '[name].js',
publicPath: '/examples/',
library: '[name]',
libraryTarget: 'umd',
},
至此,该问题完美解决。
后记:依样画葫芦的坑有时候更大呀,而且极难理出排查思路。「别的组件入口都没问题,偏偏这个组件有问题」。有种情况叫负负得正了:)。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.