仓库源文站点原文


layout: post title: 从React脚手架工具学习React项目的最佳实践(上):前端基础配置 modified: 2017-10-10 tags: [javascript, React] image: feature: abstract-3.jpg credit: dargadgetz creditlink: http://www.dargadgetz.com/ios-7-abstract-wallpaper-pack-for-iphone-5-and-ipod-touch-retina/ comments: true

share: true

这篇文章不是聊React这门技术本身,而是关于如何维护好一个React项目。

文本可能会涉及一些Webpack的基础知识,如果你还不太了解Webpack的用法的话,可以从我之前的一篇文章《Webpack 速成》入门,深入浅出,童叟无欺。

编程领域中的“脚手架(Scaffolding)”指的是能够快速搭建项目“骨架”的一类工具。例如大多数的React项目都有src目录,public目录,webpack配置文件,babel配置等等,而src目录中又通常包含components目录,reducers目录等等。每次在新建项目时,你不得不手动创建这些固定的文件目录,繁琐而累赘。脚手架的作用就是帮助你完成这些重复性的工作,包括一键生成主要的目录结构、安装依赖等等。Yeoman就是著名的脚手架工具。

当你进入一个公司参与React项目时,你要做的可能只是开发指定的组件,执行命令启动项目查看运行和调试,最后发布打包上线。你可能不会去思考为什么目录结构是这个样子,那么多配置文件是干什么用的(我曾经也是这个样子)。今天我选择了两个React项目的脚手架工具 create-react-app(以下简称CRA)和react-starter-kit(以下简称RSK),根据它们的说明文档以及一些个人的经验,来逐个解析不同文件的作用。

这些知识并不仅仅适用于React项目,文件的背后代表的是工具,工具的背后代表的其实是要解决的问题。不同的公司不同团队使用的工具可能会不同,将来也会有新的技术或者框架出现,但这些解决问题的思路同样能够复用。或者当你需要立项一个React项目而又不想依赖脚手架时,它们会是一份好的教科书。

因为CRA是Facebook官方推出的脚手架工具,所以我们以CRA为主线索展开,它的User Guide文档最为(特)丰(别)富(长),本文的大部分内容也都参(翻)考(译)自这份文档,如果也理解不恰当之处还多多指教。其中也会穿插react-starter-kit的相关内容。

这个系列的文章会分为上下两个部分。在这上篇中,讲解的是一些常规项目搭建的基础配置,而在下篇的计划中,则会讲解高级配置,涉及开发环境的后端功能以及测试和部署。

最后一句废话想强调的是,任何脚手架生成的项目结构都仅供参考。实际的组织方式和使用工具都要依据实际情况而定。

项目目录

首先让我们从最基本的目录文件夹开始

CRA中有两个非常重要的目录有两个,srcpublic

当然,src目录之下子目录的命名和组织就没有那么讲究了。如果你开发的是redux项目,自然会有components、reducers、actions等文件夹,甚至在components中分别为container component和stateless component建立文件夹都没问题。

最后,src目录的入口是src/index.js,不妨可以看看index.js的内容

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

很简单,它将入口组件渲染至页面上,并且注册service worker。

虽然public中存放的不是组件,public目录同样存在入口,即/index.html,也即是用户在域名根路径下访问到的页面。在CRA中规定,只有public文件夹内的资源才能被index.html使用。而html引用静态资源的方式也比较特别,并非是通过相对路径或者绝对路径的,而是通过全局变量引用。这个话题我们放在后面资源使用环节再说。

public文件夹有时候也被命名为assets甚至resources,这都没有关系。如果更加规矩一点,你可以在public中建立子文件夹dist用于存储发布上线的脚本和样式(dist其实就是distribute的缩写,也意味着发布的意思),或者建立build文件夹用于存储开发中构建后的脚本

srcpublic是最重要的两个文件夹。CRA中的文件夹只有这两个。我们不妨再可以看看RSK中的文件夹还有哪些:

各式各样的配置文件

越来越多的工具被发明来用于辅助我们的开发,但不同的工具配合不同的项目需要进行不同的配置。所以有各式各样的配置文件可能存在于我们的项目文件中。这些工具和配置文件你不一定都会用上,但至少你在过目之后不会再对它们陌生,或许在以后解决问题的过程中能够派的上用场。

以下的配置文件摘自RSK脚手架中(如果你第一次看到脚手架为你生成了这么多从来没有看到过的文件你一定会感到害怕,反正我是这么觉得的。)

