前后端分离之路 - Vue2项目多入口模板改造方案

yushenhw 8年前
   <p>做前后端分离也有一段时间了,业务一直在用Vue@1.x的多入口方案,也一直懒癌发作没搞2.x的版本。适逢最近在等某宝小程序的构建,由于迟迟不定技术方案,只好暂缓先捯饬一下Vue@2.x项目多入口的构建方案。</p>    <h2>关于项目模板</h2>    <p>项目模板没有选择重新开发,而是直接选用了vue官方模板 vuejs-templates/webpack 。熟悉的开发者应该都了解,这是一个SPA模板,只有一个入口,而现在我们需要把它改成多入口,并且修改添加一些开发功能,以配合Koa-grace时的开发流程。</p>    <p>可用的改造方案已经发布,有兴趣的同学可以 <strong>先体验基于Koa-grace的多入口项目方案</strong> :</p>    <p><strong>Grace-vue-webpack-boilerplate --- </strong> 基于Koa-grace的多入口Vue@2.x项目构建方案 :rocket:</p>    <p>关于官方模板,就不再分享学习的过程,下面仅讨论改造过程中 <strong>遇到的问题</strong> 和 <strong>实施方案的理由</strong> 。</p>    <h2>关于目录结构</h2>    <p>由于模板是为了基于Koa-grace的项目而设计,所以在目录结构上需保持基本的 Koa-grace项目结构 。</p>    <p>以下列举了项目中关键文件夹及文件的结构关系,仅供参考。</p>    <pre>  <code class="language-javascript">.  ├── package.json        // 项目依赖  ├── node_modules  │  ├── mock                // mock数据文件  ├── controller          // node层路由目录  │   ├── defaultCtrl.js  │   └── home.js  │  ├── views               // 静态模板源码目录  ├── static              // 静态资源源码目录  │   ├── image  │   ├── fonts  │   ├── css  │   └── js  │  ├── build               // 编译脚本目录,本次重点改造  │   └── config          // 项目配置,供编译过程  └── vues                // 源码目录,下属文件夹将视作独立的页面入口,‘_’开头的文件夹将被忽略      ├── _components     // 组件库,‘_’开头时将不会被作为入口      ├── demo            // /demo页入口      └── home            // /home页入口          ├── index.js          ├── index.vue          └── router.js</code></pre>    <h2>关于多入口(Multiple Entry)</h2>    <p>调整完文件结构,下面要解决的事情就是,在原有模板的基础上改造多入口方案。</p>    <p>其实这一步很简单,只需拓展webpack配置文件中entry的获取方式即可:</p>    <pre>  <code class="language-javascript">// build/webpack.base.conf.js  /**   * [entries 入口合成器]   * @param  {object} opt 指定的入口,优先级高于自动抓取入口   * @return {object}     返回合成的入口对象   */  function entries (opt) {    var ens = exec('cd ./vues && ls').split('\n').map(function(item) {      var obj = {}      // 将忽略所有以下划线“_”开头的文件夹      if (!/^_[\w-]+$/.test(item)) {        obj[item] = './vues/'+item+'/'      }      return obj    })    return Object.assign.apply(null, [].concat(ens, opt))  }    module.exports = {    entry: entries({      // 此处可以手动指定其他需打包的页面/文件入口      common: [        './static/css/common/reset.less',        './static/css/common/index.less',        './static/js/common/hello.js',      ]    }),    ...  }</code></pre>    <p>以上代码效果相当于:</p>    <pre>  <code class="language-javascript">module.exports = {    entry: {      home: './vues/home/index.js',      demo: './vues/demo/index.js',      common: [        './static/css/common/reset.less',        './static/css/common/index.less',        './static/js/common/hello.js',      ]    },    ...  }</code></pre>    <p>对于多入口项目, 避免了手工更新入口的麻烦,在源码目录中创建入口文件夹并添加源码文件 即可。</p>    <p>以上就完成了入口的改造,但这样还无法将文件按需要的目录结构产出,所以还需要调整产出配置。</p>    <h2>关于文件产出(Files Output)</h2>    <h3>产出规则及需求</h3>    <p>按照项目结构,整理文件产出的需求:</p>    <ul>     <li>js      <ul>       <li>文件:编译后文件名应为build.js</li>       <li>路径:需输出到static/js下,并存放于入口名称的文件夹内,如:static/js/home/build.js</li>      </ul> </li>     <li>css      <ul>       <li>文件:规则同上,如:build.css</li>       <li>路径:规则同上,如:static/css/home/build.css</li>      </ul> </li>     <li>html      <ul>       <li>文件:产出文件名应为inde.html</li>       <li>路径:需输出到views/下,如:views/home/index.html</li>      </ul> </li>    </ul>    <p>改造时直接进行了webpack.prod.conf的全功能配置,webpack.dev.conf进行简化即可(开发阶段无需分离css、压缩代码、生成map、抽取公共依赖等步骤)。</p>    <p>通过查看原项目模板的编译流程可以了解:</p>    <ul>     <li>js:作为entry配置,output直接影响输出路径及文件名</li>     <li>css:entry中配置的部分同上,由vue文件中抽离的部分则需要插件 ExtractTextPlugin 抽离</li>     <li>html:需要插件 HtmlWebpackPlugin 配合入口进行产出,可以使用ejs模板</li>    </ul>    <h3>产出html入口文件</h3>    <p>首先,在webpack文档中可以了解到chunk的概念,它和entry是一一对应的,在多入口项目中尤为重要。</p>    <p>Passing an array of file paths to the entry property creates what is known as a "multi-main entry". This is useful when you would like to <strong>inject multiple dependent files together</strong> and graph their dependencies <strong>into one "chunk"</strong> . --- webpack.js.org/entry-points</p>    <p>其次,需要先产出可用的HTML文件,才能调试其他静态资源的加载。由于多入口化,HTML在产出时需要按chunk进行输出,才能保证对应入口的编译文件引用到对应的HTML中。因此需要按entry初始化对应的HtmlWebpackPlugin:</p>    <pre>  <code class="language-javascript">// build/webpack.prod.conf.js    var webpackConfig = merge(utils.setEntrys(baseWebpackConfig), {      ...    }    //    // build/util.js    function setEntrys (conf) {      ...      var htmlConfig = {        // 压缩HTML选项,dev时不压缩        minify: {          removeComments: isNotDev,          collapseWhitespace: isNotDev,          removeAttributeQuotes: isNotDev        }      }      var entries = Object.keys(conf.entry)      entries.map(function(ent) {        ...        // 根据entry添加新插件到plugins中        conf.plugins.push(new HtmlWebpackPlugin(Object.assign({          // HTML输出地址,形如:/Users/thunf/fe/server/app/demo/views/home/index.html          filename: `${path.resolve(config.base.outputRoot, 'views')}/${ent}/index.html`,          // 允许引用的chunk,包含本身及公共部分          chunks: [ent, 'vendor', 'manifest', 'common'],          // 模板路径,形如:/Users/thunf/fe/server/app/demo/views/_common/_template.ejs          template: path.resolve(projectRoot, 'views/_common/_template.ejs'),          // 自动插入静态资源,由于结合使用了Koa-grace在Node预渲染HTML的功能,所以关闭该功能          inject: false,          // Chunks排序规则          chunksSortMode: 'dependency'        }, htmlConfig)))      });      return conf;    }</code></pre>    <p>现在,我们可以通过Koa-grace提供的服务来打开对应的页面了,但是页面中的静态资源链接还存在问题。</p>    <h3>产出js/css静态资源</h3>    <p>此时需要配置输出的参数 output ,参考webpack文档( webpack.js.org/output | webpack-china.org/output ),可以查看output的说明和配置,其中本次需要使用的参数及有效说明如下:</p>    <p><a href="/misc/goto?guid=4959738422897201348" rel="nofollow,noindex">output.filename</a> : This option <strong>determines the name</strong> of each output bundle.</p>    <p><a href="/misc/goto?guid=4959738422994930041" rel="nofollow,noindex">output.path</a> : The output directory as an <strong>absolute</strong> path.</p>    <p><a href="/misc/goto?guid=4959738423083300477" rel="nofollow,noindex">output.publicPath</a> : This option specifies the <strong>public URL</strong> of the output directory when referenced in a browser.</p>    <p>简单来说,就是:</p>    <ul>     <li>output.filename:决定产出文件的名称和后缀</li>     <li>output.path:影响文件输出的绝对位置(本机输出文件夹的绝对路径)</li>     <li>output.publicPath:决定资源在浏览器中加载的路径(比如添加CDN、指定公开URL)</li>    </ul>    <p>假设我们需要在home页产出的html中生成 /demo/static/js/home/build.js?v=12345 的资源链接,对应以上规则有:</p>    <ul>     <li>output.filename: <strong> static/js/home/build.js?v=12345 </strong></li>     <li>output.path: /Users/thunf/fe/server/app/demo      <ul>       <li>此路径为本地产出目录 <strong>绝对地址</strong> ,只影响本地产出目录,不影响html中输出的链接</li>      </ul> </li>     <li>output.publicPath: <strong> /demo/ </strong>      <ul>       <li>在html输出时,会直接 <strong>添加到filename前</strong> ,生成完整链接</li>       <li><strong>将影响其他所有在html中引用的资源链接</strong></li>      </ul> </li>    </ul>    <p>于是output应按以上规则配置,实际代码中 <strong>复用了路径配置及拼合路径的方法</strong> ,此处就不再进行代码分析。</p>    <h3>文件加戳:Query or fileName</h3>    <p>关于文件加戳的问题,为什么我们选择了 build.js?v=12345 ,而不是 build.12345.js ,是出于以下考虑的:</p>    <p>1、每次改动build都会产出新文件,长久以往会导致线上代码版本过多</p>    <p>2、上线仓储将越来越大,并且不易清理</p>    <p>但实际应用上,Query戳对比fileName戳也有问题存在,比如</p>    <p>1、无法有效利用CDN组合加速文件</p>    <p>2、无法存在多版本</p>    <p>但目前阶段根据业务需求,暂时足够使用,若有需要更改加戳方式的需求,只需在上述 output.filename 处将文件命名方式进行调整即可,此处不再累述。</p>    <h3>其他资源路径问题</h3>    <p>项目模板中目前还有图片资源及字体资源未提及,该两种资源在原模板中已存在loader及引用方式,下面描述碰到的问题及解决方法:</p>    <p>生成图片引用路径</p>    <p>由于在处理图片资源时,并没有很好的理解 output.publicPath 的作用,没有使用形如 /demo/ 的 <strong>相对于服务模式(server-relative)</strong> ,导致始终无法产出期望的文件路径。后来在webpack文档中找到具体的说明,才完成配置。</p>    <p>The value of the option is prefixed to every URL created by the runtime or loaders. Because of this <strong>the value of this option ends with /</strong> in most cases. --- output.publicPath</p>    <p>图片资源引用方式</p>    <p>正常情况下,在.vue文件中可以使用相对路径来引用图片资源,但通过 <strong>配置别名</strong> 及 ~alias 的引用方式将 <strong>更简洁</strong> :</p>    <pre>  <code class="language-javascript"><!-- Same effect, but alias write less -->  <img class="logo" src="../../static/image/logo-grace.png">  <img class="logo" src="~image/logo.png"></code></pre>    <p>配置别名alias如下:</p>    <pre>  <code class="language-javascript">// build/webpack.base.conf.js  module.exports = {    ...    resolve: {      ...      alias: {        'static': resolve('static'),        'image': resolve('static/image'),        'components': resolve('vues/_components')      }    },    ...  }</code></pre>    <p>.vue文件中其他静态资源引用同理</p>    <pre>  <code class="language-javascript"><!-- JS/Component -->  <script>  import Grace from 'components/grace.vue'  </script>  ...  <!-- CSS/LESS -->  <style lang="less">  @import '~static/fonts/iconfont.less';  </style></code></pre>    <h2>关于提升开发效率</h2>    <p>考虑实际开发中,有需要进行诸如本地配置、打开浏览器、创建新文件等重复性操作的场景。</p>    <p>为了进一步</p>    <p>懒癌发作 <strong>提升开发效率</strong></p>    <p>,特别增加了一些增强功能,目前刚开始进行,欢迎抛出宝贵的建议。</p>    <h3>自动配置开发环境</h3>    <p>由于Koa-grace作为前后端分离框架,允许 <strong>同时解析多域名请求到多项目</strong> ,故以往在切换项目开发时,需要手动更改server.json配置及重启服务(官方吐槽:好烦啊喂)。那么既然项目启动需要单独 npm run dev 一次,为何不考虑将项目配置(开发环境)交由项目本身进行呢?</p>    <pre>  <code class="language-javascript">// build/check-server.js  // 匹配grace下配置目录  function matchServerJson(graceRoot) {    return glob.sync(path.join(graceRoot, '*/config/main.development.js')) || []  }  ...  // 匹配grace目录  function findServerFolder(graceRoot, scb, ecb) {    var confMatch = matchServerJson(graceRoot)    ;(1 === confMatch.length) ? callback(scb)({      serverRoot: path.resolve(confMatch[0], '../..'),      serverConf: confMatch[0]    }) : callback(ecb)(confMatch.length)  }</code></pre>    <p>于是本着尽可能</p>    <p>懒癌发作 <strong>让系统自己找</strong></p>    <p>的思想,通过引入glob进行关键文件的路径匹配,来验证当前项目是否符合Koa-grace目录规范,顺便解析出当前Koa-grace启动的文件夹(default: server)名称,并添加到config。</p>    <p>有了server的路径,就可以按路径读取配置文件信息了,顺便也可以验证并添加本项目的配置:</p>    <pre>  <code class="language-javascript">// build/open-browser.js  function lookForHost(hosts, autoOpenBrowser) {    return Object.keys(hosts).filter(function(key, value) {      // if vhost-matched, use the match one      return hosts[key] === config.base.moduleName    })[0] || (autoOpenBrowser && writeHostConf({      // if auto-open & no-vhost-matched, auto set      "127.0.0.1": config.base.moduleName    }) || [      // if no-auto-open & no-vhost-matched, to tip      '> Maybe you have not set vhost to this app: ' + chalk.cyan(config.base.moduleName),      '> Please set ' + chalk.magenta('vhost') + ' in ' + chalk.magenta('/server/config/server.json') + ' like this:',      '  ' + chalk.green( JSON.stringify({        merge: {vhost: {"127.0.0.1": config.base.moduleName } }      }, null, 2).replace(/\n/g, '\n    ') ), '', ''    ])  }</code></pre>    <p>此处代码实现风格比较诡异,其实做了3种情况的判断:</p>    <ul>     <li>如果匹配到本项目的vhost配置,那就使用该host</li>     <li>如果未匹配到,且允许自动打开浏览器,就 <strong> 写入默认配置 {127.0.0.1: moduleName} </strong></li>     <li>如果未匹配到,且不允许自动打开浏览器,就发起提示</li>    </ul>    <h3>自动打开项目首页</h3>    <p>其实这个功能在原项目模板中已经存在,只不过原项目自带server并且只有一个入口,启动时只需打开固定的首页即可。</p>    <p>当变成多入口项目后,就需要面临新的问题:</p>    <ul>     <li>Koa-grace启动的host及端口不确定</li>     <li>首页路径不确定</li>    </ul>    <p>第一个问题,通过上述方案已经可以解决。</p>    <p>第二个问题,暂时需在config配置 autoOpenPage ,需在配置文件中添加如下配置,特此说明。</p>    <pre>  <code class="language-javascript">// build/config/index.js  devConf = {    ...    autoOpenBrowser: true, // 是否自动打开浏览器    autoOpenDelay: 2000,   // 延迟多少ms打开浏览器,koa-grace服务检测到路由文件变化会自动重启    autoOpenPage: 'home',  // 自动打开时的项目入口(路由)    ...  };</code></pre>    <h2>关于ESLint</h2>    <h3>检查你的代码</h3>    <p>Code linting is a type of static analysis.</p>    <p>ESLint is designed to have all rules completely pluggable. --- eslint.org</p>    <p>很早之前就了解过这个玩意了,曾经组里也有几次技术调研涉及这个,一直没正式投入使用过,也就自己搞一搞。</p>    <p>其实开始使用时也是挺蛋疼的(模板自带配置使用标准: feross/standard ,感觉要求超级严格,比如“ { ”后换行并留一行空格都要error),但耐心看一下提示,就能知道格式哪里不标准,然后逐步就习惯了(避免写的时候太飘逸)。</p>    <p>This module helps hold our code to a high standard of quality.</p>    <p>This module ensures that new contributors follow some basic style standards. --- feross/standard</p>    <p>这里吐个槽, 如果团队协作开发一个仓储,图快而不加以规范,简直就是给维护者挖坑 (版本越老坑越大,自己深有体会,靠嘴遁要求统一风格,真心没用,什么诡异的代码风格都有)。</p>    <h3>翻一翻文档</h3>    <p>某:eslint限制太严格</p>    <p>我:嗯,确实严格,习惯就好</p>    <p>某:能不能改成只有提示,但是不影响编译流程的</p>    <p>我:看看文档</p>    <ul>     <li>eslint-loader: <a href="/misc/goto?guid=4959738423172353991" rel="nofollow,noindex">github.com/MoOx/eslint-loader</a></li>     <li>eslint:      <ul>       <li>en: <a href="/misc/goto?guid=4959650077301376092" rel="nofollow,noindex">eslint.org/docs</a></li>       <li>zh: <a href="/misc/goto?guid=4959738423298756208" rel="nofollow,noindex">eslint.cn/docs</a></li>      </ul> </li>    </ul>    <p>研究了一下俩文档,理论上可以有2个方案实现 <strong>只提示而不影响编译流程</strong> ,但是似乎又各自有问题,以下是这两种方案。</p>    <p>配置extends/rules</p>    <p>在ESlint的官方文档里可以了解到 configuring#extending-configuration-files :</p>    <ul>     <li><strong>extends</strong> :      <ul>       <li>a string that specifies a configuration</li>       <li>an array of strings: each additional configuration extends the preceding configurations</li>      </ul> </li>     <li><strong>rules</strong> : which rules are enabled and at what error level      <ul>       <li>enable additional rules</li>       <li>change an inherited rule’s severity without changing its options</li>       <li>override options for rules from base configurations</li>      </ul> </li>    </ul>    <p>接下来通过查看 feross/standard 的配置文件 eslint-config-standard/eslintrc.json 可以得知,这个标准一言不合就error,而且 <strong>全部都是error</strong> (没办法,就是这么任性)。不过 feross/standard 的作者也这么表态了:</p>    <p>The word "standard" has more meanings than just "web standard" :-)</p>    <p>--- FAQ: But this isn't a real web standard!</p>    <p>那么方案也就显而易见:</p>    <ul>     <li>找一份完全符合心意的标准(看起来比较难)</li>     <li>写个插件,然后 <strong>自己定义规则</strong> ( eslint.org/docs/rules | eslint.cn/docs/rules ),添加到extends      <ul>       <li>比如强制缩进就给error,不关心是否空行就给warning,随心所欲so easy</li>      </ul> </li>     <li>在.eslintrc中,通过 <strong>添加rules属性,强行覆盖</strong> 现有extends提供的rules</li>    </ul>    <p>优缺点也一目了然:</p>    <ul>     <li>优:配置化并完全可以自定义,完全可拔插,可持续维护;</li>     <li>缺:想找到一个完全符合心意的标准几乎不可能(若自己写规则,一般人怕是没这心情,虽然规则不是很多,哈)</li>    </ul>    <p>配置failOnWarning</p>    <p>当然先讲一句,我其实 <strong>不推荐</strong> 这种配置,原因在后面说。</p>    <p>首先在eslint-loader文档里,找到这么一句话:</p>    <p>So even ESLint warnings will fail the build. --- eslint-loader#noerrorsplugin</p>    <p>即使是warning也会阻断build过程。但有趣的是,option配置可以改变提示行为,默认值都是false:</p>    <p>emitError: Loader will always return errors if this option is set to true.</p>    <p>emitWarning: Loader will always return warnings if option is set to true.</p>    <p>failOnWarning: Loader will cause the module build to fail if there are any eslint warnings.</p>    <p>failOnError: Loader will cause the module build to fail if there are any eslint errors.</p>    <p>以我的理解,eslint-loader应该是在preLoad阶段,以报错的形式影响webpack编译进程,并显示提示信息。</p>    <p>所以在 eslint-loader的源码 里,找到了这么个地方:</p>    <pre>  <code class="language-javascript">// default behavior: emit error only if we have errors    var emitter = res.errorCount ? webpack.emitError : webpack.emitWarning      // force emitError or emitWarning if user want this    if (config.emitError) {      emitter = webpack.emitError    }    else if (config.emitWarning) {      emitter = webpack.emitWarning    }      if (emitter) {      emitter(messages)      if (config.failOnError && res.errorCount) {        throw new Error("Module failed because of a eslint error.\n"          + messages)      }      else if (config.failOnWarning && res.warningCount) {        throw new Error("Module failed because of a eslint warning.\n"          + messages)      }    }</code></pre>    <p>这段代码表明,正常流程上的warning和error是调用webpack自带的方法来emit的,只不过我们可以通过配置:</p>    <p>1.emitError/emitWarning:取消default behavior,使emitter强制变成error/warning类型</p>    <p>2.failOnWarning/failOnError:抛出一个Error来打断webpack的build过程</p>    <p>那么问题来了,默认的配置 {emitError: false, failOnError: false} ,只是由webpack进行了一次emitError,并没有实际抛出一个Error,那实际上应该 <strong>不会 cause the module build to fail</strong> 才对?但实际开发时,比如为什么“ { ”后换行并留一行空格,不只触发了规则 no-trailing-spaces 的error,还导致webpack没有实际build出新文件呢?</p>    <p>那么基本上可以断定这和webpack.emitError有关系了,来看一下webpack的源码:</p>    <pre>  <code class="language-javascript">// webpack/lib/NormalModule.js    ...      emitWarning: function(warning) {        module.warnings.push(new ModuleWarning(module, warning));      },      emitError: function(error) {        module.errors.push(new ModuleError(module, error));      },    ...</code></pre>    <p>这里API功能是收集error和warning,那么理论上还有地方,会根据error和warning数量决定是否可以继续编译。</p>    <p>中间调试的过程就不累述,最终在webpack的Compiler.js中先找到了判定emit并return的地方:</p>    <pre>  <code class="language-javascript">// webpack/lib/Compiler.js    ...    if(self.compiler.applyPluginsBailResult("should-emit", compilation) === false) {      return self._done(null, compilation);    }    ...</code></pre>    <p>经过寻找 should-emit 在哪里触发,然后找到在NoEmitOnErrorsPlugin中,处理errors数量的逻辑:</p>    <pre>  <code class="language-javascript">// webpack/lib/NoEmitOnErrorsPlugin.js    ...    compiler.plugin("should-emit", (compilation) => {      if(compilation.errors.length > 0)        return false;    });    ...</code></pre>    <p>此处可以看出,若errors中存在error,NoEmitOnErrorsPlugin会返回false,而对warning没有处理。这一行为会导致Compiler.js中的compile流程被中断,当然就不会有文件产出啦。</p>    <p>那么为了验证这个逻辑,将NoEmitOnErrorsPlugin从项目build/webpack.dev.conf.js中注释掉之后,带着已知的格式问题(如多个空行,不影响实际编译),仍然可以编译出新文件,有兴趣的小伙伴可以自行尝试。当然这个插件在dev环境开发时还会影响其他loader的表现,不建议砍掉它。</p>    <p>那么回到我们的问题上来,通过强制eslint触发emitWarning而不是emitError,即在eslint的option中配置 {emitWarning: true} , <strong>格式检测规则(编译无关的规则)</strong> 就不会终止webpack编译产出文件了:</p>    <pre>  <code class="language-javascript">...    {      test: /\.(js|vue)$/,      loader: 'eslint-loader',      ...      options: {        ...        /* ======= begin ====== */         emitWarning: true        /* =======  end  ====== */       }    },    ...</code></pre>    <p>当然如果是会引起编译的错误,在接下来的编译中该断还是得断,还有可能产生多次log(毕竟没有被eslint在preLoad过程中拦下来,eslint提示完,后面的loader还会继续提示,比如重复显示error,调试过程简直最强大脑有木有)</p>    <p>所以尽管可以实现 <strong>只提示而不影响编译流程</strong> 的目标,但又带来了新问题,虽然简单快捷但并不优雅,也没有让eslint发挥出什么作用(对非强迫症患者,仅提示还是拦不住的各种诡异风格的)。</p>    <p>这就是不推荐的理由。总之blabla这么多,就是想说下面这句话。</p>    <h3>不想用就别硬用</h3>    <p>实在不能接受,那就干掉吧,推荐下面的方式。</p>    <ul>     <li>1、 Fork It And Make Your Own</li>     <li>2、Comment out or delete the codes below (the part between comments): <pre>  <code class="language-javascript">// build/webpack.base.conf.js  ...  module: {    rules: [      /* ==================== eslint =================== */       /* If eslint makes you mad, just delete these code */      {        test: /\.(js|vue)$/,        loader: 'eslint-loader',        enforce: "pre",        include: [resolve('vues'), resolve('test')],        options: {          formatter: require('eslint-friendly-formatter')        }      },      /* ================== eslint end ================= */      ...    ]    ...  }  ...</code></pre> </li>     <li>3、Enjoy your code : )</li>    </ul>    <h2>其他问题</h2>    <h3>报错信息不显示</h3>    <p>有小伙伴反应这一现象,如下图所示。其实这跟个人使用的terminal配色方案有关(webpack显示错误代码的颜色,正好跟terminal的背景色相同,就看不到了嘛),。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/849f4a9aba792722855947fbd5dcd136.png"></p>    <p>经过调试,发现这种提示是由 eslint-friendly-formatter 打印出来的,根据源码显示:</p>    <pre>  <code class="language-javascript">...    var parseBoolEnvVar = function(varName) {      var env = process.env || { };      return env[varName] === 'true';    };    var subtleLog = function(args) {      return parseBoolEnvVar('EFF_NO_GRAY') ? args : chalk.gray(args);    };    ...</code></pre>    <p>avoidTerminalColorGray</p>    <p>只需配置 process.env.EFF_NO_GRAY = true 即可阻止使用chalk.gray显示文字。</p>    <p>那么这个配置也将 <strong>加入项目的配置文件,如下配置</strong> 即可</p>    <pre>  <code class="language-javascript">// build/config/index.js    ...    devConf = {      ...      avoidTerminalColorGray: true    };    ...</code></pre>    <h2>NEXT</h2>    <h3>抽取代码(TODO)</h3>    <p>有时间将抽离 <strong>提升开发效率</strong> 部分的逻辑为插件,以帮助更多其他技术栈的Koa-grace项目更好的提升开发体验。</p>    <h3>自动创建新入口(TODO)</h3>    <p>已经有同学提出需要自动创建新入口的脚本及命令,这个近期也会提供(每次添加新入口要创建好几个文件夹及文件也是麻烦)</p>    <h3>热加载(TODO)</h3>    <p>该功能在原模板中存在,但升级多入口后,server将由Koa-grace接管,目前已移除该部分代码,下一步考虑添加Koa-grace的中间件,来配合完成vue项目的热加载方案</p>    <h3> </h3>    <p> </p>    <p>来自:http://feclub.cn/post/content/grace-vue-boilerplate</p>    <p> </p>