SegmentFault 前端小站最新的文章
2019-03-22T18:02:16+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
vue开发项目完全指南
https://segmentfault.com/a/1190000018619576
2019-03-22T18:02:16+08:00
2019-03-22T18:02:16+08:00
hyangteng
https://segmentfault.com/u/hyangteng
327
<blockquote>
<p>这篇文章总结了vue项目的所遇到的问题,包括跨域、用户认证、接口统一管理、路由配置、兼容性处理,性能优化等内容。</p>
<p>项目github地址 :</p>
<ul>
<li>前端 <a href="https://link.segmentfault.com/?enc=MTA9p7CjUOYjrJijcQNfFg%3D%3D.iJ0iJ%2FkwDBqRdmVvsxOGfIKOO5kSSdM0tgpQUiB7PTlH6dNrLA7qkX8r0gjisu8E" rel="nofollow">https://github.com/huangyangt...</a>
</li>
<li>后端: <a href="https://link.segmentfault.com/?enc=xnrZbOP8QRUvzyYZiYXMuw%3D%3D.ZqopT6TlR2hPylTsQjaDTjGq407lajd%2B6fOydDuT%2Bjt0mXW7TUChLiMI90YPwZ8qOrqoys6KkEQXromjCj%2BYoA%3D%3D" rel="nofollow">https://github.com/huangyangt...</a>
</li>
</ul>
</blockquote>
<p><img src="/img/remote/1460000018619579" alt="image-20190318090248419" title="image-20190318090248419"></p>
<h2>一、环境依赖安装</h2>
<h3>1. node环境</h3>
<h4>1.1 node和npm环境的安装</h4>
<blockquote>根据以下教程安装,然后设置好环境变量</blockquote>
<p><a href="https://link.segmentfault.com/?enc=A%2FVAYSCl3O88bWhhEx0R2g%3D%3D.1ye3zkluwUM1VETl3MbIr5LjhK586OuDCQwqI9LzPvRyq%2F9sCDP7Z05pokhynD8s1yd5VobFXjC3gGxuIXHnVQ%3D%3D" rel="nofollow">http://www.runoob.com/nodejs/...</a></p>
<p>视频教程 <a href="https://link.segmentfault.com/?enc=C81JKwm9pQplcxnmtvv7Ew%3D%3D.1HYlaR3iXAf9NIQw2A8ZMKTJHaAHnurNnvNMaUZwIIpuX2kxZOxW79R%2FxrwyfDlQE0WYKxkUe9b%2BeaCxun7cDcY1LjXkPUkL9Zb8%2BhYIH%2FAKL58PKaUue9LoC8KXEIUd" rel="nofollow">http://101.110.118.22/github....</a></p>
<p>centos如果装不上看这里:<a href="https://link.segmentfault.com/?enc=joBfR4TTmGYKTOjWlrgbeQ%3D%3D.J1ocH8%2Bf6ufM9wUZ2EkL2LHhJHfokauprHSAWw%2Fu79N%2BQnQVwazMLezyokUFmJQ0rEakUaXViK02uynxGk4wjatdjAZ38K29iTmIioaMNek%3D" rel="nofollow">https://www.rosehosting.com/b...</a></p>
<h4>1.2 为npm更改源</h4>
<blockquote>npm默认使用的源的服务器在国外下载速度慢,所以需要更换源<p>以下两种方法任选一种</p>
</blockquote>
<h5>1.2.1使用cnpm代替npm</h5>
<blockquote>参考链接:<a href="https://link.segmentfault.com/?enc=RSaYbEYs2802mBMDCqIfig%3D%3D.IvnqOn8SjPyMh26FFD5lcoc77%2B6FYueym7w7p7x219E%3D" rel="nofollow">https://npm.taobao.org/</a>
</blockquote>
<pre><code class="shell"># 安装
npm install -g cnpm --registry=https://registry.npm.taobao.org
#安装完cnpm,之后再按照依赖就要使用cnpm
cnpm install [包名]</code></pre>
<h5>1.2.2为npm更换源</h5>
<blockquote>参考链接 <a href="https://segmentfault.com/a/1190000004444283">https://segmentfault.com/a/11...</a>
</blockquote>
<p>修改源为淘宝的源</p>
<pre><code class="shell">npm config set registry http://registry.npm.taobao.org/</code></pre>
<p>我们在发布自己包的时候需要将官方的源改回来</p>
<pre><code class="shell">npm config set registry https://registry.npmjs.org/</code></pre>
<h4>1.3 管理(更新)nodejs的版本</h4>
<blockquote>切换nodejs版本有两种方式,分别是<code>nvm</code>和<code>n</code>,n更简单推荐使用</blockquote>
<h5>使用n管理nodejs版本</h5>
<blockquote>参考链接 <a href="https://link.segmentfault.com/?enc=GGjNrJjMVCV7S%2FKbJ%2BPffA%3D%3D.lYzNwrbZIWHwGU0oZsdpB61sc%2BSOdJX2ddiPs9gnjkzXzIc6RhhygpupT31OyggG" rel="nofollow">https://www.jianshu.com/p/c64...</a><p>官网 <a href="https://link.segmentfault.com/?enc=FbWIiVxzHY0sBscFXDZJLQ%3D%3D.m4X8Qmp%2BGlmtYFTT8L2xEDk6tFgfl4CyYnh4mpN5AOo%3D" rel="nofollow">https://github.com/tj/n</a></p>
</blockquote>
<pre><code class="shell">#安装
npm install -g n
#使用n下载所需node版本
n 版本号
#下载最新版本
n latest
# 切换版本
输入 n,
然后选中所需版本
#以指定的版本来执行版本
n use 7.4.0 index.js</code></pre>
<p>linux使用n安装新版本nodejs之后,如果<code>node -v</code>还是原来的版本,那么就需要改变一下环境变量</p>
<p><code>vim .bash_profile</code></p>
<pre><code class="shell">export NODE_HOME=/usr/local #NODE_HOME改成新版本nodejs安装的目录,如果找不到,find / -name node
export PATH=$NODE_HOME/bin:$PATH
export NODE_PATH=$NODE_HOME/lib/node_modules:$PATH</code></pre>
<p>修改环境变量参考:<a href="https://link.segmentfault.com/?enc=XpIwZ1Qk51JfZyPMBG1hDA%3D%3D.wpIrXaarLS6BQvoRoCyppFRbeSRhikA5AKHGTIl2ZeXS2iji%2BFtRXoy5eu9w36GWKDLanxE%2F%2BVhvceAIDSn54A%3D%3D" rel="nofollow">https://blog.csdn.net/yi412/a...</a></p>
<h4>1.4 package.json文件详解</h4>
<blockquote>参考文档 <a href="https://link.segmentfault.com/?enc=CaG1QniMUQWpnHNHerzUqQ%3D%3D.Dlo72zmC6hhPzN5Iss8IV4uASv38MipucUz7pf4YQyP9ymhDEZVJ9hp24CN5V4T8l95x942rFUlsjSMrKXE%2Bug%3D%3D" rel="nofollow">http://javascript.ruanyifeng....</a>
</blockquote>
<h3>2. vue脚手架</h3>
<blockquote>vue-cli目前已经更新到3版本,vue-cli3把webpack相关的配置隐藏起来了,所有的配置都在vue.config.js文件夹中,所以使用vue-cli3需要的webpack水平较高,建议使用vue-cli2</blockquote>
<h4>3.1 vue-cli2.x安装</h4>
<p>参考链接:<a href="https://link.segmentfault.com/?enc=HO40Sp4RdiseaSLnyiy2SQ%3D%3D.aIryolqDBmgrLkLrndlw%2F8O4mq7ys%2F6imxz52g2p54JdQbuVqU6OKZ%2Fu8cDC778FCOZkArb6SxZGTdTAt82edA%3D%3D" rel="nofollow">https://github.com/vuejs/vue-...</a></p>
<p>安装:</p>
<pre><code class="jade">npm install -g vue-cli</code></pre>
<p>用法:</p>
<pre><code>$ vue init < template-name > < project-name ></code></pre>
<p>例:</p>
<pre><code>$ vue init webpack my-project</code></pre>
<p>目前可用的模块包括:</p>
<ul>
<li>
<a href="https://link.segmentfault.com/?enc=HEKe6EXxG4s48pifxH50VQ%3D%3D.AnNecxko5J%2BL%2BjqlXu5Rk4gF6t4%2BG9nJJVMXIA8X3jvECqBzySrunHl9zh1eTTUc" rel="nofollow">webpack</a> - 一个功能齐全的Webpack + vue-loader设置,具有热重载,linting,测试和css提取功能。</li>
<li>
<a href="https://link.segmentfault.com/?enc=21OsZYnVODEJEGLm5Eykxw%3D%3D.CKa%2Bye6LLX8IgIjTpdwkP8UsZWAAkWvgQGUvqQlWEFSKM68GCAI1ppITxCsrRbI%2BaC2L%2BPkRTEBogrsHm0s2iw%3D%3D" rel="nofollow">webpack-simple</a> - 一个简单的Webpack + vue-loader设置,用于快速原型设计。</li>
<li>
<a href="https://link.segmentfault.com/?enc=K%2BoSDLc1w%2Fq3dJt5lsiBfQ%3D%3D.XgLepbb%2FTjxE3SHao4UFJ5acsY4Bv4QbqOGELsXZevW3wHOR2io6ym0CSye4bh9j" rel="nofollow">browserify</a> -全功能Browserify + vueify设置用热重装载,linting&单元测试。</li>
<li>browserify <a href="https://link.segmentfault.com/?enc=xeCCqOuc6%2BovEgSuhvqqqg%3D%3D.k118vZlLjKAQo%2FB3UanQtcNbzPp%2BG9rOmpesbLbaPogWoVP8sh5etnrLSWTHDdmRQTQgNNDvz1Ow50fb1jZLWA%3D%3D" rel="nofollow">-simple</a> - 一个简单的Browserify + vueify设置,用于快速原型设计。</li>
<li>
<a href="https://link.segmentfault.com/?enc=Ixk06Jg1j1e7RliDHaeYig%3D%3D.qXUTMXpcNHO3tnq9E6RBK%2FFZSWJMgLZlF9AZNmnKokcylCs8CazrUixFBaHLy3S1" rel="nofollow">pwa</a> - 基于webpack模板的vue-cli的PWA模板</li>
<li>
<a href="https://link.segmentfault.com/?enc=NhDmpd3f%2F1c57V8ajJjiyQ%3D%3D.gATK8Squ%2F%2BUvsng3fns%2Bf5nfImg5Hdd%2F0wxTm9p9n54iVS4TybwAVPuM%2BMdcyX2J" rel="nofollow">simple</a> - 单个HTML文件中最简单的Vue设置</li>
</ul>
<h4>3.2 vue-cli3.x安装及配置(仅供参考)</h4>
<p>vue-cli3x的官方文档:<a href="https://link.segmentfault.com/?enc=paPM3kBgHoQObYLX3UdLuA%3D%3D.BP04FRDkwlDelZDTbXy18GLHixJ9wcZB%2Bjrk6eQAEl8%3D" rel="nofollow">https://cli.vuejs.org/</a></p>
<p>Vue-cli3 中vue.config.js文件配置参考文档:<a href="https://link.segmentfault.com/?enc=ptfZSU9D3aWvU4urevJRhQ%3D%3D.SZOJ3VuMNjj87NTJRpc%2B%2B0gQaOusITwcXTVu%2B2vMZGJ8ShkDhCh0vUTEB%2BPSBply" rel="nofollow">https://cli.vuejs.org/zh/conf...</a></p>
<p>Vue CLI 的包名称由 <code>vue-cli</code> 改成了 <code>@vue/cli</code>。 如果你已经全局安装了旧版本的 <code>vue-cli</code>(1.x 或 2.x),你需要先通过 <code>npm uninstall vue-cli -g</code> 或 <code>yarn global remove vue-cli</code> 卸载它。</p>
<p>安装</p>
<pre><code class="js">npm install -g @vue/cli</code></pre>
<p>安装了vue-cli3如果还想使用vue-cli2的init功能,需要安装一个桥接功能</p>
<pre><code class="js">npm install -g @vue/cli-init</code></pre>
<pre><code class="javascript">// vue.config.js 配置说明
//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
// 这里只列一部分,具体配置参考文档
module.exports = {
// 部署生产环境和开发环境下的URL。
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
//例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 baseUrl 为 /my-app/。
baseUrl: process.env.NODE_ENV === "production" ? "./" : "/",
// outputDir: 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)
outputDir: "dist",
//用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
assetsDir: "assets",
//指定生成的 index.html 的输出路径 (打包之后,改变系统默认的index.html的文件名)
// indexPath: "myIndex.html",
//默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。你可以通过将这个选项设为 false 来关闭文件名哈希。(false的时候就是让原来的文件名不改变)
filenameHashing: false,
// lintOnSave:{ type:Boolean default:true } 问你是否使用eslint
`lintOnSave`: true,
//如果你想要在生产构建时禁用 eslint-loader,你可以用如下配置
// lintOnSave: process.env.NODE_ENV !== 'production',
//是否使用包含运行时编译器的 Vue 构建版本。设置为 true 后你就可以在 Vue 组件中使用 template 选项了,但是这会让你的应用额外增加 10kb 左右。(默认false)
// runtimeCompiler: false,
/**
* 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
* 打包之后发现map文件过大,项目文件体积很大,设置为false就可以不输出map文件
* map文件的作用在于:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。
* 有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。
* */
productionSourceMap: false,
// 它支持webPack-dev-server的所有选项
devServer: {
host: "localhost",
port: 1111, // 端口号
https: false, // https:{type:Boolean}
open: true, //配置自动启动浏览器
// proxy: 'http://localhost:4000' // 配置跨域处理,只有一个代理
// 配置多个代理
proxy: {
"/api": {
target: "<url>",
ws: true,
changeOrigin: true
},
"/foo": {
target: "<other_url>"
}
}
}
};
</code></pre>
<h2>二、开发</h2>
<blockquote>
<p>以下内容依赖环境为 : vue-cli 版本2.9.x </p>
<p>项目github地址 :</p>
<ul>
<li>前端 <a href="https://link.segmentfault.com/?enc=RTsJX4Zh5CQ1%2BTNcrRcKew%3D%3D.Kg1B0u%2FK%2BRHpZgvP8NTcESRn%2BzjDq%2FWzohUcLjkNSFhXaHse9rcKtALNrP1DnyvF" rel="nofollow">https://github.com/huangyangt...</a>
</li>
<li>后端: <a href="https://link.segmentfault.com/?enc=gXqal%2FrT8Z%2FQZBdsFvYgsw%3D%3D.i9xGl%2BKV24f8nXBi%2B4kozaDmcEbUXGA3DDb0ehJ3bScl%2B%2FV%2BHuzJnDeZiOrH1RmLYLD8lpT8pE0clUecyZOWXA%3D%3D" rel="nofollow">https://github.com/huangyangt...</a>
</li>
</ul>
</blockquote>
<p>安装完以上依赖后,就可以开始一个项目了,我们先看下后端api的定义</p>
<h3>前后端交互报文定义以及数据api接口</h3>
<h4><strong>前后端交互报文定义</strong></h4>
<p>请求</p>
<pre><code class="js">http request header{ //除登录注册以外的请求,发起请求时要在请求头中加入token
authorization:jwt
}
http request body{
}</code></pre>
<p>返回</p>
<pre><code class="js">http response header{
}
http response body{
code:业务处理状态码
msg:业务处理描述
token:jwt token
data:业务数据
}</code></pre>
<h4>项目中使用的后台api定义如下</h4>
<p><code>注:服务器端的host为118.24.85.97,端口为22222</code></p>
<h5><code>1.测试api是否可用</code></h5>
<ol>
<li>uri: <a href="https://link.segmentfault.com/?enc=xlxybRjaxspzUgDunzz17w%3D%3D.VQvdgCJufqmAFMsgeMorkS7AhU7iQv3nKSTijZDkk9Y%3D" rel="nofollow">http://118.24.85.97</a>:22222/api</li>
<li>描述:测试接口是否能用,能用的话返回 'API WORDS'字符串</li>
<li>请求类型 GET</li>
<li>请求参数 无</li>
<li>返回值 {'Api Works'}</li>
</ol>
<h5><code>2.注册</code></h5>
<ol>
<li>uri: <a href="https://link.segmentfault.com/?enc=dywv1kqTK70PMckeostknw%3D%3D.r%2FarJ1FWKjB9QMR2SpCtiPe8BawfZSEQ0L8CveZkcJY%3D" rel="nofollow">http://118.24.85.97</a>:22222/api/users/reg</li>
<li>描述:注册</li>
<li>请求类型 POST</li>
<li>请求参数</li>
</ol>
<table>
<thead><tr>
<th>序号</th>
<th>参数名</th>
<th>是否必填</th>
<th>描述</th>
</tr></thead>
<tbody>
<tr>
<td>1</td>
<td>name</td>
<td>y</td>
<td>用户名</td>
</tr>
<tr>
<td>2</td>
<td>pass</td>
<td>y</td>
<td>密码</td>
</tr>
</tbody>
</table>
<ol><li>返回参数 不重要</li></ol>
<h5><code>3.登录</code></h5>
<ol>
<li>uri: <a href="https://link.segmentfault.com/?enc=jwhbs1GzJRMOGMhpkL3Qlg%3D%3D.MKu57SqnERwcPa5D9cuSKEzDrCdjDkfVsSJ5PTIFMvs%3D" rel="nofollow">http://118.24.85.97</a>:22222/api/users/login</li>
<li>描述:登录</li>
<li>请求类型 POST</li>
<li>请求参数 </li>
</ol>
<table>
<thead><tr>
<th>序号</th>
<th>参数名</th>
<th>是否必填</th>
<th>描述</th>
</tr></thead>
<tbody>
<tr>
<td>1</td>
<td>name</td>
<td>y</td>
<td>用户名</td>
</tr>
<tr>
<td>2</td>
<td>pass</td>
<td>y</td>
<td>密码</td>
</tr>
</tbody>
</table>
<ol><li>返回参数</li></ol>
<table>
<thead><tr>
<th>序号</th>
<th>参数名</th>
<th>描述</th>
</tr></thead>
<tbody>
<tr>
<td>1</td>
<td>msg</td>
<td>ok</td>
</tr>
<tr>
<td>2</td>
<td>token</td>
<td>用于验证用户身份的token</td>
</tr>
</tbody>
</table>
<h5><code>4.获取当前用户信息</code></h5>
<ol>
<li>uri: <a href="https://link.segmentfault.com/?enc=wg7iO8DaafedUsFBrdYtsw%3D%3D.RCvP%2FoTzJGs30iTnnnfuDs1r89hFKNz%2FnpGCeOAUwnc%3D" rel="nofollow">http://118.24.85.97</a>:22222/api/users/current</li>
<li>描述:获取用户信息</li>
<li>请求类型 GET</li>
<li>请求参数 无</li>
<li>返回参数</li>
</ol>
<table>
<thead><tr>
<th>序号</th>
<th>参数名</th>
<th>描述</th>
</tr></thead>
<tbody>
<tr>
<td>1</td>
<td>id</td>
<td>用户id</td>
</tr>
<tr>
<td>2</td>
<td>token</td>
<td>用于验证用户身份的token</td>
</tr>
</tbody>
</table>
<h3>0.初始化项目</h3>
<p>在终端中输入</p>
<pre><code class="shell">vue init webpack vue2_template</code></pre>
<p>然后会有一些选项让你选,按照项目需求选择,例如我不需要eslint,unit test,就可以选No,现在选no将来如果需要的话也可以自己安装</p>
<p><img src="/img/remote/1460000018619580" alt="image-20190301151747514" title="image-20190301151747514"></p>
<p>安装完成之后,按照提示切换到相应目录,执行相应指令,然后在浏览器打开网址,这样一个简单的vue项目就启动起来了</p>
<p><img src="/img/remote/1460000018619581" alt="image-20190301152115255" title="image-20190301152115255"></p>
<h3>1. 项目文件介绍</h3>
<h4>整个文件介绍:</h4>
<p><img src="/img/remote/1460000018619582" alt="image-20190301153205422" title="image-20190301153205422"></p>
<p>注意:</p>
<ol>
<li>开发主要使用src文件夹</li>
<li>webpack的配置文件配置文件详解看这里:<a href="https://segmentfault.com/a/1190000014804826">https://segmentfault.com/a/11...</a>
</li>
<li>package.json配置详解 <a href="https://link.segmentfault.com/?enc=19%2FdWQqVgpFevxrz%2Fe3WkA%3D%3D.K36yt7kajY%2FmjtdD6F9Wdi4bsCMOy4C694%2BASIw8cC%2FfgtxgsJxquzz1bFYdETnxlNBXk7tV%2F8XcL6ug%2FnCPHQ%3D%3D" rel="nofollow">http://javascript.ruanyifeng....</a>
</li>
</ol>
<h4>src目录介绍</h4>
<p>首先在src目录下新建一个文件夹views,用来放我们的主要页面,然后在assets文件夹中建立fonts styles imgs,用来存放相应的资源,建完之后,文件夹如下</p>
<p><img src="/img/remote/1460000018619583" alt="image-20190301155249665" title="image-20190301155249665"></p>
<h3>2. 跨域、axios配置与api管理</h3>
<p>在这个项目中,我们使用axios进行数据请求</p>
<blockquote>axios中文文档: <a href="https://link.segmentfault.com/?enc=xXrJCwPwnaB9YDxJ5JFC0Q%3D%3D.0Sl2YiRXo5GD1pYdRp5xo8kyKqx1k5x3T5qNyrOMT1YlWWv8Ob4xARsuC0kzOzPm" rel="nofollow">https://www.kancloud.cn/yunye...</a>
</blockquote>
<pre><code class="shell"># 安装axios
npm/cnpm i axios -S # -S 指安装到package.json中的dependencies中</code></pre>
<p>安装完成后,我们要在main.js中引入,然后测试一下是否成功引入</p>
<pre><code class="js">//main.js文件
import axios from 'axios'
axios.get('https://api.github.com/users?since=10') //使用github接口做一下测试
.then(res=>console.log(res))
.catch(err=>console.log(err))
</code></pre>
<p>浏览器显示以下信息,说明引入成功<img src="/img/remote/1460000018619584" alt="image-20190301160510216" title="image-20190301160510216"></p>
<p>github提供的接口配置了cors,所以我们能够能够在浏览器正常访问到,但cors兼容性最低到ie10,而且后台不一定会配置cors,所以在开发时我们需要配置一下跨域</p>
<p>参考链接:</p>
<ol><li>cors详解 <a href="https://link.segmentfault.com/?enc=XqZslsV1Qd657ZZQ%2B%2FTHwg%3D%3D.gR%2BSgIXI9wz5uM2o1UlKvl5iuM7EaFIZ5jBxXUf391gD7kmGydlYFni7HFvja8EoR0zRUjs9dIy2osfAjSqPHA%3D%3D" rel="nofollow">http://www.ruanyifeng.com/blo...</a>
</li></ol>
<h4>2.1配置跨域</h4>
<blockquote>参考文档:<a href="https://segmentfault.com/a/1190000017905030">https://segmentfault.com/a/11...</a>
</blockquote>
<p>先找个没有设置cors的api使用axios访问一下</p>
<pre><code class="js">axios.get('http://118.24.85.97:22222/api')
.then(res=>console.log(res))
.catch(err=>console.log(err))</code></pre>
<p>浏览器会因为同源策略报错</p>
<p><img src="/img/remote/1460000018619585" alt="image-20190307094529285" title="image-20190307094529285"></p>
<p>下面进行跨域的配置</p>
<blockquote>配置目录 config/index.js 13行</blockquote>
<pre><code class="js">proxyTable: {
'/apis':{
target:'http://118.24.85.97:22222',//后台地址 proxyTable 把/apis映射成target 即 /apis=http://118.24.85.97:22222
changeOrigin:true,//是否跨域
pathRewrite:{
'^/apis':''
}
}
}</code></pre>
<p>再进行访问数据时就要在接口前面加上/apis(/apis就相当于<a href="https://link.segmentfault.com/?enc=EDqeNMls%2B%2F%2FgqUKuQiyT%2Fg%3D%3D.rYyTNCVJ0OijE1hl%2FysLmlonprlNV3%2FUcoDWISUQsC4%3D" rel="nofollow">http://118.24.85.97</a>:22222)</p>
<pre><code class="js">axios.get('/apis/api')
.then(res=>console.log(res))
.catch(err=>console.log(err))
</code></pre>
<p>然后就发现浏览器访问成功了</p>
<p><img src="/img/remote/1460000018619586" alt="image-20190307095002857" title="image-20190307095002857"></p>
<p>proxyTable原理:跨域是浏览器禁止的,服务端并不禁止跨域 ,所以浏览器可以发给自己的服务端然后,由自己的服务端再转发给要跨域的服务端,做一层代理。proxyTable使用的是<code>http-proxy-middleware</code>中间件,内部用的是http-proxy</p>
<p>以上配置的跨域是开发环境下的,在生产环境就自动失效了,而且这样配置我们开发时访问接口时,都要写成<code>/apis/xxx/xxx</code>格式,在部署到服务器中时,我们要把/apis拿掉,才能访问到正确的url。有两种方法,一种是在开发环境中设置(通过axios的baseURL),另一种是在服务器上修改nginx的配置设置。</p>
<h4>2.2生产环境去除/apis前缀</h4>
<p>在这里详细说下第一种方式,原理是这样的:</p>
<p>通过检测是开发环境和生产环境,设置不同的baseURL,使生产环境和开发环境都能正确访问url</p>
<p>在src目录下新建一个<code>apis</code>目录,然后在apis目录下新建一个<code>api.config.js</code>文件</p>
<pre><code class="js">//判断是否是生产环境
//webpack在开发环境和生产环境分别执行不同的js文件,process.env.NODE_ENV设置了不同的值,process.env.NODE_ENV在生产环境中值为'production'(这个值是在build/build.js中第4行设置的)
var isPro = process.env.NODE_ENV=== 'production'
// 如果是生产环境 我们就使用服务器的uri,如果是开发环境,我们就添加/apis前缀
module.exports = {
baseUrl: isPro ? 'http://118.24.85.97:22222' : '/apis'
}
</code></pre>
<p>在main.js中引入这个文件,然后设置axios的<code>baseURL</code></p>
<pre><code class="js">//引入api.config.js文件,然后设置axios的baseURL
import apiConfig from './apis/api.config'
axios.defaults.baseURL=apiConfig.baseUrl</code></pre>
<p>再来测试一下不加/apis的接口</p>
<pre><code class="js">axios.get('/api')
.then(res=>console.log(res))
.catch(err=>console.log(err))
</code></pre>
<p>浏览器显示是ok的。这样我们以后使用axios访问接口就可以不加/apis了,打包后访问也不用手动去除/apis</p>
<h4>2.3 api统一管理</h4>
<blockquote>在vue项目开发过程中,会涉及到很多接口的处理,当项目足够大时,就需要统一管理接口。<p>具体方法应该挺多的,这里只介绍一种:使用axios+async/await进行接口的统一管理</p>
</blockquote>
<p>一般来说,后台的接口是分模块的,例如我们后台的测试接口</p>
<ul>
<li>身份认证 /api/login /api/reg</li>
<li>用户信息 /v1/api/user</li>
</ul>
<p>我们首先在src目录下新建一个apis文件夹,后台提供的所有接口都在这里定义</p>
<p>第二步,按照后台提供的模块新建js文件,我们新建<code>user.js</code> <code>auth.js</code></p>
<p>第三步,引入axios,做相应的配置</p>
<p>在apis目录下新建一个http.js,在里面做axios相应的配置</p>
<ol>
<li>我们上文中是在main.js文件引入的axios,设置的baseURL,以上代码可以去除,改为在http.js中引入</li>
<li>我们做的主要是:引入axios,创建一个axios的实例(实例的功能和axios一样)</li>
</ol>
<pre><code class="js">import axios from 'axios'
import apiConfig from './api.config'
//创建axios的一个实例
var instance = axios.create({
baseURL:apiConfig.baseUrl,
timeout: 6000
})
//------------------- 一、请求拦截器 后面介绍
instance.interceptors.request.use(function (config) {
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
//----------------- 二、响应拦截器 后面介绍
instance.interceptors.response.use(function (response) {
return response.data;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
/**
* 使用es6的export default导出了一个函数,导出的函数代替axios去帮我们请求数据,
* 函数的参数及返回值如下:
* @param {String} method 请求的方法:get、post、delete、put
* @param {String} url 请求的url:
* @param {Object} data 请求的参数
* @returns {Promise} 返回一个promise对象,其实就相当于axios请求数据的返回值
*/
export default function (method, url, data = null) {
method = method.toLowerCase();
if (method == 'post') {
return instance.post(url, data)
} else if (method == 'get') {
return instance.get(url, { params: data })
} else if (method == 'delete') {
return instance.delete(url, { params: data })
}else if(method == 'put'){
return instance.put(url,data)
}else{
console.error('未知的method'+method)
return false
}
}</code></pre>
<p>第四步,在<code>apis/xxx.js</code>文件中引入http.js导出的函数,拿其中一个文件<code>auth.js</code>说明</p>
<pre><code class="js">//auth.js 用于定义用户的登录、注册、注销等
import req from './http.js'
//定义接口
//在这里定义了一个登陆的接口,把登陆的接口暴露出去给组件使用
export const LOGIN =params=>req('post','/api/users/login',params)
//这里使用了箭头函数,转换一下写法:
// export const LOGIN=function(params){
// return req('post','/api/login',params)
// }
//定义注册接口
export const REG =params=>req('post','/api/users/reg',params)
</code></pre>
<p>最后一步,在需要用的该api的组件中引入并调用,我们在App.vue文件中测试下</p>
<pre><code class="js"><template>
<div>
<h2>登录</h2>
用户名<input type="text" v-model="user">
密码<input type="password" v-model="pass">
<input type="button" @click="reg" value="注册">
<input type="button" @click="login" value="登录">
</div>
</template>
<script>
import {LOGIN,REG} from '../../apis/auth.js'
export default {
data() {
return {
user:'',
pass:'',
err:[]
}
},
methods: {
async reg(){
try {
const data = await REG({ name: this.user,pass: this.pass })
console.log(data)
alert(JSON.stringify(data))
this.cleanForm()
} catch (error) {
console.log(error)
}
},
async login(){
try {
const data = await LOGIN({ name: this.user,pass: this.pass })
alert(JSON.stringify(data))
this.cleanForm()
} catch (error) {
console.log(error)
}
},
cleanForm(){
this.user=''
this.pass=''
}
},
}
</script>
</code></pre>
<p>注:如果要打开Login.vue,需要配置对应的路由</p>
<p>上面的代码引入了<code>auth.js</code>定义的api,并在对应的方法中使用。代码中用到了async/await,其实很简单,可以假设async是个标识,说明这个函数中有异步请求,await翻译为'等',后面接一个异步请求,等后面的异步请求执行完成之后,会把结果赋给<code>=</code>左边的值</p>
<blockquote>参考链接 <a href="https://link.segmentfault.com/?enc=Nk%2Bfg3o4dIIhTvR%2F7mTJrg%3D%3D.EOBTVOSM8xFjfeCIjQD4%2FS%2Fz15nu1joKDZKJkf5LcixB3I2r7F9Vzd%2FbWwFYSxQ5" rel="nofollow">http://www.runoob.com/w3cnote...</a>
</blockquote>
<p>总结一下,像上面那样定义接口虽然麻烦点,但有两个好处:</p>
<ol>
<li>代码看起来规范,所有的接口都在一个文件夹定义,不用分散的各个组件,维护起来简单,例如后台的一些url变了,改起来也方便</li>
<li>可以做到接口一次定义,到处使用</li>
</ol>
<h3>3. 路由配置</h3>
<blockquote>Vue Router官方文档 <a href="https://link.segmentfault.com/?enc=TTdyMcvLL59G1LYzo3zkVA%3D%3D.fs7LYQnabBBLLWmPCs6uNTNDYarAAnD7wUKaA1macXE%3D" rel="nofollow">https://router.vuejs.org/zh/</a><p>前端路由原理:<a href="https://segmentfault.com/a/1190000018219705">https://segmentfault.com/a/11...</a></p>
</blockquote>
<h4>3.1 最简配置</h4>
<blockquote>路由的配置文件在router/index.js文件中<p>先引入文件,再进行配置</p>
</blockquote>
<p>首先在<code>views目录中新建以下页面</code>,主页(Home/Home.vue),登录页(Login/Login.vue),测试页(Test/Test.vue)</p>
<p>然后配置下路由</p>
<pre><code class="js">import Vue from 'vue'
import Router from 'vue-router'
//@表示 src目录 webpack的配置在webpack.base.conf.js第29行 alias{'@':resolve('src')}
import Home from '@/views/Home/Home.vue'
import Login from '@/views/Login/Login.vue'
import Test from '@/views/Test/Test.vue'
Vue.use(Router)
export default new Router({
routes: [//路由规则
{
path: '/',
name: 'Home',
component: Home
},
{
path:'/login',
name:'Login',
component:Login
},
{
path:'/test',
name:'Test',
component:Test
}
]
})
</code></pre>
<p>路由规则在<code>routes</code>中进行配置,<code>routes</code>是一个数组,接受一系列路由规则,每个路由规则是一个对象,包括路径、路由名字,和路径匹配的组件,建议给每个路由加个名字,在后面可能会用到。</p>
<p>打开浏览器,输入相应的url查看配置的路由是否正确,不正确的话检查下自己的配置</p>
<h4>3.2配置路由懒加载</h4>
<blockquote>参考文档:<p>路由懒加载官方文档:<a href="https://link.segmentfault.com/?enc=0jMyBBNG2wb0%2B9eB2eiaIg%3D%3D.AXHLZhlzrHyTvKmOx0i98ysnZ2GR31nZSx4WOp%2FuY5T%2BIz6u3AsQEb4SJr%2B32qWUZHb2tmup5AyG2gvyrGMKUw%3D%3D" rel="nofollow">https://router.vuejs.org/zh/g...</a></p>
<p>webpack之mainfest解读:<a href="https://link.segmentfault.com/?enc=VcYKmbzGwuWuJmqQeEn0AQ%3D%3D.r%2F3qYsQSVPSd%2Fv%2FKk%2F9alQZGg1ahuSBvPyM%2BEyP6mlGtTteReVM1CUCZBVy4%2F6HR" rel="nofollow">https://github.com/younth/blo...</a></p>
</blockquote>
<p>当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。所以,懒加载的含义是当路由被访问时再去加载对应的js代码。</p>
<p>首先,不做路由懒加载的情况下,我们打包一下(切换到项目目录,执行<code>npm run build</code>),然后会发现项目下生产了3个js文件<br><img src="/img/remote/1460000018619587" alt="image-20190304110812440" title="image-20190304110812440"><br>简单介绍一下作用:</p>
<ol>
<li>vendor.js 第三方库,一般是 node_modules里面的依赖进行打包 体积最大</li>
<li>app.js 入口js打包的结果,即我们编写的所有代码都会打包进去</li>
<li>manifest.js 主要是一些异步加载的实现方法(通过建立script方式动态引入js),内容上包含异步js的文件名和路径。</li>
</ol>
<p>然后我们实现一下路由懒加载 <code>@/router/router.js</code></p>
<pre><code class="js">import Vue from 'vue'
import Router from 'vue-router'
// import Home from '@/views/Home/Home.vue'
// import Login from '@/views/Login/Login.vue'
// import Test from '@/views/Test/Test.vue'
// 懒加载方式
const Home=()=>import('@/views/Home/Home.vue')
const Login=()=>import('@/views/Login/Login.vue')
const Test=()=>import('@/views/Test/Test.vue')
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path:'/login',
name:'Login',
component:Login
},
{
path:'/test',
name:'Test',
component:Test
}
]
})
</code></pre>
<p>懒加载只是改变了一下组件的引用方式,由原来的直接引入变成异步引入,当我们访问对应的路由path时,才会加载相应的路由组件。</p>
<p>配置完成后再执行一次打包,结果如下:<br><img src="/img/remote/1460000018619588" alt="image-20190304112607087" title="image-20190304112607087"></p>
<p>我们会发现目录中多出来3个js文件,并且<code>app.js</code>文件变小了。这说明配置了懒加载之后,app.js中其他组件的内容被抽离出来,分配到各自的js文件中。配置懒加载之后,刚开始打开页面只会加载app.js文件,只有在用户点击相应路由时,才会加载对应的js代码。当我们的业务代码非常多时,懒加载是个很好的选择。</p>
<h4>3.3 配置history模式</h4>
<blockquote>官方文档:<a href="https://link.segmentfault.com/?enc=lCkxwWEOkTk5WfKCLWoQjw%3D%3D.0Aa1Asz4Hn9zTLZHNHGi5Ypiovd0qwgCA0gu%2BEyBqgnKa1sGxjRPjrXttam2gTbtPuTrRh6CzbYFR%2FsDf1syjw%3D%3D" rel="nofollow">https://router.vuejs.org/zh/g...</a>
</blockquote>
<p>配置history模式有两个原因,一是因为hash模式看很丑,二是因为预加载要用到History模式,配置非常简单,只需要配置属性<code>mode</code>的值为'history'</p>
<pre><code class="js">const router = new VueRouter({
mode: 'history',
routes: [...]
})</code></pre>
<p>不过这种方式需要后台的支持,当匹配不到url时,返回url/index.html页面</p>
<p>nginx配置如下</p>
<pre><code class="js">location / {
try_files $uri /index.html;
}</code></pre>
<h3>4. 权限管理</h3>
<blockquote>参考链接:<p>json web token入门教程 <a href="https://link.segmentfault.com/?enc=rrRm21HhTpfPRri7Lv%2FD1w%3D%3D.Lg1VDkyITvCk8p3jIWN2CkIoU7UX%2Fk3K7Z%2B7SHJciaxLZlNaFNrcL%2BFSfAUkXcrzrB4pnG2rWEtVkcXS5apwyfrMV%2BbnwDxzmbqTeclSgII%3D" rel="nofollow">http://www.ruanyifeng.com/blo...</a></p>
<p>jwt官网 <a href="https://link.segmentfault.com/?enc=dRM0%2BkBcXyBFtG63tXpXAA%3D%3D.r6YCe9DSc%2BvVIFO1UT2qcA%3D%3D" rel="nofollow">https://jwt.io/</a></p>
</blockquote>
<h4>4.1 token验证</h4>
<p>我们通过jwt进行用户认证,jwt的原理是:服务器认证以后,生成一个json对象,发回给用户.</p>
<pre><code class="js">{
"id":"001",
"姓名":"小明",
"角色":"管理员",
"到期时间":"2019年3月3日12时30分"
}</code></pre>
<p>以后用户与服务端通信的时候,都要发回这个json对象。服务器完全靠这个对象认定用户身份(一般是通过这个对象的中id去数据库请求数据)。为了防止用户篡改数据,服务器会在生成这个对象的时候,加上签名。就像这种形式:</p>
<p><code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c</code></p>
<p>关于JWT保存更新的业务流程如下:</p>
<ol>
<li>保存:登录后保存token</li>
<li>添加:每次发送请求之前检查token是否存在,存在,添加到请求头中,发送请求</li>
<li>更新:每次发送请求服务器返回数据之后更新token</li>
</ol>
<p>主要逻辑包括:</p>
<ol>
<li>登录之后,在<code>localStorage</code>中保存token</li>
<li>每次发送请求之前,使用axios请求拦截器将token放到请求头中</li>
<li>每次发送请求服务器返回数据之后在axios的响应拦截器中更新token</li>
</ol>
<pre><code class="js">//1.登录之后保存token login.vue
async login(){
const data = await LOGIN({ name: this.user,pass: this.pass })
//保存token
localStorage.setItem('token',data.token)
//查看是否保存成功
console.log(localStorage.getItem('token'))
}
</code></pre>
<pre><code class="js">//每次发送请求之前,讲token放到请求头中 api/http.js
//---使用axios的请求拦截器,每次发送请求之前拦截一下
instance.interceptors.request.use(function (config) {
// 给头添加token
if (localStorage.getItem('token')){//存在token,加入头
config.headers.authorization=localStorage.getItem('token')
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
//完成之后,记得发送一个请求,看看是否正确添加token
//---响应拦截器,服务器响应后先到达这里
instance.interceptors.response.use(function (response) {
if(response.data.code=='2000'){//成功响应,更新token
if(response.data.token){
localStorage.setItem('token',response.data.token)
}
}else{
//错误处理 根据不同的状态码,进行错误处理
}
return response.data;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});</code></pre>
<h4>4.2 对页面的访问权限</h4>
<p>除了对token的操作,我们还要判断用户有没有权限访问这个页面(有些页面是用户必须登录才能访问的),具体配置要使用Vue Router的导航守卫</p>
<blockquote>参考链接:<a href="https://link.segmentfault.com/?enc=wet%2FvatbXDuehylf%2F6SrNQ%3D%3D.nQHn%2FJmXRONXvkjq%2FEIkQhjUDhyCIFg5aCv3FQaehIVS9z0D%2Ba%2F9vSW3V8SGVxw6jtSCBHSx%2Ff0ZJNXlFa2wjT25Jk7KSzYAMEG0pIf7sAk%3D" rel="nofollow">https://router.vuejs.org/zh/g...</a>
</blockquote>
<p>在全局前置守卫中进行验证</p>
<pre><code class="js">
//在router/index.js进行配置
//在每次进行路由跳转之前进行
router.beforeEach((to,from,next)=>{//增加登录验证
const isLogin=localStorage.getItem('token')?true:false;
if(to.path=='/login'){//如果是登录页面,不需要token
next();
}else{//如果不是登录页面就要判断是否登录
isLogin?next():next('/login');
}
})</code></pre>
<h3>5. 将界面交给第三方UI库</h3>
<blockquote>iview官网:<a href="https://link.segmentfault.com/?enc=n2%2BA1VybSsOxaqQOBF3NAw%3D%3D.GLi%2FAyHFVE0wcyhKsLlq3mrmjAtkXH%2FV3QnhqHSgbl4%3D" rel="nofollow">https://www.iviewui.com/</a>
</blockquote>
<p>为节省开发时间,我们往往会使用一些第三方ui库,比如iview elementui等</p>
<p>我们在这里只介绍iview,其他ui库大同小异</p>
<h4>iview的安装与引入</h4>
<h5>安装</h5>
<pre><code class="js">cnpm i iview --save</code></pre>
<h5>按需引入组件</h5>
<p>官网说,需要下载插件才能按需引入,<a href="https://link.segmentfault.com/?enc=cCownRxZQIMO9hamgexI2g%3D%3D.YP8YNFgRG1N%2Fei4lBd9YcflqAswMfNV31ruJxmFMsppDLjpnDfXrhFDVyAeN4CY7" rel="nofollow">官网说明</a>,但是不下好像也可以正常引入</p>
<pre><code class="js">//在main.js文件中引入项目需要的组件
import {Button,Table,Message} from 'iview'
//然后注册组件
Vue.component('Button',Button)
Vue.component('Table',Table)
Vue.component('Message',Message)</code></pre>
<p>这样注册的话太繁琐,所以需要优化一下</p>
<pre><code class="js">//main.js
import {Button,Table,Message} from 'iview'
const iviewComs={Button,Table,Message}
Object.keys(iviewComs).forEach(key=>{Vue.component(key,component[key])})</code></pre>
<p>代码都写在main.js中显得太拥挤,我们可以把代码拿出去,写成一个插件</p>
<p>我们在components文件夹中新建一个文件<code>iview-coms</code>,用来放iview中引入的组件</p>
<pre><code class="js">//components/iview-coms.js
import {Button,Table,Message} from 'iview'
const components={Button,Table,Message}
const install = function(Vue, opts = {}){
Object.keys(components).forEach(key=>{
Vue.component(key,components[key])
})
}
export default install
</code></pre>
<p>然后在main.js中引入,<code>use</code>这个插件</p>
<pre><code class="js">import iviewComs from './components/iview-coms'
Vue.use(iviewComs)</code></pre>
<p>ok了,接下来看自定义主题</p>
<h5>自定义主题</h5>
<p>官网链接:<a href="https://link.segmentfault.com/?enc=xfu57L%2F58Si7%2BRrZQK4zeg%3D%3D.Q2akU3BeGRgvIbJCzKBEG8aZCOgFi110GL8Xi1sGKM2YAnmQsxqUNAxg4pyEZIBs" rel="nofollow">https://www.iviewui.com/docs/...</a></p>
<p>原理很简单,就是把ivew的less文件引入,并且覆盖掉,然后在main.js文件中引入自己的less文件</p>
<p>首先,我们需要下载解析less文件的loader ,<code>less</code>和<code>less-loader</code>,这里有个坑,下载less的时候要下载3版本以下的,不然会报一堆错误</p>
<pre><code class="shell">cnpm i less@2.7.2 less-loader -D</code></pre>
<p>下载完就ok了,不需要在webpack中进行配置,因为已经配置好了</p>
<p>然后,在assets/styles/base.less(没有需要自己新建)中,引入iview的样式文件,并且覆盖掉</p>
<p>默认变量列表:<a href="https://link.segmentfault.com/?enc=EvSBAD6g3O13NhnZAmbO0Q%3D%3D.4HpI7XyXMxJdjTwBxTHo7lfNCkFbsVHRaCAc%2FEphJsMedhmTQUO%2FeaoX%2B%2FEW%2BOkVMM1WX4JhG%2BbHJq7qdSCXwA%3D%3D" rel="nofollow">https://github.com/iview/ivie...</a></p>
<pre><code class="js">//assets/styles/base.less
//------ 引入iview样式
@import '~iview/src/styles/index.less';
//------ 覆盖iview的样式
@primary-color: #E91E63;
@error-color : #FF3300;</code></pre>
<p>最后在main.js引入该less文件</p>
<pre><code class="js">//main.js
import './assets/styles/base.less'</code></pre>
<p>此时,引入的组件就可以在.vue文件中使用了,看一下效果:</p>
<p><img src="/img/remote/1460000018619589?w=603&h=283" alt="image-20190307150805499" title="image-20190307150805499"></p>
<p>ok了。最后还要补充一下,在项目开发过程中,不可避免的要覆盖iview默认的样式,我们分为两种情况,一种是全局覆盖,一种是局部覆盖。</p>
<p>全局覆盖的话我们要新建一个less文件,比如叫<code>cover-iview.less</code>所有覆盖iview样式的代码都放在这里,然后在base.less中引入这个文件。</p>
<p>局部覆盖的话要注意不要影响到别的样式,所以要充分利用less的作用域,例如我们只需要改home页面下的iview按钮样式,我们可以这样:</p>
<pre><code class="less">.home{
.ivu-btn{
}
}</code></pre>
<h3>6.开发中注意问题</h3>
<h4>6.1编写自己的工具库插件</h4>
<blockquote>参考文档:<p>vue插件说明:<a href="https://link.segmentfault.com/?enc=GeGaoVN3LajcNWYXxs3gig%3D%3D.wpcDX0r7j9R0P0LFYofR5fOJ9prMBtifnlDMy7cT3op5j3uPLEEUnGdlkCXtb%2FZk" rel="nofollow">https://cn.vuejs.org/v2/guide...</a></p>
</blockquote>
<p>项目中往往会使用一些通用的函数,比如获取当前时间、时间格式转化,防抖,节流等,我们可以把这个公用的部分封装成插件,在main.js中引入。</p>
<p>首先,在src目录下新建<code>utils</code>文件夹,在里面新建<code>index.js</code>,<code>utils.js</code>文件</p>
<p>我们在<code>utils.js</code>中编写自己的工具库,然后导出</p>
<pre><code class="js">class Utils{
constructor(){
this.d=new Date();//date对象
this.instance=null;
}
static getInstance(){//单例模式
if(!this.instance){
this.instance = new Utils();
}
return this.instance;
}
pick(obj,arr){//pick({ a: 1, b: '2', 'c': 3 }, ['a', 'c']) =>{a:1,c:3}
return arr.reduce((acc,curr)=>{
return (curr in obj && (acc[curr] = obj[curr]), acc)
},{})
}
dateFormat(datetime,pattern=""){
let vWeek = ["星期天","星期一","星期二","星期三","星期四","星期五","星期六"];
let dt=new Date(datetime);
let y=dt.getFullYear();
let m=(dt.getMonth()+1).toString().padStart(2,'0');
let d=dt.getDate().toString().padStart(2,'0');
let hh=dt.getHours().toString().padStart(2,'0');
let mm=dt.getMinutes().toString().padStart(2,'0');
let ss=dt.getSeconds().toString().padStart(2,'0');
let vWeek_s = dt.getDay();//星期
if(pattern.toLowerCase() === 'yyyy-mm-dd'){
return `${y}-${m}-${d}`
}else if(pattern.toLowerCase() === 'mm-dd'){
return `${m}-${d}`
}else if(pattern.toLowerCase() === 'yyyymmddhhmmss'){
return `${y}${m}${d}${hh}${mm}${ss}`
}else {
return `${y}-${m}-${d} ${hh}:${mm}:${ss} ${vWeek[vWeek_s]}`
}
}
}
const UTIL = Utils.getInstance();
// console.log(UTIL.dateFormat(new Date(),'yyyymmddhhmmss')) //=>20190312110722
// console.log(UTIL.dateFormat(new Date()))//=>2019-03-12 11:07:22 星期二
// console.log(UTIL.pick({ a: 1, b: '2', 'c': 3 }, ['a', 'c']))//=>{a:1,c:3}
export default UTIL;
</code></pre>
<p>然后在index.js中编写插件,导出</p>
<pre><code class="js">//utils/index.js
import UTIL from './utils.js'
const UtilPlugin={}
UtilPlugin.install=function(Vue,options){//插件必须有install方法,接受两个参数,一个是Vue构造器,一个是参数
Vue.prototype.$utils=UTIL//在vue prototype上添加实例方法
}
export default UtilPlugin
</code></pre>
<p>最后在main.js中引入并use插件</p>
<pre><code class="js">// utils
import Util from './utils/index'
Vue.use(Util)
console.log(Vue.prototype.$util)//打印下是否引入成功</code></pre>
<p>之后就可以在组件中通过使用<code>this.$utils</code>调用方法了</p>
<h3>7. 兼容性处理</h3>
<blockquote>我们的目标是兼容到ie9,对ie8及以下的浏览器做相应的跳转处理(跳转到浏览器下载界面)<p>兼容性对一个程序来说是非常重要的,兼容性测试越早越好</p>
</blockquote>
<p><img src="/img/remote/1460000018619590" alt="image-20190307151841810" title="image-20190307151841810"></p>
<h4>7.1 对ie8及以下浏览器的跳转处理</h4>
<p>在项目根目录下中的html中head中加入下面代码</p>
<pre><code class="html"><!--[if lte IE 8]><script>window.location.href="https://support.dmeng.net/upgrade-your-browser.html?referrer="+encodeURIComponent(window.location.href);</script><![endif]--></code></pre>
<p>目的是检测ie浏览器的版本,如果低于<=ie8,就跳转到下面这个页面</p>
<p><img src="/img/remote/1460000018619591" alt="image-20190307153138889" title="image-20190307153138889"></p>
<h4>7.2 兼容ie9</h4>
<blockquote>参考链接:<a href="https://link.segmentfault.com/?enc=SapQCkAz0h1EqJpebEGcHw%3D%3D.SLueA%2F3Lq3loECFjT8tzuknV0FtN%2FYk4p0gD1wBDZvIsFfiy4HRpY0rfn%2Ft%2FjbTF" rel="nofollow">https://juejin.im/post/5b2868...</a>
</blockquote>
<h5>7.2.1 ES6兼容</h5>
<p>我们把浏览器调到ie9,然后看控制台报错信息</p>
<p><img src="/img/remote/1460000018619592" alt="image-20190307154807782" title="image-20190307154807782"></p>
<p>报这个错的原因是es6的新对象,新表达式,ie9不支持,为解决这个问题,我们需要引入<code>babel-polyfill</code></p>
<pre><code class="js">cnpm i babel-polyfill -D</code></pre>
<p>安装完成之后,在main.js文件中引入</p>
<pre><code class="js">import 'babel-polyfill'</code></pre>
<p>在项目使用 <code>vue-cli</code> 生成的代码中,根目录有一个 <code>.babelrc</code> 文件,这是项目使用 babel 的配置文件。在默认生成的模板内容中,增加 <code>"useBuiltIns": "entry"</code> 的设置内容,这是一个指定哪些内容需要被 polyfill(兼容) 的设置</p>
<p>useBuiltIns 有三个设置选项</p>
<ul>
<li>
<code>false</code> - 不做任何操作</li>
<li>
<code>entry</code> - 根据浏览器版本的支持,将 polyfill 需求拆分引入,仅引入有浏览器不支持的polyfill</li>
<li>
<code>usage</code> - 检测代码中 <code>ES6/7/8</code> 等的使用情况,仅仅加载代码中用到的 polyfill</li>
</ul>
<h5>7.2.2建立自己的polyfill</h5>
<p>加入这些代码后,工程中大部分代码已可以兼容到ie9版本,但还是会有少部分不兼容的特性,例如<code>requestAnimationFrame</code>、<code>classList</code>等。对于这些内容,我们需要自己定义polyfill来解决,在src目录下新建一个文件夹polyfill,然后在polyfill文件夹下面建一个polyfill.js,我们在polyfill.js中加入我们的兼容代码</p>
<p>然后在main.js中引入这个文件</p>
<pre><code class="js">import './polyfill/polyfill'</code></pre>
<p>解决兼容方式的正确姿势是:<code>拿到ie9浏览器下的报错信息,去goole或者baidu搜索,得到polyfill,然后加入到自己的polyfill.js文件中</code></p>
<h2>三、优化</h2>
<h3>1. webpack3.x优化打包速度</h3>
<p>我们执行一下<code>npm run build</code>,结果如下:</p>
<p><img src="/img/remote/1460000018619593" alt="image-20190307161705933" title="image-20190307161705933"></p>
<p>整个打包过程花了32s左右,现在我们的项目只是引入了相关的依赖,一些业务逻辑还没有写,打包速度就那么慢了,等到我们写完整个项目,打包速度还会继续变长,所以我们需要优化一下。</p>
<blockquote>优化打包速度,我们修改的主要是<code>webpack.prod.conf.js</code>文件</blockquote>
<h5>替换代码压缩工具</h5>
<p>Webpack 默认提供的 UglifyJS 插件,由于采用单线程压缩,速度慢 ;</p>
<p><a href="https://link.segmentfault.com/?enc=SGtFFow2iRPqm7VHk9jd6Q%3D%3D.EP3aFGeGpJNtNCd3JDTxO6eEv0ZlHskfQDUoyWjiW149WLEn6roRETgxUtA%2BMpxp%2Bkbf4EV0SnyGw8jI08EYvA%3D%3D" rel="nofollow">webpack-parallel-uglify-plugin</a> 插件可以并行运行 UglifyJS 插件,更加充分而合理的使用 CPU 资源,这可以大大减少的构建时间;</p>
<pre><code class="js">//安装
cnpm i webpack-parallel-uglify-plugin -D</code></pre>
<pre><code class="js">//配置 webpack.prod.conf.js
//首先删除项目中的 UglifyJsPlugin插件及配置,第二次打包时提高速度,要把.cache文件加入到gitignore中
// new webpack.optimize.UglifyJsPlugin({
// compress: {
// warnings: false,
// drop_console: true
// },
// sourceMap: true
// }),
//然后引入并使用我们刚才装的插件
</code></pre>
<p>==注意:版本控制工具提交时,要忽略.<code>cache</code>文件==</p>
<p>配置完后我们执行<code>npm run build</code>,发现打包速度降到了23s</p>
<p><img src="/img/remote/1460000018619594" alt="image-20190307162957635" title="image-20190307162957635"></p>
<p>再执行一次<code>npm run build</code>,发现打包速度降到了12s</p>
<p><img src="/img/remote/1460000018619595" alt="image-20190307164513348" title="image-20190307164513348"></p>
<p>时间降低那么多是因为文件没有改动,直接利用了缓存中的js文件</p>
<h5>happypack开启多核构建项目</h5>
<p>一般node.js是单线程执行编译,而happypack则是启动node的多线程进行构建,大大提高了构建速度。</p>
<p>首先安装,</p>
<p>修改<code>webpack.base.conf.js</code></p>
<pre><code class="js">const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
...
...
// 增加plugins
plugins: [
new HappyPack({
id: 'happy-babel-js',
loaders: ['babel-loader?cacheDirectory=true'],
threadPool: happyThreadPool,
})
]
...
...
// 修改对应loader
{
test: /\.js$/,
loader: 'happypack/loader?id=happy-babel-js',
include: [resolve('src'), resolve('test')],
}</code></pre>
<p>配置完成,执行<code>npm run build</code></p>
<p><img src="/img/remote/1460000018619596" alt="image-20190307165549102" title="image-20190307165549102"></p>
<p>what??并没有提高速度 不要用这个鬼东西了</p>
<h5>hardSourceWebpackPlugin节省70%的时间</h5>
<blockquote><a href="https://link.segmentfault.com/?enc=kMdQ%2Btnh6KO4x2%2FiAWAcqw%3D%3D.PSjs5i9mxI4Vpe1DlMUEnNDGA4b%2BCNEXiEmYFW28XIfr2CQRyWovT6ErevO2KdIdlo0S5bavtc2xZdmD6%2FUI%2FQ%3D%3D" rel="nofollow">https://github.com/mzgoddard/...</a></blockquote>
<pre><code class="shell">#安装
cnpm install --save-dev hard-source-webpack-plugin</code></pre>
<p>使用,在webpack.prod.conf.js中引入并使用</p>
<pre><code class="js">const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
context: // ...
entry: // ...
output: // ...
plugins: [
new HardSourceWebpackPlugin()
]
}</code></pre>
<p>结果:</p>
<p><img src="/img/remote/1460000018619597" alt="image-20190307171310562" title="image-20190307171310562"></p>
<p>注:要第二次打包才生效</p>
<p>总结下,使用了三个插件,我们的打包速度从30s降低到4s,awesome!</p>
<h3>2. webpack3.x优化首屏加载速度</h3>
<p>首先要说明一下,首屏加载速度优化针对的是打包后dist文件。我们如果要在本地进行测试的话,需要本地有个服务器,我们在这里使用nginx。</p>
<h4>2.1本地安装nginx</h4>
<blockquote>下载地址: <a href="https://link.segmentfault.com/?enc=vluXWzKwzh3bB9nQ30NBhw%3D%3D.LhBLedJSx4ovjmQPlJgfxMJTIFOeMU9R2%2FseN5WvGHGK7c8BheGaqZkua0kaqiif" rel="nofollow">http://nginx.org/en/download....</a>
</blockquote>
<p>在官网上找到自己系统适合的nginx版本,下载到本地</p>
<h5>2.1.1window安装</h5>
<ol>
<li>解压文件</li>
<li>双击运行nginx.exe,在任务管理器中出现nginx的进程,则表示安装成功</li>
</ol>
<h5>2.1.2 mac/linux安装</h5>
<pre><code class="js">#1.解压文件
tar -xzf nginx-1.14.0.tar.gz #mac可以使用解压缩工具解压,不必用命令行
#2. 配置安装路径 --prefix指定安装路径 假设我要装到/usr/local/nginx文件夹中
./configure --prefix=/Users/best9/local/nginx
#编译
make
##安装
make install</code></pre>
<p>安装完成后进入到<code>—prefix</code>指定的文件夹中,执行<code>ll</code>,会发现文件夹下有以下目录</p>
<p><img src="/img/remote/1460000018619598" alt="image-20190308144717721" title="image-20190308144717721"></p>
<p>我们要关心就是我上面标出来的三个目录</p>
<p>进到sbin目录中,启动nginx程序</p>
<p><img src="/img/remote/1460000018619599" alt="image-20190308145219037" title="image-20190308145219037"></p>
<pre><code class="shell">cd sbin
#需要使用root权限,否则会报错 报错信息可以在日志中查看到,错误日志目录 /logs/error.log
sudo ./nginx </code></pre>
<p>正常的话,nginx会默认在localhost:80端口启动,在浏览器访问<code>localhost</code>,就会显示默认界面</p>
<p><img src="/img/remote/1460000018619600" alt="image-20190308145304691" title="image-20190308145304691"></p>
<p>如果电脑的80端口被占用的话,在<code>conf/nginx.conf</code>文件中修改端口</p>
<h4>2.2 nginx常用命令</h4>
<p>nginx使用-s发送信号操作运行中的进程,常用命令如下:</p>
<p>注意:使用命令需要在<code>sbin</code>目录下</p>
<pre><code class="shell">#启动nginx
./nginx
#立即停止服务 -s stop
./nginx -s stop
#优雅地停止服务 -s quit
./nginx -s quit
#重启服务 -s reload
./nginx -s reload</code></pre>
<h4>2.3 nginx配置静态文件服务器</h4>
<blockquote>我们在这里使用nginx配置一个最简单的静态文件服务器,更复杂的配置稍后再讲</blockquote>
<p>nginx的配置文件地址:<code>conf/nginx.conf</code></p>
<p>使用vim或者其他编辑器打开该文件,修改配置文件第43-45行:</p>
<p><code>vim conf/nginx.conf</code></p>
<p><img src="/img/remote/1460000018619601" alt="image-20190308150902046" title="image-20190308150902046"></p>
<pre><code class="js">location / {
alias /Users/best9/github/vue2_template/dist; #访问/相当于访问alias配置的目录
}</code></pre>
<p>配置完成后保存,然后重启服务</p>
<p><code>sudo ./sbin/nginx -s reload</code> 要使用root权限重启</p>
<p>打开浏览器访问localhost</p>
<p><img src="/img/remote/1460000018619602" alt="image-20190308151058193" title="image-20190308151058193"></p>
<p>因为没有登录,会自动跳转到登录界面</p>
<p>到这里静态文件服务器就配置好了,但我们刷新下页面,会报错404</p>
<p><img src="/img/remote/1460000018619603" alt="image-20190308151213416" title="image-20190308151213416"></p>
<p>这是因为我们使用了vue router的history模式,我们需要在nginx中加入以下配置</p>
<p><img src="/img/remote/1460000018619604" alt="image-20190308151523068" title="image-20190308151523068"></p>
<pre><code class="nginx">location / {
try_files $uri $uri/ /index.html;
}</code></pre>
<p>然后重启nginx,再刷新页面就没问题了</p>
<h4>2.4 优化首屏加载速度</h4>
<p>以上步骤就绪后,我们就可以来优化加载速度了</p>
<p>打开chrome的devTools面板,切换到<code>Network</code>,禁用浏览器缓存,刷新测试下加载速度,发现整个应用加载大约需要1.97s,如下图:</p>
<p><img src="/img/remote/1460000018619605?w=1879&h=533" alt="image-20190308152853851" title="image-20190308152853851"></p>
<p>把网络环境切换到<code>Fast 3G</code>,再测试一次,发现加载用了7.56s,白屏时间6.89s</p>
<p><img src="/img/remote/1460000018619606" alt="image-20190308165101522" title="image-20190308165101522"></p>
<p>我们使用预渲染插件进行优化</p>
<h5>2.4.1 预渲染</h5>
<blockquote>使用插件:prerender-spa-plugin<p>参考链接:<a href="https://link.segmentfault.com/?enc=J%2FfyTyYfImrIP68USoxk8w%3D%3D.TXNFsC59bojB2R0QUhsizy8STelPC9pV%2Bh97ZTgToXDmTySaq9KJeDlQkLhOIrtQ" rel="nofollow">https://juejin.im/post/59d49d...</a></p>
</blockquote>
<p>首先,安装 <code>prerender-spa-plugin</code>,安装时件略长,因为其依赖了 <code>phantomjs</code></p>
<pre><code>cnpm install prerender-spa-plugin --save-dev</code></pre>
<p>我们只在生产环境中进行预渲染,修改<code> build/webpack.prod.conf.js</code>,在配置插件的地方加入如下代码。</p>
<pre><code class="js">//引入 预渲染插件
const PrerenderSpaP=require('prerender-spa-plugin')
//在plugins中配置
new PrerenderSpaP(
// 输出目录的绝对路径
path.join(__dirname,'../dist'),
//预渲染路由
['/home','/login']
)
</code></pre>
<p>再次执行打包,然后再进行测试:</p>
<p><img src="/img/remote/1460000018619607" alt="image-20190308165347855" title="image-20190308165347855"></p>
<p>发现白屏时间为4.10s,在弱网环境下,使用预渲染,大约能缩减2.5秒的白屏时间</p>
<h6>预渲染注意事项</h6>
<ul>
<li>预渲染的路由不能是动态加载的,否则会报webpackJsonp is not define的错误,要想解决这个错误,可以看这里 <a href="https://link.segmentfault.com/?enc=vrBSzSNyugPm54RPuaWPHA%3D%3D.Wn%2BEXBEkcFgaPXrHmRBuUcOmgh%2BxRxoKdH3oq4COWcz8jiyH0TWxB6JKeHD35WZWArz0AOlsepsMMLrEwUP7Xw%3D%3D" rel="nofollow">https://juejin.im/entry/5911a...</a>
</li>
<li>预渲染的路由不能是需要权限才能访问的页面。预渲染的机制是在本地跑一个chromium浏览器,然后去爬取你预渲染页面的Html,如果你的页面需要权限(登录)才能进入,就爬不到,也不会报错,最终只会渲染不需要权限的页面</li>
</ul>
<p>举个例子:</p>
<p>插件配置如下:</p>
<pre><code class="js">new PrerenderPlugin({
staticDir:path.join(__dirname,'../dist')
routes:['/','/about','/login']
})</code></pre>
<p>路由配置如下:</p>
<p><img src="/img/remote/1460000018619608" alt="image-20190314164834830" title="image-20190314164834830"></p>
<h5>2.4.2 配置gzip压缩</h5>
<blockquote>gzip官方文档 <a href="https://link.segmentfault.com/?enc=p4YO6EBcoCkMYTopPbwQlA%3D%3D.vUFL%2BBBnKTBDzYRKBrP%2BaS8ELJAQaPLPV5u0XwLmfYhAhU2A%2ByahX2sNCoKtjRBOAvUr%2BrdRDEbCPiIgvCu8xA%3D%3D" rel="nofollow">http://nginx.org/en/docs/http...</a>
</blockquote>
<p>nginx默认是关闭gzip的,我们需要自己打开,并进行一些配置:</p>
<p><img src="/img/remote/1460000018619609" alt="image-20190311103518953" title="image-20190311103518953"></p>
<pre><code class="nginx">gzip:on; #打开gzip,关闭为off
gzip_min_length 1; #小于gzip_min_length,不进行压缩(默认单位为byte)
gzip_comp_level 2; #压缩级别
gzip_types text/plain text/css application/javascript text/javascript image/jpeg image/gif image/png;#指定类型进行gzip压缩</code></pre>
<p>配置完成后,我们再测试一下加载速度:</p>
<p><img src="/img/remote/1460000018619610" alt="image-20190311103446777" title="image-20190311103446777"></p>
<p>发现白屏时间为1.95s,加载文件的体积也变小了</p>
<h2>四、部署</h2>
<h4>1. nginx配置反向代理</h4>
<blockquote>我们要在本地部署测试,所以后台的地址是127.0.0.1:22222</blockquote>
<p>项目开发完成后需要部署到服务器,因为是前后端分离,所以前端的应用部署到nginx,后端的应用部署到自己对应的服务器,所以我们需要配置一下,把后端的服务器变成上游服务,nginx做反向代理服务器</p>
<blockquote>反向代理:服务器根据客户端的请求,从其关系的一组或多组后端服务器上获取资源,然后将这些资源返回给客户端。</blockquote>
<p>由于上游服务器(后台服务器)要处理非常复杂的逻辑,所以性能不怎么样,我们使用nginx作为反向代理服务器后,可以将请求按照负载均衡算法代理给多台上游服务器。配置如下:</p>
<p><img src="/img/remote/1460000018619611" alt="image-20190311112112788" title="image-20190311112112788"></p>
<p>以上配置是将所有的请求转发给上游服务器,但如果我们只想将动态请求转发给上游服务器,静态资源由nginx自己处理,就可以这样做:</p>
<p>判断是否是后台api(根据location的匹配规则),如果是的话,就进行转发</p>
<p>匹配规则看这里:<a href="https://link.segmentfault.com/?enc=nL2fH8NTRmb%2FMeyUJmIW6w%3D%3D.nQqAUyInSoCqQ7T%2FGkadbSZs%2BAdFKi2NE6kev%2FLUjDIJbludy89%2FX2f7blNyQq6WfVkgv1hS5vJ6MyKa8V6UDXzoW6BF%2B7rZtP4FFKQDkas%3D" rel="nofollow">https://stackoverflow.com/que...</a></p>
<pre><code class="nginx">upstream local{
server 127.0.0.1:22222; #假设在本地部署
}
server{
listen:80;
server_name localhost;
location ~ /api/ { #以`/api/`开头的uri就行转发,否则不转发 ~代表正则表达式匹配
proxy_set_header: Host $host;
proxy_set_header: X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://local;
}
location / {
#... alias index等配置
}
}
</code></pre>
<p><strong>这里需要注意一个问题</strong>:proxy_pass是转发请求的模块,当你访问<code>localhost:80/api/users/login</code>时,会被转发到<code>local</code>的地址,即<code>127.0.0.1:22222/api/users/login</code>,所以开发环境下访问后台接口的URI要写你部署到nginx的URI,而不是真正的后台地址(因为被转发了)</p>
<p>前端配置</p>
<pre><code class="js">//apis/api.config.js
//判断是否是生产环境
var isPro = process.env.NODE_ENV=== 'production'
module.exports = {
baseUrl: isPro ? 'http://localhost:80' : '/apis'//生产环境下的baseURl是nginx的hoost:port
}
</code></pre>
<h4>2. 持续部署</h4>
<p>项目做完需要发布到服务器,但每次手动打包,然后ftp传上去的话就太麻烦了,所以我们的需求是:git或者svn提交后,自动打包发布到服务器。使用的工具是jenkins.</p>
<blockquote>参考文档:<a href="https://link.segmentfault.com/?enc=LD2BXgFWdPue7%2FAnTtqNBA%3D%3D.bZ6tj9tEl%2FZGWfEO5x741kTOcEu%2BURmrROsqWGkIidYKXUNP25n93ewTOMPZHbZm" rel="nofollow">https://juejin.im/post/5ad198...</a><p>官网:<a href="https://link.segmentfault.com/?enc=jXdgCzqutu7iRsijVBDygQ%3D%3D.0DO9g3krlz%2BxmQHS3Hw8%2BGYK3Q5k6AXz8zwl14%2F8bFg%3D" rel="nofollow">https://jenkins.io/</a></p>
</blockquote>
<h5>jenkins安装与启动</h5>
<blockquote>jenkins一般情况下会装在服务器,但如果是同一个局域网的话,装在本机也可以</blockquote>
<p>linux:</p>
<ol>
<li><a href="https://link.segmentfault.com/?enc=Tutsf8FOmRdXJz1gEWcj1A%3D%3D.K0Eamp1cfr1t7Oq4grvc0EqCPi9gjTg2KLzyqZ4LQB6BEkpYzWurAbD2o5ZiF53j7glwQVDlFumIEPLZO9X7%2BEtg%2BJ4g6KH1AYe1tUNoVAY%3D" rel="nofollow">https://blog.csdn.net/fenglai...</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=LxOB5Ldu5W5j%2BsHvitCjyQ%3D%3D.RiZhWcUhSA3AQJzXe4qQkEjeaM2oDmWpY5iWYJkMyJev1zmBHHkt3%2FNk%2FAaLSEdL" rel="nofollow">https://www.jianshu.com/p/8a7...</a> (centos)</li>
<li>配置文件地址 /etc/sysconfig/jenkins</li>
<li>工作空间 /var/lib/jenkins</li>
</ol>
<p>windows下:</p>
<ol>
<li>从Jenkins官网<a href="https://link.segmentfault.com/?enc=YrcFFjsVPh%2BNdOK2ceelVA%3D%3D.Oi5yr4PWDEsVtbAInGEyldNbvgwbQI%2F4pFjOBJKjxfNaGHSwLaIqnJRaVrSLxYBApYFs3WS%2Fnc1%2BPhNRVxM0FKF%2BVmF%2ByjaFVCFdBZjhI0o%3D" rel="nofollow">下载</a>最新war文件。</li>
<li>运行<code>java -jar jenkins.war</code>即可。</li>
</ol>
<p>mac:</p>
<ol>
<li>从官网<a href="https://link.segmentfault.com/?enc=RfT9%2Bnj5pl6lMMIDwXj6ag%3D%3D.5mzWaRGQFmmoLVWJR2wUrNcWarYWfV8K4BXiB9zEG0k%2FBM%2BG5y7ocJBQZH9IXk4HjEsm7h%2Btubhfr9iZ7sXomDJY%2Fq3VphTiwu7s2Qzgp7A%3D" rel="nofollow">下载</a>pkg文件</li>
<li>双击安装,安装之后自己就会启动</li>
</ol>
<h5>jenkins初始化</h5>
<ol>
<li>jenkins的默认端口是8080,启动成功后在浏览器打开。</li>
<li>进入后会让我们输管理员密码,打开网页上提示路径下的文件,复制密码粘贴输入即可。</li>
<li>然后会让安装需要的插件,此处选默认即可,等待安装完成。</li>
<li>创建一个管理员账户。</li>
<li>上面都完成后会看到这个界面。</li>
</ol>
<p><img src="/img/remote/1460000018619612" alt="image-20190314171915326" title="image-20190314171915326"></p>
<h5>创建任务</h5>
<p>在主页上点击<code>创建</code></p>
<p><img src="/img/remote/1460000018619613" alt="image-20190314172049214" title="image-20190314172049214"></p>
<p>直接点<code>保存</code>,然后去安装插件</p>
<p><img src="/img/remote/1460000018619614" alt="image-20190314172224487" title="image-20190314172224487"></p>
<h5>安装插件</h5>
<p>首先返回主页,然后点击左侧菜单 <code>系统管理</code>-><code>插件管理</code></p>
<p><img src="/img/remote/1460000018619615" alt="image-20190315091230194" title="image-20190315091230194"></p>
<p>需要安装的插件有:</p>
<ul>
<li>Generic Webhook Trigger 实现git提交触发更新功能</li>
<li>Publish Over SSH 实现服务器部署功能</li>
<li>nvm wrapper 引入node</li>
</ul>
<p>安装插件的方式:</p>
<p><img src="/img/remote/1460000018619616" alt="image-20190315091837845" title="image-20190315091837845"></p>
<p>安装完插件之后重启一下jenkins(安装完插件后,有个重启的选项,勾选即可)</p>
<h5>实现git钩子功能</h5>
<blockquote>当我们向github/码云等远程仓库push我们的代码时,jenkins能知道我们提交了代码,这是自动构建自动部署的前提,钩子的实现原理是在远端仓库上配置一个Jenkins服务器的接口地址,当本地向远端仓库发起push时,远端仓库会向配置的Jenkins服务器的接口地址发起一个带参数的请求,jenkins收到后开始工作</blockquote>
<p>打开创建的项目(进入工程->点击<code>配置</code>)</p>
<p><img src="/img/remote/1460000018619617" alt="image-20190315092840660" title="image-20190315092840660"></p>
<p>构建触发器</p>
<p>勾选 Generic Webhook Trigger</p>
<p><img src="/img/remote/1460000018619618" alt="image-20190315094851281" title="image-20190315094851281"></p>
<p>github仓库配置钩子:</p>
<p>进入github项目中该项目页面,点击<code>setting</code>-><code>webhooks</code>,添加payload URL,</p>
<p>URL格式为 <code>http://<User ID>:<API Token>@<Jenkins IP地址>:端口/generic-webhook-trigger/invoke</code> userid和api token在jenkins的<code>系统管理</code>-<code>管理用户</code>-<code>选择你的用户点进去</code>-<code>左侧设置</code>里</p>
<p><img src="/img/remote/1460000018619619" alt="image-20190315095453871" title="image-20190315095453871"></p>
<h5>实现自动化构建</h5>
<p>自动化构建:jenkins实现安装依赖,打包(npm install && npm run build),此外还可以执行一些测试行为</p>
<p>点击<code>构建环境</code>,勾选<code>nvm</code>,输入node版本</p>
<p><img src="/img/remote/1460000018619620" alt="image-20190315103055020" title="image-20190315103055020"></p>
<p>点击<code>构建</code>,选择<code>执行shell</code>,输入执行命令,多个命令使用&&分开</p>
<pre><code class="shell">npm config set registry http://registry.npm.taobao.org/ &&
npm install &&
npm run build</code></pre>
你知道前端单页面路由是怎么实现的吗?
https://segmentfault.com/a/1190000018219705
2019-02-20T16:00:56+08:00
2019-02-20T16:00:56+08:00
hyangteng
https://segmentfault.com/u/hyangteng
25
<blockquote>首先要学习一下history对象,history对象保存着用户的上网记录,从浏览器窗口打开的那一刻算起。出于安全的考虑,开发人员无法得知用户浏览过的URL。不过,借由用户访问过的页面列表,同样可以在不知道实际URL的情况下实现后退与前进</blockquote>
<h2>一、history对象的方法</h2>
<h3><code>go(Stirng|number)</code></h3>
<p>使用go方法可以在用户的历史记录中任意跳转,可以向后也可以向前。这个方法接受一个参数,表示向后或向前跳转的页面数的一个整数值。负数表示向后跳转(类似浏览器的后退按钮),正数表示向前跳转(类似浏览器的前进按钮)。来看下例子</p>
<pre><code class="js">//后退一页
history.go(-1)
//前进一页
history.go(1)
//前进两页
history.go(2)
</code></pre>
<p>也可以给go()方法船体一个字符串参数,此时浏览器会跳转到历史记录中包含改字符串的第一个位置,可能后退也可能前进,具体要看哪一个位置最近。如果历史记录中不包含该字符串,则什么都不做。例如:</p>
<pre><code class="js">//跳转到最近的wrox.com页面
history.go("wrox.com")
//跳转到最近的douban.cn页面
history.go("douban.cn")</code></pre>
<h3>
<code>back()</code>和<code>forward</code>
</h3>
<p>这两个方法可以来代替go(),模仿浏览器的后退和前进功能</p>
<p>back()相当于 go(-1) 后退一个页面</p>
<p>forward相当于go(1) 前进一个页面</p>
<p><strong>注:接下来几个方法是html5新增的方法</strong></p>
<h2>二、html5中history新增的方法</h2>
<h3><code>pushState(state,title,url)</code></h3>
<p>该方法的作用是 在历史记录中新增一条记录,改变浏览器地址栏的url,<strong>但是,不刷新页面</strong>。</p>
<p>pushState对象接受三个参数,</p>
<ul>
<li>
<code>state</code>:一个与添加的记录相关联的状态对象,主要用于<code>popstate</code>事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填<code>null</code>。</li>
<li>
<code>title</code>:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。</li>
<li>
<code>url</code>:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。</li>
</ul>
<p>举个例子,假设当前网址是hello.com/1.html,使用puchState()方法在浏览记录中添加一个新纪录</p>
<pre><code class="js">var stateObj={foo:'bar'}
history.pushState(starteObj,'','2.html')</code></pre>
<p>添加新纪录后,浏览器的地址栏立刻显示<code>`hello.com/2.html</code>,但不会跳转到2.html,也不会检查2.html是否存在,它只是成为浏览历史中的最新记录。</p>
<p>总之,pushState()方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应,使用该方法后,就可以使用history.state属性读出状态对象</p>
<pre><code class="js">var stateObj={foo:'bar'}
history.pushState(starteObj,'','2.html')
history.state //=> {foo:"bar"}</code></pre>
<p>注意:如果pushState的URL参数设置了一个新的hash值,并不会触发hashchange事件。</p>
<h3><code>replaceState(state,title,url)</code></h3>
<p>replaceState方法的作用是替换当前的历史记录,其他的都与pushState()方法一模一样。</p>
<p>假定当前网页是<code>example.com/example.html</code>。</p>
<pre><code class="js">history.pushState({page: 1}, 'title 1', '?page=1')
// URL 显示为 http://example.com/example.html?page=1
history.pushState({page: 2}, 'title 2', '?page=2');
// URL 显示为 http://example.com/example.html?page=2
history.replaceState({page: 3}, 'title 3', '?page=3');
// URL 显示为 http://example.com/example.html?page=3
history.back()
// URL 显示为 http://example.com/example.html?page=1
history.back()
// URL 显示为 http://example.com/example.html
history.go(2)
// URL 显示为 http://example.com/example.html?page=3</code></pre>
<h2>三、popstate事件</h2>
<blockquote>popstate事件是<code>window</code>对象上的事件,配合pushState()和replaceState()方法使用。当<code>同一个文档</code>(可以理解为同一个网页,不能跳转,跳转了就不是同一个网页了)的浏览历史出现变化时,就会触发popstate事件。</blockquote>
<p>上面我们说过,调用<code>pushState()</code>或者<code>replaceState()</code>方法都会改变当前的历史记录,仅仅调用<code>pushState()</code>方法或<code>replaceState()</code>方法 ,并不会触发该事件,另外一个条件是用户必须点击浏览器的倒退按钮或者前进按钮,或者使用js调用history.back()或者history.forward()等方法。</p>
<p>所以,记住popstate事件触发的条件</p>
<pre><code class="js">1. 处在同一个文档(同一个html页面)
2. 文档的浏览历史(即history对象)发生改变</code></pre>
<p>只要符合这两个条件,popstate事件就会触发</p>
<p>具体例子</p>
<pre><code class="html">//index.html
<head>
<script>
window.onpopstate=function(){
alert('location '+document.location+',state '+JSON.stringify(event.state))
}
</script>
</head>
<body>
<!--第二步 -->
<button onclick="window.history.back()">后退</button>
<button onclick="window.history.forward()">前进</button>
<!--第一步 -->
<button onclick="window.history.pushState(null,'','1.html')">pushState</button>
</body>
</code></pre>
<p>先点击pushState按钮,在点击后退按钮,就会触发popstate事件</p>
<p>再来一个例子</p>
<pre><code class="html">//index.html
<head>
<script>
window.onpopstate=function(){
alert('location '+document.location+',state '+JSON.stringify(event.state))
}
</script>
</head>
<body>
<a href="#one">#one</a>
</body>
</code></pre>
<p>直接点击<code>a</code>标签,也可以触发popstate事件</p>
<h2>四、浏览器兼容性</h2>
<p>图片来自mdn<a href="https://link.segmentfault.com/?enc=kY7x5%2B0gY%2FPAkBFiu2PVyA%3D%3D.Kz088Q1j7b9zFqkPfDi1ZS1Xu254Qqgonhbxh8AV%2Fy2RYbADU8hGdJPfH%2F0z4tpdZ4s0ZRYjmyAflvX78m%2BoVg%3D%3D" rel="nofollow">传送门</a></p>
<p><img src="https://ws3.sinaimg.cn/large/006tKfTcgy1g0bqxlo1edj31aa0siam4.jpg" alt="image-20190219150537866" title="image-20190219150537866"></p>
<h2>五、单页面路由原理</h2>
<blockquote>前端路由的本质是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新。</blockquote>
<p>目前单页面使用的路由就只有两种实现方式</p>
<ul>
<li>hash模式</li>
<li>history模式</li>
</ul>
<h3>hash模式</h3>
<p>www.test.com/##/就是Hash URL,当<code>##</code>后面的哈希值发生变化时,不会向服务器请求数据,可以通过hashchange事件来监听到URL的变化,从而进行跳转页面</p>
<p>网上偷来的一张图:</p>
<p><img src="https://ws1.sinaimg.cn/large/006tKfTcgy1g0cxkrq23gj30q60dpmxy.jpg" alt="hash" title="hash"></p>
<h3>history模式</h3>
<p>history模式相比hash模式更美观,需要用到Html5新增的几个api实现,原理如下:</p>
<p>继续偷图:</p>
<p><img src="https://ws3.sinaimg.cn/large/006tKfTcgy1g0cxnqyelij30yk0g975m.jpg" alt="history" title="history"></p>
<h2>五、实例,使用history api实现简单的单页面路由</h2>
<p>在介绍实例前先介绍下location对象,location对象提供了与当前窗口中加载的文档有关的信息。它包含以下属性:</p>
<table>
<thead><tr>
<th>属性名</th>
<th>例子</th>
<th>说明</th>
</tr></thead>
<tbody>
<tr>
<td>host</td>
<td>www.hello.com:8080</td>
<td>返回服务器名称和端口号(如果有的话)</td>
</tr>
<tr>
<td>hostname</td>
<td>www.hello.com</td>
<td>返回服务器名称,不带端口号</td>
</tr>
<tr>
<td>href</td>
<td><a href="https://link.segmentfault.com/?enc=cZQyuC3urePFIvxp8F782g%3D%3D.ichSzjwLLUYyg5tvPYZ8oOn%2FAeVfHQ1AqztNan4rXI4%3D" rel="nofollow">http://www.hello.com</a></td>
<td>返回当前加载页面的完整url</td>
</tr>
<tr>
<td>pathname</td>
<td>/user/ming</td>
<td>返回url中的目录</td>
</tr>
<tr>
<td>hash</td>
<td>#content</td>
<td>返回url中的hash,如果没有返回空字符串</td>
</tr>
<tr>
<td>search</td>
<td>?q=javascript</td>
<td>返回Url的查询字符串,这个字符串以问号开头</td>
</tr>
</tbody>
</table>
<p>我们在下方的示例中需要用到<code>pathname</code>属性拿到访问的路径</p>
<p>一个简单的history模式单页面路由实现如下:</p>
<pre><code class="js">//1. 路由规则
const routes={
'/user':user, //user是引入的视图 import user from './view/user'
'/about':about
}
//2. 路由控制类
class Router {
start() {
// 点击浏览器后退/前进按钮时会触发window.onpopstate事件, 我们在这时切换到相应页面
// https://developer.mozilla.org/en-US/docs/Web/Events/popstate
window.addEventListener('popstate', () => {
this.load(location.pathname)
})
// 打开页面时加载当前页面 在单页面入口文件中要调用start方法
this.load(location.pathname)
}
// 前往path, 变更地址栏URL, 并加载相应页面
go(path) {
// 变更地址栏URL
history.pushState({}, '', path)
// 加载页面
this.load(path)
}
// 加载path路径的页面
load(path) {
// 首页
if (path === '/') path = '/foo'
// 创建页面实例
const view = new routes[path]()
// 调用页面方法, 把页面加载到document.body中
view.mount(document.body)
}
}</code></pre>
<p>Router类的作用是控制页面根据当前Url切换</p>
<ul>
<li>
<p>start()</p>
<ul>
<li>作用1: 监听onpopstate事件,在浏览器前进或后退时加载相应的页面</li>
<li>作用2: 打开页面时加载当前页面,需要在单页面的入口文件引入,并执行</li>
</ul>
</li>
<li>
<p>go(path)</p>
<ul><li>跳转到path对应的页面</li></ul>
</li>
<li>
<p>load(path)</p>
<ul><li>加载path路径的页面</li></ul>
</li>
</ul>
<h2>参考链接</h2>
<ul>
<li>高程三 p215(history对象) p207(location对象)</li>
<li><a href="https://link.segmentfault.com/?enc=8Png2cz%2F3fb9F6sLU1zEng%3D%3D.H%2FrKua6yyzWfsHHCnPxXyqMNrkJPxOVGx8Gbj3725%2B0nXLC43iQAsOk6RWJ3zlV8LEHZj6Tywvi3RNd9lMVrfg%3D%3D" rel="nofollow">JavaScript标准参考教程-阮一峰</a></li>
<li><a href="https://link.segmentfault.com/?enc=CmceIvzL4MS4v3MUuhhaYQ%3D%3D.CyKfeXZIdt1Iw6iUhZsXnq5riPLoVdPqLHP%2BmuqFCBTWbrgIqrOe3MTjXmfbjjTEm6njshGTExXGbg1bqnWHYsHKe3J40XIA38aODbTWxxKpWJQvollJhnr8fJD1SYBO" rel="nofollow">interviewMap</a></li>
</ul>
宇宙最强vscode教程(基础篇)
https://segmentfault.com/a/1190000017949680
2019-01-19T13:30:38+08:00
2019-01-19T13:30:38+08:00
hyangteng
https://segmentfault.com/u/hyangteng
471
<blockquote>本文主要介绍vscode在工作中常用的快捷键及插件,目标在于提高工作效率<p>本文的快捷键是基于mac的,windows下的快捷键放在括号里 Cmd+Shift+P(win Ctrl+Shift+P)</p>
</blockquote>
<p>[TOC]</p>
<h2>零、快速入门</h2>
<blockquote>有经验的可以跳过<code>快速入门</code>或者大致浏览一遍</blockquote>
<h3>1. 命令面板</h3>
<blockquote>命令面板是vscode快捷键的主要交互界面,可以使用<code>f1</code>或者<code>Cmd+Shift+P</code>(win Ctrl+Shift+P)打开。</blockquote>
<p>在命令面板中你可以输入命令进行搜索(中英文都可以),然后执行。</p>
<p>命名面板中可以执行各种命令,包括编辑器自带的功能和<code>插件</code>提供的功能。</p>
<p>所以一定要记住它的快捷键<code>Cmd+Shift+P</code></p>
<p><img src="/img/remote/1460000017956978?w=2036&h=1012" alt="image-20190120143658078" title="image-20190120143658078"></p>
<h3>2. 界面介绍</h3>
<blockquote>刚上手使用vscode时,建议要先把它当做一个文件编辑器(可以打字然后保存),等到有了一定经验再去熟悉那些快捷键</blockquote>
<p>先来熟悉一下界面及快捷命令(不用记)</p>
<p><img src="/img/remote/1460000017956979?w=1920&h=1304" alt="" title=""></p>
<h3>3. 在命令行中使用vscode</h3>
<p>如果你是 Windows用户,安装并<code>重启系统</code>后,你就可以在命令行中使用 <code>code</code> 或者 <code>code-insiders</code>了,如果你希望立刻而不是等待重启后使用,可以将 VS Code 的安装目录添加到系统环境变量 <code>PATH</code>中</p>
<p>如果你是mac用户,安装后打开命名面板<code>Cmd+Shift+P</code>,搜索<code>shell</code>命令,点击<code>在PAth中安装code命令</code>,然后重启终端就ok了</p>
<p><img src="/img/remote/1460000017956980?w=2030&h=572" alt="image-20190120144757840" title="image-20190120144757840"></p>
<p>最基础的使用就是使用code命令打开文件或文件夹</p>
<p><code>code 文件夹地址</code>,vscode 就会在新窗口中打开该文件夹</p>
<p>如果你希望在已经打开的窗口打开文件,可以使用<code>-r</code>参数</p>
<p><img src="/img/remote/1460000017956981?w=1276&h=700" alt="" title=""></p>
<p>vscode命令还有其他功能,比如文件比较,打开文件跳转到指定的行和列,如有需要自行百度:bowing_woman:</p>
<p><strong>注意:</strong></p>
<p>在继续看文章之前记住记住打开命令面板的快捷键<code>Cmd+shift+P</code>(win下是Ctrl+shift+p)</p>
<h2>一、代码编辑</h2>
<blockquote>windows下的快捷键放在括号里</blockquote>
<h3>光标的移动</h3>
<h4>基础</h4>
<ol>
<li>移动到行首 Cmd+左方向键 (win Home)</li>
<li>移动到行尾 Cmd+右方向键 (win End)</li>
<li>移动到文档的开头和末尾 Cmd+上下方向键 (win Ctrl+Home/End)</li>
<li>在花括号{}左边右边之间跳转 Cmd+Shift+ (win Ctrl+Shift+)</li>
</ol>
<h4>进阶</h4>
<ol>
<li>回到上一个光标的位置,Cmd+U(win Ctrl+U) 非常有用,有时候vue文件,你改了html,需要去下面改js,改完js又需要回去,这时候Cmd+U直接回</li>
<li>在不同的文件之间回到上一个光标的位置 Control+- (win 没测试,不知道),你改了a文件,改了b文件之后想回到a文件继续编辑,mac使用<code>controls+-</code>
</li>
</ol>
<h3>文本选择</h3>
<ol>
<li>你只需要多按一个shift键就可以在光标移动的时候选中文本</li>
<li>选中单词 <code>Cmd+D</code> 下面要讲的多光标也会讲到Cmd+D</li>
<li>对于代码块的选择没有快捷键,可以使用<code>cmd+shift+p</code>打开命令面板,输入<code>选择括号所有内容</code>,待会说下如何添加快捷键</li>
</ol>
<p><img src="/img/remote/1460000017949683?w=723&h=389" alt="1" title="1"></p>
<h3>删除</h3>
<ol>
<li>你可以选中了代码之后再删除,再按Backpack(是backpack吗)或者delete删除,但是那样做太low了</li>
<li>所以,最Geek的删除方式是<code>Cmd+Shift+K</code> <code> (win Ctrl+Shift+K)</code>,想删多少删多少,当前你可以使用<code>ctrl+x</code>剪切,效果一样的</li>
</ol>
<p><img src="/img/remote/1460000017949684?w=600&h=393" alt="2" title="2"></p>
<h3>代码移动</h3>
<ul><li>Option+上下方向键(win Alt+上下)</li></ul>
<p><img src="/img/remote/1460000017949685?w=600&h=393" alt="3" title="3"></p>
<ul><li>代码移动的同时按住shift就可以实现代码复制 Option+Shift+上下<img src="/img/remote/1460000017949686?w=600&h=393" alt="3" title="3">
</li></ul>
<h3>添加注释</h3>
<p>注释有两种形式,单行注释和块注释(在js中,单行注释//,块注释/**/)</p>
<ul>
<li>单行注释 <code>Cmd+/</code> (win Ctrl +/)</li>
<li>块注释 <code>Option+Shift+A</code>
</li>
</ul>
<p>注意:不同语言使用的注释不同</p>
<h2>二、代码格式</h2>
<h3>代码格式化</h3>
<ul>
<li>对整个文档进行格式化:<code>Option+Shift+F</code> (win <code>Alt+Shift+F</code>),vscode会根据你使用的语言,使用不同的插件进行格式化,记得要下载相应格式化的插件</li>
<li>对选中代码进行格式化: <code>Cmd+K Cmk+F</code> win(<code>Ctrl+K Ctrl+F</code>)</li>
</ul>
<h3>代码缩进</h3>
<ul>
<li>真个文档进行缩进调节,使用Cmd+Shift+P打开命令面板,输入<code>缩进</code>,然后选择相应的命令</li>
<li>选中代码缩进调节:Cmd+] Cmd+[ 分别是减小和增加缩进(win 下不知道,自行百度)</li>
</ul>
<h2>三、一些小技巧</h2>
<ul>
<li>调整字符的大小写,选中,然后在命令面板输入<code>转化为大写</code>或者<code>转化为小写</code><p><img src="/img/remote/1460000017949687?w=600&h=393" alt="" title=""></p>
</li>
<li>合并代码行,多行代码合并为一行,<code>Cmd+J</code>(win下未绑定) <p><img src="/img/remote/1460000017949688?w=600&h=393" alt="" title=""></p>
</li>
<li>行排序,将代码行按照字母顺序进行排序,无快捷键,调出命令面板,输入<code>按升序排序</code>或者<code>按降序排序</code><p><img src="/img/remote/1460000017949689?w=600&h=393" alt="" title=""></p>
</li>
</ul>
<h2>四、多光标特性</h2>
<h3>使用鼠标:</h3>
<p>按住<code>Option</code>(win <code>Alt</code>),然后用鼠标点,鼠标点在哪里哪里就会出现一个光标</p>
<p>注意:有的mac电脑上是按住<code>Cmd</code>,然后用鼠标点才可以</p>
<p><img src="/img/remote/1460000017949690?w=600&h=393" alt="" title=""></p>
<h3>快捷命令</h3>
<ol>
<li>
<code>Cmd+D</code> (win <code>Ctrl+D</code>) 第一次按下时,它会选中光标附近的单词;第二次按下时,它会找到这个单词第二次出现的位置,创建一个新的光标,并且选中它。(注:<code>cmd-k cmd-d</code> 跳过当前的选择)<p><img src="/img/remote/1460000017949691?w=600&h=393" alt="" title=""></p>
</li>
<li>
<code>Option+Shift+i</code> (win Alt+Shift+i) 首先你要选中多行代码,然后按<code>Option+Shift+i</code>,这样做的结果是:每一行后面都会多出来一个光标<p><img src="/img/remote/1460000017949692?w=600&h=393" alt="" title=""></p>
</li>
</ol>
<h3>撤销多光标</h3>
<ul>
<li>使用<code>Esc </code>撤销多光标</li>
<li>鼠标点一下撤销</li>
</ul>
<h2>五、快速跳转(文件、行、符号)</h2>
<h3>快速打开文件</h3>
<p><code>Cmd+P</code> (win Ctrl+P)输入你要打开的文件名,回车打开</p>
<p><img src="/img/remote/1460000017949693?w=600&h=393" alt="" title=""></p>
<p>这里有个小技巧,选中你要打开的文件后,按<code>Cmd+Enter</code>,就会在一个新的编辑器窗口打开(窗口管理,见下文)</p>
<p><img src="/img/remote/1460000017949694?w=600&h=393" alt="" title=""></p>
<p>在tab不同的文件间切换,cmd+shift+[]</p>
<h3>行跳转</h3>
<p>加入浏览器报了个错,错误在53行,如何快速跳转到53行</p>
<p><code>Ctrl+g</code> 输入行号</p>
<p><img src="/img/remote/1460000017949695?w=600&h=393" alt="" title=""></p>
<p>如果你想跳转到某个文件的某一行,你只需要先按下 “Cmd + P”,输入文件名,然后在这之后加上 “:”和指定行号即可。</p>
<p><img src="/img/remote/1460000017949696?w=600&h=393" alt="" title=""></p>
<h3>符号跳转</h3>
<blockquote>符号可以是文件名、函数名,可以是css的类名</blockquote>
<p><code>Cmd+Shift+O</code>(win Ctrl+Shift+o) 输入你要跳转的符号,回车进行跳转</p>
<p>win下输入<code>Ctrl+T</code>,可以在不同文件的符号间进行搜索跳转</p>
<p><img src="/img/remote/1460000017949697?w=600&h=393" alt="" title=""></p>
<h3>定义(definition)和实现(implementation)处</h3>
<p><code>f12</code>跳到函数的定义处</p>
<p><code>Cmd+f12</code>(win Ctrl+f12)跳转到函数的实现处</p>
<p><img src="/img/remote/1460000017949698?w=600&h=393" alt="" title=""></p>
<h3>引用跳转</h3>
<p>很多时候,除了要知道一个函数或者类的定义和实现以外,你可能还希望知道它们被谁引用了,以及在哪里被引用了。这时你只需要将光标移动到函数或者类上面,然后按下 <code>Shift + F12</code>,VS Code 就会打开一个引用列表和一个内嵌的编辑器。在这个引用列表里,你选中某个引用,VS Code 就会把这个引用附近的代码展示在这个内嵌的编辑器里。</p>
<p><img src="/img/remote/1460000017949699?w=805&h=526" alt="" title=""></p>
<h2>六、代码重构</h2>
<p>当我们想修改一个函数或者变量的名字时候,我们只需把光标放到函数或者变量名上,然后按下 <code>F2</code>,这样这个函数或者变量出现的地方就都会被修改。</p>
<p><img src="/img/remote/1460000017949700?w=775&h=408" alt="" title=""></p>
css可变色图标及原理分析
https://segmentfault.com/a/1190000017946369
2019-01-18T22:21:59+08:00
2019-01-18T22:21:59+08:00
hyangteng
https://segmentfault.com/u/hyangteng
11
<blockquote>本文的目标是:使用css可以控制图标的颜色,大小<p>需要工具:<a href="https://link.segmentfault.com/?enc=CuFEq5mltBvYdTBPqG5ofg%3D%3D.d40yeC6n%2Fyu2hZSupAa7LvTl7QZp0uTFlBdNdVZKItGMjERzY8akCXPjoLfUZ%2BQf" rel="nofollow">png转svg工具(如果ui给的svg就更好了)</a> <a href="https://link.segmentfault.com/?enc=KzV2xkfQBalbejvAa6fHzQ%3D%3D.5uXnnB4hfe0K2DFIlWUCqLIkJaosmB5XPhEBlTVOi54%3D" rel="nofollow">iconfont(图标转代码)</a></p>
</blockquote>
<h2>第一步,把图标转成svg格式的</h2>
<ol>
<li>打开网址 <a href="https://link.segmentfault.com/?enc=vG5%2BdgnY4BiVnIR5Tsp0ZQ%3D%3D.l%2F9Ofr%2Bw9WmQctu7qfnqlXHK4mYQfMsnKJh17shW2%2BcRiniVvHVKQOaEUi4zOkrn" rel="nofollow">https://www.bejson.com/conver...</a>
</li>
<li>上传</li>
<li>下载</li>
</ol>
<p><img src="/img/remote/1460000017946372" alt="image-20190118111938753" title="image-20190118111938753"></p>
<h2>第二步,使用iconfont生成代码</h2>
<p>打开网址:<a href="https://link.segmentfault.com/?enc=4R90MQTSN7AJCPuJ3C2YlA%3D%3D.i5x4e7YbCIELhGHyUDfm6stiaUmmxbDfV5arjHzz6Bw%3D" rel="nofollow">https://www.iconfont.cn</a></p>
<p>点击<code>图标管理->我的图标</code>,如下图</p>
<p><img src="/img/remote/1460000017946373" alt="image-20190118112104561" title="image-20190118112104561"></p>
<p>进入到<code>我的图标</code>之后,点击上传icon,如下图</p>
<p><img src="/img/remote/1460000017946374?w=1239&h=549" alt="image-20190118112203237" title="image-20190118112203237"></p>
<p><code>点此上传</code> 选中svg文件,</p>
<p><img src="/img/remote/1460000017946375" alt="image-20190118112313024" title="image-20190118112313024"></p>
<p>上传之后,点击<code>去除颜色并提交</code></p>
<p><img src="/img/remote/1460000017946376?w=1162&h=613" alt="image-20190118112337939" title="image-20190118112337939"></p>
<p>上传完之后就会自动回到图标管理页,点击<code>批量操作->批量加入购物车</code>,然后进入购物车</p>
<p><img src="/img/remote/1460000017946377?w=1037&h=576" alt="image-20190118112725107" title="image-20190118112725107"></p>
<p>在购物车中,点击<code>下载代码</code></p>
<p><img src="/img/remote/1460000017946378" alt="image-20190118112823232" title="image-20190118112823232"></p>
<h2>第三步,生成的代码运用到项目中</h2>
<p>生成的项目目录如下:</p>
<p><img src="/img/remote/1460000017946379?w=419&h=361" alt="image-20190118113226086" title="image-20190118113226086"></p>
<p>点击demo_index.html,可以查看官网给出的使用方式</p>
<p><img src="/img/remote/1460000017946380?w=701&h=910" alt="image-20190118113545069" title="image-20190118113545069"></p>
<p>我项目中使用的是第二种,所以只介绍第二种使用方式</p>
<p>第一步,把iconfont.css复制到你的css样式中</p>
<p><img src="/img/remote/1460000017946381?w=1333&h=517" alt="image-20190118113700383" title="image-20190118113700383"></p>
<p>第二步,根据iconfont.css中你需要的字体,把生成的项目中的字体文件和svg文件复制到你的项目目录中,需要和css文件同级目录,否则需要修改<code>iconfont.css</code>中引入字体文件的路径,默认情况下引入下图的字体(如果不需要兼容ie8和ios4.1-,可移除相应的字体,详情看下面的介绍)</p>
<p><img src="/img/remote/1460000017946382?w=317&h=278" alt="image-20190118113932573" title="image-20190118113932573"></p>
<p>第三步,使用</p>
<pre><code class="html"><span class="icon iconfont icon-play"></span>
//或者
<i class="icon iconfont icon-play"></i></code></pre>
<p>改变样式的话</p>
<pre><code class="css">span{
color:red
}
i{
color:red
}</code></pre>
<h2>最后,原理分析</h2>
<h3>1. 理解@font face规则</h3>
<p>@font face的本质是<code>变量</code>,该规则支持的css属性有很多,我们常用的有:<code>font-family</code>,<code>src</code>,<code>font-style</code>,<code>font-weight</code></p>
<p>首先来看一下,font-family,这里的font-family可以看成一个<code>变量</code>,名字可以随便取,比如用一个$符,但要注意不要覆盖系统已经存在的字体</p>
<pre><code class="css">/*font-family如果是符号的话需要用引号包起来*/
@font-face{
font-family:'$'
}
/*使用iconfont生成的@font-face规则font-family如下:*/
@font-face{
font-family:'iconfont'
}</code></pre>
<p>然后看下我们的重点<code>src</code>属性,src属性用来引入字体资源,引入的字体资源可以使本地的(使用<code>local()</code>引入),也可以是外链字体(使用<code>url()</code>引入),注意local要ie9及以上版本才支持</p>
<p>这里我们重点来看下url功能符</p>
<pre><code class="css">@font-face{
font-family:'icon';
src:url('icon.eot') format('eot');/*ie6-8*/
src: url('icon.eot#iefix') format('embedded-opentype'),
url('icon.woff2') format('woff2')
url('icon.woff') format('woff'),
url('icon.ttf') format('typetrue'),
url('icon.svg#icon') format('svg');
}
//format的作用是让浏览器提前知道加载字体的格式,以决定是否加载字体
</code></pre>
<p>上面代码一共出现了五种字体格式,分别是eot,woff,woff2,ttf,svg</p>
<ul>
<li>svg格式是为了兼容ios4.1及之前的版本,现在的ios版本都到11了,所以完全可以舍弃</li>
<li>eot是ie私有的,所有版本的ie都支持eot格式,并不是只有ie6-8。只是,ie6-ie8仅支持eot这一种格式</li>
<li>woff(web open font format)是专门为web开发而设计的字体,其字体尺寸更小,加载更快,应该优先使用。Android4.4开始全面支持</li>
<li>woff2是woff的二代,字体文件尺寸更小,但是仅chrome和firefox支持的比较好</li>
<li>ttf作为系统安装字体的格式,兼容性很好,但体积较大</li>
</ul>
<p>综上,我们得出以上结论</p>
<ol>
<li>svg格式果断舍弃</li>
<li>如果无需兼容ie8,eot格式舍弃</li>
<li>如果无需兼容Android4.3以前的版本,ttf格式舍弃</li>
</ol>
<h3>2. iconfont生成字体图标原理</h3>
<p>iconfont生成的字体如下:</p>
<pre><code class="css">@font-face {
font-family: "iconfont";
src: url('iconfont.eot?t=1547714990292');
/* IE9 */
src: url('iconfont.eot?t=1547714990292#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAP4AAsAAAAACBgAAAOrAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCCcAqEJINPATYCJAMICwYABCAFhG0HLhvzBhHVm0XJviiwjblCNx4e1RAbqvoScwd3wUWGawQq7bPvQzw8v+7p3Hnv/52F1bbLColMUKi7FapgBBOVZ2CcDsadACIZ7O93+tTc9nW/JuKoRNUmmiAW/joh4qVzC50Q2dyMtzUqQrEk53k/fUfSbiDkVwD8cbjXpgc4313a5Tjnoi7AOJAC3QPXAgmkD/LJv+Mfxi5oibsJNNLNEDGmNWWOUmYOCsSW0/qVyoxcbkgNpVCvmJnFaTVlcaQ4A3Aq+Pn4SjFKCrXEzJixbuwaIz4AKyLtrnZb4iEwptOCzSNhCDIxp1I/TYr8QySNVH/Wi22lBh8uVeXRN/uPRxB1Jr8JxHahXxPDYV4qdQEy7bmG94PORFqVlcjqH+baAbu7AM71qavD+25xUwr1TdTSXbmOgjo6aOTEtdzzhlD07Wfwh3kNLWxrj7Bjinp06gAWdAde7N/YCAfvirOKDa3s6mN/sJ8ePobHHoaNg/S9S30z1dTNb5s47+kQFh7MCP6N69BdPqII7qzvF2OXbAPsvszfu4zfS9cuo4/fe41Hu47H1ORtqTPw0k8353NTd41KqYcHt8d4mj81ujIHd/JT9939WKmT19BaV8T0GUgcUlMYh5QtAZn08c/zD3y/2idiavJz5oyGdIuw8C6CcGkCHZZGgb2tr32WF30s8se1QLHTBzjI4vpTFv32/uArMNIVuOMe/9uiattoCCv1s7JmyNmnsM132Ghb8VY9/qd0iaO4yFgqGsi3pQVkDaEjVc7aqf7juAtroRNgSjfcFDl2BGnfaPlxuxjJ20utDr0Oy/3YZ/qLFceoimHNsa4yGnacG0Tnm91qCBkydLHIzmuI+HxPexAwAlQofaTjX/0d42/y/5Y3r/8vYQbPKfBKYK1QBnQzUt67IfiHjI51WWOZiiIrjMpw4Zr0bkoaaYQKw+ZW3g41tiXRhpIzIBQa6ApJqQ8yY4dATWPDoK40HBoZbPz8xlrbJYncEIMcABBauAqFZu5C0sJjZMa+gZp2vkNdi0jQyLwgV2yM6hdRjWbUgg7MH6LdGtkqUWHqDf27aE6zAvwL+YwumPoxH7piQ55izPnxswgB8VZhBfthKRvsvCW00geRfRkGqrpRb7faKaMZtaAD5g+i3Rp5zUUVvn9D/y6aG2qqtC/kM3YOJr2xA/LKb51qbuWQ8+NnIgSItwpWoMVi+TewV3dLaKUXBnj3xYAaUVd+v7ywvt0WaMSUFGHlJnlH0f+uAwA=') format('woff2'), url('iconfont.woff?t=1547714990292') format('woff'), url('iconfont.ttf?t=1547714990292') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('iconfont.svg?t=1547714990292#iconfont') format('svg');
/* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 18px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
}
.icon-play:before {
content: "\e601";
}</code></pre>
<p>这里有两个需要关注的东西,一个是<code>字体</code>,另一个是<code>字符</code>,这两个东西就是字体图标技术的本质。</p>
<p>字体的本质是字符集与图形的一种映射关系。</p>
<p>字体图标技术的实现就是把通常的字符集映射成了另外的图标形状.<br>例如把 e601映射成了<img src="/img/remote/1460000017946383?w=98&h=82" alt="image-20190118221544393" title="image-20190118221544393"></p>
vue项目接口管理
https://segmentfault.com/a/1190000017925880
2019-01-17T13:21:00+08:00
2019-01-17T13:21:00+08:00
hyangteng
https://segmentfault.com/u/hyangteng
88
<blockquote>在vue开发中,会涉及到很多接口的处理,当项目足够大时,就需要定义规范统一的接口,如何定义呢?<p>方法可能不只一种,本文使用axios+async/await进行接口的统一管理</p>
<p>本文使用vue-cli生成的项目举例</p>
</blockquote>
<h2>举例</h2>
<p>拿segmentfault的官网简单举个例子,先看一下官网</p>
<p><img src="/img/remote/1460000017925883?w=2771&h=1080" alt="image-20190116232014857" title="image-20190116232014857"></p>
<p>看下网站,脑补一下后台给出的文档,如果后台不傻的话给出的文档肯定分模块的,假设后台给出的文档分为了以下几个模块(假设的,不要较真哈)</p>
<ul>
<li>资讯模块 (我的订阅、热门资讯)</li>
<li>问答模块</li>
<li>专栏模块</li>
<li>讲堂模块</li>
<li>圈子模块</li>
<li>发现模块</li>
<li>个人信息模块</li>
<li>用户登录注册模块</li>
<li>...还有其他很多</li>
</ul>
<p>一般来说,网站的首页都是复杂的,会用到很多其他页面也会用到api,所以接口统一管理可以做到api的复用</p>
<p><img src="/img/remote/1460000017925884?w=2136&h=1080" alt="image-20190116232821886" title="image-20190116232821886"></p>
<p>那么如何做呢?</p>
<h2>动手</h2>
<h3>1.</h3>
<p>首先,在src目录下新建一个文件夹,我这里叫apis,后台提供的所有接口都在这里定义</p>
<pre><code class="shell">cd src #切换到src目录
mkdir apis #新建apis文件</code></pre>
<h3>2.</h3>
<p>第二步,按照后台文档划分的模块新建js文件,这里简单举个例子</p>
<ul>
<li>资讯模块: info.js</li>
<li>登录注册模块: member.js</li>
<li>个人信息模块 user_info.js</li>
<li>....</li>
</ul>
<pre><code class="shell">cd apis #切换到apis目录
touch info.js member.js user_info.js #新建js文件</code></pre>
<p>现在的目录大概是这个样子</p>
<pre><code class="js">src
apis
info.js
member.js
user_info.js
main.js</code></pre>
<h3>3.</h3>
<p>第三步,需要引入axios做相应的配置</p>
<p>在apis下新建一个文件夹,叫http.js,在里面做axios相应的配置</p>
<pre><code class="shell">touch http.js //新建http.js文件</code></pre>
<p>配置如下:</p>
<pre><code class="js">import axios from 'axios'
//创建axios的一个实例
var instance = axios.create({
baseURL:xxx,
timeout: 6000
})
//------------------- 一、请求拦截器 忽略
instance.interceptors.request.use(function (config) {
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
//----------------- 二、响应拦截器 忽略
instance.interceptors.response.use(function (response) {
return response.data;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default function (method, url, data = null) {
method = method.toLowerCase();
if (method == 'post') {
return instance.post(url, data)
} else if (method == 'get') {
return instance.get(url, { params: data })
} else if (method == 'delete') {
return instance.delete(url, { params: data })
}else if(method == 'put'){
return instance.put(url,data)
}else{
console.error('未知的method'+method)
return false
}
}</code></pre>
<p>配置简要说明一下:</p>
<ul>
<li>引入axios,新建了个axios的实例(axios的实例的axios的功能一样)</li>
<li>请求拦截器响应拦截器不是本文重点,略过</li>
<li>第30行代码及以后是重点,拉出来重点看一下</li>
</ul>
<pre><code class="js">
/**
* 使用es6的export default导出了一个函数,导出的函数代替axios去帮我们请求数据,
* 函数的参数及返回值如下:
* @param {String} method 请求的方法:get、post、delete、put
* @param {String} url 请求的url:
* @param {Object} data 请求的参数
* @returns {Promise} 返回一个promise对象,其实就相当于axios请求数据的返回值
*/
export default function (method, url, data = null) {
method = method.toLowerCase();
if (method == 'post') {
return instance.post(url, data)
} else if (method == 'get') {
return instance.get(url, { params: data })
} else if (method == 'delete') {
return instance.delete(url, { params: data })
}else if(method == 'put'){
return instance.put(url,data)
}else{
console.error('未知的method'+method)
return false
}
}</code></pre>
<h3>4.</h3>
<p>第四步、在apis下面的js文件中引入http.js导出的函数,拿其中一个文件<code>member.js</code>说明</p>
<pre><code class="js">//member.js 用于定义用户的登录、注册、注销等
import req from './http.js'
//定义接口
//在这里定义了一个登陆的接口,把登陆的接口暴露出去给组件使用
export const LOGIN =params=>req('post','/operator/login',params)
//这里使用了箭头函数,转换一下写法:
//export const LOGIN=function(req){
// return req('post','/operator/login',params)
//}
//定义注册接口
export const REG =params=>req('post','/operator/reg',params)
//定义注销接口
export const LOGOUT =params=>req('post','/operator/logout',params)</code></pre>
<h3>5.</h3>
<p>第五步,在组件中使用接口</p>
<p>登陆组件Login.js使用登录接口</p>
<pre><code class="vue">//Login.vue
<template>
<div>
<input type='text' v-modal='user.userId'>
<input type='text' v-modal='user.pass'>
</div>
</template>
<script>
//1. 引入登录的接口定义
import {LOGIN} from '../../apis/user.js'
export default{
data(){
return {
user:{
userId:'',
pass:''
}
}
},
methods{
//2. 定义登录的逻辑
async login(){
//2.1 awiat LOGIN(this.user)
//等待LOGIN(this.user)执行完,
//把返回值给user_info
let user_info=await LOGIN(this.user)
//2.2假设登录成功,返回的数据应该是
//user_info={code:200,msg:'success',data:{token:'xxxxx'}}
//然后根据返回的数据做相应的处理,比如储存token
}
},
mounted(){
//3. 执行登录
this.login()
}
}
</script></code></pre>
<h3>6.</h3>
<p>这样定义接口虽然看起来麻烦点,但有以下几个好处:</p>
<ul>
<li>代码看起来规范,所有的接口都在一个文件夹定义,不用分散的各个组件,维护起来简单,例如后台的一些url变了,改起来也方便</li>
<li>可以做到接口一次定义,到处使用</li>
</ul>
<p>例如首页使用了很多api</p>
<pre><code class="js">//index.js 首页的js文件
import {GET_USER_INFO} from 'apis/user_info.js' //用户信息
import {GET_CURRENT_INFO,GET_HOT_INFO} from 'apis/info.js' //热门资讯 最新资讯
import {GET_HOT_LECTRUE} from'apis/lectrue.js' //讲座推荐</code></pre>
<p>用户界面也会用到用户信息api</p>
<pre><code class="JS">//user_info.js用户信息页面
import {GET_USER_INFO} from 'apis/user_info.js' //用户信息</code></pre>
<p>就先写到这里了,如有错误,请在评论区指正,下次整理下<code>用户权限相关的内容,包括jwt,token保存和更新,页面权限问题</code></p>
vue开发环境配置跨域,一步到位
https://segmentfault.com/a/1190000017905030
2019-01-15T23:22:48+08:00
2019-01-15T23:22:48+08:00
hyangteng
https://segmentfault.com/u/hyangteng
124
<blockquote>本文要实现的是:使用vue-cli搭建的项目在开发时配置跨域,上线后不做任何任何修改,接口也可以访问<p>阅读时间需要三分钟</p>
</blockquote>
<p>production:产品 生产环境</p>
<p>development:开发 开发环境</p>
<h2>1.开发环境设置跨域</h2>
<ul>
<li>使用工具:vue-cli自带的配置</li>
<li>配置目录 /config/index.js</li>
</ul>
<p><img src="/img/remote/1460000017905033?w=1810&h=802" alt="image-20190115222845702" title="image-20190115222845702"></p>
<pre><code class="js">//自行复制黏贴
proxyTable: {
'/apis':{
target: 'http://10.1.63.26:19080/', // 后台api
changeOrigin: true, //是否跨域
// secure: true,
pathRewrite: {
'^/apis': '' //需要rewrite的,
}
}
}</code></pre>
<p>注意:以上配置只有在生产环境下有效,你打包之后就不起作用了</p>
<p>这样就存在一个问题,你的接口都是/apis开头的,打包之后部署到服务器要去除/apis,才能正常访问后台接口,如何解决呢?</p>
<h2>2.生产环境设置跨域</h2>
<blockquote>使用工具 axios <a href="https://link.segmentfault.com/?enc=j9V%2BHvZMauIv%2F9EY3qxt6w%3D%3D.0h%2F%2Bm6Cx1XtixG56AnmBBhd3FfzUoyYZD1wHenHI12Cgmwn5olQUMfeA%2BwTqocEY" rel="nofollow">中文文档地址</a>
</blockquote>
<h3>思路</h3>
<p>解决的思路是这样的:</p>
<p>首先,axios的默认实例有一个baseURL的属性,配置了baseURL之后,你访问接口时就会自动带上</p>
<pre><code class="js">假设你vue-cli起了一个开发环境,地址为http://localhost:8080
//例1 当不设置baseURL时
axios.get('/user') //访问/user相当于访问 http://localhost:8080/user
//例2
axios.defaults.baseURL='/apis'
axios.get('/user') //访问/user就相当于访问 http://localhost:8080/apis/user
//例3
axios.defaults.baseURL='https://sbsb.com'
axios.get('/user') //访问/user就相当于访问 https://sbsb.com/user
//例4
axios.defaults.baseURL='https://sbsb.com/apis'
axios.get('/user') //访问/user就相当于访问 https://sbsb.com/apis/user
</code></pre>
<p>然后我们要根据现在的环境是开发环境还是生产环境,配置不同的baseURL</p>
<pre><code class="js">//判断是否是生产环境
var isPro = process.env.NODE_ENV === 'production' //process.env.NODE_ENV用于区分是生产环境还是开发环境
//配置不同的baseURL
module.exports = {
baseURL: isPro ? 'http://sbsb.com:8888/' : '/apis'
}</code></pre>
<p><code>process.env.NODE_ENV</code>用于区分是生产环境还是开发环境,这个值是webpack设置的</p>
<h3>动手操作</h3>
<blockquote>假设后台api的rul是<code>http://sbsb.com:8888/</code>
</blockquote>
<p>首先在/config目录下新建一个文件,我这里叫api.config.js<br>写入以下代码</p>
<pre><code class="js">//判断是否是生产环境
var isPro = process.env.NODE_ENV === 'production' //process.env.NODE_ENV用于区分是生产环境还是开发环境
//根据环境不同导出不同的baseURL
module.exports = {
baseURL: isPro ? 'http://sbsb.com:8888/' : '/apis'
}</code></pre>
<p>然后,在main.js中引入axios和刚才那个文件</p>
<pre><code class="js">//main.js
import Vue from 'vue'
import axios from 'axios'
import apiConfig from '../config/api.config.js'
axios.defaults.baseURL=apiConfig.baseURL
//axios的其他配置...</code></pre>
<p>这样配置之后,打包部署到服务器上也不用再手工去除/apis</p>
<p>如果配置过程中出现了问题,自己调试,看看访问的url正不正确</p>
宇宙最强postman教程
https://segmentfault.com/a/1190000017890654
2019-01-14T21:12:16+08:00
2019-01-14T21:12:16+08:00
hyangteng
https://segmentfault.com/u/hyangteng
8
<blockquote>postman是一个用来测试api的工具,支持所有的http method,测试保存后可以生成超级完善的文档。今天在csdn上发现了一些超级详细的文章,整理出来分享给大家。</blockquote>
<h3><a href="https://link.segmentfault.com/?enc=6Q3r%2FGZuGyh5knKo09Ib2w%3D%3D.aR3kE4K5uGYlMILcmYOS1cXJvUapcy5Y069ITYwuksRlons7B6Dz3YaCx7HyRpQ4t9O7fVp5WuV%2F3ISSqlPEZQ%3D%3D" rel="nofollow">1.基础使用</a></h3>
<h3><a href="https://link.segmentfault.com/?enc=isoDYEtdm%2FUJxafjsK5neA%3D%3D.BfwM3O7xdSDwBMlJqR6wLiunVu2gzV7V%2BvosD8YhnfHyPcxlK5YzSZESwYznGS992cCmpC2xYLiOMM8u%2FLRL1w%3D%3D" rel="nofollow">2.postman添加环境变量、做测试</a></h3>
<h3><a href="https://link.segmentfault.com/?enc=EobtdYHSC45Ev03K1zMc2Q%3D%3D.%2Bsgs%2B8AuE2D%2BEBmY%2Fdeb%2FSRGAp%2FDpbLEVf9otby%2FTG99tep%2B1CqXvIBUw0j2%2BCUTD3b0uwbQImhnTvLS4QRfWA%3D%3D" rel="nofollow">3.使用script</a></h3>
<h3><a href="https://link.segmentfault.com/?enc=kCRexbBn%2Bt%2FwGpica6Ohbw%3D%3D.vCWi1AtKheduHW70kLsoUH6rWYKNezvDCDhKr8kay%2Bu6J3iazCz9XUqXek7YSveU%2FQXg4oIK4ik0Rtc%2BNWGlPQ%3D%3D" rel="nofollow">4.使用postman模拟</a></h3>
<h3><a href="https://link.segmentfault.com/?enc=LudlMEiiBgeKAa3oMoatew%3D%3D.i39A1RO1WsBjcGMGfQm7MevOiTwKh4b6jNaUkAYedC3LxaDK3V6Oow0zqZC7J3KZsT4IogfGqS80acII1vMmmQ%3D%3D" rel="nofollow">5.使用postman生成文档和示例</a></h3>
<p>补充:</p>
<p><a href="https://link.segmentfault.com/?enc=L%2FQC9TSkvFkN1vW3Z6CStg%3D%3D.WP9bflgPeZzlPytB8fRoF8FkuIgiM1NXRaYcLmpdUGRs7gx%2BD7Gps1TFS91dcZDo1HsQK71iU797NyvzWmYVCg%3D%3D" rel="nofollow">使用postman自动获取token</a></p>
超详细的webpack原理解读
https://segmentfault.com/a/1190000017890529
2019-01-14T21:01:01+08:00
2019-01-14T21:01:01+08:00
hyangteng
https://segmentfault.com/u/hyangteng
82
<h2>webpack原理解读</h2>
<blockquote>本文抄自《深入浅出webpack》,建议想学习原理的手打一遍,操作一遍,给别人讲一遍,然后就会了<p>在阅读前希望您已有webpack相关的实践经验,不然读了也读不懂</p>
<p>本文阅读需要几分钟,理解需要自己动手操作蛮长时间</p>
</blockquote>
<h3>0 配置文件</h3>
<p>首先简单看一下webpack配置文件(webpack.config.js):</p>
<pre><code class="js">var path = require('path');
var node_modules = path.resolve(__dirname, 'node_modules');
var pathToReact = path.resolve(node_modules, 'react/dist/react.min.js');
module.exports = {
// 入口文件,是模块构建的起点,同时每一个入口文件对应最后生成的一个 chunk。
entry: {
bundle: [
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080',
path.resolve(__dirname, 'app/app.js')
]
},
// 文件路径指向(可加快打包过程)。
resolve: {
alias: {
'react': pathToReact
}
},
// 生成文件,是模块构建的终点,包括输出文件与输出路径。
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
},
// 这里配置了处理各模块的 loader ,包括 css 预处理 loader ,es6 编译 loader,图片处理 loader。
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
}
],
noParse: [pathToReact]
},
// webpack 各插件对象,在 webpack 的事件流中执行对应的方法。
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};</code></pre>
<h3>1. 工作原理概述</h3>
<h4>1.1 基本概念</h4>
<p>在了解webpack原理之前,需要掌握以下几个核心概念</p>
<ul>
<li>Entry: 入口,webpack构建第一步从entry开始</li>
<li>module:模块,在webpack中一个模块对应一个文件。webpack会从entry开始,递归找出所有依赖的模块</li>
<li>Chunk:代码块,一个chunk由多个模块组合而成,用于代码合并与分割</li>
<li>Loader: 模块转换器,用于将模块的原内容按照需求转换成新内容</li>
<li>Plugin:拓展插件,在webpack构建流程中的特定时机会广播对应的事件,插件可以监听这些事件的发生,在特定的时机做对应的事情</li>
</ul>
<h4>1.2 流程概述</h4>
<p>webpack从启动到结束依次执行以下操作:</p>
<pre><code class="mermaid">graph TD
初始化参数 --> 开始编译
开始编译 -->确定入口
确定入口 --> 编译模块
编译模块 --> 完成编译模块
完成编译模块 --> 输出资源
输出资源 --> 输出完成
</code></pre>
<p>各个阶段执行的操作如下:</p>
<ol>
<li>初始化参数:从配置文件(默认webpack.config.js)和shell语句中读取与合并参数,得出最终的参数</li>
<li>开始编译(compile):用上一步得到的参数初始化Comiler对象,加载所有配置的插件,通过执行对象的run方法开始执行编译</li>
<li>确定入口:根据配置中的entry找出所有的入口文件</li>
<li>编译模块:从入口文件出发,调用所有配置的Loader对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过处理</li>
<li>完成编译模块:经过第四步之后,得到了每个模块被翻译之后的最终内容以及他们之间的依赖关系</li>
<li>输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再将每个chunk转换成一个单独的文件加入输出列表中,这是可以修改输出内容的最后机会</li>
<li>输出完成:在确定好输出内容后,根据配置(webpack.config.js && shell)确定输出的路径和文件名,将文件的内容写入文件系统中(fs)</li>
</ol>
<p>在以上过程中,webpack会在特定的时间点广播特定的事件,插件监听事件并执行相应的逻辑,并且插件可以调用webpack提供的api改变webpack的运行结果</p>
<h4>1.3 流程细节</h4>
<p>webpack构建流程可分为以下三大阶段。</p>
<ol>
<li>初始化:启动构建,读取与合并配置参数,加载plugin,实例化Compiler</li>
<li>编译:从Entry出发,针对每个Module串行调用对应的Loader去翻译文件中的内容,再找到该Module依赖的Module,递归的进行编译处理</li>
<li>输出:将编译后的Module组合成Chunk,将Chunk转换成文件,输出到文件系统中</li>
</ol>
<p>如果只执行一次,流程如上,但在开启监听模式下,流程如下图</p>
<pre><code class="mermaid">graph TD
初始化-->编译;
编译-->输出;
输出-->文本发生变化
文本发生变化-->编译
</code></pre>
<h5>1.3.1初始化阶段</h5>
<p>在初始化阶段会发生的事件如下</p>
<table>
<thead><tr>
<th>事件</th>
<th>描述</th>
</tr></thead>
<tbody>
<tr>
<td>初始化参数</td>
<td>从配置文件和shell语句中读取与合并参数,得出最终的参数,这个过程还会执行配置文件中的插件实例化语句 new Plugin()</td>
</tr>
<tr>
<td>实例化Compiler</td>
<td>实例化Compiler,传入上一步得到的参数,Compiler负责文件监听和启动编译。在Compiler实例中包含了完整的webpack配置,全局只有一个Compiler实例。</td>
</tr>
<tr>
<td>加载插件</td>
<td>依次调用插件的apply方法,让插件可以监听后续的所有事件节点。同时向插件中传入compiler实例的引用,以方便插件通过compiler调用webpack的api</td>
</tr>
<tr>
<td>environment</td>
<td>开始应用Node.js风格的文件系统到compiler对象,以方便后续的文件寻找和读取</td>
</tr>
<tr>
<td>Entry-option</td>
<td>读取配置的Entrys,为每个Entry实例化一个对应的EntryPlugin,为后面该Entry的递归解析工作做准备</td>
</tr>
<tr>
<td>After-plugins</td>
<td>调用完所有内置的和配置的插件的apply方法</td>
</tr>
<tr>
<td>After-resolvers</td>
<td>根据配置初始化resolver,resolver负责在文件系统中寻找指定路径的文件</td>
</tr>
</tbody>
</table>
<p>#### 1.3.2 编译阶段 (事件名全为小写)</p>
<table>
<thead><tr>
<th>事件</th>
<th>解释</th>
</tr></thead>
<tbody>
<tr>
<td>run</td>
<td>启动一次编译</td>
</tr>
<tr>
<td>Watch-run</td>
<td>在监听模式下启动编译,文件发生变化会重新编译</td>
</tr>
<tr>
<td>compile</td>
<td>告诉插件一次新的编译将要启动,同时会给插件带上compiler对象</td>
</tr>
<tr>
<td>compilation</td>
<td>当webpack以开发模式运行时,每当检测到文件的变化,便有一次新的compilation被创建。一个Compilation对象包含了当前的模块资源、编译生成资源、变化的文件等。compilation对象也提供了很多事件回调给插件进行拓展</td>
</tr>
<tr>
<td>make</td>
<td>一个新的compilation对象创建完毕,即将从entry开始读取文件,根据文件类型和编译的loader对文件进行==编译==,编译完后再找出该文件依赖的文件,递归地编译和解析</td>
</tr>
<tr>
<td>after-compile</td>
<td>一次compilation执行完成</td>
</tr>
<tr>
<td>invalid</td>
<td>当遇到错误会触发改事件,该事件不会导致webpack退出</td>
</tr>
<tr>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p>在编译阶段最重要的事件是compilation,因为在compilation阶段调用了Loader,完成了每个模块的==转换==操作。在compilation阶段又会发生很多小事件,如下表</p>
<table>
<thead><tr>
<th>事件</th>
<th>解释</th>
</tr></thead>
<tbody>
<tr>
<td>build-module</td>
<td>使用相应的Loader去转换一个模块</td>
</tr>
<tr>
<td>Normal-module-loader</td>
<td>在使用loader转换完一个模块后,使用<a href="https://link.segmentfault.com/?enc=iqCXRtXpC3rymbc27nXEQQ%3D%3D.%2F8lDPAAgx%2BWw4VH6Q8I5Rve3U10UVtPNDqfpqx4vppYglzhT4rWTBi3dnnAYn9Jp" rel="nofollow">acorn</a>解析转换后的内容,输出对应的抽象语法树(AST),以方便webpack对代码进行分析</td>
</tr>
<tr>
<td>program</td>
<td>从配置的入口模块开始,分析其AST,当遇到require等导入其他模块的语句时,便将其加入依赖的模块列表中,同时对于新找出来的模块递归分析,最终弄清楚所有模块的依赖关系</td>
</tr>
<tr>
<td>seal</td>
<td>所有模块及依赖的模块都通过Loader转换完成,根据依赖关系生成Chunk</td>
</tr>
<tr>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<h5>2.3 输出阶段</h5>
<p>输出阶段会发生的事件及解释:</p>
<table>
<thead><tr>
<th>事件</th>
<th>解释</th>
</tr></thead>
<tbody>
<tr>
<td>should-emit</td>
<td>所有需要输出的文件已经生成,询问插件有哪些文件需要输出,有哪些不需要输出</td>
</tr>
<tr>
<td>emit</td>
<td>确定好要输出哪些文件后,执行文件输出,==可以在这里获取和修改输出的内容==</td>
</tr>
<tr>
<td>after-mit</td>
<td>文件输出完毕</td>
</tr>
<tr>
<td>done</td>
<td>成功完成一次完整的编译和输出流程</td>
</tr>
<tr>
<td>failed</td>
<td>如果在编译和输出中出现错误,导致webpack退出,就会直接跳转到本步骤,插件可以在本事件中获取具体的错误原因</td>
</tr>
</tbody>
</table>
<p>在输出阶段已经得到了各个模块经过转化后的结果和其依赖关系,并且将相应的模块组合在一起形成一个个chunk.在输出阶段根据chunk的类型,使用对应的模板生成最终要输出的文件内容. |</p>
<pre><code class="js">//以下代码用来包含webpack运行过程中的每个阶段
//file:webpack.config.js
const path = require('path');
//插件监听事件并执行相应的逻辑
class TestPlugin {
constructor() {
console.log('@plugin constructor');
}
apply(compiler) {
console.log('@plugin apply');
compiler.plugin('environment', (options) => {
console.log('@environment');
});
compiler.plugin('after-environment', (options) => {
console.log('@after-environment');
});
compiler.plugin('entry-option', (options) => {
console.log('@entry-option');
});
compiler.plugin('after-plugins', (options) => {
console.log('@after-plugins');
});
compiler.plugin('after-resolvers', (options) => {
console.log('@after-resolvers');
});
compiler.plugin('before-run', (options, callback) => {
console.log('@before-run');
callback();
});
compiler.plugin('run', (options, callback) => {
console.log('@run');
callback();
});
compiler.plugin('watch-run', (options, callback) => {
console.log('@watch-run');
callback();
});
compiler.plugin('normal-module-factory', (options) => {
console.log('@normal-module-factory');
});
compiler.plugin('context-module-factory', (options) => {
console.log('@context-module-factory');
});
compiler.plugin('before-compile', (options, callback) => {
console.log('@before-compile');
callback();
});
compiler.plugin('compile', (options) => {
console.log('@compile');
});
compiler.plugin('this-compilation', (options) => {
console.log('@this-compilation');
});
compiler.plugin('compilation', (options) => {
console.log('@compilation');
});
compiler.plugin('make', (options, callback) => {
console.log('@make');
callback();
});
compiler.plugin('compilation', (compilation) => {
compilation.plugin('build-module', (options) => {
console.log('@build-module');
});
compilation.plugin('normal-module-loader', (options) => {
console.log('@normal-module-loader');
});
compilation.plugin('program', (options, callback) => {
console.log('@program');
callback();
});
compilation.plugin('seal', (options) => {
console.log('@seal');
});
});
compiler.plugin('after-compile', (options, callback) => {
console.log('@after-compile');
callback();
});
compiler.plugin('should-emit', (options) => {
console.log('@should-emit');
});
compiler.plugin('emit', (options, callback) => {
console.log('@emit');
callback();
});
compiler.plugin('after-emit', (options, callback) => {
console.log('@after-emit');
callback();
});
compiler.plugin('done', (options) => {
console.log('@done');
});
compiler.plugin('failed', (options, callback) => {
console.log('@failed');
callback();
});
compiler.plugin('invalid', (options) => {
console.log('@invalid');
});
}
}</code></pre>
<pre><code class="shell">#在目录下执行
webpack
#输出以下内容
@plugin constructor
@plugin apply
@environment
@after-environment
@entry-option
@after-plugins
@after-resolvers
@before-run
@run
@normal-module-factory
@context-module-factory
@before-compile
@compile
@this-compilation
@compilation
@make
@build-module
@normal-module-loader
@build-module
@normal-module-loader
@seal
@after-compile
@should-emit
@emit
@after-emit
@done
Hash: 19ef3b418517e78b5286
Version: webpack 3.11.0
Time: 95ms
Asset Size Chunks Chunk Names
bundle.js 3.03 kB 0 [emitted] main
[0] ./main.js 44 bytes {0} [built]
[1] ./show.js 114 bytes {0} [built]</code></pre>
<h3>2 输出文件分析</h3>
<h4>2.1 举个栗子</h4>
<p>下面通过 Webpack 构建一个采用 CommonJS 模块化编写的项目,该项目有个网页会通过 JavaScript 在网页中显示 <code>Hello,Webpack</code>。</p>
<p>运行构建前,先把要完成该功能的最基础的 JavaScript 文件和 HTML 建立好,需要如下文件:</p>
<p>页面入口文件 <code>index.html</code></p>
<pre><code class="html"><html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app"></div>
<!--导入 Webpack 输出的 JavaScript 文件-->
<script src="./dist/bundle.js"></script>
</body>
</html></code></pre>
<p>JS 工具函数文件 <code>show.js</code></p>
<pre><code class="js">// 操作 DOM 元素,把 content 显示到网页上
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
// 通过 CommonJS 规范导出 show 函数
module.exports = show;</code></pre>
<p>JS 执行入口文件 <code>main.js</code></p>
<pre><code class="js">// 通过 CommonJS 规范导入 show 函数
const show = require('./show.js');
// 执行 show 函数
show('Webpack');</code></pre>
<p>Webpack 在执行构建时默认会从项目根目录下的 <code>webpack.config.js</code> 文件读取配置,所以你还需要新建它,其内容如下:</p>
<pre><code class="js">const path = require('path');
module.exports = {
// JavaScript 执行入口文件
entry: './main.js',
output: {
// 把所有依赖的模块合并输出到一个 bundle.js 文件
filename: 'bundle.js',
// 输出文件都放到 dist 目录下
path: path.resolve(__dirname, './dist'),
}
};</code></pre>
<p>由于 Webpack 构建运行在 Node.js 环境下,所以该文件最后需要通过 CommonJS 规范导出一个描述如何构建的 <code>Object</code> 对象。</p>
<pre><code>|-- index.html
|-- main.js
|-- show.js
|-- webpack.config.js</code></pre>
<p>一切文件就绪,在项目根目录下执行 <code>webpack</code> 命令运行 Webpack 构建,你会发现目录下多出一个 <code>dist</code>目录,里面有个 <code>bundle.js</code> 文件, <code>bundle.js</code> 文件是一个可执行的 JavaScript 文件,它包含页面所依赖的两个模块 <code>main.js</code> 和 <code>show.js</code> 及内置的 <code>webpackBootstrap</code> 启动函数。 这时你用浏览器打开 <code>index.html</code> 网页将会看到 <code>Hello,Webpack</code>。</p>
<h4>2.2 bundle.js文件做了什么</h4>
<p>看之前记住:一个模块就是一个文件,</p>
<p>首先看下bundle.js长什么样子:</p>
<p><img src="https://ws3.sinaimg.cn/large/006tNc79gy1fz5a7ouqzaj31cf0jijzy.jpg" alt="image-20190113213327207" title="image-20190113213327207"></p>
<p>注意:序号1处是个自执行函数,序号2作为自执行函数的参数传入</p>
<p>具体代码如下:(建议把以下代码放入编辑器中查看,最好让index.html执行下,弄清楚执行的顺序)</p>
<pre><code class="js">(function(modules) { // webpackBootstrap
// 1. 缓存模块
var installedModules = {};
// 2. 定义可以在浏览器使用的require函数
function __webpack_require__(moduleId) {
// 2.1检查模块是否在缓存里,在的话直接返回
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 2.2 模块不在缓存里,新建一个对象module=installModules[moduleId] {i:moduleId,l:模块是否加载,exports:模块返回值}
var module = installedModules[moduleId] = {
i: moduleId,//第一次执行为0
l: false,
exports: {}
};//第一次执行module:{i:0,l:false,exports:{}}
// 2.3 执行传入的参数中对应id的模块 第一次执行数组中传入的第一个参数
//modules[0].call({},{i:0,l:false,exports:{}},{},__webpack_require__函数)
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 2.4 将这个模块标记为已加载
module.l = true;
// 2.5 返回这个模块的导出值
return module.exports;
}
// 3. webpack暴露属性 m c d n o p
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
__webpack_require__.p = "";
// 4. 执行reruire函数引入第一个模块(main.js对应的模块)
return __webpack_require__(__webpack_require__.s = 0);
})
([ // 0. 传入参数,参数是个数组
/* 第0个参数 main.js对应的文件*/
(function(module, exports, __webpack_require__) {
// 通过 CommonJS 规范导入 show 函数
const show = __webpack_require__(1);//__webpack_require__(1)返回show
// 执行 show 函数
show('Webpack');
}),
/* 第1个参数 show.js对应的文件 */
(function(module, exports) {
// 操作 DOM 元素,把 content 显示到网页上
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
// 通过 CommonJS 规范导出 show 函数
module.exports = show;
})
]);</code></pre>
<p>以上看上去复杂的代码其实是一个自执行函数(文件作为自执行函数的参数),可以简写如下:</p>
<pre><code class="js">(function(modules){
//模拟require语句
function __webpack_require__(){}
//执行存放所有模块数组中的第0个模块(main.js)
__webpack_require_[0]
})([/*存放所有模块的数组*/])</code></pre>
<p>bundles.js能直接在浏览器中运行的原因是,在输出的文件中通过<code>__webpack_require__</code>函数,定义了一个可以在浏览器中执行的加载函数(加载文件使用ajax实现),来模拟Node.js中的require语句。</p>
<p>原来一个个独立的模块文件被合并到了一个单独的 bundle.js 的原因在于浏览器不能像 Node.js 那样快速地去本地加载一个个模块文件,而必须通过网络请求去加载还未得到的文件。 如果模块数量很多,加载时间会很长,因此把所有模块都存放在了数组中,执行一次网络加载。</p>
<p>修改main.js,改成import引入模块</p>
<pre><code class="js">import show from './show';
show('Webpack');</code></pre>
<p>在目录下执行<code>webpack</code>,会发现:</p>
<ol><li>生成的代码会有所不同,但是主要的区别是自执行函数的参数不同,也就是2.2代码的第二部分不同</li></ol>
<pre><code class="js">([//自执行函数和上面相同,参数不同
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__show__ = __webpack_require__(1);
Object(__WEBPACK_IMPORTED_MODULE_0__show__["a" /* default */])('Webpack');
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = show;
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
})
]);</code></pre>
<p>参数不同的原因是es6的import和export模块被webpack编译处理过了,其实作用是一样的,接下来看一下在main.js中异步加载模块时,bundle.js是怎样的</p>
<h4>2.3异步加载时,bundle.js代码分析</h4>
<p><code>main.js</code>修改如下</p>
<pre><code class="js">import('./show').then(show=>{
show('Webpack')
})</code></pre>
<p>构建成功后会生成两个文件</p>
<ol>
<li>bundle.js 执行入口文件</li>
<li>0.bundle.js 异步加载文件</li>
</ol>
<p>其中0.bundle.js文件的内容如下:</p>
<pre><code class="js">webpackJsonp(/*在其他文件中存放的模块的ID*/[0],[//本文件所包含的模块
/* 0 */,
/* 1 show.js对应的模块 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony export (immutable) */
__webpack_exports__["default"] = show;
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
})
]);</code></pre>
<p>bundle.js文件的内容如下:</p>
<p>注意:bundle.js比上面的bundle.js的区别在于:</p>
<ol>
<li>多了一个<code>__webpack_require__.e</code>,用于加载被分割出去的需要异步加载的chunk对应的文件</li>
<li>多了一个webpackJsonp函数,用于从异步加载的文件中安装模块</li>
</ol>
<pre><code class="js">(function(modules) { // webpackBootstrap
// install a JSONP callback for chunk loading
var parentJsonpFunction = window["webpackJsonp"];
// webpackJsonp用于从异步加载的文件中安装模块
// 将webpackJsonp挂载到全局是为了方便在其他文件中调用
/**
* @param chunkIds 异步加载的模块中需要安装的模块对应的id
* @param moreModules 异步加载的模块中需要安装模块列表
* @param executeModules 异步加载的模块安装成功后需要执行的模块对应的index
*/
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, i = 0, resolves = [], result;
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if(installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
while(resolves.length) {
resolves.shift()();
}
};
// The module cache
var installedModules = {};
// objects to store loaded and loading chunks
var installedChunks = {
1: 0
};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
/**
* 用于加载被分割出去的需要异步加载的chunk对应的文件
* @param chunkId 需要异步加载的chunk对应的id
* @returns {Promise}
*/
__webpack_require__.e = function requireEnsure(chunkId) {
var installedChunkData = installedChunks[chunkId];
if(installedChunkData === 0) {
return new Promise(function(resolve) { resolve(); });
}
// a Promise means "currently loading".
if(installedChunkData) {
return installedChunkData[2];
}
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
installedChunkData[2] = promise;
// start chunk loading
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = "text/javascript";
script.charset = 'utf-8';
script.async = true;
script.timeout = 120000;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
var timeout = setTimeout(onScriptComplete, 120000);
script.onerror = script.onload = onScriptComplete;
function onScriptComplete() {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if(chunk !== 0) {
if(chunk) {
chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
}
installedChunks[chunkId] = undefined;
}
};
head.appendChild(script);
return promise;
};
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
__webpack_require__.p = "";
// on error function for async loading
__webpack_require__.oe = function(err) { console.error(err); throw err; };
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = 0);
})
/************************************************************************/
([//存放没有经过异步加载的,随着执行入口文件加载的模块
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 1)).then(show=>{
show('Webpack')
})
/***/ })
]);</code></pre>