(三)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 三個方面分析了近年來前端開發界發生的變化以及如何使用最新的技術棧搭建出一個擴充性良好的自研腳手架。
在下一節中我們将正式進入企業管理系統搭建的讨論,從頁面的基礎布局開始一步步剖析企業管理系統中的痛點與解決方案。