(三)React系列-实战02:脚手架搭建
脚手架的搭建
代码参考项目
点击获取 本项目的脚手架
经过了刀耕火种的插件时代,伴随着npm越来越繁荣的生态圈,近几年来前端开发的三大件HTML、CSS、Javascript都发生了很多的变化,这也让很多开发在选型上的难度增加了很多,也是一个项目开端的第一个难点。在react的生态圈中,虽然已经有 create-react-app这样的官方制定脚手架,但是为了更好更深入了解一个react前端脚手架所需要的责任与能够解决的问题,我们不妨亲自做一个最简单以来最少的功能齐全的项目脚手架。
HTML部分
当下主流的前端框架都是用js框架接管DOM的相关操作,HTML相关的工作大大减少了,基本上都是采用了提供一个可以注入的根节点即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>Title</title>
<link rel="shortcut icon" href="/favicon.ico" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" type="image/x-icon">
<link rel="icon" href="/favicon.ico" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" type="image/x-icon">
</head>
<body>
<div id="app">
</div>
</body>
</html>
为了让页面缩放比例能与客户的终端屏幕尺寸保持一致,我们在头部加入viewprot 属性这对支持移动项目至关重要。
<meta name="viewport" content="width=device-width, initial-scale=1.0">
接下来,我们修改HTML中的标题,这里我们不采用硬编码的方式吧title写死,我们通过配置适配的方式;使用webpack的插件让一个变量注入进来,为了增强普通语言html的功能,我们使用ejs模板;
<title><%= htmlWebpackPlugin.options.title %></title>
new HtmlWebpackPlugin({
title: 'My App',
filename: './index.html',
template: './index.ejs'
}),
除了上边说的title部分,我们还可以使用webpack把编译后的js和css文件路径也注入到模板中
<% for (var chunk in htmlWebpackPlugin.files.css) { %>
<link rel="preload" href="<%= htmlWebpackPlugin.files.css[chunk] %>" target="_blank" rel="external nofollow" as="style">
<% } %>
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<link rel="preload" href="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>" target="_blank" rel="external nofollow" as="script">
<% } %>
除了变量的注入,ejs这类html模板还支持条件判断的功能,举一个在不同的环境下禁止浏览器搜索引擎抓取的开关;示例如下:
<% if (htmlWebpackPlugin.options.IS_PROD) { %>
<meta name="robots" content="index, follow">
<% } else { %>
<meta name="robots" content="noindex, nofollow">
<% } %>
根据项目的需要我们还可以在模板中定义应用 favicon 等传统 HTML 支持的属性,这里不再赘述。
CSS 部分
相较于HTML,CSS作为前端应用中的另一核心部分受到js发展的冲击几乎很小,以Less、Sass为代表的css预处理工具极大增强了css的功能;
但为了打通基于 webpack 的整体项目编译流程,我们也需要在 webpack 中合理地配置 CSS 的编译方式,使得 Sass(Less)、CSS 及 webpack 可以无缝衔接。
区别对待项目中的css和node_modules中的css
node_modules的css:
{
test: /\.css$/,
include: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer({
browsers: 'last 5 versions'
})],
sourceMap: true,
},
},
]
}
在项目开发的过程中,我们很有可能还需要引入一些包含 CSS 的第三方库。这里需要注意的是,为了避免有些第三方库提供的 CSS 没有做浏览器兼容性处理,我们在加载 node_moduels 中的 CSS 之前还要使用 postcss-loader 再统一处理一遍,以确保所有进入生产环境的 CSS 都经过了相应的浏览器兼容性处理。
项目中的css:
{
test: /\.scss$/,
exclude: /node_modules/,
use: IS_PROD ? [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer({
browsers: 'last 5 versions'
})],
sourceMap: true,
},
},
{
loader: 'sass-loader',
options: {
includePaths: [
SOURCE_DIR,
],
},
},
] : [{
loader: 'style-loader',
options: {
singleton: true
},
},
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer({
browsers: 'last 5 versions'
})],
sourceMap: true,
},
},
{
loader: 'sass-loader',
options: {
includePaths: [
SOURCE_DIR,
],
},
},
],
}
这里要注意,使用sass-loader 的includePaths设置为 src/ 目录,是为了让项目中的sass文件找打绝对路径并相互引用。同事开发时使用style-loader 而不是使用css-loader来加载css,是为了结合webpack-dev-server 的热更新功能,在本地开发的时候直接将所有的css嵌入在html中加快速度。
{
test: /\.less$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
}, {
loader: 'less-loader',
options: {
strictMath: true,
noIeCompat: true,
}
}]
}
{
test: /\.less$/,
exclude: /node_modules/,
use: IS_PROD ? [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer({
browsers: 'last 5 versions'
})],
sourceMap: true,
},
},
{
loader: 'less-loader',
options: {
includePaths: [
SOURCE_DIR,
],
strictMath: true,
noIeCompat: true,
},
},
] : [{
loader: 'style-loader',
options: {
singleton: true
},
},
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer({
browsers: 'last 5 versions'
})],
sourceMap: true,
},
},
{
loader: 'less-loader',
options: {
includePaths: [
SOURCE_DIR,
],
strictMath: true,
noIeCompat: true,
},
},
],
}
样式变量与mixin
上边说了css作为独立的一部分一直以来受到前端工程化的影响很小,但是很多主流前端框架开始一味地追求开发效率;很多时候并没有把css当作一门语言来对待。
最常见的例子就是对于css中的颜色管理,很多开发者直接复制十六进制的代码,并不去考虑颜色在整个项目中的复用性和统一性;对于mixin也是一样的,比如一个立体式的阴影等需要很多个css属性组合,很多时候我们都是采用直接复制粘贴的粗暴方式解决。
那么,我们应该如何规避这些出现的问题,项目中我们央视放在的styles/文件夹下。并将这些通用的变量和mixin提前定义好了
// 项目 颜色
$ui-white: #fff !default;
$ui-white-darken-2: #f5f5f5 !default;
$ui-white-darken-3: #efefef !default;
$ui-white-darken-4: #d8d8d8 !default;
$ui-white-darken-5: #9b9b9b !default;
$ui-black: #40485a !default;
$ui-black-lighten: #5a6274 !default;
$ui-black-darken: #272f41 !default;
$ui-green: #3ac88e !default;
$ui-green-lighten: #54e2a8 !default;
$ui-green-darken: #21af75 !default;
$ui-red: #ff4e4e !default;
$ui-red-lighten: #ff6868 !default;
$ui-red-darken: #e63535 !default;
$ui-blue: #286fdc !default;
$ui-blue-lighten: #4289f6 !default;
$ui-blue-darken: #0f56c3 !default;
// 引用
$ui-text: $ui-black !default;
$ui-text-lighten: $ui-white-darken-5 !default;
$ui-disabled: $ui-white-darken-4 !default;
$ui-success: $ui-green !default;
$ui-info: $ui-blue !default;
$ui-primary: $ui-red !default;
$ui-background: $ui-white-darken-2 !default;
为的就是在使用页面样式的时候,不适用任何硬编码的值来保证项目样式的统一,为项目中样式的变更打下扎实的基础。
Javascript 部分
JavaScript 作为近几年来变化最大的一部分,总结下来的改变主要集中在三个方面:一是需要将使用 ES2015、ES2016、ES2017 特性的 JavaScript 代码编译至大多数浏览器普遍支持的 ES5(对应工具为 Babel),二是需要将编译好的 JavaScript、CSS 及 HTML 整合起来,也就是我们常说的打包(对应工具为 webpack),三是需要对代码风格及规范进行检查(对应工具为 ESLint)。
Babel 配置
- .babelrc 作为Babel的配置文件,最核心的两部分就是presets 和 plugins.
- presets 代表了Babel配置的核心部分,其中 @babel/preset-env (babel-presets-env)整合了 es2015、es2016、es2017三个原来独立的preset,开发者直接引入 env 这样一个preset就可以使用上述的三个版本的新特性。
- plgins 根据官网的描述,更像是一个针对preset的补充,供开发者们自定义一些presets之外的功能,其中比较常见的如对象扩展集 … 就需要引入 babel-plugin-transform-object-rest-spread开启;除了js部分的扩展,Babel对React也做了相应只支持,如把jsx编译成React原生的React.createElement 方法及为对应React组件添加 displayName属性等等。
本脚手架的babel配置:
{
"sourceMaps": true,
"presets": [
"@babel/env",
"@babel/react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-syntax-dynamic-import",
["module-resolver", {
"root": ["./src"]
}]
]
}
Babel 作为一个基于插件系统打造的 JavaScript 编译工具,其可定制度是非常高的,开发者们完全可以根据自己的使用需要与编码习惯去选择或开发合适的插件以达到提升开发效率的效果。
webpack 配置
webpack 作为现在最流行的前端打包工具,其一路走来的发展史也是许多前端开发者的血泪史。webpack 1 到 webpack 2 时破坏式的升级导致了许多前端项目直到今天都仍然停留在 webpack 1,而 webpack 3 到 webpack 4 时彻底重构了的内部插件系统又导致了第二次断崖式升级。但值得庆幸的是,webpack 在最新的 4+ 版本中终于承认了「约定大于配置」并大幅减少了在功能与插件方面配置代码的数量。
webpack 配置的核心一是源代码的入口(entry)与打包后代码的出口(output),二是不同资源的加载器(loader),三是插件,常用的如处理 CSS 的 mini-css-extract-plugin,处理 HTML 的 html-webpack-plugin 等。具体实用的 webpack 配置大家可以参考示例项目 react-boilerplate 中的 webpack.config.js 部分。
ESLint 配置
相较于 Babel 与 webpack,ESLint 更像是一个可选项,因为它并不会直接影响最终编译完成的代码,而是在编写阶段对开发者的编码风格进行约束,帮助开发者写出更好的 JavaScript 代码。
写代码是一门手艺,对于手艺人来说从资深手艺人那里学来的经验就是自己成长路上最宝贵的财富。ESLint 让你可以不需要师从哪一位或哪几位优秀的程序员,只需要遵守他们定下的代码规范就可以写出和他们一样优秀的代码。现在市面上最流行的 ESLint 配置就是由 Airbnb 所提供的,我们只需要在 .eslintrc 中配置 extends 为 airbnb 就可以开启 Airbnb 的 JavaScript 编写规范。当然,为了满足自定义的需求,在 .eslintrc 的 rules 中我们也可以独立地开启或关闭任意一条代码检查规则。
文件目录
介绍完上边的基础和配置,我们可以先写一个建的helloworld;很简单,我们这里不做源码的展示。直接入正题,脚手架除了能够帮助团队解决上述提出的一些技术栈配置的问题外,还有就是能梳理项目的标准目录组织节后。从当下主流的规则划分,一般一个完整的项目至少包含一下几个部分。
- layouts/: 存放布局级别的组件
- views/: 存放页面级别的组件
- components/: 存放业务级别的 UI 组件
- hocs/: 存放业务级别的逻辑组件(看情况可与 components/ 合并,但建议分开)
- app/: 存放应用级别的配置信息,如菜单、路由等,以及应用初始化的相关代码,如初始化 redux store 等
- utils/: 存放通用的功能性函数,如数据聚合、处理等
- styles/: 存放全局的 CSS 样式、变量、mixins 等
- assets/: 存放静态资源,如图标、图片等
-
i18n/: 存放应用国际化需要的多语言文件
在将这些文件夹都添加到我们的脚手架后,让我们来写一个复杂点的页面.
最后关于 redux 部分的设置,根据业务需要可能会有所区别,个人可以参考以下的几个条件渐进式地选择数据流工具。React实战企业管理项目系列(三)React系列-实战02:脚手架搭建脚手架的搭建
redux
- 我需要一个全局数据源且其他组件可以直接获取/改变全局数据源中的数据
- 我需要全程跟踪/管理 action 的分发过程/顺序
redux-thunk
- 我需要一个全局数据源且其他组件可以直接获取/改变全局数据源中的数据
- 我需要全程跟踪/管理 action 的分发过程/顺序
- 我需要组件对同步或异步的 action 无感,调用异步 action 时不需要显式地传入 dispatch
redux-saga
- 我需要一个全局数据源且其他组件可以直接获取/改变全局数据源中的数据
- 我需要全程跟踪/管理 action 的分发过程/顺序
- 我需要组件对同步或异步的 action 无感,调用异步 action 时不需要显式地传入 dispatch
- 我需要声明式地来表述复杂异步数据流(如长流程表单、请求失败后重试等),命令式的 thunk 对于复杂异步数据流的表现力有限
虽然在设计脚手架时的一大原则就是尽可能少地引入第三方依赖,但因为 React 并不是一个大而全的框架,所以在搭建脚手架时还是难免需要引入 redux、react-router、babel、webpack 等这些必需的第三方依赖。而在后续维护中,根据业务场景的不同我们可以有以下两种不同的维护方式。
一是稳定压倒一切,即不更新依赖,使用搭建完成的脚手架直到不能够满足业务的需要时再推倒重来。二是及时更新,即对脚手架所有的第三方依赖进行定期(半个月或一个月)的升级,保证脚手架所使用的第三方依赖永远都是最新的稳定版本。对于业务场景并不复杂的企业来说,稳定压倒一切是提升生产力的不二法门。而对于大厂或者说业务场景较为复杂的企业来说,及时更新却是必须的。做好技术基础设施建设是解决未来不可预见的技术难题的基础,技术项目的落后很多时候是一步落后,步步落后,在遇到具体问题时再去寻求完美的解决方案是不现实的。
总结
在本节中我们从 HTML、CSS、JavaScript 三个方面分析了近年来前端开发界发生的变化以及如何使用最新的技术栈搭建出一个扩展性良好的自研脚手架。
在下一节中我们将正式进入企业管理系统搭建的讨论,从页面的基础布局开始一步步剖析企业管理系统中的痛点与解决方案。