├── img ├── MomentJs.png ├── fileName.png ├── dateCompare.png └── bundle-analyze.png ├── README.md └── docs ├── ajaxLoading.md ├── vue-cli3-introduce.md ├── translate └── This Is My 10 Questions React Code Reviewing Routine.md ├── vue-cli3-upgrade2.md ├── vue-cli3-plugin.md └── vue-cli3-upgrade.md /img/MomentJs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeDebugTest/blog/master/img/MomentJs.png -------------------------------------------------------------------------------- /img/fileName.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeDebugTest/blog/master/img/fileName.png -------------------------------------------------------------------------------- /img/dateCompare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeDebugTest/blog/master/img/dateCompare.png -------------------------------------------------------------------------------- /img/bundle-analyze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeDebugTest/blog/master/img/bundle-analyze.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blog 2 | ## 译文 3 | - [React code review 日常十问](https://github.com/codeDebugTest/blog/blob/master/docs/translate/This%20Is%20My%2010%20Questions%20React%20Code%20Reviewing%20Routine.md) 4 | 5 | ## 博文 6 | - [ajax 请求时loading菊花显示问题](https://github.com/codeDebugTest/blog/blob/master/docs/ajaxLoading.md) 7 | - [vue老项目升级vue-cli3指南(一):vue cli3 介绍](https://github.com/codeDebugTest/blog/blob/master/docs/vue-cli3-introduce.md) 8 | - [vue老项目升级vue-cli3指南(二):vue cli3 升级手册](https://github.com/codeDebugTest/blog/blob/master/docs/vue-cli3-upgrade.md) 9 | - [vue老项目升级vue-cli3指南(三):vue cli3 打包优化](https://github.com/codeDebugTest/blog/blob/master/docs/vue-cli3-upgrade2.md) 10 | - [vue-cli3 模版插件开发指南](https://github.com/codeDebugTest/blog/blob/master/docs/vue-cli3-plugin.md) 11 | - [使用vue-cli 搭建 vue-ssr指南](https://github.com/codeDebugTest/blog/issues/4) 12 | 13 | ## todo 14 | * vue-cli 详解 15 | -------------------------------------------------------------------------------- /docs/ajaxLoading.md: -------------------------------------------------------------------------------- 1 | # ajax 请求时loading菊花显示问题 2 | 3 | ## 背景 4 | > 前端通过ajax技术可以在不刷新整个页面的情况下,向后台获取数据达到局部更新目的。但同时在请求没有返回数据时页面通常不可操作。为避免用户操作以及等待而失去耐心,我们通常会显示loading菊花来避免问题的发生。 5 | ## 问题 6 | 但在引入了loading‘菊花’后,同时也会引入其他方面的问题。 7 | 1. 多个请求并发时,怎样知道请求结束从而消失‘菊花’。 8 | 2. 假如一个请求时间很短如200ms, 怎样避免‘菊花’闪烁。 9 | 3. 串联请求时,怎样保证‘菊花’一直出现,而不是过多闪烁。 10 | ## 方案 11 | - **方案一**: 对于问题一,一种方案是找到时间最长的那个就可以了。但这不符合我们的背景,我们无法网络请求时间。某个请求这次时间短,不代表下次时间也短。自然会想到计数器的方案每发送一个请求时计数器加一,结束时减一,计数器为零时不显示菊花。我们会在请求工厂类增加 beforeRequestHook, afterResponseHook两个方法来实现计数器功能。如果用axios去发送请求的话,可以在拦截器interceptors.request,interceptors. response快速实现此功能。思考:问题二,三并没有解决调。 12 | 13 | - **方案二**: 问题二,直接舍弃小于300ms(这个时间较难以界定,长短都不合适,300ms稍微合适吧)。怎么做嫩?我们可以延时计数如: 14 | ``` javascript 15 | let timer; 16 | async () => { 17 | clearTimeout(timer); 18 | timer = null; 19 | timer =setTimeout(() => counter++, 300); 20 | ... ... 21 | await request(); 22 | ... ... 23 | clearTimeout(timer); 24 | timer = null; 25 | if (counter > 0) { // 一定大于0,用counter无法排除小于0状况 26 | counter--; 27 | } 28 | } 29 | ``` 30 | 上述timer 之所以放在外面是为了避免### 同一个请求连续发送n次的状况。只要记录最后一个请求时间就可以了。所以发送请求前会cleartime。后面那个cleartime 消除<300ms的timer。该解决方案还是无法避免问题3 所带来闪烁。 31 | 32 | - **方案三**: 既然掐头不行,何不考虑去尾。对于每个请求都延时处理,也就是增加显示时间。做法:reponse时延时计数器自减,这样如果后面紧接着请求由于计数器不为零所以不会导致闪烁。缺点是本来很短的请求被强制等待菊花消失。code: 33 | ``` javascript 34 | async () => { 35 | let timer; 36 | ... ... 37 | await request(); 38 | ... ... 39 | timer = setTimeout(() => { 40 | couter--; 41 | clearTimeout(timer); 42 | }, 300); 43 | } 44 | ``` -------------------------------------------------------------------------------- /docs/vue-cli3-introduce.md: -------------------------------------------------------------------------------- 1 | # vue老项目升级vue-cli3指南(一):vue cli3 介绍 2 | 3 | ## 前言 4 | - vue cli3 发布已有段时间了,相比 vue-cli 2.X 创建的目录: vue-cli 3 创建的目录不见了 webpack 的配置,config目录也被移除,从而减少了维护成本。同时 webpack也升级到了4,性能有了很大提升(编译,打包速度很快)。最大的好处还是不需eject,通过升级 CLI service 和插件便可进行修复或更新配置。因此本次升级可谓一劳永逸。 5 | - 本篇内容主要介绍vue cli3整个升级过程以及升级中遇到的问题及解决方案(webpack-chain,加载器,插件方面)。希望对自己或者他人在后续升级时有些许的帮助。 6 | 7 | 8 | ## vue cli3 特性及变化 9 | ### vue.config.js 10 | - 这是一个**可选**配置文件,目的减少webpack 配置,让开发人员集中在业务代码中。 11 | - 如果你的项目是多入口或者要做 splitChunks优化或者有特殊的loader或plugin处理等情况则需要增加此文件完成你的配置。亦或是在package.json中增加vue的json配置。 12 | - **建议用vue.config.js形式进行配置。** 13 | 14 | ### Babel 15 | - 因为Vue CLI 使用了 Babel 7 中的新配置格式 ,所以项目需要通过** babel.config.js** 进行配置。.babelrc 或 package.json 中的 babel 字段配置不再生效。 16 | - 另外通过引入** "@vue/cli-plugin-babel"** devDependency (依赖了**"@vue/babel-preset-app"**它包含了 babel-preset-env、JSX 支持以及为最小化包体积优化过的配置),去除对**"transform-vue-jsx", "transform-runtime", "babel-loader"**的依赖。 17 | 18 | ### index 文件 19 | - 根目录下的index.html默认移入到 public/index.html中 20 | - 客户端环境变量可直接被使用,如BASE_URL:` 21 | ` 22 | - 放置在 public 目录下的资源将会直接被拷贝,**而不会经过 webpack 的处理**。并通过通过 <%= BASE_URL %> 设置链接前缀。如: 23 | ``` html 24 | 25 | ``` 26 | ### 环境变量和模式 27 | - 模式:默认** 'development' 'production' 'test'** 三种模式。可通过 ** --mode** 选项参数覆盖默认的模式 如:`"build-off": "vue-cli-service build --mode off"` 为off 模式 28 | - 特殊环境变量 NODE_ENV:体的值取决于运行的模式。上述例子 NODE_ENV的值为 'off' 29 | - 特殊环境变量 BASE_URL: 等于 vue.config.js 中baseUrl 选项值(默认'/'),即应用部署的基础路径。 30 | - 自定义变量:**必须以VUE_APP_ 为开头定义变量**,只有这样才能在代码中访问如:VUE_APP_SDK_CONFIG在代码中可通过 process.env.VUE_APP_SDK_CONFIG 访问。 31 | - 自定义变量可在 .env 文件中以“键=值”对的形式进行定义。这样可在所有的环境中被载入进行访问。如果需要在在指定的模式下载入 需要以 `.env.[mode]`定义文件名。 -------------------------------------------------------------------------------- /docs/translate/This Is My 10 Questions React Code Reviewing Routine.md: -------------------------------------------------------------------------------- 1 | # 翻译 - React code review React 日常十问 2 | 3 | ## 原文地址:[This Is My 10 Questions React Code Reviewing Routine](https://www.chakshunyu.com/blog/this-is-my-10-questions-react-code-reviewing-routine/) 4 | ## 原文作者:Chak Shun Yu 5 | 6 | 无论你是什么语言的开发者,作为团队中的一员 code review 已是你日常工作的一部分。作为一个React开发人员也不例外。有很多资源可以教你如何优雅地写 React 代码,但是很少有文章,视频或者教程指导你Review React代码。 7 | 8 | 尽管review 同事代码是我们程序员的一项重要责任,但是这并不是许多程序员所期待的责任。比起写代码,许多程序员觉得code review枯燥无味,没有意义,觉得自己就是个‘看门’的,帮同事把把关。 9 | 10 | 就我个人而言,过去我也持有相同的观点,不太喜欢code review。但是作为一名React程序员,我在花了大量时间去code review并且深入研究后,意识到之所以不愿意code review是因为我不知道如何正确地review React代码。 11 | 12 | 通常我都是打开同事提交的代码,机械地从头到尾看一遍,添加一些我注意到的问题的评论。毫不夸张地说我不喜欢code review,并且review地质量也不高 13 | 14 | 但现在我都是有计划以及针对性去 code review。自从我开始这样做之后,code review变得不再无聊,并且我的评论得到了同事的一致好评。 15 | 16 | 我甚至喜欢看同事的代码,通过review 他们的代码,给我提供了一种了解他们编码风格,理解他们代码,询问有关代码的问题,学习新东西的方式。最重要的是为了提高代码质量,code review 提供了一个反馈机制。 17 | 18 | 本文给大家分享一些我在code review时,我经常询问自己的一些问题。如果你不确定如何review react代码,或者不知道该关注什么,那么这些问题将对你有所帮助。通过这些基础,可以使你建立自己的review 清单,并开始有意识地进行code review,同时为你的团队提供有价值的代码评论,甚至有可能开始享受code review。 19 | 20 | ## 1. 代码能否运行? 21 | code review最重要地先确定代码能够正常运行。这并不是一件非常容易验证的事情。大部分情况下你将依赖于持续集成(CI)或者你自己本地骑服务验证代码能否正常运行。同时代码在合入时也要能够跑起来。 22 | 23 | 因此,你在code review时质疑代码能否正常运行没啥坏处。最差也就是你能够更好地理解代码。更好的是,你将会注意到代码提交时遗漏了一些细节,而这些细节则有助于提高代码质量。 24 | 25 | ## 2. 你能理解代码实现的功能吗? 26 | 通常,程序员会像代码检查工具或者静态分析工具一样review代码。他们只关注代码的实现以及是否正确地是实现。问题在于一个静态分析工具可以更快,更高效,更可靠地确保此事。尽管关注细节很好,但是如果不了解代码实际的功能,那么它不应该成为你关注的点。 27 | 28 | 无论是为了提供有意义的反馈,还是预计未来会参与此项开发,你都必须先了解代码所实现的功能。要想理解代码提交者背后逻辑,你必须先要理解代码所实现的功能。这涉及到很多东西,比如代码上下文, 代码目的以及实现。 29 | 30 | ## 3. 代码可读性如何? 31 | 我们不应该只满足于代码能否正常执行某个功能。这是最基本的要求,但不是最关键的。代码在合入到仓库后,需要其他人来维护的。代码的开发者不会永远使用它。甚至最初的开发者在几个月后也难以理解自己的代码。 32 | 33 | 因此,代码的可读性非常重要。可读性高的代码意味着其他开发人员能够更轻松地理解以及维护代码。举例来讲,states、条件渲染、自定义hooks, 以及如何正确地组织代码,函数以及变量命名。 34 | 35 | 代码可读性没有固定的模式,这是关于开发人员如何轻松地理解整个代码。最后代码可读性是团队的一项长期投资,并且它从code review 时就开始了。 36 | 37 | ## 4. 组件或hooks 是否承担了过多职责? 38 | 软件开发中一个经典的反模式是所谓的God对象。这指的是任何实例,比如对象、类或函数,它们承担了所有的职责。它知道太多,做太多,包含太多相互依赖的流。导致这个实例很难理解、使用、维护、重构,并且非常脆弱。 39 | 同样我们在开发React 代码也要避免此类事情发生。特别是react 注重于可复用性,可扩展性,单一职责原则。多留意组件以及hooks的用途,以及检查它们是否承担了过多的职责。如果是这样的话,那么建议通过抽象来解决,以防止代码库质量下降。 40 | 41 | ## 5. 是否有必要提取一个组件或hooks? 42 | 抽象不够会创建一个God 组件或hook, 而另一方面是过度抽象。将所有东西抽象成组件或hook, 最终也将会降低React 代码质量。 43 | 44 | 在层级结构之间引入一个额外的组件将会带来prop drilling, react 反模式,或者失去了官方推荐的组合模式。为每一个逻辑创建一个新的自定义hook,将会导致过度抽象,使得代码变得杂乱无章。 45 | 46 | 在code review 代码时,你作为代码的审阅人,同时也是导致代码杂乱无章的从犯。记住这一点并不断地询问自己这个结构是否有必要,这也使得你在不写代码的情况下为代码体系结构作出贡献。 47 | 48 | ## 6. 这个API是否设计的足够简单? 49 | 无论是函数参数,还是React 组件props,亦或是hook 参数,设计它们都不是一件容易的事情。特别是在定义组件props时,很容易创意个复杂且冗余的API。 50 | 51 | 如果你有两个类似功能的布尔类型UI props,那么可以考虑用一个枚举类型prop作为替代。如果两个props 总是一起使用,最好是把它们组合在一起。如果props在特定条件下(如:基于一个枚举或布尔类型prop)有效,则可以考虑拆分组件。如果为一个单独的items定义一个数组和一个render function, 最好是采用render props 模式实现。如果有太多的prop drilling, 考虑组合模式也许是值得的。 52 | 53 | 所有这些建议当你在考虑React props API 设计时,都可以提出具体的例子。但这不仅仅局限于这些特定的例子,它也适用于util 函数以及自定义hook。最重要的是牢记这一点,理解代码提交者的API设计并提供相应地建议。 54 | 55 | ## 7. 测试过吗? 56 | 通常你在code review 时,要求代码测试过,会觉得有点怪并觉得没有意义。但现实中你经常发现大家会忘记测试,甚至忽视测试。确保新的功能、修复,提交都经过了测试,将对你团队产生长期的帮助。 57 | 58 | 这不仅仅包括新增测试用例,同时也包括更新测试用例。一般来说最起码要有一个测试用例。但也可以根据团队实际情况来保证这次改动。 59 | 60 | ## 8. 测试都有意义吗? 61 | 有些人认为全面测试总比没有强,但我不这么认为。虽然测试是确保代码质量的第一步,但也可能是一个非常具有欺骗性的一步。糟糕的测试导致错误的安全感,代码无法运行,同时也浪费时间和精力。 62 | 63 | 确保提交的测试用例是否是有必要的,是否依赖于具体的实现细节,配置是否正确,是否达到了合适的状态。所有不同的分支情况都已经覆盖到了,即使是一个非常小的改动也会给测试结果带来非常大的影响。 64 | 65 | ## 9. 可访问性如何? 66 | web开发的一个主要组成部分是,我们的应用程序应该对不同类型的用户都是可访问的。不幸的是,并不是每个web应用程序都针对屏幕阅读器(视障人士使用)、键盘控制(运动功能障碍使用)或其他部分进行了优化。可访问性的目的是使你的网站/应用程序在尽可能多的环境中被尽可能多的人使用,而不仅仅是那些使用高性能台式计算机的用户。极端的例子可能包括:使用较旧设备的用户(他们可能没有最新的浏览器),设备性能不高的用户(他们可能具有较慢的处理器)。 67 | 68 | 没有开发人员知道如何为现有的所有功能实现适当的可访问性。作为一名评审员,要记住这个主题,提醒提交者是否忘记实施它,忘记了提供资源,并在必要时提出建议。 69 | 70 | ## 10. 文档更新了吗? 71 | 重构或者更改代码时最经常忘的是更新相关文档。无论是代码注释,文档还是README,都要确保它们是最新的。 72 | 73 | 如果你发现官方文件,文档或者注释需要更新,那么一定要在code review 时提出来。尤其是当代码提交者并负责该部分代码时,那么他基本不可能了解文档的每一部分。保持文档最新需要包括你在内的团队每个成员都要为之努力。 74 | 75 | ## 最后 76 | code review最重要的是要知道关注什么,并有意识地养成习惯。不幸的是,很多开发人员都不这样做,这也使得code review变得无聊、乏味和毫无意义。 77 | 在现实中,code review时确保代码质量的一种极其有效的方法。为此,本文提出了10个你应该在code review 询问的自己的问题。这些将帮助您了解code review要关注的主题,形成自己的日常review规范,并提高code review的质量。 78 | -------------------------------------------------------------------------------- /docs/vue-cli3-upgrade2.md: -------------------------------------------------------------------------------- 1 | # vue老项目升级vue-cli3指南(三):vue cli3打包优化 2 | 3 | ## webpack-bundle-analyzer 4 | 工欲善其事,必先利其器。webpack有个插件,可以查看项目打包,每个包的体积,每个包里面的包一些情况: webpack-bundle-analyzer,借助此插件可进行打包分析。我们先看下怎样引入此插件。 5 | 6 | ### webpack配置(未使用 vue-cli3): 7 | 未使用vue-cli3时我们可以进行如下配置: 8 | - package.json 9 | ```javascript 10 | // ... 11 | "analyze": "NODE_ENV=production npm_config_report=true npm run build" 12 | // ... 13 | "devDependencies": { 14 | // ... 15 | "webpack-bundle-analyzer": "^3.0.3" 16 | } 17 | ``` 18 | 19 | - config/index.js 20 | ```javascript 21 | // Run the build command with an extra argument to 22 | // View the bundle analyzer report after build finishes: 23 | // `npm run build --report` 24 | // Set to `true` or `false` to always turn it on or off 25 | bundleAnalyzerReport: process.env.npm_config_report 26 | ``` 27 | 28 | - webpack.prod.conf.js 29 | ```javascript 30 | if (config.common.bundleAnalyzerReport) { 31 | let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 32 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()); 33 | } 34 | ``` 35 | 36 | ### vue-cli3 配置 37 | **需说明的我们不需要在package.json devDependencies 中添加webpack-bundle-analyzer的依赖配置。vue-cli-service 中包含了webpack-bundle-analyzer依赖。** 38 | 在vue-cli3中我们可以在vue.config.js 中通过env环境变量来判断是否开启 analyze 39 | 40 | - vue.config.js 41 | ```javascript 42 | // bundle analyze 43 | config.when(process.env.OPEN_ANALYZE, config => { 44 | config.plugin('BundleAnalyzerPlugin') 45 | .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin); 46 | }); 47 | ``` 48 | 然后在package.json 中增加analyze 指令 49 | ```javascript 50 | "analyze": "vue-cli-service build --mode analyze" 51 | ``` 52 | [结合前一篇文章](https://github.com/codeDebugTest/blog/blob/master/docs/vue-cli3-upgrade.md)我们需要增加annalyze env文件配置。新增env.analyze文件,内容如下: 53 | ```javascript 54 | NODE_ENV=production 55 | OPEN_ANALYZE=true 56 | ``` 57 | 再次运行 npm run analyze 就可以查看项目打包状况: 58 | ![](https://github.com/codeDebugTest/blog/blob/master/img/bundle-analyze.png) 59 | 60 | 61 | ## Moment.js 62 | 通过打包分析工具会发现最终打包的文件中将 Moment.js 的全部语言包都打包了,导致最终文件徒然增加 100+kB。为了去除不必要引入的语言包,有两个webpack插件可以使用: 63 | 1. IgnorePlugin 64 | 2. ContextReplacementPlugin 65 | 66 | ### IgnorePlugin 67 | 在vue.config.js中进行如下配置,将移除掉所有语言包 68 | ```javascript 69 | configureWebpack: { 70 | // ... 71 | plugins: [ 72 | // Ignore all locale files of moment.js 73 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) 74 | ] 75 | } 76 | ``` 77 | 改方案也被使用在[create-react-app](https://github.com/facebook/create-react-app/blob/a0030fcf2df5387577ced165198f1f0264022fbd/packages/react-scripts/config/webpack.config.prod.js#L350-L355)中 78 | 79 | ### ContextReplacementPlugin 80 | 如果想保留某个语言包,可以使用ContextReplacementPlugin 在vue.config.js中进行如下配置: 81 | ```javascript 82 | const webpack = require('webpack'); 83 | // ... 84 | configureWebpack: { 85 | // ... 86 | plugins: [ 87 | // load `moment/locale/zh-cn.js` 88 | new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /zh-cn/) 89 | ] 90 | } 91 | ``` 92 | 93 | ### 你可能不需要 Moment.js 94 | 如果你的项目中没有用到 **时区**,仅仅用了一些简单的函数,这会导致项目引入了很多没有实用的方法。即使你移除了语言包,最终打包体积也有50K+的大小 95 | ![](https://github.com/codeDebugTest/blog/blob/master/img/MomentJs.png) 96 | 这里推荐两个库 97 | 1. dayjs 98 | - 体积小 99 | - 与Moment.js 具有非常相似的API,因此很容易从moment 平滑过渡到day.js 100 | 2. date-fns 101 | - API 很简单 102 | - Webpack 完美的伴侣,可以使用`Tree-shaking`代码优化技术 103 | 104 | #### 比较 105 | ![](https://github.com/codeDebugTest/blog/blob/master/img/dateCompare.png) 106 | 107 | 108 | | Name | Size(gzip) | Tree-shaking | Popularity(stars) | Methods richness | Pattern | Timezone Support | Locale | 109 | | ---------------------------------------- | --------------------------------- | ------------ | ----------------- | ---------------- | ---------- | --------------------- | ------ | 110 | | [Moment.js](https://momentjs.com/) | 329K(69.6K) | No | 39k | High | OO | Good(moment-timezone) | 123 | 111 | | [date-fns](https://date-fns.org) | 78.4k(13.4k) without tree-shaking | Yes | 15k | High | Functional | Not yet | 50 | 112 | | [dayjs](https://github.com/iamkun/dayjs) | 6.5k(2.6k) without plugins | No | 17k | Medium | OO | Not yet | 39 | 113 | 114 | #### References 115 | 1. 你不需要Moment.js [https://github.com/you-dont-need/You-Dont-Need-Momentjs.](https://github.com/you-dont-need/You-Dont-Need-Momentjs) 116 | 2. 移除语言包 117 | - [http://stackoverflow.com/questions/25384360/how-to-prevent-moment-js-from-loading-locales-with-webpack/37172595](http://stackoverflow.com/questions/25384360/how-to-prevent-moment-js-from-loading-locales-with-webpack/37172595) 118 | - [https://github.com/moment/moment/issues/2373](https://github.com/moment/moment/issues/2373) 119 | 3. webpack optimize [https://github.com/jmblog/how-to-optimize-momentjs-with-webpack](https://github.com/jmblog/how-to-optimize-momentjs-with-webpack) 120 | -------------------------------------------------------------------------------- /docs/vue-cli3-plugin.md: -------------------------------------------------------------------------------- 1 | # vue-cli3-plugin 模版插件开发指南 2 | 3 | ## 前言 4 | 在vue-cli 中我们可以通过定制自己的模版库,然后在vue init 命令中指定模版库从而按照自定义模版快速搭建新vue项目。在官方推出vue-cli3后,虽然减少了很多webpack配置。但是在我们的项目中难免会定制自己的vue.config.js、babel.config.js、eslintrc.js文件甚至环境配置文件env.xxx也会定义自己的项目环境变量。那么这时需要新建一个业务项目,这些配置拷贝过去?显然我们也可以像vue-cli 那样定义自己的vue-cli3 项目模版,避免重复操作,避免copy过程中可能出现的误操作。本篇将给你带来如何搭建自己的vue-cli3 项目模版,做到开箱即用。 5 | 6 | ## 设计 7 | vue-cli3 有两种插件一种是cli插件,一种是service 插件。显然cli 插件符合我们的需求,我们的目标是 使用 **vue create xxx** 命令初始化一个前端项目时,可以从 git repo 去拉取项目初始化信息。 8 | 在vue-cli3 官网中提到[远程preset](https://cli.vuejs.org/zh/guide/plugins-and-presets.html#%E8%BF%9C%E7%A8%8B-preset) 9 | 因此我们的项目结构如下: 10 | > * preset.json: 包含 preset 数据的主要文件(必需)。 11 | > * generator.js: 一个可以注入或是修改项目中文件的 Generator。 12 | > * prompts.js: 一个可以通过命令行对话为 generator 收集选项的 prompts 文件。 13 | 14 | ``` bash 15 | # 从 GitHub repo 使用 preset: 使用username/repo, 不要指定 git clone 后的地址 16 | vue create --preset username/repo my-project 17 | ``` 18 | ``` bash 19 | # 公司内部 repo 使用 preset: direct:url 确保使用 --clone 选项 20 | vue create --preset direct:url my-project 21 | ``` 22 | repo 参数指定参考:[download-git-repo](https://github.com/flipxfx/download-git-repo) 23 | 24 | 25 | ### preset.json 26 | preset 是一个包含创建新项目所需预定义选项和插件的 JSON 对象,**让用户无需在命令提示中选择它们**。 27 | 在 vue create 过程中保存的 preset 会被放在你的 home 目录下的一个配置文件中 (~/.vuerc)。你可以通过直接编辑这个文件来调整、添加、删除保存好的 preset。preset示例: 28 | ``` json 29 | { 30 | "useConfigFiles": true, 31 | "cssPreprocessor": "less", 32 | "plugins": { 33 | "@vue/cli-plugin-babel": {}, 34 | "@vue/cli-plugin-eslint": { 35 | "config": "standard", 36 | "lintOn": ["save"] 37 | }, 38 | "vue-cli-plugin-xxxx-tpl": { 39 | "prompts": true 40 | } 41 | }, 42 | "router": true, 43 | "vuex": false 44 | } 45 | ``` 46 | * 显示指定插件的版本: 47 | ``` json 48 | { 49 | "plugins": { 50 | "@vue/cli-plugin-babel": { 51 | "version": "^3.5.0" 52 | } 53 | } 54 | } 55 | ``` 56 | **对于官方插件来说这不是必须的,CLI自动使用 registry 中的最新版本** 57 | 58 | * 允许插件的命令提示,保持灵活性 59 | 插件在项目创建的过程中都可以注入它自己的命令提示,**不过当你使用了一个 preset,这些命令提示就会被跳过**,因为 Vue CLI 假设所有的插件选项都已经在 preset 中声明过了。 60 | 61 | **那怎样在 preset 只声明需要的插件,同时让用户选择插件注入的命令提示来保留一些灵活性呢?** 62 | 答案是在插件选项中指定 "prompts": true 来允许注入命令提示: 63 | ``` json 64 | { 65 | "plugins": { 66 | "@vue/cli-plugin-eslint": { 67 | // 让用户选取他们自己的 ESLint config 68 | "prompts": true 69 | } 70 | } 71 | } 72 | ``` 73 | 74 | ### generator 75 | generator主要用于向项目中注入或者修改项目中的文件。 76 | CLI 插件可以包含一个 generator.js 或 generator/index.js 文件。插件内的 generator 将会在两种场景下被调用: 77 | > * 项目的初始化创建过程中,CLI 插件作为项目创建 preset 的一部分被安装。 78 | 79 | > * 插件在项目创建好之后通过 vue invoke 独立调用时被安装。 80 | ``` javascript 81 | /** 82 | * generator 应该导出一个函数 83 | * @param {Object} api GeneratorAPI 实例 84 | * @param {Object} opts 插件prompts.js 解析后的答案作为选项被传递进来 85 | */ 86 | module.exports = (api, opts) => { 87 | // 扩展package.json 文件 88 | api.extendPackage({ 89 | scripts: {}, 90 | dependencies: {} 91 | }); 92 | 93 | // 复制并用 ejs 渲染 `./template` 内所有的文件 94 | api.render('./template', opts); 95 | 96 | // 上述template写入磁盘后,调用该回调函数 97 | api.onCreateComplete(callback); 98 | } 99 | ``` 100 | 101 | vue-cli3 提供的核心Api: (可以查看vue-cli3源码GeneratorAPI.js) 102 | #### extendPackage 103 | 扩展项目中的 package.json。如:dependencies, scripts 104 | 105 | ``` javascript 106 | module.exports = (api, opts) => { 107 | api.extendPackage({ 108 | scripts: { 109 | 'dev': 'npm run serve', 110 | 'build-dev': 'vue-cli-service build --mode development', 111 | 'analyze': 'vue-cli-service build --report' 112 | }, 113 | dependencies: { 114 | 'axios': '^0.18.0', 115 | 'lodash': '^4.17.11', 116 | 'moment': '^2.24.0' 117 | }, 118 | devDependencies: { 119 | 'body-parser': '^1.18.3', 120 | 'multiparty': '^4.2.1' 121 | } 122 | }); 123 | } 124 | ``` 125 | 126 | #### render 127 | 将模版中的文件用ejs 渲染,并拷贝到初始化的项目中。如: 128 | ``` javascript 129 | /** 130 | * generator 应该导出一个函数 131 | */ 132 | module.exports = (api, opts) => { 133 | // ... 134 | // 复制并用 ejs 渲染 `./template` 内所有的文件 135 | api.render('./template', opts); 136 | } 137 | ``` 138 | 1. **当你需要创建一个以 . 开头的文件时,模板项目中需要用 _ 替代** 139 | ![](https://github.com/codeDebugTest/blog/blob/master/img/fileName.png) 140 | 141 | 2. **第二个参数是插件prompts.js 解析后,输入的答案作为选项被传递进来** 142 | 143 | 3. **模版使用EJS进行渲染处理** 144 | 145 | 146 | ### prompts 147 | prompts.js 其实就是你在初始化项目时,系统会询问你的配置选项问题,比如你的项目需不需要安装 vuex? 需不需要安装 vue-router? 如: 148 | ```javascript 149 | const getGitUserInfo = require('./lib/gitUserInfo'); 150 | 151 | module.exports = [ 152 | { 153 | name: 'author', 154 | type: 'input', 155 | required: true, 156 | message: 'Author?', 157 | default: getGitUserInfo() 158 | }, 159 | { 160 | name: 'replaceTemplates', 161 | type: 'confirm', 162 | message: 'Use custom templates? ', 163 | default: true 164 | } 165 | ]; 166 | ``` 167 | 168 | ## 本地调试与测试 169 | 是时候验证下插件的效果了。那么怎样在插件发布前,测试插件的正确性呢? 170 | 1. 创建一个新vue 项目 171 | ```bash 172 | $ vue create my-app 173 | ``` 174 | 175 | 2. 安装插件 176 | ```bash 177 | $ npm install --save-dev file:/user/home/xxx/xxx/your/plugin 178 | ``` 179 | 180 | 3. invoke plugin 181 | ```bash 182 | $ vue invoke vue-cli-plugin- 183 | ``` 184 | 185 | ## 参考栗子 186 | 1. [vuetifyjs](https://github.com/vuetifyjs/vue-cli-plugin-vuetify) 187 | 2. [custom-tpl](https://github.com/natee/vue-cli-plugin-custom-tpl) 188 | 2. [vue cli 3.0 自定义 template](https://github.com/vuejs/vue-cli/issues/2400) 189 | 3. [How to build a Vue CLI plugin](https://dev.to/vuevixens/how-to-build-a-vue-cli-plugin-3b6b) 190 | -------------------------------------------------------------------------------- /docs/vue-cli3-upgrade.md: -------------------------------------------------------------------------------- 1 | # vue老项目升级vue-cli3指南(二):vue cli3升级手册 2 | 3 | ## 升级步骤 4 | ### 1. 初始化新项目 5 | 笔者先用vue cli3 初始化一个新项目(vue create xxxx), 具体过程参考 [vue 创建一个项目](https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create)。 6 | 配置过程: 7 | 1. 选取 <手动选择特性>配置 8 | 2. 选取 , 9 | 3. 选取 标准配置 10 | 4. 选取 保存时 Eslint检查代码 11 | 12 | 初始化后得到 package.json依赖配置文件(主要使用devDependencies配置), babel.config.js 配置文件。这其中的配置基本上可以完全使用到我们的老项目中。从而减少了自主配置过程。 13 | 14 | #### 1.1 devDependencies 变化: 15 | 新配置: 16 | ``` javascript 17 | "devDependencies": { 18 | "@babel/core": "^7.0.0", 19 | "@vue/cli-plugin-babel": "^3.2.0", 20 | "@vue/cli-plugin-eslint": "^3.2.0", 21 | "@vue/cli-service": "^3.2.0", 22 | "@vue/eslint-config-standard": "^4.0.0", 23 | "babel-eslint": "^10.0.1", 24 | "babel-plugin-lodash": "^3.3.4", 25 | "babel-plugin-veui": "^1.0.0-alpha.21", 26 | "body-parser": "^1.18.3", 27 | "eslint": "^5.8.0", 28 | "eslint-plugin-vue": "^5.0.0", 29 | "eslint-plugin-html": "^5.0.0", 30 | "less": "^3.0.4", 31 | "less-loader": "^4.1.0", 32 | "less-plugin-est": "^2.1.0", 33 | "vue-template-compiler": "^2.5.21" 34 | } 35 | ``` 36 | 是不是有种清爽的感觉,对比老项目发现少了令人恶心的babel依赖,因为babel-loader babel-eslint, babel-plugin-transform-vue-jsx手动配置起来那简直。。。。。。 37 | 38 | #### 1.2 babel.config.js: 39 | ``` javascript 40 | module.exports = { 41 | 'presets': [ 42 | '@vue/app' 43 | ] 44 | }; 45 | ``` 46 | 对比之前的 presets, plugins 配置真良心啊!!! 47 | 48 | ### 2. vue-config.js 49 | 这是一个可选的配置文件,你可以通过此文件修改全局的配置。并可通过vue inspect 命令审查项目webpack config. 50 | #### 2.1 多入口 51 | 在 `pages`里配置,build打包后会产出 common.xxx.js, vendor.xxx.js, [entryName].xxx.js 几个文件,默认都会引用这几个文件。 52 | **可通过明确配置chunks,优化入口的引用文件** 如: 53 | ```javascript 54 | pages: { 55 | home: { 56 | entry: 'src/home/main.js', 57 | template: 'entry/home.html', 58 | filename: 'home/index.html', 59 | chunks: ['chunk-vendors', 'home'] // home.html 只会引用 chunk-vendors.xxx.js, home.xxx.js, 并不会引用 common.xxx.js 60 | }, 61 | client: { 62 | entry: 'src/client/main.js', 63 | template: 'entry/client.html', 64 | filename: 'client/index.html' 65 | } 66 | } 67 | ``` 68 | #### 2.2 transpileDependencies 69 | 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。如: 70 | ``` javascript 71 | transpileDependencies: ['vue-awesome', 'vue-void'] 72 | ``` 73 | #### 2.3 css.loaderOptions 74 | 向 CSS 相关的 loader 传递选项。如: 75 | ``` javascript 76 | css: { 77 | loaderOptions: { 78 | less: { 79 | javascriptEnabled: true, 80 | modifyVars: { 81 | '@default-base-font-family': 'Helvetica Neue, Arial, PingFang SC, STHeiti, Microsoft YaHei, SimHei, sans-serif', 82 | '@default-base-heading-family': 'Helvetica Neue, Arial, PingFang SC, STHeiti, Microsoft YaHei, SimHei, sans-serif' 83 | } 84 | } 85 | } 86 | } 87 | ``` 88 | #### 2.4 webpack: 89 | Vue cli 中有默认配置,一般来说通过 configureWebpack 修改配置够用了,该配置最终会被webpack-merge合并入最终配置。但对于复杂的,精细粒度的配置则需要通过 chainWebpack 进行配置。[webpack-chain 参考配置](https://github.com/neutrinojs/webpack-chain) 90 | - 条件配置: 91 | ``` javascript 92 | // Example: Only add minify plugin during production 93 | config 94 | .when(process.env.NODE_ENV === 'production', config => { 95 | config 96 | .plugin('minify') 97 | .use(BabiliWebpackPlugin); 98 | }); 99 | 100 | // Example: Only add minify plugin during production, 101 | // otherwise set devtool to source-map 102 | config 103 | .when(process.env.NODE_ENV === 'production', 104 | config => config.plugin('minify').use(BabiliWebpackPlugin), 105 | config => config.devtool('source-map') 106 | ); 107 | ``` 108 | - Config module rules: 109 | 1. 新增 110 | ``` javascript 111 | // Example 112 | config.module 113 | .rule('compile') 114 | .use('babel') 115 | .loader('babel-loader') 116 | .options({ presets: ['@babel/preset-env'] }); 117 | ``` 118 | 2. 修改 option 119 | ``` javascript 120 | config.module 121 | .rule('compile') 122 | .use('babel') 123 | .tap(options => merge(options, { 124 | plugins: ['@babel/plugin-proposal-class-properties'] 125 | })); 126 | ``` 127 | 3. 条件规则 oneOfs 128 | ``` javascript 129 | config.module 130 | .rule('css') 131 | .oneOf('inline') 132 | .resourceQuery(/inline/) 133 | .use('url') 134 | .loader('url-loader') 135 | .end() 136 | .end() 137 | .oneOf('external') 138 | .resourceQuery(/external/) 139 | .use('file') 140 | .loader('file-loader') 141 | ``` 142 | - 插件配置: 143 | 1. 新增 144 | ``` javascript 145 | config.plugin('hot') 146 | .use(webpack.HotModuleReplacementPlugin); 147 | ``` 148 | 2. 修改参数 149 | ``` javascript 150 | // Example 151 | config.plugin('env') 152 | .tap(args => [...args, 'SECRET_KEY']); 153 | ``` 154 | 155 | ## 踩坑问题 156 | ### 1. less inline javascript 157 | **问题:Inline JavaScript is not enabled. Is it set in your options?** 158 | **原因:** less3.x以后的版本需要增加 **javascriptEnabled: true** 159 | **解决方案:** 160 | ``` javascript 161 | css: { 162 | loaderOptions: { 163 | less: { 164 | javascriptEnabled: true 165 | } 166 | } 167 | } 168 | ``` 169 | 170 | 171 | ### 2. incorrect peerDependencies for vue-template-compiler 172 | **问题:** Error: [vue-loader] vue-template-compiler must be installed as a peer dependency, or a compatible compiler implementation must be passed via options. 173 | 174 | 但是 vue-template-complier, vue-loader 都是已经安装了啊。并且vue-cli3 默认配置里面(config.module.rule)已经进行了如下配置: 175 | ``` javascript 176 | { 177 | test: /\.vue$/, 178 | use: [ 179 | { 180 | loader: 'cache-loader', 181 | options: { 182 | cacheDirectory: 'xxx/node_modules/.cache/vue-loader', 183 | cacheIdentifier: 'ab103f44' 184 | } 185 | }, 186 | { 187 | loader: 'vue-loader', 188 | options: { 189 | compilerOptions: { 190 | preserveWhitespace: false 191 | }, 192 | cacheDirectory: 'xxx/node_modules/.cache/vue-loader', 193 | cacheIdentifier: 'ab103f44' 194 | } 195 | } 196 | ] 197 | } 198 | ``` 199 | **原因:**经过在vue-loader github仓库发现有人提到了这个问题。并且尤大进行了解释: 200 | ***You don't use it directly. However, its version must be the same with vue to ensure correct behavior. Making it a peer dep makes it possible to explicitly pin both vue and vue-template-compiler to the same version.*** 意思说 **vue 与 vue-template-complier 版本号不一致** [issue 详情](https://github.com/vuejs/vue-loader/issues/560) 201 | 确实发现项目依赖vue 与 vue-template-complier 版本号不一致。保持一致问题就解决了。 202 | 203 | ### 3. Parsing error: Adjacent JSX elements must be wrapped in an enclosing tag 204 | **原因:** 该问题是由于 eslint 配置:"parser": "babel-eslint" 的位置不对。把此配置移入到 “parserOptions” 中即可。 205 | ``` javascript 206 | { 207 | "root": true, 208 | - "parser": "babel-eslint", 209 | + "parserOptions": { 210 | + "parser": "babel-eslint" 211 | + }, 212 | ``` 213 | 214 | ### 4. svg 文件始终会以URL的形式引入到项目中。 215 | 需求 svg-inline-loader, svg-loader 分别对不同路径下的svg进行处理。 216 | 最开始的配置是: 217 | ``` javascript 218 | configureWebpack: { 219 | module: { 220 | rules: [ 221 | { 222 | test: /\.svg$/, 223 | loader: 'svg-inline-loader', 224 | include: [ 225 | resolve('src/common/img/icons') 226 | ] 227 | }, 228 | { 229 | test: /\.svg$/, 230 | loader: 'svgo-loader', 231 | include: [ 232 | resolve('static/img') 233 | ] 234 | } 235 | ] 236 | } 237 | } 238 | ``` 239 | **原因**: 240 | 通过vue inspect 发现原来有个vue cli 有个**默认svg配置**: 241 | ``` javascript 242 | /* config.module.rule('svg') */ 243 | { 244 | test: /\.(svg)(\?.*)?$/, 245 | use: [ 246 | /* config.module.rule('svg').use('file-loader') */ 247 | { 248 | loader: 'file-loader', 249 | options: { 250 | name: 'img/[name].[hash:8].[ext]' 251 | } 252 | } 253 | ] 254 | } 255 | ``` 256 | **解决方案一**: 257 | 通过 chainWebpack 删除原有配置 258 | ``` javascript 259 | chainWebpack: config => { 260 | config.module.rules.delete('svg'); 261 | } 262 | ``` 263 | 结合上面的configureWebpack配置,满足需求。 264 | 265 | **解决方案二**: 266 | 通过 chainWebpack 覆盖原有svg配置 267 | ``` javascript 268 | chainWebpack: config => { 269 | config.module 270 | .rule('svg') 271 | .include 272 | .add(resolve('src/common/img/icon')) 273 | .end() 274 | .use('file-loader') 275 | .loader('svg-inline-loader') 276 | } 277 | } 278 | ``` 279 | 280 | ### 5. webpack optimization split chunk 281 | 由于原项目最后打包体积过大(2M左右),因此在webpack2 使用了CommonsChunkPlugin 进行了代码分离。分离后的代码块不会变动可以被浏览器进行缓存。但是在webpack4 中该插件被移入到 optimization.splitChunks 中。默认状态下webpack4会基于以下条件,自动进行代码分离: 282 | - 分离的块可以被共享或者来自node_modules的模块 283 | - 分离的块必须大于 30Kb(minimize, gzip压缩前) 284 | - 根据需要加载块时的最大并行请求数将小于或等于5 285 | - 初始页面加载时的最大并行请求数将小于或等于3 286 | > **为了满足最后两个条件,webpack有可能受限于包的最大数量值,生成的代码体积往上增加。** 287 | 288 | #### 配置 289 | - minSize(默认是30000, 30kb):形成一个新代码块最小的体积 290 | - minChunks(默认是1):在分割之前,这个代码块最小应该被引用的次数(译注:保证代码块复用性,默认配置的策略是不需要多次引用也可以被分割) 291 | - maxInitialRequests(默认是3):一个入口最大的并行请求数 292 | - maxAsyncRequests(默认是5):按需加载时候最大的并行请求数。 293 | - chunks的值应该是[all, async, initial]其中的一个。 294 | - **缓存组(Cache Group)** 295 | cacheGroup是最关键的配置上面那些参数可以不用管。 296 | > cacheGroup 默认模式会将所有来自node_modules的模块分配到一个叫vendors的缓存组;所有重复引用至少两次的代码,会被分配到default的缓存组。一个模块可以被分配到多个缓存组,优化策略会将模块分配至跟高优先级别(priority)的缓存组,或者会分配至可以形成更大体积代码块的组里。 297 | 298 | 299 | ### optimization split chunk 配置 300 | > 原始配置webpack2 如下: 301 | ``` javascript 302 | new HtmlWebpackPlugin({ 303 | filename: 'index.html', 304 | template: 'index.html', 305 | inject: true, 306 | minify: { 307 | removeComments: true, 308 | collapseWhitespace: true, 309 | removeAttributeQuotes: true 310 | }, 311 | chunks: ['vendor', 'quill', `app`], 312 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 313 | chunksSortMode: 'dependency' 314 | })), 315 | new webpack.optimize.CommonsChunkPlugin({ 316 | name: 'vendor', 317 | minChunks: function (module, count) { 318 | // any required modules inside node_modules are extracted to vendor 319 | return ( 320 | module.resource && 321 | /\.js$/.test(module.resource) && 322 | module.resource.indexOf( 323 | path.join(__dirname, '../node_modules') 324 | ) === 0 && 325 | module.resource.indexOf('quill') === -1 326 | ); 327 | } 328 | }), 329 | new webpack.optimize.CommonsChunkPlugin({ 330 | name: 'quill', 331 | chunks: config.entries.map(({name}) => name) 332 | }), 333 | ``` 334 | 335 | > 最终配置: vue-cli3(webpack4) 336 | ``` javascript 337 | // split chunks optimization when not 'development' mode 338 | config.when(process.env.NODE_ENV !== 'development', config => { 339 | config.plugin('html') 340 | .use(HtmlWebpackPlugin) 341 | .tap(args => [{ 342 | filename: 'index.html', 343 | template: 'public/index.html', 344 | inject: true, 345 | minify: { 346 | removeComments: true, 347 | collapseWhitespace: true, 348 | removeAttributeQuotes: true 349 | }, 350 | chunks: ['vendor', 'quill', 'app'], 351 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 352 | chunksSortMode: 'dependency' 353 | }]); 354 | // split chunks 355 | config.optimization 356 | .splitChunks({ 357 | cacheGroups: { 358 | default: false, 359 | vendor: { 360 | chunks: 'initial', 361 | name: 'vendor', 362 | test(module, count) { 363 | // any required modules inside node_modules are extracted to vendor 364 | return ( 365 | module.resource 366 | && /\.js$/.test(module.resource) 367 | && module.resource.indexOf( 368 | path.join(__dirname, './node_modules') 369 | ) === 0 370 | && module.resource.indexOf('quill') === -1 371 | && module.resource.indexOf('lego-sdk') === -1 372 | ); 373 | } 374 | }, 375 | quill: { 376 | chunks: 'initial', 377 | name: 'quill', 378 | test(module, count) { 379 | return ( 380 | module.resource 381 | && module.resource.indexOf( 382 | path.join(__dirname, './node_modules') 383 | ) === 0 384 | && module.resource.indexOf('quill') !== -1 385 | ); 386 | } 387 | } 388 | } 389 | }); 390 | }); 391 | ``` 392 | 393 | //TODO: 394 | ### 6. dev server --------------------------------------------------------------------------------