摘要:以为例,编写来帮助我们完成重复的工作编译压缩我只要执行一下就可以检测到文件的变化,然后为你执行一系列的自动化操作,同样的操作也发生在这些的预处理器上。的使用是针对第三方类库使用各种模块化写法以及语法。
一:前端工程化的发展
很久以前,互联网行业有个职位叫做 “软件开发工程师” 在那个时代,大家可能会蒙,放在现今社会,大家会觉得这个职位太过笼统,所以在此提一下那个时候的互联网行业状态。
那个时候的APP还不是ios/安卓 多数是嵌入式应用(和网页没关系 使用c++或java开发)后续到了Symbian时代出现了可移植的sis[x]类型应用,曾经一统移动APP市场。
那个时候css百分之90还是用来做布局
那个时候js仅仅是为了类似弹出提示的功能而存在的
那个时候从服务器 - 数据库 - 业务逻辑 - 页面 全都由所谓的 “软件开发工程师” 来完成
所以大家不必问软件开发工程师具体是干啥的,我只能说 啥都干
1:个人对前端开发的体验过程第一个阶段就是开始对着W3C的文档,拿着txt文本文件一个字母字母的敲着代码,那个年代,真的单纯舒服,上来就是一个项目的文件夹,然后就开始img、js、css三个完美的文件夹,再接上一个index.html,就开始到网上各种下载类库jquery、underscore.js,然后手动的引入各种类库,当然过程也伴随着痛苦。
每次来一个项目就开始建立各种繁琐的文件夹,和拷贝复制类库
引入完类库的时候发现控制台报错,$ is not defined,依赖关系出错,部分类库需要把jquery作为依赖
新写一个页面就需要去重新复制其他页面的header资源,维护变的困难(完全没有组件化的想法)
部分css3属性需要自己不断的手动添加样式前缀
最大的问题再维护的时候,不敢轻易的去动一些类库的引入,不清楚各个库之前的依赖关系
第二个阶段就是在接触到Vue的时候直接上vue-cli的时候,几行脚本就可以启动本地开发服务和打包线上资源,初次尝试了webpack这个打包工具,好像对其他问题不需要做过多的考虑,直接开始业务开发,其他的事情cli都帮助处理好了。vue init webpack helloWorld, cd helloWorld && cnpm install && npm run dev,真香警告,对工程化一知半解,但是好用,方便
前端需要一些工具来处理重复的大量繁琐的事 (资源压缩 代码混淆 css前缀 ),以前会用Gulp等Task处理任务
前端需要一些需要用一些预处理器来处理样式文件,less以前用第三方工具去编译 -> less提供的命令行去编译-> 配置到webpack中自动化实时编译
前端需要更加细粒度的配置一下代码体积的优化,代码混淆压缩的操作
前端部分业务越多,代码量越多(文件体积越大)需要做文件合并,压缩以及按需加载等
开发阶段依然在本地开发,但同时保持和线上API的同步,为此我们需要本地服务器(可以是静态服务器)代理转发(Nginx webpack-dev-server node-server等方式)
第三个阶段就是给公司项目升级webpack过程中体验到了更细粒度的控制工程,体验工程化的过程,实际的体验到了工程化为我们做的事情
模块化开发,不用在担心以往开发方式带来的全局作用域的污染问题,当然以往也可以通过闭包来实现私有变量的概念
组件化开发,代码重用度高,便于维护
多了构建,编译过程,可以在适当的时间去做一些提高工程质量的任务,比如代码规范的检测,常用的是Eslint (Airbnb, Prettier 规范等)
提高开发效率,比如css浏览器前缀的自动添加,使用postCss甚至可以提前使用一些好用的东西,比如css变量的概念
通过chunkHash,contentHash 等实现资源的缓存
根据工程代码通过合理的代码分割提升用户体验,做到按需加载,甚至可以在未来做一些用户使用的习惯,做一些提前的预加载
二:webpack的基本使用 1:webpack和Grunt / Gulp的区别这两类是不同的东西,一个可以理解为是任务执行器而另外一个是模块打包器,任务执行器是可以自动化的执行一些以前你需要手动操作的过程,见下面简单的代码
// coffee 源码 console.log "Hello World" //coffee转 js coffee -c a.coffee (function(){ console.log("Hello World"); }).call(this); //执行编译压缩 uglify -s a.js -o a.min.js (function(){console.log("Hello World")}).call(this);
coffee需要编译成浏览器支持的js,你需要手动的去执行上面的几个命令,如果现在需要去修改源码,在Hello World 后面加上一个!,加完你需要手动的去执行两条命令重复的去编译操作。以 gulp 为例,编写 gulpfile.js来帮助我们完成重复的工作
gulp = require("gulp") coffee = require("gulp-coffee") uglify = require("gulp-uglify") rename = require("gulp-rename") file = "./src/js/a.coffee" gulp.task("coffee", function(){ gulp.src(file) .pipe(coffee()) // 编译 .pipe(uglify()) // 压缩 .pipe(rename({ extname: ".min.js" })) .pipe(gulp.dest("./build/js")) gulp.task("watch", function(){ gulp.watch(file, ["coffee"]) }) gulp.task("default", ["coffee"])
我只要执行一下 gulp watch 就可以检测到coffee文件的变化,然后为你执行一系列的自动化操作,同样的操作也发生在less, scss, 这些css的预处理器上。在修改到源文件的情况下的编译,压缩这些重复操作都交由它来完成,在我看来Grunt / Gulp 算是一个能够自动化执行一些繁琐重复的操作,提高生产效率,算是一个任务执行器。
这些操作同样也可以由webpack完成,接下来我们看一下官网给出的webpack的定义。
webpack is a module bundler
官方给出的解释是,webpack是一个模块打包器,不是一个任务执行器,它是可以配合Grunt / Gulp 使用的,相关链接webpack集成 。webpack打包器(bundler)帮助你生成准备用于部署的 JavaScript 和样式表,将它们转换为适合浏览器的可用格式。例如,JavaScript的压缩、chunk split和懒加载,以提高性能。所以webpack和Gulp/Grunt之间是有一定功能的重叠,但是处理合适,是可以一起配合工作的,不存在所谓的谁替代谁,只是在某些场景下webpack的能够独当一面,完成了Grunt/ Gulp的功能。
2:webpack3.x 和 webpack4.x 的对比rollup以及Parcel的出现,号称零配置,足以让一个配置成本比较高的webpack出现了4.0版本,当然也号称零配置使用,开箱即用,现在来一起看看webpack4.x和3.x比较大的区别,先给出一个Release链接webpack Release v4.0.0下面简要的介绍一些大的改动
Node环境的升级,不在支持node 4.0的版本,最低支持6.11.5
配置增加了mode:"production", "development", "none" ,所谓的开箱即用的支持点,在不同的mode下开启了一些默认的优化手段
生产模式开启了各种优化去生成bundle文件、默认开启作用域提升、process.env.NODE_ENV设置为production
开发模式优化内部rebuild流程,提升开发效率和体验
删除了和添加了一些配置项,NoEmitOnErrorsPlugin、ModuleConcatenationPlugin(default in production mode)、NamedModulesPlugin(default in development mode) ---> 转到optimization.*的配置项
原生支持处理JSON文件格式,不需要json-loader
内置的插件 uglifyjs-webpack-plugin 升级到了 V1, 而V1是可以支持并行处理压缩混淆JS的,webpack3之前的内置依赖的版本0.4.6 不支持并行处理。常用手段使用webpack-uglify-parallel插件并行处理,利用多核CPU的优势,升级到webapck4可以不需要了,使用默认也可以
一个算是比较大的改动,webpack3.x的ComoonsChunkPlugin废弃,代替的是optimization.splitChunks 和 optimization.runtimeChunk (会在下文着重介绍)
3:webpack4.x 的一些基本概念首先先看下图整体了解一下webpack的一些常用配置项
接下来简单的了解一下webpack的一些基本概念
mode:三种模式,production、development、none。设置mode,webpack会根据mode做相应的优化
entry:入口,webpack会从入口递归寻找所有的依赖,形成依赖关系图(dependency graph)。目前应用主要分为单入口和多入口,直观上表现为经过webpack处理之后是一个JS文件还是多个JS文件(在没有代码分割以及懒加载的前提下)
output:输出,主要通过filename来定义生成chunk的命名,可以通过标识符[name]、[contenthash]、[chunkhash]来实现资源的缓存,chunkFileName针对async chunk
loader:loader用于对模块的源代码进行转换。既然是模块打包器,那么就会出现依赖各种各样的文件和内容,图片,字体,它们需要经过编译才能被浏览器识别(less、sass,、stylus、ES2015、ts等module) ,都需要通过对应的loader转换成现代浏览器支持的东西。loader支持链式传递,loader运行在Node.js中,并且能够执行任何可能的操作,比如存在一些不是用来转换文件的loader,thread-loder(多个node进程处理loader转码文件,提高编译速度)
plugin:插件和loader不同的地方在于,loader是针对模块,比如import以及require的module文件进行转码。plugin是在webapck的complier整个生命周期中起作用,在这个编译阶段你可以在提供的hook中执行你需要的任何操作。比如htmlWebpackPligun插件,可以在webpack编译emit文件的钩子中,生成html去使用这些webpack生成的JS Chunk
module:module的概念可以理解为一个个需要加载的文件,Js也好,Css文件也好,都需要经过loader处理,module里面的rules去就是配置module需要什么loader去处理
chunks:两种,init chunks(这些chunks文件是会以script标签添加到htmlWebpackPlugin生成的html文件中,当然也可以通过插件内置到html文件中,比如mainifest文件)和async chunks。初始化chunks是从提供给webapck的entries中开始递归的寻找依赖的module,生成的一个chunks。异步的chunks可以理解为是需要按需加载的,主要可以分为以下3个来源:第一、从初始化的chunk中抽出去的代码,形成的chunk文件(这个就是webapck的splitChunk和runtimeChunk的配置)。第二、可以通过webpack识别的特定语法require.ensure (vue-router中懒加载的写法)。第三、ES6的动态导入import(/chunkname/)
optimization:这是webpack4.x出现的一个优化类的配置项,常用配置项:splitChunk、runtimeChunk(下文介绍)、minimize、noEmitOnErrors、namedModules、sideEffects(配合tree shaking使用)等等
resolve:如上图,这个配置会增量的告知webpack如何的去寻找依赖,alias(避免一些深层次引用module代码的别名)、modules(从哪些目录寻找依赖)、extensions(module的扩展名)、mainFields(第三方类库存在多个版本的时候,优先使用哪个版本)。alias可以手动指定第三方库的使用,比如当Vue没有用CDN的时候,如果从node_modules引用,引入的代码是runtime运行时的代码,是没有包含解析单文件.vue的template部分的功能,这个时候需要依赖它的其他版本,手动指定,常见于用Vue-cli去生成项目架构的时候,发现alias默认有一项,告知webpack引入Vue的时候module的位置是vue/dist/vue.esm.js(包含了解析template的代码)。mainFields的使用是针对第三方类库使用各种模块化写法以及语法。有ES6的mport、export的,也有CommonJs的模块导出。有压缩的min.js也有Ts版本的,这些会在package.json中看到,至于引入第三方模块的引入那个版本,对于一些成熟的类库比如Vue,Vue-router等有多个版本,可以通过设置mainFields告知webpack从package.json中的那个字段导入类库。ES6的improt、export存在静态分析,配合tree shaking使用,这也是webpack号称能提速98%的原因,但是目前的状况是第三方库参差不齐,很多都没有提供ES6模块导出的版本,所有目前效果还不是很理想
externals:指定外部扩展。从bundle中排除依赖,比如项目一些基本不会变更版本的第三方类库,通过引用CDN资源,常见Vue、VueRouter、element-ui、echart等等类库
devServer:开发模式的配置。在webpack4.x之前的版本通过node的express框架搭建的本地服务器,配合webpack-dev-middleware和webpack-hot-middleware搭建的开发环境。现在可以通过webapck-dev-server类库结合devServer配置项去开启本地开发环境,这个类库封装了express的操作,同时内部使用了webpack-dev-middleware。给出配置链接:devServer配置项
三:项目webpack升级流程先简短的介绍一下项目,升级的项目为多入口项目,每个module模块代码一个入口,然后共用很多业务组件
*/build webpack.base.conf.js // dev和prod共用部分 webpack.dev.conf.js // dev模式下特有配置 webapck.prod.conf.js // prod模式下特有配置 */common components // 多个模块共用的组件 assets // 静态资源 */src module1 // 模块1的代码 index.js // 模块1代码的入口 module2 // 模块2的代码 module3 // 模块3的代码 */mock mock-xxx.js // mock api的接口 */config // 配置文件 index.js dev.js prod.js1:package.json依赖的管理
升级webpack,安装webpack-cli。全局安装npm-check-updates,查看package.json中可升级的依赖的版本。
cnpm install -g npm-check-updates //全局安装 // 在项目根目录下执行ncu ncu // 可升级的依赖,列举部分依赖情况 axios 0.17.1 --> 0.18.0 webpack 3.5.5 --> 4.16.5 webpack-merge 4.1.0 --> 4.1.4 // 升级webpack 安装webpack-cli cnpm install webpack webpack-cli --save-dev // 升级相应的loader和plugin cnpm install url-loader file-loader vue-loader sass-loader css-loader babel-loader html-webpack-plugin --save-dev2:修改webpack配置
公司使用的vue-cli2.0的脚手架生成的项目,简单的列举关于webpack的目录,针对需要可进行自行调整
*/build webpack.base.conf.js // dev和prod共用部分 webpack.dev.conf.js // dev模式下特有配置 webapck.prod.conf.js // prod模式下特有配置 // 通过webpack-merge合并配置输出最后的webpack配置
1:增加mode模式
// webpack.dev.conf.js 开发模式的配置 开启webpack默认的配置优化 mode: "development" // webpack.prod.conf.js 生产模式的配置 mode: "production"
2:升级vue-loader,vue-loaderv.15版本和之前的有所区别,vue-loader不在使用自身的配置,而是解析.vue文件之后使用webpack里配置的loader,详细文档见Vue-loader的使用,补充一点:在.vue 文件中style提供的scoped标记,就是通过vue-loader去实现在template中加入了适当的hash,配合样式去做到组件内样式的独立
// webpack.config.js const VueLoaderPlugin = require("vue-loader/lib/plugin") // 插件解析.vue文件把template script style 分别交给webpack配置的loader去处理 module.exports = { // ... plugins: [ new VueLoaderPlugin() ] }
3:部分webpack的插件已经配置停用。如有的话,按照提示删除掉,比如module里的loaders,webpack4不再支持。然后部分插件转化为通过optimazation 进行配置,主要配置点在于code split以及mainifest文件的提取,同样见下文splitChunks和runtimeChunks的分析
4:开发模式加入devServer。项目是否需要通过手动node搭建本地服务器,取决于是否需要node层面去处理其他东西,在公司项目中启动服务前有两个操作,编译scss文件(皮肤文件),以及mock文件夹(mock接口),所以还是保留了手动档搭建node层面启动服务,其实完全可以有webpck-dev-server的before,after配置项完成
// 下面代码块针对devServer的部分配置项做说明,结合相关知识进行分析 devServer: { contentBase: path.join(__dirname, "../static"), // 静态资源提供,不是webpack生成的bundle,生成的bundle在内存中见注释1 host: host || "localhost", // 主机名,如果你希望服务器外部可访问,需要设置为0.0.0.0,默认localhost port: process.env.PORT || port, // 端口号 historyApiFallback: true, // handle fallback for HTML5 history API 了解一下这个东西见注释2 proxy: proxyTable, // 后端接口转发见注释3 hot: true, // 热更新见注释4 quiet: true, // webpack打包信息省略 publicPath: assetsPublicPath, // bundle 的位置 outpath: publicPath 类似 clientLogLevel: "none" // HRM WDS 在浏览器控制台的输出 } // 注释1:写过express的就知道有一个指定static中间件用来指定资源目录的 const express = require("express") const app = express() app.use(staticPath, express.static(xxx)) // xxx/js/vendor.js,就可以通过localhost:prot/staticPath/js/vendor.js访问相关静态资源。所以contentBase就是利用express静态中间价提供个一种访问静态资源文件的能力 // 注释2:vue-router的history模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面 example: history.pushState({a:1}, "测试", "/attendance/index") ,然后内部去处理相应的router对象的展示Vue页面和逻辑,所以这就是你顺着程序点路由可以进去,但是刷新的时候,就显示404的原因,因为该路由在服务器上不是真实存在的,而是在index.html中通过JS去解析模拟的,这就需要我们生产模式下生产的dist文件所有的请求都转发到index.html。处理方式:在服务器上通过nignx 代理,或者起一个express服务,通过第三方类库 connect-history-api-fallback,当然也可以原生Node去写,此时Node只是一个文件服务器 const http = require("http") const fs = require("fs") const httpPort = 80 http.createServer((req, res) => { fs.readFile("index.htm", "utf-8", (err, content) => { if (err) { console.log("We cannot open "index.htm" file.") } res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }) res.end(content) }) }).listen(httpPort, () => { console.log("Server listening on: http://localhost:%s", httpPort) }) // 注释3:请求代理,常常被问起跨域请求有哪些方案,jsonp、CORS(后端配合)、window iframe等方式,还有一个就是通过node代码转发请求,服务端请求不存在跨域的概念,webpack中可以node自己使用http-proxy-middleware 去进行代理,本次更新使用的wbepack-dev-server模块同时也依赖了http-proxy-middleware的库,目前Node不作为纯后端,而是作为中间代码,连接纯后端(java php)和前端。 // 注释4:热更新,它允许在运行时更新各种模块,而无需进行完全刷新,目前的公司项目都是纯刷新的方式,热更新HRM,这个是需要你是用的loader或插件帮助你完成,他们能监听webpck complier期间得钩子,然后给出相应源码更新后需要patch,推送到前端,打补丁,然后实现热更新,而不是刷新整个页面去重新加载页面。好处自然提高开发效率,在修改.vue文件的tempalte和style以及script中不是vue生命周期函数时,是能够保留到当时的vue的各种状态
5:针对生产模式的优化配置,废弃生产模式中使用的优化插件,转为webpack4的optimization.* 配置
NoEmitOnErrorsPlugin // 编译错误跳过编译阶段,不生成文件 (default in production)
ModuleConcatenationPlugin // 作用域提升,加快代码执行 (default in production)
NamedModulesPlugin // 默认的module在打包进入chunks的时候都会以module.id 为标识(这个是随着依赖递增的),这会影响到缓存,使用这个插件将会使用文件的路径作为标识,详细见splitChunks and runtimeChunk分析
CommonsChunkPlugin // 最为晦涩难懂的webpack插件,作用代码分割,被splitChunks && runtimeChunks代替
下面为升级之前的配置
// 依赖module(require, import 导入的文件)来自node_modules且以.js 结尾的文件将会被打包到名为vendor的bundle中 new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: function (module, count) { // any required modules inside node_modules are extracted to vendor return ( module.resource && /.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, "../node_modules") ) === 0 ) } }), // 在vendor的bundle中把manifest提取出来(mainifest算是webpack实现的在浏览器端进行模拟加载模块和具体加载逻辑的代码块,这个最好拆出来,不然没法做缓存,具体见到长效缓存分析) new webpack.optimize.CommonsChunkPlugin({ name: "manifest", chunks: ["vendor"] }) // 打包之后就会生成一个vendor.js (里面有着来自node_modules 的第三方类库)和 mainifest.js (具体加载逻辑)
升级之后,完成原有的配置很简单,简单的照着API实现即可,本次升级伴随着两个重点,开发模式的rebuild时间,生成环境的打包文件体积和时间,所以还需要做其他优化
某些第三方类库只有某个module才加载,比如只有module1中用mint-ui,这是可以多带带提出来一个chunk或者直接就加载到module1这个入口的init chunk中 ----> 配置层面
某些公共组件比如component下公共组件layout在基本每个系统模块全部都条用了,可以考虑拉出来共用,减少重复代码量 ----> 配置层面
利用ES的语法去动态的引入模块import("a.js").then(module => {})去懒加载一个模块,比如只有在某些vue中才会用到的vue-qrcode-component的类库,这个类库不应该出现在vendor中(来自node_modules) ----> 代码层面
optimization: { splitChunks: { cacheGroups: { // module只要满足下面就会从原来的chunks中抽取出来打包到对应的chunks之中 // 这个vendors是在至少同时有两个initial Chunk中引入的来自node_modules的第三方类库会被打包到chunks-vendors中 vendors: { name: "chunk-vendors", test: /[/]node_modules[/]/, priority: -10, minChunks: 2, // 至少同时有几个chunk满足才会有可能从这些chunks中提取一些代码到新的chunk中,也就是至少两个共用才会提取,不然就直接打包都所在module的init chunk中 chunks: "initial" // chunk的概念:代码分割这些操作作用于那些chunk文件,initial是通过提供给webpack入口生成的chunks, async是通过之前提到的import() 路由懒加载形成的chunk, all就是所有的chunk文件 }, // 这个common跟上面的区别在于没有test检测module来源,只是只要有两个chunk共用就是提取出来,很显然就范围来讲,下面的大于上面,这时候到底这个来自node_modules的module进入哪个chunk,取决于priority(优先级),谁高进入哪个chunk common: { name: "chunk-common", minChunks: 2, priority: -12, chunks: "initial", minSize: 0 } } }, runtimeChunk: { name: "manifest" } } // 比如某些第三方类库只在某个Vue文件中使用,通过动态引入import("vue-qrcode-component").then() 一个类库只在一个地方用,完全没有必要打包到vendor中(因为vue-qrcode-component 来自node_module) // import 就是新建一个chunk 这个chunk是没有名字的,需要通过/* webpackChunkName: "loadsh"*/ 生成这个loadsh的chunk.name import(/* webpackChunkName: "loadsh"*/ "loadsh").then(m => { const _ = m.default console.log(_.join(["hello", "world"])) })
总结:一般经过这几步骤就能完成一个webapck项目的升级,对于自己项目的复杂的地方需要额外的处理,写着写着发现篇幅越来越长了,把上文一直出现的splitChunk和runtimeChunk留到下个篇幅着重介绍一下。在公司做升级之前,给的指标不仅仅是升级框架,还需要在dev模式开发的rebuild的速度更快(修改一个地方,rebuild的时间12s左右才能看到效果,痛苦,可以通过lessModule来解决),在prod模式下打包的项目文件体积减小已经打包所需要的时间更短(一次测试环境发布需要5,6分钟)。现在vue-cli3.0已经出现了,找时间把vue-cli3.0源码给大家分析一下,简单的包装了一下操作
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/104805.html
摘要:它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。可以将多种静态资源转换成一个静态文件,减少了页面的请求。因此我们不再按文件文件的方式运行指令,而是直接运行这样便能实现打包。 一、什么是webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。它做的事情是,分析你的项目结构,找到J...
摘要:开箱即用的多页面脚手架基于模块化开发可复用的现代化网站感兴趣的朋友,请点个及时关注项目更新请点个项目请提特性支持前后端分离开发配置完整的打包方案支持本地开发热更新集成代码风格校验支持编写源码,编译生成生产代码内置开发环境,自动加样式前缀自 Webpack-seed 开箱即用的多页面脚手架, 基于webpack4.2x babel7.1x模块化开发可复用的现代化网站(Without Vu...
摘要:先看下官方文档中对模块的描述在模块化编程中,开发者将程序分解成离散功能块,并称之为模块。每个模块具有比完整程序更小的接触面,使得校验调试测试轻而易举。 先看下webpack官方文档中对模块的描述: 在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块。每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易...
摘要:前几天,企鹅电竞团队开源了自己的多渠道打包工具,比美团的更全面一些。四可商用的多渠道打包方案在开源之前,市面上支持签名的多渠道打包方案,就属美团的了,下面简单比对一下它们的优缺点。 showImg(https://segmentfault.com/img/remote/1460000013436224?w=900&h=500); 一、前言 Hi,大家好,我是承香墨影! 当我们需要发布一...
阅读 2845·2021-09-27 13:35
阅读 622·2021-09-23 11:22
阅读 2891·2019-08-30 15:54
阅读 1610·2019-08-29 16:27
阅读 2467·2019-08-29 15:05
阅读 2349·2019-08-23 18:11
阅读 3522·2019-08-23 16:32
阅读 2939·2019-08-23 14:56