<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Fangxw&#39;s Blog</title>
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://fangxw.me/"/>
  <updated>2021-06-25T01:03:07.852Z</updated>
  <id>http://fangxw.me/</id>
  
  <author>
    <name>Fangxw</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Vue 实战技巧</title>
    <link href="http://fangxw.me/2020/02/03/vue-tricks/"/>
    <id>http://fangxw.me/2020/02/03/vue-tricks/</id>
    <published>2020-02-03T13:15:00.000Z</published>
    <updated>2021-06-25T01:03:07.852Z</updated>
    
    <content type="html"><![CDATA[<p>虽然在日常使用 Vue 已经很熟悉了，但是在开发中还是会遇到一些一时解释不了的原因，其实还是自己对原理的理解不够深刻。下面总结了一些在日常开发的遇到的问题的一些思考，还有 Vue 的一些小技巧。</p>
<a id="more"></a>
<h2 id="v-if-会复用已有的元素"><a href="#v-if-会复用已有的元素" class="headerlink" title="v-if 会复用已有的元素"></a>v-if 会复用已有的元素</h2><p>Vue 对渲染做了非常多的优化，经常在可以复用元素的时候会尽量复用元素，避免从头开始渲染。</p>
<p>比如下面的代码示例</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span> <span class="attr">v-if</span>=<span class="string">"loginType === 'username'"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">label</span>&gt;</span>Username<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">placeholder</span>=<span class="string">"Enter your username"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">template</span> <span class="attr">v-else</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">label</span>&gt;</span>Email<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">placeholder</span>=<span class="string">"Enter your email address"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>上面的代码中通过 <code>loginType</code> 切换两个不同的 template 的时候，因为两个模板使用了相同的元素，Vue 会复用 <code>input</code> 元素，只通过替换 <code>placeholder</code> 来切换。</p>
<p>这样做除了渲染非常快，还可以保留输入框中的文本。你可以自己实际操作试试。</p>
<p>但是这样也会导致一些不符合需求的情况发生，因为不同的状态切换时他总是同一个元素，如果我们有需要手动从操作元素操作 DOM 的时候，则会导致以为情况的发生。</p>
<p>这个时候，我们只需要为元素添加一个 <code>key</code> 属性即可。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span> <span class="attr">v-if</span>=<span class="string">"loginType === 'username'"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">label</span>&gt;</span>Username<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">placeholder</span>=<span class="string">"Enter your username"</span> <span class="attr">key</span>=<span class="string">"username-input"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">template</span> <span class="attr">v-else</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">label</span>&gt;</span>Email<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">placeholder</span>=<span class="string">"Enter your email address"</span> <span class="attr">key</span>=<span class="string">"email-input"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>这样，每次切换状态时，输入框都讲被重新渲染。</p>
<h2 id="父子组件传值"><a href="#父子组件传值" class="headerlink" title="父子组件传值"></a>父子组件传值</h2><h3 id="1-props-emit"><a href="#1-props-emit" class="headerlink" title="1. props/$emit"></a>1. props/$emit</h3><p>这是最常见的一种父子组件传值的方式。</p>
<p>通过 <code>props</code> 我们可以将值从父组件传递到子组件。</p>
<p><code>$emit</code> 可以使我们出发一个事件，通过参数将值从子组件传递给父组件。</p>
<h3 id="2-ref-refs"><a href="#2-ref-refs" class="headerlink" title="2. ref/refs"></a>2. ref/refs</h3><p>在元素上定义 ref 属性，再通过 refs 获取。如果 ref 是定义在 DOM 元素上，获取的则是 DOM 元素的引用，如果是用在组件上，获取的则是组件的示例，我们可以通过这个示例直接调用组件的方法和访问组件间的数据。</p>
<p>下面看一个例子。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">// 子组件 child.vue</span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="undefined">  data () &#123;</span></span><br><span class="line"><span class="javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="javascript">      name: <span class="string">'foo'</span></span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;,</span></span><br><span class="line"><span class="undefined">  methods: &#123;</span></span><br><span class="line"><span class="undefined">    say () &#123;</span></span><br><span class="line"><span class="javascript">      <span class="built_in">console</span>.log(<span class="string">'foo component'</span>)</span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;</span></span><br><span class="line"><span class="undefined">&#125;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line">// 父组件 app.vue</span><br><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">child</span> <span class="attr">ref</span>=<span class="string">"child"</span>&gt;</span><span class="tag">&lt;/<span class="name">child</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">  <span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="undefined">    mounted () &#123;</span></span><br><span class="line"><span class="javascript">      <span class="keyword">const</span> comA = <span class="keyword">this</span>.$refs.child;</span></span><br><span class="line"><span class="javascript">      <span class="built_in">console</span>.log(comA.name);  <span class="comment">// foo</span></span></span><br><span class="line"><span class="javascript">      comA.say();  <span class="comment">// foo component</span></span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>
<h3 id="3-children-parent"><a href="#3-children-parent" class="headerlink" title="3. $children/$parent"></a>3. $children/$parent</h3><p>在子组件中可以用 <code>this.$parent</code> 访问父组件的实例，而子组件的示例则会被推入父组件示例的 <code>$children</code> 数组中。</p>
<p>与 props/\$emit 一样，这两种方式都是用于父子组件的通信，但是 props 的通信方式更加普遍，而且官方也建议节制地使用 $children/$parent, 更多的应该作为应急方案使用。</p>
<h3 id="4-provide-inject"><a href="#4-provide-inject" class="headerlink" title="4. provide/inject"></a>4. provide/inject</h3><p><code>$parent</code> property 无法很好的扩展到更深层级的嵌套组件上。Vue2.2 之后提供了两个新的示例选项 <code>provide</code> 和 <code>inject</code>。父组件通过 <code>provide</code> 来提供变量，子组件通过 <code>inject</code> 来注入变量，父组件借此可以将值注入到深层次的子组件中。</p>
<p>下面看就看一个例子。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">// foo.vue</span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">bar</span>&gt;</span><span class="tag">&lt;/<span class="name">bar</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">  <span class="keyword">import</span> Bar <span class="keyword">from</span> <span class="string">'.bar.vue'</span></span></span><br><span class="line"><span class="javascript">  <span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="javascript">    name: <span class="string">"Foo"</span>,</span></span><br><span class="line"><span class="undefined">    provide: &#123;</span></span><br><span class="line"><span class="javascript">      <span class="keyword">for</span>: <span class="string">"demo"</span></span></span><br><span class="line"><span class="undefined">    &#125;,</span></span><br><span class="line"><span class="undefined">    components:&#123;</span></span><br><span class="line"><span class="undefined">      bar</span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// bar.vue</span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    &#123;&#123;demo&#125;&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">comC</span>&gt;</span><span class="tag">&lt;/<span class="name">comC</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">  <span class="keyword">import</span> baz <span class="keyword">from</span> <span class="string">'./baz.vue'</span></span></span><br><span class="line"><span class="javascript">  <span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="javascript">    name: <span class="string">"Bar"</span>,</span></span><br><span class="line"><span class="javascript">    inject: [<span class="string">'for'</span>],</span></span><br><span class="line"><span class="undefined">    data() &#123;</span></span><br><span class="line"><span class="javascript">      <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="javascript">        demo: <span class="keyword">this</span>.for</span></span><br><span class="line"><span class="undefined">      &#125;</span></span><br><span class="line"><span class="undefined">    &#125;,</span></span><br><span class="line"><span class="undefined">    components: &#123;</span></span><br><span class="line"><span class="undefined">      baz</span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// baz.vue</span><br><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    &#123;&#123;demo&#125;&#125;</span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">  <span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="javascript">    name: <span class="string">"Baz"</span>,</span></span><br><span class="line"><span class="javascript">    inject: [<span class="string">'for'</span>],</span></span><br><span class="line"><span class="undefined">    data() &#123;</span></span><br><span class="line"><span class="javascript">      <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="javascript">        demo: <span class="keyword">this</span>.for</span></span><br><span class="line"><span class="undefined">      &#125;</span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>另外的对于非父子组件之间的复杂状态的管理，建议引入 eventBus 和 Vuex 进行管理。这两种方式将在其他文章中进行介绍。</p>
<h2 id="组件-v-model"><a href="#组件-v-model" class="headerlink" title="组件 v-model"></a>组件 v-model</h2><p>Vue 2.0 中 v-model 内置的 <code>v-model</code> 指令是一个语法糖，可以拆解为 props:value 和 events:input。只要在组件中提供一个名为 value 的 props，以及名为 input 的自定义事件，满足这两个条件，父组件中就能在子组件上使用 <code>v-model</code> 指令。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">"changeValue(-1)"</span>&gt;</span>-1<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;&#123;val&#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">"changeValue(1)"</span>&gt;</span>+1<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="undefined">  props: &#123;</span></span><br><span class="line"><span class="undefined">    value: &#123;</span></span><br><span class="line"><span class="javascript">      type: <span class="built_in">Number</span></span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;,</span></span><br><span class="line"><span class="undefined">  data() &#123;</span></span><br><span class="line"><span class="javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="javascript">      val: <span class="keyword">this</span>.value</span></span><br><span class="line"><span class="undefined">    &#125;;</span></span><br><span class="line"><span class="undefined">  &#125;,</span></span><br><span class="line"><span class="undefined">  methods: &#123;</span></span><br><span class="line"><span class="undefined">    changeVal(val) &#123;</span></span><br><span class="line"><span class="javascript">      <span class="keyword">this</span>.val += <span class="built_in">parseInt</span>(val);</span></span><br><span class="line"><span class="javascript">      <span class="keyword">this</span>.$emit(<span class="string">"input"</span>, <span class="keyword">this</span>.val); <span class="comment">// 定义input事件</span></span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;</span></span><br><span class="line"><span class="undefined">&#125;;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>只需要通过以下方式就能使用 <code>v-model</code></p>
<p><code>&lt;counter v-model=&quot;val&quot;/&gt;</code></p>
<p>使用 <code>v-model</code> 指令可以方便的在子组件中同步父组件的数据。2.2 后的版本中，可以定制 <code>v-model</code> 指令的 prop 和 event 名称。参考下面的代码</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  model: &#123;</span><br><span class="line">    prop: <span class="string">'value'</span>,</span><br><span class="line">    event: <span class="string">'input'</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="Vue-动态编译模板"><a href="#Vue-动态编译模板" class="headerlink" title="Vue 动态编译模板"></a>Vue 动态编译模板</h2><p>思考一下以下需求：在多语言需求的项目中，我们需要在页面上显示的某一段文案显示一个按钮，并且这个按钮点击后有相应的点击事件触发。如下面代码所示，ID 为 <code>myBtn</code> 的按钮就是在文案中定义的按钮。</p>
<p><code>{ text: &#39;I\&#39;m the &lt;button id=&quot;myBtn&quot;&gt;Button&lt;/button&gt;&#39; }</code></p>
<p>我们可以将整段文案当成 html 渲染，并在组件的 <code>mounted</code> 事件后获取改按钮，并注册点击事件。代码如下所示。那有没有其他方式实现呢，操作 DOM 元素总显得有点不够优雅。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">v-html</span>=<span class="string">"$t(text)"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="undefined">  data() &#123;</span></span><br><span class="line"><span class="javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="xml">      text: 'I\'m the <span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">"myBtn"</span>&gt;</span>Button<span class="tag">&lt;/<span class="name">button</span>&gt;</span>'</span></span><br><span class="line"><span class="undefined">    &#125;;</span></span><br><span class="line"><span class="undefined">  &#125;,</span></span><br><span class="line"><span class="undefined">  mounted () &#123;</span></span><br><span class="line"><span class="javascript">    <span class="keyword">const</span> $el = <span class="built_in">document</span>.getElementById(<span class="string">'myBtn'</span>)</span></span><br><span class="line"><span class="javascript">    $el.addEventListener(<span class="string">'click'</span>, handleClick.bind(<span class="keyword">this</span>))</span></span><br><span class="line"><span class="undefined">  &#125;,</span></span><br><span class="line"><span class="undefined">  methods: &#123;</span></span><br><span class="line"><span class="undefined">    handleClick () &#123;</span></span><br><span class="line"><span class="javascript">      <span class="built_in">console</span>.log(<span class="string">'click'</span>)</span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;</span></span><br><span class="line"><span class="undefined">&#125;;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>答案是有的，我们可以使用组件的实例属性 <code>template</code> 来动态定义组件的模板，组件中使用 <code>this.$options.template</code> 获取和定义模板。上面的例子可以使用下面的方式代替。注意，我们不需要在 Vue 单文件里定义 template 模块了。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="undefined">  data() &#123;</span></span><br><span class="line"><span class="javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="xml">      text: 'I\'m the <span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">"myBtn"</span> @<span class="attr">click</span>=<span class="string">"handleClick"</span>&gt;</span>Button<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="undefined">    &#125;;</span></span><br><span class="line"><span class="undefined">  &#125;,</span></span><br><span class="line"><span class="undefined">  created () &#123;</span></span><br><span class="line"><span class="javascript">    <span class="keyword">this</span>.$options.template = <span class="keyword">this</span>.text</span></span><br><span class="line"><span class="undefined">  &#125;,</span></span><br><span class="line"><span class="undefined">  methods: &#123;</span></span><br><span class="line"><span class="undefined">    handleClick () &#123;</span></span><br><span class="line"><span class="javascript">      <span class="built_in">console</span>.log(<span class="string">'click'</span>)</span></span><br><span class="line"><span class="undefined">    &#125;</span></span><br><span class="line"><span class="undefined">  &#125;</span></span><br><span class="line"><span class="undefined">&#125;;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;虽然在日常使用 Vue 已经很熟悉了，但是在开发中还是会遇到一些一时解释不了的原因，其实还是自己对原理的理解不够深刻。下面总结了一些在日常开发的遇到的问题的一些思考，还有 Vue 的一些小技巧。&lt;/p&gt;
    
    </summary>
    
    
      <category term="vue" scheme="http://fangxw.me/tags/vue/"/>
    
  </entry>
  
  <entry>
    <title>Babel 插件开发实践</title>
    <link href="http://fangxw.me/2019/01/03/babel-plugin/"/>
    <id>http://fangxw.me/2019/01/03/babel-plugin/</id>
    <published>2019-01-03T07:18:47.000Z</published>
    <updated>2021-06-25T01:03:07.852Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在上一篇介绍 webpack 升级 webpack 4 版本的时候，在最后提到几个在实际项目中遇到的问题里，有一个是在配合 webpack 升级的过程中，vue-loader 需要对应升级到 15.x，但是这个升级导致原有的用 commonjs 写法去 <code>require</code> vue 组件时出错了，原因是在 vue-loader 的 14 版本后 vue 文件导出的模块一定是 esModule。详见这个 <a href="https://github.com/vuejs/vue-loader/issues/1172" target="_blank" rel="noopener">Issue</a>。</p>
<p>Issue 中尤大大提到的解决方案是可以写一个 Babel 插件去解决这个问题。Babel 大家应该都很熟悉，我们写的 ES6 和 JSX 代码都是靠 Babel 转成浏览器兼容的代码。那 Babel 插件呢，下面开始介绍一下 Babel 插件。</p>
<a id="more"></a>
<h2 id="Babel-运行过程"><a href="#Babel-运行过程" class="headerlink" title="Babel 运行过程"></a>Babel 运行过程</h2><p>介绍 Babel 插件前，我们先来看看 Babel 转码的三个处理步骤。首先先介绍一下抽象语法树</p>
<h3 id="抽象语法树（AST）"><a href="#抽象语法树（AST）" class="headerlink" title="抽象语法树（AST）"></a>抽象语法树（AST）</h3><p>在 Babel 的处理过程中的每一步都涉及到创建或是操作抽象语法树，亦称 AST。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">square</span>(<span class="params">n</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> n * n;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面的代码可以被表示成下面的树形结构</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  type: &quot;FunctionDeclaration&quot;,</span><br><span class="line">  id: &#123;</span><br><span class="line">    type: &quot;Identifier&quot;,</span><br><span class="line">    name: &quot;square&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  params: [&#123;</span><br><span class="line">    type: &quot;Identifier&quot;,</span><br><span class="line">    name: &quot;n&quot;</span><br><span class="line">  &#125;],</span><br><span class="line">  body: &#123;</span><br><span class="line">    type: &quot;BlockStatement&quot;,</span><br><span class="line">    body: [&#123;</span><br><span class="line">      type: &quot;ReturnStatement&quot;,</span><br><span class="line">      argument: &#123;</span><br><span class="line">        type: &quot;BinaryExpression&quot;,</span><br><span class="line">        operator: &quot;*&quot;,</span><br><span class="line">        left: &#123;</span><br><span class="line">          type: &quot;Identifier&quot;,</span><br><span class="line">          name: &quot;n&quot;</span><br><span class="line">        &#125;,</span><br><span class="line">        right: &#123;</span><br><span class="line">          type: &quot;Identifier&quot;,</span><br><span class="line">          name: &quot;n&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>其中的每一层类似 <code>{ type: &#39;FunctionDeclaration&#39;, ... }</code> 的结构都被称为节点（Node），一个 AST 可以由单一的节点或是成百上千个节点构成。 它们组合在一起可以描述用于静态分析的程序语法。</p>
<p>字符串形式的 type 字段表示节点的类型，我们后续的插件就是通过 type 来判断节点进行不同的处理</p>
<h3 id="Babel-的处理步骤"><a href="#Babel-的处理步骤" class="headerlink" title="Babel 的处理步骤"></a>Babel 的处理步骤</h3><p>Babel 转码过程分成三个阶段：分析(parse)、转换(transform)、生成(generate)</p>
<p>其中，分析、生成阶段由 Babel 核心完成，而转换阶段，则由 Babel 插件完成，这也是我们下面介绍的重点</p>
<p><strong>分析</strong></p>
<p>Babel读入源代码，经过词法分析、语法分析后，生成抽象语法树（AST）。</p>
<p><strong>转换</strong></p>
<p>经过前一阶段的代码分析，Babel 得到了 AST。在原始 AST 的基础上，Babel 通过插件，对其进行修改，比如新增、删除、修改后，得到新的 AST。</p>
<p><strong>生成</strong></p>
<p>通过前一阶段的转换，Babel 得到了新的 AST，然后就可以逆向操作，生成新的代码。</p>
<p>代码生成其实很简单：深度优先遍历整个 AST，然后构建可以表示转换后代码的字符串。</p>
<h2 id="Babel-插件"><a href="#Babel-插件" class="headerlink" title="Babel 插件"></a>Babel 插件</h2><p>Babel 插件的主要工作就是在转换的步骤中对 AST 中的节点进行新增、删除和修改操作。</p>
<p><strong>Visitor（访问者）</strong></p>
<p>Babel 在递归遍历 AST 语法树时，会访问节点，之所以用访问这个词，是因为有访问者模式这个概念。</p>
<p>访问者(Visitor)是用于 AST 遍历的跨语言模式。简单说他是一个对象，定义了用于在树状结构中获取具体节点的方法。看一下下面的例子</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> MyVisitor = &#123;</span><br><span class="line">  Identifier() &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"Called!"</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>这是一个简单的访问者，把它用于遍历中时，每当在树中遇见一个 Identifier 的时候会调用 Identifier() 方法。</p>
<p>如果我们需要在遇到调用表达式（CallExpression）的时候做一些处理，就可以定义一个 <code>CallExpression</code> 回调方法做相应处理</p>
<p><strong>Path（路径）</strong></p>
<p>AST 中有很多节点，每个节点可能有不同的属性，并且节点之间可能存在关联。path 是个对象，它代表了两个节点之间的关联。你可以在 path 上访问到节点的属性，也可以通过 path 来访问到关联的节点（比如父节点、兄弟节点等）</p>
<p>例如，如果有下面这样一个节点及其子节点︰</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  type: <span class="string">"FunctionDeclaration"</span>,</span><br><span class="line">  id: &#123;</span><br><span class="line">    type: <span class="string">"Identifier"</span>,</span><br><span class="line">    name: <span class="string">"square"</span></span><br><span class="line">  &#125;,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>将子节点 Identifier 表示为一个路径（Path）的话，看起来是这样的：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"parent"</span>: &#123;</span><br><span class="line">    <span class="string">"type"</span>: <span class="string">"FunctionDeclaration"</span>,</span><br><span class="line">    <span class="string">"id"</span>: &#123;...&#125;,</span><br><span class="line">    ....</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">"node"</span>: &#123;</span><br><span class="line">    <span class="string">"type"</span>: <span class="string">"Identifier"</span>,</span><br><span class="line">    <span class="string">"name"</span>: <span class="string">"square"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>路径对象中还会包含添加、更新、移动和删除节点等有关的其他方法</p>
<p>例如我们想替换路径中的节点，可以使用 <code>replaceWith</code> 方法，还有很多其他方法，可以通过 Babel 官方文档查看。</p>
<h3 id="编写插件"><a href="#编写插件" class="headerlink" title="编写插件"></a>编写插件</h3><p>前面介绍的是下面开发我们的插件必备的只是，还有其他未提及的插件相关的知识可以看<a href="https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md" target="_blank" rel="noopener">开发手册</a>。开发插件主要是构建 Visitor，有下面两步</p>
<ul>
<li>确定访问条件</li>
<li>确定转换逻辑</li>
</ul>
<p>但是在构建 Visitor 之前，我们要先分析源文件和目标文件的抽象语法树。通过 <a href="http://astexplorer.net/#/Z1exs6BWMq" target="_blank" rel="noopener">AST explorer</a>清晰地看到我们的语法树</p>
<p><strong>回到背景</strong></p>
<p>说回我们编写插件的背景。在 vue-loader 版本升级后，默认的 vue 单文件导出默认变成了 esModule 模块导出。这就导致了之前我们通过 require 方式引入的 vue 组件</p>
<p><code>const component = require(&quot;./component.vue&quot;)</code></p>
<p>必须变成下面的方式引用</p>
<p><code>const component = require(&quot;./component.vue&quot;).default</code></p>
<p>当然我们可以修改所有源码加上 <code>.default</code> 属性调用，但作为一个有追求的程序员，这样显得不够优雅</p>
<p>通过我们前面了解的 Babel 插件的知识，我们可以优雅的处理这个问题</p>
<p><strong>插件思路</strong></p>
<p>首先分析一下转换前的语法树</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;type&quot;: &quot;CallExpression&quot;,</span><br><span class="line">  &quot;start&quot;: 18,</span><br><span class="line">  &quot;end&quot;: 44,</span><br><span class="line">  &quot;loc&quot;: ...</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;callee&quot;: &#123;</span><br><span class="line">    &quot;type&quot;: &quot;Identifier&quot;,</span><br><span class="line">    &quot;start&quot;: 18,</span><br><span class="line">    &quot;end&quot;: 25,</span><br><span class="line">    &quot;loc&quot;: &#123;</span><br><span class="line">      &quot;start&quot;: &#123;</span><br><span class="line">        &quot;line&quot;: 1,</span><br><span class="line">        &quot;column&quot;: 18</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;end&quot;: &#123;</span><br><span class="line">        &quot;line&quot;: 1,</span><br><span class="line">        &quot;column&quot;: 25</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;identifierName&quot;: &quot;require&quot;</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;name&quot;: &quot;require&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;arguments&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;type&quot;: &quot;StringLiteral&quot;,</span><br><span class="line">      &quot;start&quot;: 26,</span><br><span class="line">      &quot;end&quot;: 43,</span><br><span class="line">      &quot;loc&quot;: &#123;</span><br><span class="line">        &quot;start&quot;: &#123;</span><br><span class="line">          &quot;line&quot;: 1,</span><br><span class="line">          &quot;column&quot;: 26</span><br><span class="line">        &#125;,</span><br><span class="line">        &quot;end&quot;: &#123;</span><br><span class="line">          &quot;line&quot;: 1,</span><br><span class="line">          &quot;column&quot;: 43</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;extra&quot;: &#123;</span><br><span class="line">        &quot;rawValue&quot;: &quot;./component.vue&quot;,</span><br><span class="line">        &quot;raw&quot;: &quot;\&quot;./component.vue\&quot;&quot;</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;value&quot;: &quot;./component.vue&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到 require 调用被转换成 一个 CallExpression，我们需要将 CallExpression 装换成另外一个语句</p>
<p>下面再看看目标代码的语法树</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;type&quot;: &quot;MemberExpression&quot;,</span><br><span class="line">  &quot;start&quot;: 18,</span><br><span class="line">  &quot;end&quot;: 52,</span><br><span class="line">  &quot;loc&quot;: ...,</span><br><span class="line">  &quot;object&quot;: &#123;</span><br><span class="line">    &quot;type&quot;: &quot;CallExpression&quot;,</span><br><span class="line">    &quot;start&quot;: 18,</span><br><span class="line">    &quot;end&quot;: 44,</span><br><span class="line">    &quot;loc&quot;: ...,</span><br><span class="line">    &quot;callee&quot;: &#123;</span><br><span class="line">      &quot;type&quot;: &quot;Identifier&quot;,</span><br><span class="line">      &quot;start&quot;: 18,</span><br><span class="line">      &quot;end&quot;: 25,</span><br><span class="line">      &quot;loc&quot;: ...,</span><br><span class="line">        &quot;identifierName&quot;: &quot;require&quot;</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;name&quot;: &quot;require&quot;</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;arguments&quot;: [</span><br><span class="line">      &#123;</span><br><span class="line">        &quot;type&quot;: &quot;StringLiteral&quot;,</span><br><span class="line">        &quot;start&quot;: 26,</span><br><span class="line">        &quot;end&quot;: 43,</span><br><span class="line">        &quot;loc&quot;:...,</span><br><span class="line">        &quot;extra&quot;: &#123;</span><br><span class="line">          &quot;rawValue&quot;: &quot;./component.vue&quot;,</span><br><span class="line">          &quot;raw&quot;: &quot;\&quot;./component.vue\&quot;&quot;</span><br><span class="line">        &#125;,</span><br><span class="line">        &quot;value&quot;: &quot;./component.vue&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;property&quot;: &#123;</span><br><span class="line">    &quot;type&quot;: &quot;Identifier&quot;,</span><br><span class="line">    &quot;start&quot;: 45,</span><br><span class="line">    &quot;end&quot;: 52,</span><br><span class="line">    &quot;loc&quot;: ...,</span><br><span class="line">      &quot;identifierName&quot;: &quot;default&quot;</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;name&quot;: &quot;default&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;computed&quot;: false</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到我们的 CallExpression 被一个叫 MemberExpression 取代了，<code>property</code> 属性是一个名为 <code>default</code> 的 Identifier。同时 MemberExpression 的 <code>object</code> 对应的正是前面的 MemberExpression，内容基本一样。</p>
<p>按照上面的分析，我们可以在 visitor 的访问过程中，处理 CallExpression 的回调方法，判断调用的的方法名称是 <code>require</code>，且参数是以 <code>.vue</code> 结尾的字符串，我们就可以用一个 MemberExpression 来替换这个表达式。具体我们可以用到 path 中的 <code>replaceWith</code> 方法。</p>
<p>完整的代码如下</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">&#123; types: t &#125;</span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> isRequireCall = <span class="function">(<span class="params">path</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> path.node.callee.name === <span class="string">'require'</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> isRequireVue = <span class="function">(<span class="params">path</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> firstArg = path.node.arguments[<span class="number">0</span>]</span><br><span class="line">    <span class="keyword">if</span> (t.isStringLiteral(firstArg) &amp;&amp; firstArg.value.endsWith(<span class="string">'.vue'</span>)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> isParentMemberExpression = <span class="function">(<span class="params">path</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> path.parent.type === <span class="string">'MemberExpression'</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="comment">// name: "add-vue-module-exports",</span></span><br><span class="line">    visitor: &#123;</span><br><span class="line">      CallExpression(path) &#123;</span><br><span class="line">        <span class="comment">// 判断为 require vue 文件 且 require 没有调用其他属性</span></span><br><span class="line">        <span class="keyword">if</span> ( !isRequireCall(path) || !isRequireVue(path) || isParentMemberExpression(path) ) &#123;</span><br><span class="line">          <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        path.replaceWith(</span><br><span class="line">          t.MemberExpression(</span><br><span class="line">            path.node,</span><br><span class="line">            t.identifier(<span class="string">'default'</span>)</span><br><span class="line">          )</span><br><span class="line">        )</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这个插件在是尤大提供后去了解 Babel 的插件机制后试着写出来的，也体会到了 AST 的强大之处。AST 可以做到很多事情，也让我想起来前段时间有个开发者不满微信小程序不能使用 eval 动态执行脚本，自己写了一个运行在小程序中的 JavaScript 的解释器，其中也离不开 AST。上面记录了编写插件的思路过程，希望对有需要的同学有所帮助。</p>
<p>参考</p>
<ul>
<li><a href="https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md" target="_blank" rel="noopener">Babel 插件手册</a></li>
</ul>
]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;在上一篇介绍 webpack 升级 webpack 4 版本的时候，在最后提到几个在实际项目中遇到的问题里，有一个是在配合 webpack 升级的过程中，vue-loader 需要对应升级到 15.x，但是这个升级导致原有的用 commonjs 写法去 &lt;code&gt;require&lt;/code&gt; vue 组件时出错了，原因是在 vue-loader 的 14 版本后 vue 文件导出的模块一定是 esModule。详见这个 &lt;a href=&quot;https://github.com/vuejs/vue-loader/issues/1172&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Issue&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Issue 中尤大大提到的解决方案是可以写一个 Babel 插件去解决这个问题。Babel 大家应该都很熟悉，我们写的 ES6 和 JSX 代码都是靠 Babel 转成浏览器兼容的代码。那 Babel 插件呢，下面开始介绍一下 Babel 插件。&lt;/p&gt;
    
    </summary>
    
    
      <category term="babel" scheme="http://fangxw.me/tags/babel/"/>
    
  </entry>
  
  <entry>
    <title>Webpack + Vue 多页面项目升级 Webpack 4 以及构建速度优化</title>
    <link href="http://fangxw.me/2018/12/30/webpack4-vue-miltiple-page/"/>
    <id>http://fangxw.me/2018/12/30/webpack4-vue-miltiple-page/</id>
    <published>2018-12-30T04:15:32.000Z</published>
    <updated>2021-06-25T01:03:07.852Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-前言"><a href="#0-前言" class="headerlink" title="0. 前言"></a>0. 前言</h2><p>早在 2016 年我就发布过一篇关于在多页面下使用 Webpack + Vue 的配置的文章，当时是我在做自己一个个人项目时遇到了多页面的配置问题，想到别人也可能遇到跟我同样的问题，就把配置的思路分享出来了，<a href="http://fangxw.me/2016/03/21/Webpack-Vue-MultiplePage/">传送门</a>在这里。</p>
<p>因为那份配置直到现在还有人在关注，同时最近公司帮助项目升级了 Webpack 4，趁机也把之前的配置也升级了一下，顺手加上了 babel 7 的配置，而且博客荒废了这么久，都快 9102 年了，不能连年均一篇博文都不到，所以有了下面的分享。</p>
<a id="more"></a>
<p>下面的配置主要是给在多页面下使用 Webpack 的同学在升级 Webpack 时提供一点思路，多页面的配置思路请点击上面的传送门。</p>
<p>下面代码的地址 <a href="https://github.com/cnu4/Webpack-Vue-MultiplePage" target="_blank" rel="noopener">https://github.com/cnu4/Webpack-Vue-MultiplePage</a></p>
<h2 id="1-Webpack-升级-4-x"><a href="#1-Webpack-升级-4-x" class="headerlink" title="1. Webpack 升级 4.x"></a>1. Webpack 升级 4.x</h2><h3 id="1-1-升级和安装相关依赖"><a href="#1-1-升级和安装相关依赖" class="headerlink" title="1.1. 升级和安装相关依赖"></a>1.1. 升级和安装相关依赖</h3><ul>
<li>webpack 升级</li>
<li>webpack-cli webapck4.x 需要新加的依赖</li>
<li>mini-css-extract-plugin 取代 extract-text-webpack-plugin</li>
<li>其他相关 loader 和 plugin <ul>
<li>css-loader</li>
<li>file-loader</li>
<li>url-loader</li>
<li>vue-style-loader</li>
<li>vue-template-compiler（注意要保持与 vue 版本一直）</li>
<li>html-webpack-plugin@next</li>
</ul>
</li>
</ul>
<h3 id="1-2-修改配置"><a href="#1-2-修改配置" class="headerlink" title="1.2 修改配置"></a>1.2 修改配置</h3><h4 id="mode-构建模式"><a href="#mode-构建模式" class="headerlink" title="mode 构建模式"></a>mode 构建模式</h4><p>设置 mode 构建模式，比如 development 会将 process.env.NODE_ENV 的值设为 development</p>
<h4 id="mini-css-extract-plugin"><a href="#mini-css-extract-plugin" class="headerlink" title="mini-css-extract-plugin"></a>mini-css-extract-plugin</h4><p>删除原 extract-text-webpack-plugin 配置，增加 mini-css-extract-plugin 配置</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="keyword">new</span>  MiniCssExtractPlugin(&#123;</span><br><span class="line">      filename:  <span class="string">'css/[name].css'</span></span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test:<span class="regexp">/\.vue$/</span>,</span><br><span class="line">        loader: <span class="string">'vue-loader'</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123; <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        use: [</span><br><span class="line">          <span class="comment">// 开发模式下使用 vue-style-loader，以便使用热重载</span></span><br><span class="line">          process.env.NODE_ENV !== <span class="string">'production'</span> ?</span><br><span class="line">            <span class="string">'vue-style-loader'</span> : MiniCssExtractPlugin.loader,</span><br><span class="line">          <span class="string">'css-loader'</span> ] &#125;,</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="optimization"><a href="#optimization" class="headerlink" title="optimization"></a>optimization</h4><p>这是 webpack 4 一个比较大的变动点，webpack 4 中删除了 <code>webpack.optimize.CommonsChunkPlugin</code>，并且使用 <code>optimization</code> 中的<code>splitChunk</code>来替代，下面的配置代替了之前的 CommonsChunkPlugin 配置，同意能提取 JS 和 CSS 文件</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  optimization: &#123;</span><br><span class="line">    splitChunks: &#123;</span><br><span class="line">      vendors: &#123;</span><br><span class="line">        name:  <span class="string">'venders'</span>,</span><br><span class="line">        chunks:  <span class="string">'all'</span>,</span><br><span class="line">        minChunks: chunks.length</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="vue-loader-升级"><a href="#vue-loader-升级" class="headerlink" title="vue-loader 升级"></a>vue-loader 升级</h4><p>vue-loader 15 注意要配合一个 webpack 插件才能正确使用</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; VueLoaderPlugin &#125; = <span class="built_in">require</span>(<span class="string">'vue-loader'</span>) </span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  plugins: [ <span class="keyword">new</span> VueLoaderPlugin() ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="html-webpack-plugin-升级"><a href="#html-webpack-plugin-升级" class="headerlink" title="html-webpack-plugin 升级"></a>html-webpack-plugin 升级</h4><p>升级到 <code>next</code>，否则开发下无法正常注入资源文件</p>
<h4 id="文件压缩"><a href="#文件压缩" class="headerlink" title="文件压缩"></a>文件压缩</h4><ul>
<li>optimize-css-assets-webpack-plugin</li>
<li>terser-webpack-plugin</li>
</ul>
<p>压缩的配置也移动到了 optimization 选项下，值得注意的是压缩工具换成了 terser-webpack-plugin，这是 webpack 官方也推荐使用的，估计在 webpack 5 中会变成默认的配置，实测打包速度确实变快了很多。</p>
<p>配置</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">    minimizer: [</span><br><span class="line">      <span class="keyword">new</span> TerserPlugin(&#123; <span class="comment">// 压缩js</span></span><br><span class="line">          cache:  <span class="literal">true</span>,</span><br><span class="line">          parallel:  <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;),</span><br><span class="line">      <span class="keyword">new</span> OptimizeCSSAssetsPlugin(&#123; <span class="comment">// 压缩css</span></span><br><span class="line">        cssProcessorOptions: &#123;</span><br><span class="line">          safe: <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="2-打包速度优化"><a href="#2-打包速度优化" class="headerlink" title="2. 打包速度优化"></a>2. 打包速度优化</h2><p>可以使用下面的插件看看打包时间主要耗时在哪</p>
<p><a href="https://github.com/stephencookdev/speed-measure-webpack-plugin" target="_blank" rel="noopener">speed-measure-webpack-plugin</a></p>
<h3 id="2-1-相关-plugin-开启-parallel-选项"><a href="#2-1-相关-plugin-开启-parallel-选项" class="headerlink" title="2.1 相关 plugin 开启 parallel 选项"></a>2.1 相关 plugin 开启 parallel 选项</h3><p>TerserPlugin 压缩插件可以开启多线程，见上面配置</p>
<h3 id="2-2-HappyPack-和-thread-loader-开启-Loader-多进程转换"><a href="#2-2-HappyPack-和-thread-loader-开启-Loader-多进程转换" class="headerlink" title="2.2 HappyPack 和 thread-loader 开启 Loader 多进程转换"></a>2.2 HappyPack 和 thread-loader 开启 Loader 多进程转换</h3><p>github 的 Demo 中没有引入，有兴趣的同学可以尝试，在一些耗时的 Loader 确实可以提高速度</p>
<p>vue-loader 不支持 HappyPack，官方建议用 thread-loader</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> HappyPack = <span class="built_in">require</span>(<span class="string">'happypack'</span>);</span><br><span class="line"></span><br><span class="line">exports.module = &#123;</span><br><span class="line">  rules: [</span><br><span class="line">    &#123;</span><br><span class="line">      test: <span class="regexp">/.js$/</span>,</span><br><span class="line">      <span class="comment">// 1) replace your original list of loaders with "happypack/loader":</span></span><br><span class="line">      <span class="comment">// loaders: [ 'babel-loader?presets[]=es2015' ],</span></span><br><span class="line">      use: <span class="string">'happypack/loader'</span>,</span><br><span class="line">      include: [ <span class="comment">/* ... */</span> ],</span><br><span class="line">      exclude: [ <span class="comment">/* ... */</span> ]</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">exports.plugins = [</span><br><span class="line">  <span class="comment">// 2) create the plugin:</span></span><br><span class="line">  <span class="keyword">new</span> HappyPack(&#123;</span><br><span class="line">    <span class="comment">// 3) re-add the loaders you replaced above in #1:</span></span><br><span class="line">    loaders: [ <span class="string">'babel-loader?presets[]=es2015'</span> ]</span><br><span class="line">  &#125;)</span><br><span class="line">];</span><br></pre></td></tr></table></figure>
<h3 id="2-3-提前打包公共代码"><a href="#2-3-提前打包公共代码" class="headerlink" title="2.3 提前打包公共代码"></a>2.3 提前打包公共代码</h3><h4 id="DllPlugin"><a href="#DllPlugin" class="headerlink" title="DllPlugin"></a>DllPlugin</h4><p>使用 DllPlugn 将 node_modules 或者自己编写的不常变的依赖包打一个 dll 包，提高速度和充分利用缓存。相当于 splitChunks 提取了公共代码，但 DllPlugn 是手动指定了公共代码，提前打包好，免去了后续 webpack 构建时的重新打包。</p>
<p>首先需要增加一个 webpack 配置文件 <code>webpack.dll.config.js</code> 专门针对 dll 打包配置，其中用到 <code>webpack.DllPlugin</code>。</p>
<p>执行 <code>webpack --config build/webpack.dll.config.js</code> 后，webpack会自动生成 2 个文件，其中<strong>vendor.dll.js</strong> 即合并打包后第三方模块。另外一个 <strong>vendor-mainifest.json</strong> 存储各个模块和所需公用模块的对应关系。</p>
<p>接着修改我们的 webpack 配置文件，在 plugin 配置中增加 <code>webpack.DllReferencePlugin</code>，配置中指定上一步生成的 json 文件，然后手动在 html 文件中引用上一步的 <strong>vendor.dll.js</strong> 文件。</p>
<p>后面如果增删 dll 中的依赖包时都需要手动执行上面打包命令来更新 dll 包。下面插件可以自动完成这些操作。</p>
<h4 id="AutoDllPlugin"><a href="#AutoDllPlugin" class="headerlink" title="AutoDllPlugin"></a>AutoDllPlugin</h4><p>安装依赖 <code>autodll-webpack-plugin</code></p>
<p>AutoDllPlugin 自动同时相当于完成了 DllReferencePlugin 和 DllPlugin 的工作，只需要在我们的 webpack 中添加配置。AutoDllPlugin 会在执行 <code>npm install / remove / update package-name</code> 或改变这个插件配件时重新打包 dll。需要注意的是改变 dll 中指定的依赖包不会触发自动重新打包 dll。</p>
<p>实际打包中生成环境是没问题的，但开发模式下在有缓存的情况下，autodll 插件不会生成新的文件，导致 404，所以在 Demo 中暂时关了这个插件。不过 dll 提前打包了公共文件，确实可以提高打包速度，有兴趣的同学可以研究下开发模式下的缓存问题，欢迎在评论中分享。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports.plugins.push(<span class="keyword">new</span> AutoDllPlugin(&#123;</span><br><span class="line">  inject: <span class="literal">true</span>, <span class="comment">// will inject the DLL bundles to html</span></span><br><span class="line">  context: path.join(__dirname, <span class="string">'.'</span>),</span><br><span class="line">  filename: <span class="string">'[name].dll.js'</span>,</span><br><span class="line">  debug: <span class="literal">true</span>,</span><br><span class="line">  inherit: <span class="literal">true</span>,</span><br><span class="line">  <span class="comment">// path: './',</span></span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="keyword">new</span> TerserPlugin(&#123;</span><br><span class="line">      cacheL <span class="literal">true</span>,</span><br><span class="line">      parallel: <span class="literal">true</span></span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> MiniCssExtractPlugin(&#123;</span><br><span class="line">      filename: <span class="string">'[name].css'</span></span><br><span class="line">    &#125;)</span><br><span class="line">  ],</span><br><span class="line">  entry: &#123;</span><br><span class="line">    vendor: [<span class="string">'vue/dist/vue.esm.js'</span>, <span class="string">'axios'</span>, <span class="string">'normalize.css'</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure>
<h2 id="3-增加-ES6-支持"><a href="#3-增加-ES6-支持" class="headerlink" title="3. 增加 ES6+ 支持"></a>3. 增加 ES6+ 支持</h2><h3 id="3-1-安装依赖"><a href="#3-1-安装依赖" class="headerlink" title="3.1 安装依赖"></a>3.1 安装依赖</h3><ul>
<li>@babel/core</li>
<li>@babel/plugin-proposal-class-properties</li>
<li>@babel/plugin-proposal-decorators</li>
<li>@babel/plugin-syntax-dynamic-import</li>
<li>@babel/plugin-transform-runtime</li>
<li>@babel/preset-env</li>
<li>@babel/runtime</li>
<li>babel-loader</li>
<li>@babel/polyfill</li>
</ul>
<p>由于项目中是第一次配置 babel，一步到位直接使用新版 7，新版 babel 使用新的命名空间 @babel，如果是老项目升级 babel 7，可以使用工具 <a href="https://github.com/babel/babel-upgrade" target="_blank" rel="noopener">babel-upgrade</a>，读一下 <a href="https://babeljs.io/docs/en/v7-migration" target="_blank" rel="noopener">升级文档</a></p>
<p>这里说下上面依赖的作用和升级 babel 7 的改动。</p>
<h4 id="babel-runtime-babel-plugin-transform-runtime"><a href="#babel-runtime-babel-plugin-transform-runtime" class="headerlink" title="@babel/runtime, @babel/plugin-transform-runtime"></a>@babel/runtime, @babel/plugin-transform-runtime</h4><p>新版中 @babel/runtime 只包含了一些 helpers，如果需要 core-js polyfill 浏览器不支持的 API，可以用 transform 提供的选项 <code>{&quot;corejs&quot;: 2}</code> 并安装依赖 <code>@babel/runtime-corejs2</code>。即使默认的 polyfill 没了，但 @babel/plugin-transform-runtime 依然可以为我们分离辅助函数，减少代码体积</p>
<h4 id="babel-polyfill"><a href="#babel-polyfill" class="headerlink" title="@babel/polyfill"></a>@babel/polyfill</h4><p>使用 @babel/runtime 的 polyfill 不会污染全局 API，因为不会改动原生对象的原型，它只是创建一个辅助函数在当前作用于生效，所以诸如 <code>[1, 2].includes(1)</code> 这样的语法也无法被 polyfill。如果不是开发第三方库，可以使用 @babel/polyfill，相反他的 polyfill 会影响到浏览器全局的对象原型</p>
<p>@babel/preset-env 提供了一个 <a href="https://babeljs.io/docs/en/next/babel-preset-env.html#usebuiltins" target="_blank" rel="noopener">useBuiltIns</a> 选项来按需引入 polyfill，而不需要引入全部。另一种方法是直接引用 core-js 包下的特定 polyfill。</p>
<h4 id="stage-presets"><a href="#stage-presets" class="headerlink" title="stage presets"></a>stage presets</h4><p>现在需要手动安装 @babel/plugin-proposal 开头的依赖是因为 babel 在新版中移除了 stage presets，为的是后续更好维护处于 proposal 阶段的语法。想要使用 proposal 阶段的语法需要单独引用对应的 plugin， 上面的配置只加了几个处于 stage 3 阶段的 plugin，老项目建议使用 babel-upgrade 升级，自动添加依赖</p>
<h3 id="3-2-添加配置文件-babelrc"><a href="#3-2-添加配置文件-babelrc" class="headerlink" title="3.2 添加配置文件 .babelrc"></a>3.2 添加配置文件 .babelrc</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"presets"</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">"@babel/preset-env"</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">"modules"</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="attr">"targets"</span>: &#123;</span><br><span class="line">          <span class="attr">"browsers"</span>: [</span><br><span class="line">            <span class="string">"&gt; 1%"</span>,</span><br><span class="line">            <span class="string">"last 2 versions"</span>,</span><br><span class="line">            <span class="string">"ie &gt;= 11"</span></span><br><span class="line">          ]</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">"useBuiltIns"</span>: <span class="string">"usage"</span> // 按需引入 polyfill</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">"plugins"</span>: [</span><br><span class="line">    <span class="string">"@babel/plugin-transform-runtime"</span>,</span><br><span class="line">    <span class="string">"@babel/plugin-syntax-dynamic-import"</span>,</span><br><span class="line">    [<span class="string">"@babel/plugin-proposal-class-properties"</span>, &#123; <span class="attr">"loose"</span>: <span class="literal">false</span> &#125;],</span><br><span class="line">    [<span class="string">"@babel/plugin-proposal-decorators"</span>, &#123; <span class="attr">"legacy"</span>: <span class="literal">true</span> &#125;],</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="3-3-增加-webpack-配置"><a href="#3-3-增加-webpack-配置" class="headerlink" title="3.3 增加 webpack 配置"></a>3.3 增加 webpack 配置</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  modules: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test:  <span class="regexp">/\.js$/</span>,</span><br><span class="line">        loader:  <span class="string">'babel-loader'</span>,</span><br><span class="line">        exclude:  <span class="regexp">/node_modules/</span></span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="4-其他问题"><a href="#4-其他问题" class="headerlink" title="4. 其他问题"></a>4. 其他问题</h2><p>下面是我公司项目中遇到的问题，各位升级过程中如果遇到同样的问题可以参考一下解决思路。</p>
<h3 id="4-1-json-loader"><a href="#4-1-json-loader" class="headerlink" title="4.1 json-loader"></a>4.1 json-loader</h3><p>webpack4 内置的json-loader 有点兼容性问题，安装 json-loader 依赖和更改配置</p>
<p>解决：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  test: <span class="regexp">/\.json$/</span>,  <span class="comment">//用于匹配loaders所处理文件拓展名的正则表达式</span></span><br><span class="line">  use: <span class="string">'json-loader'</span>, <span class="comment">//具体loader的名称</span></span><br><span class="line">  type: <span class="string">'javascript/auto'</span>,</span><br><span class="line">  exclude: <span class="regexp">/node_modules/</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="4-2-vue-loader"><a href="#4-2-vue-loader" class="headerlink" title="4.2 vue-loader"></a>4.2 vue-loader</h3><p>vue-loader 升级到 15.x 后，会导致旧的 commonjs 写法加载有问题，需要使用 <code>require(&#39;com.vue&#39;).default</code> 的方式引用组件</p>
<p>13的版本还可以设置 esModule，14 以后的版本不能设置了，vue 文件导出的模块一定是 esModule</p>
<p>解决：使用 <code>require(&#39;com.vue&#39;).default</code> 或者 <code>import</code> 的方式引用组件</p>
<p><a href="https://github.com/vuejs/vue-loader/issues/1172" target="_blank" rel="noopener">esModule option stopped working in version 14 · Issue #1172 · vuejs/vue-loader · GitHub</a></p>
<p>尤大大建议可以自己写一个 babel 插件，遇到 require vue 文件的时候自动加上 default 属性，这样就不用改动所有代码，我们在项目中也是这样处理的。</p>
<h3 id="4-3-提取公共-css-代码"><a href="#4-3-提取公共-css-代码" class="headerlink" title="4.3 提取公共 css 代码"></a>4.3 提取公共 css 代码</h3><p>scss 中 import 的代码不能被提取到公共 css 中。scss 中的 @import 是使用 sass-loader 处理的，处理后已经变成 css 文件，webpack 已经不能判断是否是同一个模块，所以不能提取到公共的 css 中，但多页面中我们还是希望一些公共的 css 能被提取到公共的文件中。 </p>
<p>解决：将需要提取到公共文件的 css 改到 js 中引入就可以，详见下面 issue</p>
<p><a href="https://github.com/webpack-contrib/mini-css-extract-plugin/issues/49" target="_blank" rel="noopener">mini-css-extract-plugin + sass-loader + splitChunks · Issue #49</a></p>
<h3 id="4-4-mini-css-extract-plugin-filename-不支持函数"><a href="#4-4-mini-css-extract-plugin-filename-不支持函数" class="headerlink" title="4.4 mini-css-extract-plugin filename 不支持函数"></a>4.4 mini-css-extract-plugin filename 不支持函数</h3><p>mini-css-extract-plugin 的 filename 选项不支持函数，但我们有时候还是希望能单独控制公共 css 文件的位置，而不是和其他入口文件的 css 使用一样的目录格式</p>
<p>解决：使用插件 FileManagerPlugin 在构建后移动文件，等 filename 支持函数后再优化</p>
<p><a href="https://github.com/webpack-contrib/mini-css-extract-plugin/issues/143" target="_blank" rel="noopener">feat: allow the option filename to be a function · Issue #143 · webpack-contrib/mini-css-extract-plugin · GitHub</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;0-前言&quot;&gt;&lt;a href=&quot;#0-前言&quot; class=&quot;headerlink&quot; title=&quot;0. 前言&quot;&gt;&lt;/a&gt;0. 前言&lt;/h2&gt;&lt;p&gt;早在 2016 年我就发布过一篇关于在多页面下使用 Webpack + Vue 的配置的文章，当时是我在做自己一个个人项目时遇到了多页面的配置问题，想到别人也可能遇到跟我同样的问题，就把配置的思路分享出来了，&lt;a href=&quot;http://fangxw.me/2016/03/21/Webpack-Vue-MultiplePage/&quot;&gt;传送门&lt;/a&gt;在这里。&lt;/p&gt;
&lt;p&gt;因为那份配置直到现在还有人在关注，同时最近公司帮助项目升级了 Webpack 4，趁机也把之前的配置也升级了一下，顺手加上了 babel 7 的配置，而且博客荒废了这么久，都快 9102 年了，不能连年均一篇博文都不到，所以有了下面的分享。&lt;/p&gt;
    
    </summary>
    
    
      <category term="vue" scheme="http://fangxw.me/tags/vue/"/>
    
      <category term="webpack" scheme="http://fangxw.me/tags/webpack/"/>
    
      <category term="前端工程" scheme="http://fangxw.me/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>CDN 介绍和思考</title>
    <link href="http://fangxw.me/2018/07/01/cdn/"/>
    <id>http://fangxw.me/2018/07/01/cdn/</id>
    <published>2018-07-01T12:20:30.000Z</published>
    <updated>2021-06-25T01:03:07.852Z</updated>
    
    <content type="html"><![CDATA[<p>提起 CDN，大家第一浮现在脑海的应该是利用 CDN 缓存可以提高我们网页的访问速度，也正因为此，在使用了 CDN 仿佛我们就不用去担忧静态资源的访问问题，比如就算在高并发下，也有 CDN 服务器替我们抗住压力。但事实真的是这样的吗？下面我从我在公司实际应用遇到的坑里，深究一下 CDN 的原理，说明 CDN 在高并发也会遇到的问题。下面从 CDN 的原理开始说起。</p>
<a id="more"></a>
<h2 id="CDN-介绍"><a href="#CDN-介绍" class="headerlink" title="CDN 介绍"></a>CDN 介绍</h2><p>CDN（内容分发网络）全称是 Content Delivery Network，建立并覆盖在承载网之上、由分布在不同区域的边缘节点服务器群组成的分布式网络，替代传统以 WEB Server 为中心的数据传输模式。</p>
<p>作用是将源内容发布到边缘节点，配合精准的调度系统；将用户的请求分配至最适合他的节点，使用户可以以最快的速度取得他所需的内容，有效解决Internet网络拥塞状况，提高用户访问的响应速度。</p>
<h3 id="传统网络访问"><a href="#传统网络访问" class="headerlink" title="传统网络访问"></a>传统网络访问</h3><p><img src="https://i.loli.net/2020/11/23/JsM2QmVpH9T6kDt.png" alt="cdn-1.png"></p>
<p>传统的网络访问过程图片见上图（图片来自网络，侵删）。首先我们需要知道真实访问目标站点的 IP 地址，这个时候需要对域名进行解析获得 IP 地址。一般本地计算机会请求本地 ISP 的 DNS 服务器，查询是否有缓存记录，如果有则直接访问结果。如果没有，ISP 的 DNS 服务器会向 DNS 根服务器查询，根域名服务器会返回授权服务器地址，告诉他可以通过这个授权服务器找到你要的 DNS 记录。这中间省掉了一个步骤，就是根域名服务器会先跟顶级域名服务器获取授权服务器的地址并向其发起请求。这时 ISP 向授权服务器获得记录后会缓存在本地以供下次访问，接着就是返回给本地计算机。计算机拿到记录里的 IP 地址后，就可以像目标地址发起请求。</p>
<p>这种传统网络访问带来的问题有</p>
<ul>
<li>所有的请求最后都会到相同的服务器上，服务器压力大</li>
<li>不同地区和运营商访问速度各不相同，有的用户体验会很差</li>
<li>容易遭受网络攻击</li>
</ul>
<h3 id="CDN-的作用"><a href="#CDN-的作用" class="headerlink" title="CDN 的作用"></a>CDN 的作用</h3><p>CDN 就是在用户和服务器之前增加了一层缓存。要知道计算机界的两大难题，一是给变量起名字，另外一个就是缓存了。缓存可以说在计算机界无处不在。</p>
<p>比如 CPU 为了更快从内存中读取数据，在其自己内部设定了缓存，也就是我们常说的一级、二级甚至多级缓存。计算机内存说起来其实也是 CPU 为了更快获取磁盘的数据的而存在的。再到今天的固态硬盘，为了兼顾速度与成本，大部分固态会使用价格昂贵但速度更快的小容量 SLC 固态作为缓存从而加快访问速度。</p>
<p>所以 CDN 也是作为一层缓存，他的加入有什么作用呢？首先当然是可以减轻源站的压力。再者是可以在跨运营商跨地域的网络中加快访问速度。第三是由于网络请求不会直接到源战，从而也在一定程度上保障了网络安全。</p>
<h3 id="使用-CDN-后的访问过程"><a href="#使用-CDN-后的访问过程" class="headerlink" title="使用 CDN 后的访问过程"></a>使用 CDN 后的访问过程</h3><p>前面说了传统的网络访问过程，那么假如 CDN 后的访问过程是怎样的呢？</p>
<p><img src="https://i.loli.net/2020/11/24/ufjV2tiMQBXZ3K7.png" alt="cdn-2.png"></p>
<p>首先本地计算机需要向本地 ISP DNS 服务器迭代查询记录，本地 ISP DNS 服务器在递归的向授权服务器获取记录。只是这次授权服务器不再是直接返回域名对应的源站的 IP 地址，而是返回了该域名配置了的对应的 DNS 服务商的智能调度 DNS 服务器的记录。这个域名的记录类型一般是 CNAME 类型。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&gt;  nslookup image01.onepLus.cn 8.8.8.8</span><br><span class="line"></span><br><span class="line">Non-authoritative answer：</span><br><span class="line">image01.oneplus.cn</span><br><span class="line">canonicalname=image01.oneplus.cn.cdn20.com.</span><br><span class="line">Name： image01.onepLus.cn.cdn20.com</span><br><span class="line">Address ：157.185.184.159</span><br></pre></td></tr></table></figure>
<p>该服务器会根据你的网络情况、地理位置和使用网络供应商等情况，返回一个最合适的 CDN 服务器的节点地址记录。本地 ISP DNS 服务器得到该地址后就会返回给本地计算机，本地计算机再根据该记录对目标 CDN 节点服务器发起请求。</p>
<p>如果该 CDN 节点存在我们想要的资源，即有缓存的话，会直接返回。如果没有，会想源站服务器发起请求并缓存在 CDN 节点服务器，以供下次请求。这里可能会存在多级缓存服务器，如果有的话会一直往上查询直至源站。</p>
<h2 id="高并发下-CDN-的问题"><a href="#高并发下-CDN-的问题" class="headerlink" title="高并发下 CDN 的问题"></a>高并发下 CDN 的问题</h2><h3 id="几个概念"><a href="#几个概念" class="headerlink" title="几个概念"></a>几个概念</h3><p><strong>回源</strong></p>
<p>回源是指浏览器在发送请求的时候，CDN 节点服务器没有我们需要的资源，一层层的向上请求直到源站服务器。除了 CDN 缓存服务器没有我们要的节点，缓存过期、该资源被配置会不缓存资源都有可能导致回源。过多的回源会导致源站服务器大，极端情况下相当于 CDN 节点不存在。</p>
<p><strong>预取</strong></p>
<p>这里引用一下七牛云的定义</p>
<blockquote>
<p>文件预取，也可称为预加热或预缓存，是指新资源提前由 CDN 拉取到 CDN 缓存节点。这样一来，用户第一次访问到该节点时，就可以直接命中缓存，无需重新回源拉取，由此提高用户第一次访问的速度。</p>
</blockquote>
<p>一般的 CDN 服务商都会提供可配置预取文件的方法。</p>
<p>适合在运营活动前对静态资源进行预热</p>
<p><strong>缓存</strong></p>
<p>相比于浏览器强制刷新来使浏览器本地缓存失效，开发者需要通过 CDN 服务商提供的“刷新缓存”操作来达到清理 CDN 边缘节点服务器缓存的目的。</p>
<h3 id="高并发下的问题"><a href="#高并发下的问题" class="headerlink" title="高并发下的问题"></a>高并发下的问题</h3><p>这里说一下我在公司直播项目中真实遇到的场景</p>
<p>我在项目中对直播评论的获取进行了改造，从原来的批量获取评论的方式，改成了使用 Websocket 技术从服务器实时推送评论的方式</p>
<p>在做技术方案的时候，由于知道直播页面的静态资源都配置了 CDN，在我心里 CDN 就想一层防火墙一样能挡住直播的大流量，所以就少了对这块的思考。这里的静态资源主要有 JS、CSS 文件和图片等，图片包括了页面上的静态不变的图片和用户的头像图片。</p>
<p>这里提到了用户的头像，每个用户的头像都是不一样的。也正是这里的不一样导致了悲剧的发生。</p>
<p>在改造上线后直播高峰流量来临的时候，我们发现源站服务器基本没办法操作了，服务器压力到了一个很高的水平。通过后台发现正是用户头像的访问占用的很大一部分的网络带宽！但是当时的我们都没有想到，为什么已经配置的 CDN 的用户头像资源还是大量回源到了源站，导致请求激增。</p>
<p>问题就出在这次的评论拉取的方式改成了 Websocket 服务器主动推送的方式，实时性大大增强。我们想象一下，成千上万的用户在同一时间收到了相同的评论，网页渲染评论的时候同时又去请求了相同的头像图片，这个时候大量的 CDN 节点服务器还没有这批实时的用户头像，于是在同一时间都回源到源站的访问图片资源。</p>
<p>由于直播时太过火爆，评论太多（多的时候一秒钟可以有几十条评论），大量的请求同时回源到了源站服务器；同时由于请求太过实时，CDN 节点还没来得及缓存当前的图片，相同的请求又过来了，所以又导致了回源，源源不断的请求到达了源站，正是这个原因导致了流量的激增。</p>
<h3 id="优化方案"><a href="#优化方案" class="headerlink" title="优化方案"></a>优化方案</h3><p>知道了原因，自然可以做出优化方案。</p>
<p>最简单的方案当然是直接删除头像，或者将用户头像简化成几个随机的简单图片，不少友商正是这么做的。</p>
<p>但是知道了原因，我们还是可以想想其他更优的方式。前面提到了预取的概念，那我们可以把所以用户头像都预取到 CDN 节点上吗？答案是理论是当然是可行的，但是一般的 CDN 服务商对每天的资源预取数量都做了限制，跟用户头像不是一个数量级。所以除非是自建的 CDN 网络，这个方案不太可行。</p>
<p>回过头来想想为什么之前批量获取头像的时候为什么没有这个问题。原来我们在批量的时候设置了定时器每个几秒钟到服务器获取一次，这就把用户的访问给错峰开，一旦有了第一个用户访问后的缓存，就不会导致后续的回源。这种错峰的做法，也是在客户端这一侧优化高并发场景下服务器压力的常见方法。所以借鉴这种做法，我们可以优化评论的展示时间，或者优化头像的展示时间，来错峰开流量。</p>
<h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><p>前面介绍了 CDN 的原理，然后又讲了一个实际项目中遇到的关于 CDN 的问题，想说明的是，有时候我们以为我们对某一项技术有了足够的了解，从而缺少了一些思考，但项目的考验会重重的打我们的脸。所以不要以为自己已经有了很深的认识，在什么时候都不能忘了对技术深入的思考。</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;提起 CDN，大家第一浮现在脑海的应该是利用 CDN 缓存可以提高我们网页的访问速度，也正因为此，在使用了 CDN 仿佛我们就不用去担忧静态资源的访问问题，比如就算在高并发下，也有 CDN 服务器替我们抗住压力。但事实真的是这样的吗？下面我从我在公司实际应用遇到的坑里，深究一下 CDN 的原理，说明 CDN 在高并发也会遇到的问题。下面从 CDN 的原理开始说起。&lt;/p&gt;
    
    </summary>
    
    
      <category term="cdn" scheme="http://fangxw.me/tags/cdn/"/>
    
  </entry>
  
  <entry>
    <title>使用 Github 来管理 Hexo 博客源代码，并使用 Travis 来自动部署</title>
    <link href="http://fangxw.me/2017/05/03/hexo-travis/"/>
    <id>http://fangxw.me/2017/05/03/hexo-travis/</id>
    <published>2017-05-03T15:46:26.000Z</published>
    <updated>2021-06-25T01:03:07.852Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>目前我的博客是部署到 GitHub 和 Coding，出于某些原因国内百度爬虫不能爬去到 GitHub 相关的内容，所以才需要同时部署到国内 Coding。</p>
<p>但每次写完文章都需要部署到两个地方，还是有点繁琐，刚好之前就听过持续集成和相关服务 <code>Travis</code>，可以用来解决这个问题。<strong>下面是具体的配置和步骤</strong>，同时也解决了怎么管理博客源码和定制主题源码的问题。</p>
<a id="more"></a>
<p>另外 Coding 在去年开始在免费使用其 page 服务的情况下，需要在网站显示有关 Coding 的广告文字。关于如何只在使用 Coding 的服务时才显示那段广告文字，可以查看我博客的源码中的解决方案。 </p>
<h2 id="配置和步骤"><a href="#配置和步骤" class="headerlink" title="配置和步骤"></a>配置和步骤</h2><h3 id="管理以及定制-Hexo-主题"><a href="#管理以及定制-Hexo-主题" class="headerlink" title="管理以及定制 Hexo 主题"></a>管理以及定制 Hexo 主题</h3><p>将主题 Fork 到自己的账号下后，在博客源代码根目录使用 <code>git submodule</code> 将主题的仓库作为子仓库</p>
<p><code>git submodule add git@github.com:cnu4/hexo-theme-apollo.git themes/apollo</code></p>
<p>进入主题目录，修改主题后按正常流程 <code>git commit</code>, <code>git push</code> 更新主题的 git 仓库，再到博客根目录 <code>commit</code> 源代码</p>
<h3 id="持续集成"><a href="#持续集成" class="headerlink" title="持续集成"></a>持续集成</h3><p>要求：持续部署到 Github 和 Coding</p>
<h4 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h4><p>将 Hexo 源代码 <code>push</code> 到 Github 的仓库，我是将博客的仓库的 <code>source</code> 分支作为源代码仓库</p>
<p>登录 <code>travis</code> 并使用 github 登录后开启上述源代码的仓库</p>
<p>安装 <code>travis cli</code> 工具后登录，使用一下命令生成私钥的加密文件，以下是 Mac 环境下的命令</p>
<p><code>gem install travis</code></p>
<p><code>travis login</code></p>
<p><code>travis encrypt-file ~/.ssh/id_rsa --add -r /cnu4/cnu4.github.io</code></p>
<p>以上命令会自动在 <code>.travis.yml</code> 文件的 <code>before_install</code> 中添加解密得到私钥的代码，并生成文件 <code>id_rsa.enc</code></p>
<p>创建 <code>.travis</code> 文件夹，在里面新建 <code>ssh_config</code> 文件，填入一下内容，避免第一次连接时的询问</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Host *</span><br><span class="line">StrictHostKeyChecking no</span><br><span class="line">IdentityFile ~/.ssh/id_rsa</span><br><span class="line">IdentitiesOnly yes</span><br></pre></td></tr></table></figure>
<p>将 <code>id_rsa.enc</code> 文件移入 <code>.travis</code> 文件夹</p>
<p>下面是 <code>.travis.yml</code> 配置文件</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">language:</span> <span class="string">node_js</span></span><br><span class="line"><span class="attr">node_js:</span> <span class="number">6.10</span><span class="number">.1</span></span><br><span class="line"><span class="attr">install:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">npm</span> <span class="string">install</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">npm</span> <span class="string">install</span> <span class="string">hexo-cli</span> <span class="bullet">-g</span></span><br><span class="line"><span class="attr">script:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">hexo</span> <span class="string">g</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">hexo</span> <span class="string">d</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">rm</span> <span class="bullet">-rf</span> <span class="string">~/.ssh</span></span><br><span class="line"><span class="attr">branches:</span></span><br><span class="line"><span class="attr">  only:</span></span><br><span class="line"><span class="bullet">  -</span> <span class="string">source</span></span><br><span class="line"><span class="attr">git:</span></span><br><span class="line"><span class="attr">  submodules:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">before_install:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">openssl</span> <span class="string">aes-256-cbc</span> <span class="bullet">-K</span> <span class="string">$encrypted_5b8902d6a626_key</span> <span class="bullet">-iv</span> <span class="string">$encrypted_5b8902d6a626_iv</span></span><br><span class="line"><span class="bullet">  -</span><span class="string">in</span> <span class="string">.travis/id_rsa.enc</span> <span class="bullet">-out</span> <span class="string">~/.ssh/id_rsa</span> <span class="bullet">-d</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">chmod</span> <span class="number">600</span> <span class="string">~/.ssh/id_rsa</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">sudo</span> <span class="string">cp</span> <span class="string">.travis/ssh_config</span> <span class="string">/etc/ssh/ssh_config</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">sudo</span> <span class="string">chmod</span> <span class="number">755</span> <span class="string">/etc/ssh/ssh_config</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">eval</span> <span class="string">"$(ssh-agent -s)"</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">ssh-add</span> <span class="string">~/.ssh/id_rsa</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">git</span> <span class="string">config</span> <span class="bullet">--global</span> <span class="string">user.name</span> <span class="string">"cnu4"</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">git</span> <span class="string">config</span> <span class="bullet">--global</span> <span class="string">user.email</span> <span class="string">"fangxw1004@qq.com"</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">git</span> <span class="string">submodule</span> <span class="string">update</span> <span class="bullet">--init</span> <span class="bullet">--recursive</span></span><br></pre></td></tr></table></figure>
<p>在 <code>before_install</code> 中先解密得到私钥并放到 <code>.ssh</code> 目录下，接下来的 <code>npm install</code> 安装 <code>hexo</code>，然后就是构建和部署命令了</p>
<p>配置中需要注意：</p>
<ul>
<li><p>配置中通过 <code>git: submodules: false</code> 使得 travis 不用 <code>https</code> 的方式处理 git 子模块，否则此时会出现克隆时身份验证不通过的情况</p>
</li>
<li><p>通过将代码 <code>git submodule update --init --recursive</code> 添加在 <code>ssh-add ~/.ssh/id_rsa</code> 之后，使用 <code>ssh</code> 处理子模块</p>
</li>
<li><p>注意加密的 ssh 私钥不能有密码保护，也就是创建私钥的时候不要输入密码</p>
</li>
</ul>
<p>接下来写文章后只需要一下命令便可以完成自动部署到 Github 和 Coding</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git add .</span><br><span class="line">git commit -m <span class="string">"commit message"</span></span><br><span class="line">git push origin <span class="built_in">source</span> <span class="comment"># 我的源文件仓库是 source 分支</span></span><br></pre></td></tr></table></figure>
<h4 id="新环境"><a href="#新环境" class="headerlink" title="新环境"></a>新环境</h4><p>在换了新电脑后，使用以下流程</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> repo_of_source.git <span class="comment"># 克隆源码</span></span><br><span class="line">git submodule init <span class="comment"># 初始化主题</span></span><br><span class="line">git submodule update</span><br><span class="line">hexo new</span><br><span class="line">git add .</span><br><span class="line">git commit</span><br><span class="line">git push origin <span class="built_in">source</span> <span class="comment"># 推送到 source 分支触发自动部署</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;目前我的博客是部署到 GitHub 和 Coding，出于某些原因国内百度爬虫不能爬去到 GitHub 相关的内容，所以才需要同时部署到国内 Coding。&lt;/p&gt;
&lt;p&gt;但每次写完文章都需要部署到两个地方，还是有点繁琐，刚好之前就听过持续集成和相关服务 &lt;code&gt;Travis&lt;/code&gt;，可以用来解决这个问题。&lt;strong&gt;下面是具体的配置和步骤&lt;/strong&gt;，同时也解决了怎么管理博客源码和定制主题源码的问题。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Travis" scheme="http://fangxw.me/tags/Travis/"/>
    
      <category term="持续集成" scheme="http://fangxw.me/tags/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/"/>
    
      <category term="Github" scheme="http://fangxw.me/tags/Github/"/>
    
      <category term="Coding" scheme="http://fangxw.me/tags/Coding/"/>
    
  </entry>
  
  <entry>
    <title>使用 redux 改写的 github-explorer</title>
    <link href="http://fangxw.me/2016/08/22/github-explorer-redux/"/>
    <id>http://fangxw.me/2016/08/22/github-explorer-redux/</id>
    <published>2016-08-22T12:02:21.000Z</published>
    <updated>2021-06-25T01:03:07.852Z</updated>
    
    <content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>刚学习了 redux 不久，恰好看到一个优秀的 react 项目 <a href="https://github.com/trungdq88/github-explorer" target="_blank" rel="noopener">github-explorer</a>，该应用使用了 RxJS 去处理数据流，为了巩固学习便有了使用 redux 改写的想法。</p>
<a id="more"></a>
<p><a href="https://github.com/cnu4/github-explorer-redux" target="_blank" rel="noopener">源码地址</a><br><a href="https://cnu4.github.io/github-explorer-redux" target="_blank" rel="noopener">DEMO地址</a></p>
<h2 id="自定义中间件"><a href="#自定义中间件" class="headerlink" title="自定义中间件"></a>自定义中间件</h2><p>应用中使用了自定义个中间件 <strong>api</strong>，方便编写异步的 action creators。异步 action 可以定义成以下方式</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">loadUserProfileRepos</span> (<span class="params">username</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    types: [USER_PROFILE_REPOS_REQUEST, USER_PROFILE_REPOS_RECEIVED,</span><br><span class="line">      USER_PROFILE_REPOS_FAILURE],</span><br><span class="line">    callAPI: <span class="function"><span class="params">()</span> =&gt;</span> api(<span class="string">'fechURL'</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>中间件接收到这种形式的action，会处理异步请求并在适当的时候 dispatch <code>types</code> 中的各项。</p>
<h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>应用的拉取数据的进度条方面，负责拉取状态 reducer 在接收到诸如 <code>xx_REQUEST</code> 和 <code>xx_RECEIVED</code> 的 actions 后，会更新表示进度条状态的数据。</p>
<p>因为只是巩固redux学习，所以原应用的部分动画效果没有加上。</p>
<p>除了数据流部分，应用大部分都是参考了原应用。</p>
<h2 id="依赖"><a href="#依赖" class="headerlink" title="依赖"></a>依赖</h2><p>使用了redux后加上的依赖</p>
<ul>
<li>redux</li>
<li>react-router-redux</li>
</ul>
<h2 id="Development"><a href="#Development" class="headerlink" title="Development"></a>Development</h2><p>开发</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm install</span><br><span class="line">npm run start</span><br></pre></td></tr></table></figure>
<p>打包</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run dist</span><br></pre></td></tr></table></figure>
<h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul>
<li><a href="https://github.com/trungdq88/github-explorer" target="_blank" rel="noopener">github-explorer</a></li>
<li><a href="http://cn.redux.js.org/index.html" target="_blank" rel="noopener">redux 文档</a></li>
</ul>
]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;介绍&quot;&gt;&lt;a href=&quot;#介绍&quot; class=&quot;headerlink&quot; title=&quot;介绍&quot;&gt;&lt;/a&gt;介绍&lt;/h2&gt;&lt;p&gt;刚学习了 redux 不久，恰好看到一个优秀的 react 项目 &lt;a href=&quot;https://github.com/trungdq88/github-explorer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github-explorer&lt;/a&gt;，该应用使用了 RxJS 去处理数据流，为了巩固学习便有了使用 redux 改写的想法。&lt;/p&gt;
    
    </summary>
    
    
      <category term="react" scheme="http://fangxw.me/tags/react/"/>
    
      <category term="redux" scheme="http://fangxw.me/tags/redux/"/>
    
  </entry>
  
  <entry>
    <title>在多页面项目下使用 Webpack + Vue</title>
    <link href="http://fangxw.me/2016/03/21/Webpack-Vue-MultiplePage/"/>
    <id>http://fangxw.me/2016/03/21/Webpack-Vue-MultiplePage/</id>
    <published>2016-03-21T14:05:51.000Z</published>
    <updated>2021-06-25T01:03:07.852Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote>
<p>这篇是 webpack 1.x 的多页面配置，4.x 的版本<a href="http://fangxw.me/2018/12/30/webpack4-vue-miltiple-page">在这里</a>。<br>但是多页面配置的思路是一样的，变的是配置，所以可以先看这篇配置，实际使用 4.x 版本的配置</p>
</blockquote>
<p>webpack + vue 能很好的完成单页面应用的开发，官方也提供了很多例子和教程。但使用 webpack 能不能用到多页面项目中，同时又能使用 vue 进行模块组件化开发呢？</p>
<p>这里将结合具体的项目，说明一下我是如何配置的。我们希望能在项目里做到</p>
<ul>
<li>在每个业务模块下会有很多页面，每个页面都是一个文件夹，所需的资源文件也都放在这个文件夹下</li>
<li>采用 vue + es6 的方式进行组件模块化开发</li>
<li>生成自动引用 webpack 打包好的 js 文件到项目需要的目录</li>
<li>具有良好的开发支持，拥有如 sourseMap，vue 组件的热替换</li>
</ul>
<a id="more"></a>
<p>下面 DEMO 的代码地址： <a href="https://github.com/cnu4/Webpack-Vue-MultiplePage/tree/v1" target="_blank" rel="noopener">https://github.com/cnu4/Webpack-Vue-MultiplePage</a></p>
<p>下面是我们项目的目录结构</p>
<h2 id="项目目录结构"><a href="#项目目录结构" class="headerlink" title="项目目录结构"></a>项目目录结构</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">├─Application (thinkphp 配置下的结构,可以结合自己项目做修改)</span><br><span class="line">│  └─Home</span><br><span class="line">│      └─View (线上用户访问的.html目录)</span><br><span class="line">│         └─index (生成的一个模块）</span><br><span class="line">│             ├─index.html (同一模块的模板放在一个模块目录下)</span><br><span class="line">│             └─info.html</span><br><span class="line">├─Public (线上资源文件目录)</span><br><span class="line">│  ├─css</span><br><span class="line">│  ├─imgs</span><br><span class="line">│  ├─js</span><br><span class="line">│  └─...</span><br><span class="line">└─source (前端开发目录)</span><br><span class="line">    ├─another (一个业务模块,每个业务下可能有多个页面)</span><br><span class="line">    │  └─index</span><br><span class="line">    │      ├─app.vue</span><br><span class="line">    │      ├─index.html</span><br><span class="line">    │      ├─index.js</span><br><span class="line">    │      └─static (资源文件)</span><br><span class="line">    ├─components (vue组件目录)</span><br><span class="line">    │  ├─A</span><br><span class="line">    │  │ ├─A.vue</span><br><span class="line">    │  │      </span><br><span class="line">    │  └─B</span><br><span class="line">    │    ├─B.vue</span><br><span class="line">    │          </span><br><span class="line">    └─index (一个业务模块,每个业务下可能有多个页面)</span><br><span class="line">        ├─index</span><br><span class="line">        │  ├─app.vue</span><br><span class="line">        │  ├─index.html</span><br><span class="line">        │  ├─index.js</span><br><span class="line">        │  └─static</span><br><span class="line">        └─info</span><br><span class="line">           └─info.html</span><br></pre></td></tr></table></figure>
<h2 id="页面文件"><a href="#页面文件" class="headerlink" title="页面文件"></a>页面文件</h2><p>每个页面都是一个文件夹，所需的资源文件也都放在这个文件夹下，不需要这个页面时，也只需要删除这个文件夹。</p>
<p>下面是 index 模块下的 index 页面</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>index - Vue Webpack Example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- webpack 会将入口 JS 文件引入的 CSS 或者 vue 组件中的 css 生成 style 标签或者生成独立的 css 文件并使用 Link 标签加载它 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">app</span>&gt;</span><span class="tag">&lt;/<span class="name">app</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- webpack 的 HtmlWebpackPlugin 插件会根据入口JS文件生成 script 标签并插入在这里或实现按需加载 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>上面是 index 页面的 html 模板，我们无需引入任何 css 和 js ，webpack 会自动帮我打包引入。</p>
<p>其中的 app 标签是我们的 vue 组件，webpac k的加载器会帮我们处理 js 文件中引入的 vue 组件,这样就能正确处理这个标签。</p>
<p>下面 index 页面对应的 js 入口文件</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">'./app'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> Vue(&#123;</span><br><span class="line">  el: <span class="string">'body'</span>,</span><br><span class="line">  components: &#123; App &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<h2 id="Webpack-配置文件"><a href="#Webpack-配置文件" class="headerlink" title="Webpack 配置文件"></a>Webpack 配置文件</h2><h3 id="用法"><a href="#用法" class="headerlink" title="用法"></a>用法</h3><p>先说下 demo 的运行命令</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 首先安装依赖</span></span><br><span class="line">npm install</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开发模式</span></span><br><span class="line"><span class="comment"># 注意非 Windows 环境在 package.json 将开发模式的命令改成：</span></span><br><span class="line"><span class="comment"># NODE_ENV=production webpack</span></span><br><span class="line">npm run dev</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打包</span></span><br><span class="line">npm run build</span><br></pre></td></tr></table></figure>
<h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>下面是 webpack 的配置文件 webpack.config.js，其中用注释指出了关键配置。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">var</span> webpack = <span class="built_in">require</span>(<span class="string">'webpack'</span>);</span><br><span class="line"><span class="comment">// 将样式提取到单独的 css 文件中，而不是打包到 js 文件或使用 style 标签插入在 head 标签中</span></span><br><span class="line"><span class="keyword">var</span> ExtractTextPlugin = <span class="built_in">require</span>(<span class="string">'extract-text-webpack-plugin'</span>);</span><br><span class="line"><span class="comment">// 生成自动引用 js 文件的 HTML</span></span><br><span class="line"><span class="keyword">var</span> HtmlWebpackPlugin = <span class="built_in">require</span>(<span class="string">'html-webpack-plugin'</span>);</span><br><span class="line"><span class="keyword">var</span> glob = <span class="built_in">require</span>(<span class="string">'glob'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> entries = getEntry(<span class="string">'./source/**/*.js'</span>); <span class="comment">// 获得入口 js 文件</span></span><br><span class="line"><span class="keyword">var</span> chunks = <span class="built_in">Object</span>.keys(entries);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  entry: entries,</span><br><span class="line">  output: &#123;</span><br><span class="line">    path: path.resolve(__dirname, <span class="string">'Public'</span>), <span class="comment">// html,css,js,图片等资源文件的输出路径，将所有资源文件放在 Public 目录</span></span><br><span class="line">    publicPath: <span class="string">'/Public/'</span>,                  <span class="comment">// html,css,js,图片等资源文件的 server 上的路径</span></span><br><span class="line">    filename: <span class="string">'js/[name].[hash].js'</span>,         <span class="comment">// 每个入口 js 文件的生成配置</span></span><br><span class="line">    chunkFilename: <span class="string">'js/[id].[hash].js'</span></span><br><span class="line">  &#125;,</span><br><span class="line">  resolve: &#123;</span><br><span class="line">    extensions: [<span class="string">''</span>, <span class="string">'.js'</span>, <span class="string">'.vue'</span>]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    loaders: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        <span class="comment">// 使用提取 css 文件的插件，能帮我们提取 webpack 中引用的和 vue 组件中使用的样式</span></span><br><span class="line">        loader: ExtractTextPlugin.extract(<span class="string">'style'</span>, <span class="string">'css'</span>)</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment">// vue-loader，加载 vue 组件</span></span><br><span class="line">        test: <span class="regexp">/\.vue$/</span>,</span><br><span class="line">        loader: <span class="string">'vue'</span></span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        <span class="comment">// 使用 es6 开发，这个加载器帮我们处理</span></span><br><span class="line">        loader: <span class="string">'babel'</span>,</span><br><span class="line">        exclude: <span class="regexp">/node_modules/</span></span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.(png|jpg|gif|svg)$/</span>,</span><br><span class="line">        <span class="comment">// 图片加载器，较小的图片转成 base64</span></span><br><span class="line">        loader: <span class="string">'url'</span>,</span><br><span class="line">        query: &#123;</span><br><span class="line">          limit: <span class="number">10000</span>,</span><br><span class="line">          name: <span class="string">'./imgs/[name].[ext]?[hash:7]'</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  babel: &#123;</span><br><span class="line">    presets: [<span class="string">'es2015'</span>],</span><br><span class="line">    plugins: [<span class="string">'transform-runtime'</span>]</span><br><span class="line">  &#125;,</span><br><span class="line">  vue: &#123; <span class="comment">// vue 的配置</span></span><br><span class="line">    loaders: &#123;</span><br><span class="line">      js: <span class="string">'babel'</span>,</span><br><span class="line">      css: ExtractTextPlugin.extract(<span class="string">'vue-style-loader'</span>, <span class="string">'css-loader'</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="comment">// 提取公共模块</span></span><br><span class="line">    <span class="keyword">new</span> webpack.optimize.CommonsChunkPlugin(&#123;</span><br><span class="line">      name: <span class="string">'vendors'</span>, <span class="comment">// 公共模块的名称</span></span><br><span class="line">      chunks: chunks,  <span class="comment">// chunks是需要提取的模块</span></span><br><span class="line">      minChunks: chunks.length</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="comment">// 配置提取出的样式文件</span></span><br><span class="line">    <span class="keyword">new</span> ExtractTextPlugin(<span class="string">'css/[name].css'</span>)</span><br><span class="line">  ]</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> prod = process.env.NODE_ENV === <span class="string">'production'</span>;</span><br><span class="line"><span class="built_in">module</span>.exports.plugins = (<span class="built_in">module</span>.exports.plugins || []);</span><br><span class="line"><span class="keyword">if</span> (prod) &#123;</span><br><span class="line">  <span class="built_in">module</span>.exports.devtool = <span class="string">'source-map'</span>;</span><br><span class="line">  <span class="built_in">module</span>.exports.plugins = <span class="built_in">module</span>.exports.plugins.concat([</span><br><span class="line">    <span class="comment">// 借鉴 vue 官方的生成环境配置</span></span><br><span class="line">    <span class="keyword">new</span> webpack.DefinePlugin(&#123;</span><br><span class="line">      <span class="string">'process.env'</span>: &#123;</span><br><span class="line">        NODE_ENV: <span class="string">'"production"'</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> webpack.optimize.UglifyJsPlugin(&#123;</span><br><span class="line">      compress: &#123;</span><br><span class="line">        warnings: <span class="literal">false</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> webpack.optimize.OccurenceOrderPlugin()</span><br><span class="line">  ]);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="built_in">module</span>.exports.devtool = <span class="string">'eval-source-map'</span>;</span><br><span class="line">  <span class="built_in">module</span>.exports.output.publicPath = <span class="string">'/View/'</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> pages = getEntry(<span class="string">'./source/**/*.html'</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> pathname <span class="keyword">in</span> pages) &#123;</span><br><span class="line">  <span class="comment">// 配置生成的 html 文件，定义路径等</span></span><br><span class="line">  <span class="keyword">var</span> conf = &#123;</span><br><span class="line">    <span class="comment">// html 文件输出路径</span></span><br><span class="line">    filename: prod? <span class="string">'../Application/Home/View/'</span> + pathname + <span class="string">'.html'</span> : pathname + <span class="string">'.html'</span>,</span><br><span class="line">    template: pages[pathname], <span class="comment">// 模板路径</span></span><br><span class="line">    inject: <span class="literal">true</span>,              <span class="comment">// js 插入位置</span></span><br><span class="line">    minify: &#123;</span><br><span class="line">      removeComments: <span class="literal">true</span>,</span><br><span class="line">      collapseWhitespace: <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">if</span> (pathname <span class="keyword">in</span> <span class="built_in">module</span>.exports.entry) &#123;</span><br><span class="line">    conf.chunks = [<span class="string">'vendors'</span>, pathname];</span><br><span class="line">    conf.hash = <span class="literal">false</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 需要生成几个 html 文件，就配置几个 HtmlWebpackPlugin 对象</span></span><br><span class="line">  <span class="built_in">module</span>.exports.plugins.push(<span class="keyword">new</span> HtmlWebpackPlugin(conf));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据项目具体需求，具体可以看上面的项目目录，输出正确的 js 和 html 路径</span></span><br><span class="line"><span class="comment">// 针对不同的需求可以做修改</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getEntry</span>(<span class="params">globPath</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> entries = &#123;&#125;,</span><br><span class="line">    basename, tmp, pathname;</span><br><span class="line"></span><br><span class="line">  glob.sync(globPath).forEach(<span class="function"><span class="keyword">function</span> (<span class="params">entry</span>) </span>&#123;</span><br><span class="line">    basename = path.basename(entry, path.extname(entry));</span><br><span class="line">    tmp = entry.split(<span class="string">'/'</span>).splice(<span class="number">-3</span>);</span><br><span class="line">    pathname = tmp.splice(<span class="number">0</span>, <span class="number">1</span>) + <span class="string">'/'</span> + basename; <span class="comment">// 正确输出 js 和 html 的路径</span></span><br><span class="line">    entries[pathname] = entry;</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="built_in">console</span>.log(entries);</span><br><span class="line">  <span class="keyword">return</span> entries;</span><br></pre></td></tr></table></figure>
<h4 id="开发模式"><a href="#开发模式" class="headerlink" title="开发模式"></a>开发模式</h4><p>运行 <code>npm run dev</code> 开发模式运行 demo</p>
<p>根据 webpack 配置文件中 output 的 publicPath 配置项和 HtmlWebpackPlugin 插件的 filename 配置项</p>
<blockquote>
<p>demo 中 dev 环境下中分别是 /View 和pathname + ‘.html’</p>
</blockquote>
<p>所以 demo 中通过 <code>http://localhost:8080/View/another/index.html</code> 可以访问到 another 模块下的 index 页面</p>
<h4 id="打包"><a href="#打包" class="headerlink" title="打包"></a>打包</h4><p>运行 <code>npm run build</code> 打包，可以看到 Application/Home/View 目录下成功生成了按模块分组的 html 文件，这正是项目需要的。</p>
<p>如 Application/Home/View/index 下的 index.html 文件</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>index - Vue Webpack Example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">"/Public/css/vendors.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>&gt;</span><span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">"/Public/css/index/index.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>&gt;</span><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">app</span>&gt;</span><span class="tag">&lt;/<span class="name">app</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/Public/js/vendors.91e0fac1fd8493060c99.js"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/Public/js/index/index.91e0fac1fd8493060c99.js"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>venders.css 和 venders.js 文件是 webpack 插件帮我们自动生成的公共样式模块和公共 js 模块。打开页面，还能看到其他资源文件也都被正确处理了。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总结一下 webpack 帮我们做了下面几件事</p>
<ul>
<li>使用 vue-loader 使我们能进行组件化开发。</li>
<li>根据项目需求自动生成按模块分组的 html 文件。</li>
<li>自动提取样式文件，并和打包后的 js 文件加入到自动生成的 html 文件。</li>
<li>将 js 打包为不同的入口文件，并使用插件抽取公用模块。</li>
<li>为开发调试提供需要的环境，包括热替换，sourceMap。</li>
</ul>
<p>代码地址： <a href="https://github.com/cnu4/Webpack-Vue-MultiplePage" target="_blank" rel="noopener">https://github.com/cnu4/Webpack-Vue-MultiplePage</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;这篇是 webpack 1.x 的多页面配置，4.x 的版本&lt;a href=&quot;http://fangxw.me/2018/12/30/webpack4-vue-miltiple-page&quot;&gt;在这里&lt;/a&gt;。&lt;br&gt;但是多页面配置的思路是一样的，变的是配置，所以可以先看这篇配置，实际使用 4.x 版本的配置&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;webpack + vue 能很好的完成单页面应用的开发，官方也提供了很多例子和教程。但使用 webpack 能不能用到多页面项目中，同时又能使用 vue 进行模块组件化开发呢？&lt;/p&gt;
&lt;p&gt;这里将结合具体的项目，说明一下我是如何配置的。我们希望能在项目里做到&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在每个业务模块下会有很多页面，每个页面都是一个文件夹，所需的资源文件也都放在这个文件夹下&lt;/li&gt;
&lt;li&gt;采用 vue + es6 的方式进行组件模块化开发&lt;/li&gt;
&lt;li&gt;生成自动引用 webpack 打包好的 js 文件到项目需要的目录&lt;/li&gt;
&lt;li&gt;具有良好的开发支持，拥有如 sourseMap，vue 组件的热替换&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="vue" scheme="http://fangxw.me/tags/vue/"/>
    
      <category term="webpack" scheme="http://fangxw.me/tags/webpack/"/>
    
      <category term="前端工程" scheme="http://fangxw.me/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>Hello World</title>
    <link href="http://fangxw.me/2016/02/21/hello-world/"/>
    <id>http://fangxw.me/2016/02/21/hello-world/</id>
    <published>2016-02-21T13:01:15.000Z</published>
    <updated>2021-06-25T01:03:07.852Z</updated>
    
    <content type="html"><![CDATA[<p> This is the very first post.</p>
 <a id="more"></a>]]></content>
    
    <summary type="html">
    
      &lt;p&gt; This is the very first post.&lt;/p&gt;
    
    </summary>
    
    
      <category term="hello world" scheme="http://fangxw.me/tags/hello-world/"/>
    
  </entry>
  
</feed>
