如何构建自己的 React 样板
什么是样板?
在编程中,术语“样板代码”是指反复使用的代码块。
假设您的开发堆栈由多个库组成,例如 React、Babel、Express、Jest、Webpack 等。当您启动一个新项目时,您会初始化所有这些库并将它们配置为相互协作。
每启动一个新项目,你都会重复自己之前的工作。你还可能在每个项目中引入不一致的库设置方式。这会导致在项目之间切换时出现混乱。
这就是样板发挥作用的地方。样板是一个模板,您可以克隆它并在每个项目中重复使用。
模块化的 JavaScript 生态系统通过各种库、框架和工具简化了应用程序开发。如果您不了解底层组件的基础知识,那么样板代码可能会令人望而生畏。让我们在创建自己的样板代码的同时,学习这些基本的构建块。
我使用的是 Webstorm、Git、NodeJS v8.9、NPM v5.6 和 React v16。启动你最喜欢的 IDE,创建一个空白项目,然后开始吧!
Git 存储库:设置
创建项目文件夹并初始化 git repo:
mkdir react-boilerplate && cd react-boilerplate
git init
您可以按照这些说明将该项目连接到 GitHub 上的您自己的 repo 。
自述文件
每个项目都应该包含一个落地页,其中包含对其他开发者有用的说明。让我们在项目根目录下创建一个README.md文件,其中包含以下内容:
# React-Boilerplate
This is my react-boilerplate
## Setup
npm install
npm run build
npm start
GitHub 在项目的登录页面上显示自述文件的内容
现在,将上述更改提交给 git:
git add .
git commit -m "created readme"
在每个部分结束时,你应该将代码提交到 git
文件夹结构
为您的项目创建以下文件夹结构:
react-boilerplate
|--src
|--client
|--server
使用以下命令:
mkdir -p src/client src/server
这个文件夹结构是基本的,并且会随着您在项目中集成其他库而不断发展。
Git 忽略
一旦我们构建了项目,就会自动生成一些文件和文件夹。让我们告诉 git 忽略一些我们事先想到的文件。
在根文件夹下创建.gitignore,内容如下:
# Node
node_modules/
# Webstorm
.idea/
# Project
dist/
.gitignore 文件中的注释以 # 为前缀
Node 包管理器
Node 项目的起点是初始化其包管理器,这会创建一个名为 package.json 的文件。该文件必须提交到 git 中。
它一般包含:
- 您的 NPM 项目描述
- 所有已安装软件包的引用列表
- 自定义命令行脚本
- 已安装软件包的配置
转到项目根目录并输入以下内容:
npm init
填写所有详细信息,接受后,npm 将创建一个如下所示的 package.json 文件:
{
"name": "react-boilerplate",
"version": "1.0.0",
"description": "Basic React Boilerplate",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/theoutlander/react-boilerplate.git"
},
"keywords": [
"Node",
"React"
],
"author": "Nick Karnik",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/theoutlander/react-boilerplate/issues"
},
"homepage": "https://github.com/theoutlander/react-boilerplate#readme"
}
静态内容
让我们创建一个静态 HTML 文件src/client/index.html,其内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React Boilerplate</title>
</head>
<body>
<div id="root">
Welcome to React Boilerplate!
</div>
</body>
</html>
Express Web 服务器
为了提供上述静态文件,我们需要在ExpressJS中创建一个 Web 服务器。
NPM v5 会自动将已安装的包保存在 package.json 的依赖项部分下,因此--save属性不是必需的
npm install express
我建议遵循文件命名约定,文件名采用小写字母,多个单词之间用点号分隔。这样可以避免跨平台遇到大小写敏感问题,并简化大型团队中多个单词的文件命名。
创建文件src/server/web.server.js并添加以下代码以通过 express 应用程序托管 Web 服务器并提供静态 html 文件:
const express = require('express')
export default class WebServer {
constructor () {
this.app = express()
this.app.use(express.static('dist/public'))
}
start () {
return new Promise((resolve, reject) => {
try {
this.server = this.app.listen(3000, function () {
resolve()
})
} catch (e) {
console.error(e)
reject(e)
}
})
}
stop () {
return new Promise((resolve, reject) => {
try {
this.server.close(() => {
resolve()
})
} catch (e) {
console.error(e.message)
reject(e)
}
})
}
}
我们上面创建了一个简单的 Web 服务器,带有启动和停止命令。
启动文件
接下来,我们需要创建一个索引文件,用于初始化各种高级组件。在本例中,我们将初始化 Web 服务器。不过,随着项目规模的扩大,您还可以初始化其他组件,例如配置、数据库、记录器等。
创建文件src/server/index.js,内容如下:
import WebServer from './web.server'
let webServer = new WebServer();
webServer.start()
.then(() => {
console.log('Web server started!')
})
.catch(err => {
console.error(err)
console.error('Failed to start web server')
});
巴别塔
要运行上面的ES6代码,我们需要先通过 Babel 将其转换为ES5。让我们安装Babel以及支持 ES2015 转译的babel-preset-env依赖项:
npm i babel-cli babel-preset-env --save-dev
在根目录下创建一个名为 .babelrc 的 babel 配置文件,并向其中添加以下详细信息:
{
"presets": ["env"]
}
env 预设隐式包含 babel-preset-es2015、babel-preset-es2016 和 babel-preset-es2017,这意味着您可以运行 ES6、ES7 和 ES8 代码。
构建命令
让我们创建命令来构建项目的服务器和客户端组件并启动服务器。在package.json的 scripts 部分下,删除包含 test 命令的行,并添加以下内容:
"scripts": {
"build": "npm run build-server && npm run build-client",
"build-server": "babel src/server --out-dir ./dist",
"build-client": "babel src/client --copy-files --out-dir ./dist/public",
"start": "node ./dist/index.js"
}
上面的 build 命令会在根目录下创建一个dist/public文件夹。build-client 命令只是将 index.html 文件复制到 dist/public 文件夹。
启动
您可以在上面的代码上运行 babel 转译器并使用以下命令启动 Web 服务器:
npm run build
npm start
打开浏览器并导航至http://localhost:3000。您应该会看到静态 HTML 文件的输出。
您可以通过按<Ctrl> C来停止 Web 服务器
测试工具:Jest
我再怎么强调在项目开始时引入单元测试的重要性也不为过。我们将使用Jest 测试框架,它旨在快速且对开发人员友好。
首先,我们需要安装jest并将其保存到开发依赖项中。
npm i jest --save-dev
单元测试
让我们添加两个测试用例来启动和停止 Web 服务器。
对于测试文件,你应该添加 .test.js 扩展名。Jest 会扫描 src 文件夹中所有文件名包含 .test 的文件,你可以将测试用例与要测试的文件放在同一文件夹下。
创建一个名为src/server/web.server.test.js的文件并添加以下代码:
import WebServer from './web.server'
describe('Started', () => {
let webServer = null
beforeAll(() => {
webServer = new WebServer()
})
test('should start and trigger a callback', async () => {
let promise = webServer.start()
await expect(promise).resolves.toBeUndefined()
})
test('should stop and trigger a callback', async () => {
let promise = webServer.stop()
await expect(promise).resolves.toBeUndefined()
})
})
测试命令
让我们在 package.json 的 scripts 部分添加一个 npm 命令来运行测试。默认情况下,jest 会运行所有文件名中带有.test 的文件。我们希望将其限制为只运行src文件夹下的测试。
"scripts": {
...
"test": "jest ./src"
...
}
安装 Jest 时会自动安装 babel-jest,如果您的项目中存在 babel 配置,它将自动转换文件。
让我们通过以下命令运行测试:
npm test
我们的应用程序已设置为通过Express Web 服务器提供静态HTML文件。我们集成了Babel以启用 ES6 和Jest进行单元测试。现在,让我们将重点转移到前端设置上。
React 设置
安装 react 和 react-dom 库:
npm i react react-dom
创建一个名为src/client/app.js 的文件,内容如下:
import React, {Component} from 'react'
export default class App extends Component {
render() {
return <div>Welcome to React Boilerplate App</div>
}
}
让我们通过src/client/index.js下的索引文件呈现应用程序:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
ReactDOM.render(<App />, document.getElementById('root'))
Babel React
如果你执行npm run build-client,你将会得到一个错误,因为我们还没有告诉 babel 如何处理 React / JSX。
让我们通过安装babel-preset-react依赖项来解决这个问题:
npm install --save-dev babel-preset-react
我们还需要修改 .babelrc 配置文件以启用转换反应:
{
"presets": ["env", "react"]
}
现在,当您运行npm run build-client时,它将在dist/public下创建 app.js 和 index.js ,并将 ES6 代码转换为 ES5。
在 HTML 中加载脚本
要将 React App 连接到 HTML 文件,我们需要在index.html文件中加载index.js文件。不要忘记清空#root节点的文本,因为 React App 将会被挂载到这个节点上:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React Boilerplate</title>
</head>
<body>
<div id="root"></div>
<script src="index.js"></script>
</body>
</html>
运行服务器
如果您启动 Web 服务器并转到http://localhost:3000,您将在控制台中看到一个带有错误的空白页。
未捕获的 ReferenceError:require 未定义
这是因为 Babel 只是一个转译器。为了支持动态加载模块,我们需要安装 webpack。
首先将 package.json 中 scripts 下的构建命令更改为 build-babel:
"scripts": {
"build-babel": "npm run build-babel-server && npm run build-babel-client",
"build-babel-server": "babel src/server --out-dir ./dist",
"build-babel-client": "babel src/client --copy-files --out-dir ./dist/public",
"start": "node ./dist/index.js",
"test": "jest ./src"
}
Webpack
Webpack 使我们能够轻松地模块化代码并将其打包成单个 JavaScript 文件。它支持众多插件,几乎任何你能想到的构建任务都有相应的插件。首先安装 Webpack:
本教程是在 webpack v4 发布之前发布的,因此我们将明确安装 webpack v3。
npm i webpack@^3
默认情况下,webpack 会查找名为webpack.config.js的配置文件,因此我们在根文件夹中创建它,并定义两个入口点,一个用于 Web 应用程序,另一个用于 Web 服务器。让我们创建两个配置对象并将它们导出为集合:
const client = {
entry: {
'client': './src/client/index.js'
}
};
const server = {
entry: {
'server': './src/server/index.js'
}
};
module.exports = [client, server];
现在,让我们指定 webpack 的输出位置,并设置目标构建,使其忽略诸如“fs”和“path”之类的原生 Node 模块。对于客户端,我们将其设置为web,对于服务器,我们将其设置为node。
let path = require('path');
const client = {
entry: {
'client': './src/client/index.js'
},
target: 'web',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist/public')
}
};
const server = {
entry: {
'server': './src/server/index.js'
},
target: 'node',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
};
module.exports = [client, server];
Babel 加载器
在运行 webpack 之前,我们需要配置它来处理 ES6 和 JSX 代码。这可以通过 loader 来完成。我们先安装babel-loader:
npm install babel-loader --save-dev
我们需要修改 webpack 配置,让 babel-loader 在所有 .js 文件上运行。我们将创建一个共享对象,定义模块部分,以便在两个 target 中复用。
const path = require('path');
const moduleObj = {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loaders: ["babel-loader"],
}
],
};
const client = {
entry: {
'client': './src/client/index.js',
},
target: 'web',
output: {
filename: '[name].js',
path: path.resolve(__dirname, '/pub')
},
module: moduleObj
};
const server = {
entry: {
'server': './src/server/index.js'
},
target: 'node',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
module: moduleObj
}
module.exports = [client, server];
对于合并嵌套共享对象,我建议查看Webpack Merge模块
排除文件
Webpack 会打包引用的库,这意味着 node_modules 中包含的所有内容都会被打包。我们不需要打包外部代码,因为这些包通常会被压缩,而且会增加构建时间和文件大小。
让我们配置 webpack 以排除 node_modules 文件夹下的所有包。这可以通过webpack-node-externals模块轻松完成:
npm i webpack-node-externals --save-dev
然后配置webpack.config.js来使用它:
let path = require('path');
let nodeExternals = require('webpack-node-externals');
const moduleObj = {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loaders: ["babel-loader"],
}
],
};
const client = {
entry: {
'client': './src/client/index.js',
},
target: 'web',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist/public')
},
module: moduleObj
};
const server = {
entry: {
'server': './src/server/index.js'
},
target: 'node',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
module: moduleObj,
externals: [nodeExternals()]
}
module.exports = [client, server];
更新构建命令
最后,我们需要对 package.json 下的脚本部分进行更改,以包含使用 webpack 的构建命令,并将index.js重命名为npm start 的server.js,因为这是 webpack 配置的输出。
"scripts": {
"build": "webpack",
"build-babel": "npm run build-babel-server && npm run build-babel-client",
"build-babel-server": "babel src/server --out-dir ./dist",
"build-babel-client": "babel src/client --copy-files --out-dir ./dist/public",
"start": "node ./dist/server.js",
"test": "jest ./src"
}
构建清洁
让我们添加一个命令来清理 dist 和 node_modules 文件夹,以便进行干净构建并确保项目仍然按预期工作。在此之前,我们需要安装一个名为rimraf的包(即rm -rf命令)。
npm install rimraf
脚本部分现在应该包含
"scripts": {
...
"clean": "rimraf dist node_modules",
...
}
使用 Webpack 进行清理构建
现在,您可以使用 webpack 成功清理和构建您的项目:
npm run clean
npm install
npm run build
这将在根文件夹下创建dist/server.js和dist/public/client.js 。
HTML Webpack 插件
但是,你可能注意到了index.html缺失了。这是因为我们之前使用 Babel 复制了未转译的文件。但是,webpack 无法做到这一点,所以我们需要使用HTML Webpack 插件。
让我们安装 HTML Webpack 插件:
npm i html-webpack-plugin --save-dev
我们需要在 webpack 配置文件的顶部包含该插件:
const HtmlWebPackPlugin = require('html-webpack-plugin')
接下来,我们需要向客户端配置添加一个插件键:
const client = {
entry: {
'client': './src/client/index.js'
},
target: 'web',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist/public')
},
module: moduleObj,
plugins: [
new HtmlWebPackPlugin({
template: 'src/client/index.html'
})
]
}
在构建项目之前,我们先修改一下 HTML 文件,删除对 index.js 脚本的引用,因为上面的插件会帮我们添加。当有一个或多个文件使用动态文件名时(例如,为了清除缓存而生成带有唯一时间戳的文件),这尤其有用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React Boilerplate</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
让我们重建项目:
npm run clean
npm install
npm run build
并且,验证我们现有的测试仍在运行:
npm test
我们进一步更新了样板以集成 React 和 Webpack,创建了额外的 NPM 命令,在 HTML 文件中动态引用 index.js,并将其导出。
酶的设置
在添加 React 测试之前,我们需要集成Enzyme,它将允许我们断言、操作和遍历 React 组件。
让我们首先安装 enzyme 和 enzyme-adapter-react-16,这是将 enzyme 连接到使用 react v16 及以上版本的项目所必需的。
enzyme-adapter-react-16 对 react、react-dom 和 react-test-renderer 具有对等依赖
npm i --save-dev enzyme enzyme-adapter-react-16 react-test-renderer
创建一个文件src/enzyme.setup.js,内容如下:
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
Enzyme.configure({
adapter: new Adapter()
})
我们需要通过在根对象下添加以下部分来配置 jest 以使用package.json中的src/enzyme.setup.js :
{
...
"jest": {
"setupTestFrameworkScriptFile": "./src/enzyme.setup.js"
}
...
}
React 组件测试
让我们测试一下 App 组件,确保它能够渲染预期的文本。此外,我们将对该组件进行快照,以确保其结构在每次测试运行后都不会发生变化。
在src/client/app.test.js下创建测试用例,内容如下:
import App from './app'
import React from 'react'
import {shallow} from 'enzyme'
describe('App', () => {
test('should match snapshot', () => {
const wrapper = shallow(<App/>)
expect(wrapper.find('div').text()).toBe('Welcome to React Boilerplate App')
expect(wrapper).toMatchSnapshot()
})
})
如果我们现在运行这个测试,它会通过并出现警告:
让我们通过安装一个名为raf的 polyfill 来解决这个问题:
npm i --saveDev raf
并将package.json下的 jest 配置更改为:
{
...
"jest": {
"setupTestFrameworkScriptFile": "./src/enzyme.setup.js",
"setupFiles": ["raf/polyfill"]
}
...
}
现在,您可以验证所有测试是否通过:
npm test
运行 React 测试后,你会注意到src/client/snapshots/app.test.js.snap处多了一个新文件。它包含 React 组件的序列化结构。必须将其提交到 git 中,以便在测试运行期间与动态生成的快照进行比较。
最终冲刺
让我们再次启动 Web 服务器并导航到http://localhost:3000以确保一切正常:
npm start
希望本文能帮助您了解如何使用 Express | React | Jest | Webpack | Babel 从零开始构建新项目。创建自己的可复用样板代码是一个好主意,这样您就能理解底层原理,并在创建新项目时抢占先机。
我们仅仅触及了表面,还有很大的改进空间来使这个样板准备好投入生产。
您可以尝试以下操作:
- 在 Webpack 中启用缓存清除
- 在 webpack 中使用 css-loader 打包 CSS 文件
- 在 webpack 中启用源映射
- 将调试命令添加到 package.json
- 热模块替换
- 通过nodemon检测到更改时自动重启 Web 服务器
您可能还喜欢
如果这篇文章有帮助,请❤️并在 Twitter 上关注我。
鏂囩珷鏉ユ簮锛�https://dev.to/theoutlander/build-your-own-react-boilerplate-4b8l