除此之外,还有一些你可能会用得上的一些文件,比如

工具脚本

还在我入行的时候,前端开发流程是很简单的,手动创建一个静态页面,然后引入你需要的脚本就可以开始了。然而到了现在,不仅引入脚本的方式发生了改变,包括调试过程,打包流程,发布上线都变得复杂而且专业,而这一切都离不开NodeJS脚本。脚本带来的好处是可复用、自动化以及批量化处理。

开发中需要使用脚本处理的环节非常的多,例如将less编译为css,将脚本编译、压缩、拼接,压缩图片等等。这些工作可以交给Webpack或者Gulp或者Grunt去做。但这些第三方库并不是万能的,它们的运作也依赖它们所处生态里的插件。在这种复杂的依赖情况下,出错的情况常容易发生,为什么不建议再使用Gulp或者Grunt了呢,详见这篇文章:Why we should stop using Grunt & Gulp。正所谓流水的工具,铁打的脚本

npm脚本都存放在package.json文件里的scripts字段里

npm命令有机会我们能单独拿出一篇文章来聊,但言归正传回到脚手架,CRA中只用到了四种npm命令,分别是

顺表说一下为什么build命令前需要加关键字run,而starttest就不需要,因为npm start是内置的预设命令,你可以理解为类似于宏的东西。如果你没有在package.json里自定义start命令的话而又执行npm start的话,它实际上执行的是node server.js。更多的内置命令请参考https://docs.npmjs.com/misc/scripts

自动格式化代码

这里所说的格式化代码并不是指美化和格式化已经压缩过的代码以便于阅读。而是在代码的提交阶段(commit)强制对代码进行格式化。所以这里用到了额外的三个库

核心类库当然是prettier,为什么在开发时仍然需要对代码格式化,prettier自己给出了几个理由,比如强制对代码进行格式化避免PR时产生不必要的语法问题,比如帮助还不熟悉的新同学规范代码,总之仍然是有必要的。

prettier解决了how的问题,但是还需要huskylint-staged解决when的问题,也就是什么时候做格式化。在CRA中,格式化的工作时放在准备提交的阶段(pre-commit),在实际项目中你还可以放在预备push的阶段。

husky解决的问题是将pre-commit的hook暴露出来。默认情况下如果你想编写pre-commit脚本,你需要编辑你项目的.git/hooks/pre-commit文件,如果我没有记错的话应该是shell脚本,并且在执行之前记得赋予它们执行权限。

然而当你安装完husky之后,你就可以把pre-commit阶段需要执行的脚本直接放在package.json里的scipts里的precommit字段里,比如:

"scripts": {
  "precommit": "eslint"
},

lint-staged解决的则是最后一公里的问题,即封装在pre-commit阶段需要执行的脚本,同样是在package.json配置,例如:

  "dependencies": {
    // ...
  },
 "lint-staged": {
   "src/**/*.{js,jsx,json,css}": [
     "prettier --single-quote --write",
     "git add"
   ]
 },
  "scripts": {

开发规范

进入到组件化的时代,一切都是组件,就连html也可以变身为组件。在RSK脚手架中,你甚至会看到一个名为Html.js的组件(然后采用后端渲染)。我们希望用组件解决一切问题,而不是把需要维护的代码遗落在各个地方,甚至包括<head />标签里的内容。<title /><meta />就交给React Helmet解决吧。

开发组件和引用组件就不赘述了,全世界都一样,相信大家也耳熟能详了。

样式

至于样式,无论你是使用Less、Sass、还是Stylus都一样,只要在Webpack中使用对应的loader就能将其编译为css。需要注意的是组织样式的方式。传统项目中样式和脚本是分离的,放在不同的文件夹中。但是在React项目中,我们只有组件一个维度,组件同时包含样式和脚本,都放在components文件夹中。例如:

components/
|--Button.js
|--Button.less

那么在Button.js中你可以直接引用样式

import React, { Component } from 'react';
import './Button.css';

或者,你也可以把所有的样式都在样式入口src/index.css中引入,然后在组件入口src/index.js中又统一引入样式入口src/index.css

除了编译样式之外还有一些额外的工作需要进行,例如压缩,例如为某些样式属性添加浏览器前缀。在CRA中会使用Autoprefixer或者postcss进行处理,当然这一切都集成在react-scripts中。你也可以独立的使用npm脚本进行处理,监视样式的变化,当样式文件发生更改时自动的进行预处理和“后”处理,这个流程对脚本文件也同样有效。

目前也有很多专门用于优化npm脚本执行的类库,在上述流程中你也能够(或者说是必须)用上:

添加图片字体等额外资源

图片与字体等资源也和样式一样都与要使用它们的组件放在同一层级,至少都应该属于同一个components文件夹中,在组件也是通过import的关键字引入,例如

import React from 'react';
import logo from './logo.png'; // Tell Webpack this JS file uses this image

console.log(logo); // /logo.84287d09.png

function Header() {
  // Import result is the URL of your image
  return <img src={logo} alt="Logo" />;
}

为了减少页面的请求数,体积小于10000 bytes的图片会返回data URI而不是实际的路径。当项目需要(为生产环境)进行构建时,Webpack会把大于10000 bytes的图片资源拷贝到最终构建的文件夹中(在CRA中的目录是/build/static/media),并且根据内容hash值进行重新命名。所以不用担心资源发生修改之后因为浏览器的缓存而不会生效

为什么要采用import的方式引用样式和图片,文档中给出了三条理由:

如果一定要引用public文件夹中的资源

并非所有的资源都能在组件中引用,又或者有的第三方类库并不支持与React集成,此时你就需要把资源放入public文件夹中,然后在html中引用,比如:

<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

那么在构建时(npm run build),Webpack会将%PUBLIC_URL%替换为实际的public目录的绝对路径。

在js文件中也可以通过访问process.env.PUBLIC_URL变量来获得public文件夹的绝对路径

render() {
  // Note: this is an escape hatch and should be used sparingly!
  // Normally we recommend using `import` for getting asset URLs
  // as described in “Adding Images and Fonts” above this section.
  return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />;
}

这种访问资源的方式有以下一些缺点(当然是相对于import方式而言),请务必了解:

但是在某些情况下可以考虑使用这种访问资源的方式

添加自定义的环境变量

CRA脚手架还允许你在process.env上添加自定义的环境变量供全局访问。

默认它会提供两个环境变量供使用,一个是上一节用到的public文件夹路径PUBLIC_URL。另一个是大家更加熟悉的NODE_ENV。后者是一个代表当前开发环境的变量,当你运行npm start时它等于development;当你运行npm test时它的值是test;当你运行npm run build时,它的值是production。你无法手动的覆盖它,它能够防止开发者不小心打包了一个开发版本部署到线上。NODE_ENV也能够帮助你有针对性的调试代码,比如你只希望非production环境下停用分析脚本:

if (process.env.NODE_ENV !== 'production') {
  analytics.disable();
}

当然你也可以添加自己的环境变量,添加方式有两种,一种是通过命令行的方式比如在Windows系统下set REACT_APP_SECRET_CODE=abcdef&&npm start。另一种是通过.env文件(也就是通过dotenv类库),把你所需要的环境变量都写在这个文件中:

REACT_APP_SECRET_CODE=abcdef

需要注意的事情是,所有的自定义环境变量都需要以REACT_APP_SECRET_CODE开头(至于理由我没有看懂:Any other variables except NODE_ENV will be ignored to avoid accidentally exposing a private key on the machine that could have the same name. 我不是很理解 exposing a private key 是什么意思)。

一旦环境变量定义完毕之后,就能在文件中使用,比如在脚本中:

render() {
  return (
    <div>
      <small>You are running this application in <b>{process.env.NODE_ENV}</b> mode.</small>
    </div>
  );
}

比如在html中:

<title>%REACT_APP_WEBSITE_NAME%</title>

最后,不同开发环境中的环境变量不必都放在.env文件中,可以划分为.env.development, .env.test, .env.production等不同的文件存放,并且不同文件之间还存在优先级的关系,详情可以访问dotenv的文档

编辑器调试

目前比较流行的IDE比如Visual Studio CodeWebStorm都支持编辑器内的代码调试,但是可能需要配置编辑器的环境变量,或者增加配置文件,又或者给浏览器安装插件。我个人使用编辑器进行调试的体验并不好,并非所有的场景都支持调试。同时因为脚手架和编辑器调试时都会启动后端环境,这之中可能需要解决冲突的地方。

具体的配置信息可以参考这两款编辑器的官方文档。

上篇完

参考资料

https://www.site2share.com/folder/20020524

你可能会喜欢