记录自己的学习过程
wuqiren / blog Goto Github PK
View Code? Open in Web Editor NEW记录自己的学习过程
记录自己的学习过程
说来甚是惭愧,antd这个组件库打我入门React的时候就开始使用,到现在还有不懂的地方。
产品一直在说系统中的时间组件显示的是英文,非常不方便,能否改成中文,我就百度搜(真不知道给我推荐的是啥玩意),搜到的答案都是moment的版本和ant内的moment版本不一致导致的,我一直使用的是moment-mini,于是全局替换了一下,使用moment,并且版本保持一致,问题还是没解决。
后来被别的需求占用了,这几天闲了下来,静心看了看antd的官网有一个全局化配置的东西
这里有一个配置antd全局英文和中文的地方,按照说明搞了一下,完美解决
import { ConfigProvider } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN';
return <ConfigProvider locale={zh_CN}><App /></ConfigProvider>;
{
"name": "fs",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
"dependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0"
},
"devDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@vitejs/plugin-react": "^1.0.0",
"typescript": "^4.3.2",
"vite": "^2.6.4",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"eslint": "^8.1.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.4.1"
}
}
上图是一个Vite创建的React-TS项目的package.json文件内的内容,发现里面的dependencies和devDependencies字段看名字很相似,而且内部都是包内容相关的东西,那么这两者有什么不同呢?
先说结论:devDependencies开发环境使用,也就是我们本地使用
dependencies生产环境使用,线上或者测试环境。
本地运行代码的时候,我们是用TypeScript代码,但是本地的环境是没办法运行TS代码,这时候借助webpack等打包工具来将它转化成JS代码,那么你就需要类似babel的东西。有的时候你引入的包需要TS版本,那么你就引入'@types/react'这种类似的包。你想让自己写的代码很规范,你需要有工具来在本地校验的你代码,这时候你引入eslint。所以类似这种在本地帮助你能够开发或者更好开发的东西,都是安装在devDependencies,他们仅仅是在本地运行就可以。
关于生产环境你要明白,生产环境跑的代码是怎样的代码,是你打包好生成代码(也就是能够在浏览器运行的代码JS),还有依赖的第三方库React等,这些都是线上环境必须的,因此都是安装在 dependencies下。我们拿eslint举列子,你的代码不规范浏览器能够识别吗?能够的,你要明白,我们的代码是给人看,让机器运行的,机器是不管你的代码规范不规范,生产环境要求的是依赖的东西在就可以,很明显eslint根本不是线上环境所依赖的
那么如何使用yarn和install安装到devDependencies和dependencies呢?
yarn add xxxx -D // 安装xxx到devDependencies
yarn add xxx // 安装xxx到dependencies
npm install xxxx --save-dev 或者 -D // 安装xxx到devDependencies
npm install xxxx --save 或者 -S // 安装xxx到dependencies
使用官方提供的命令
yarn create @vitejs/app
然后根据你的需要进行选择,我们这里选择React-TS模板
这时候仅仅是支持React语法的,我们还需要其他的配置一起来完成React项目的搭建
在用脚手架生成React项目的时候,是有一个React-TS版本的选择的,建议大家直接选择React-TS版本,这样你可以直接跳过这部分内容。
如果你选择的是React版本,那么如果配置请看下面(没必要,直接选择TS版本呗)
Vite这里自己是支持Typescript语法的,但是它只编译,不校验。意思就是它只是把TS语法编译成JS,能够在浏览器中运行,但是它不会去做TS类型的校验,即使你定义的类型有错误,但是也能是通过正常的编译,如果我们想对TS语法进行校验,可以使用tsc --noEmit命令来进行校验
// test.ts
interface A {
name:string
}
export const a:A={
name:'fishfan',
age:'18'
}
上面代码明显是存在TS语法错误的,你能够在编辑器上看到错误(VScode自带的错误提示),但是浏览器仍然能够正常运行。
我们需要安装typescript
yarn add typescript @types/react @types/react-dom -D
然后在package.json中修改打包命令
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
在根目录建立tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": ["./src"]
}
这样就会在打包的时候,对错误进行提示
关于css cariable 可以查看该链接
安装相关插件
yarn add postcss-preset-env autoprefixer -D
Vie的内部是有Postcss,如果我们想使用postcss的其他plugin的话,在根目录新建postcss.config.js配置如下,情况视项目而定。
module.exports = {
plugins: [
require('autoprefixer'),
require('postcss-preset-env'),
],
};
这个就不用多说了,必装插件之一。方便的写规范的css,它会为你提供非常完整的hack兼容方案的。当然这里需要了解一下的是,它的大部分兼容数据来源Can I Use,另外一个稍微需要了解的插件配置参数就是browsers,不过一般我们都是在package.json中配置浏览器版本相关信息。
package.json内增加如下示例,这样其他插件也能够从中获取到项目将要兼容的版本
"browserslist": [
"> 1%", // 全球浏览器使用率大于1%。
"last 2 versions" // 每个浏览器中最新的两个版本。
]
postcss-preset-env是帮postcss找到package.json中的browserlist里面的配置,通过配置加载指定的css兼容性样式
建立的css文件名称为 [name].module.css 就会被识别的css module,如果你的css文件名称不是这么定义的,你是无法使用的css module的
如果验证?建立一个css文件名称为 app.module.css
// app.module.css
.text {
color: red;
}
// app.jsx
import { useState } from 'react';
import styles from './app.module.css';
function App() {
return <div className={styles.text}>hello fishfan</div>;
}
export default App;
你再将module字段去除,你会发现css样式不起作用
如果你要是使用哪个你就安装哪个预处理器即可 我们拿less为例
yarn add less -D
安装之后你就可以使用less,无须其他配置。
eslint是用来规范代码的书写格式,团队项目必备!
pritter 自动帮我们格式化的
在根目录建立.eslintrc.js文件,eslint的规则可以由我们自己来书写,关于如何配置可以 点击此处
这里就按照我的package.json来安装就可以
packag.json配置如下
{
"name": "vite",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"serve": "vite preview",
"lint:fix": "eslint ./src --ext .jsx,.js,.ts,.tsx --quiet --fix --ignore-path ./.gitignore",
"lint:format": "prettier --loglevel warn --write \"./**/*.{js,jsx,ts,tsx,css,md,json}\" ",
"lint": "yarn lint:format && yarn lint:fix ",
"type-check": "tsc"
},
"dependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0"
},
"devDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.28.2",
"@typescript-eslint/parser": "^4.28.2",
"@vitejs/plugin-react": "^1.0.0",
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"pre-commit": "^1.2.2",
"prettier": "^2.3.2",
"typescript": "^4.3.2",
"vite": "^2.6.4"
}
}
创建eslint规则文件.eslintrc.js
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
settings: {
react: {
version: 'detect',
},
},
env: {
browser: true,
amd: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:jsx-a11y/recommended',
'plugin:prettier/recommended', // Make sure this is always the last element in the array.
],
plugins: ['simple-import-sort', 'prettier'],
rules: {
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
'react/react-in-jsx-scope': 'off',
'jsx-a11y/accessible-emoji': 'off',
'react/prop-types': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'jsx-a11y/anchor-is-valid': [
'error',
{
components: ['Link'],
specialLink: ['hrefLeft', 'hrefRight'],
aspects: ['invalidHref', 'preferButton'],
},
],
},
};
创建不需要eslint检测的文件.eslintignore
node_modules
.DS_Store
dist
dist-ssr
*.local
node_modules/*
按照规则我们会发现我们的许多代码都是不符合规范的,如果去修改需要手动的一个一个去修改(或者在commit的时候,使用eslint一键修复),这样很麻烦,我们想实现在保存的时候,能够自动地修复。
这时候就需要我们的prettier出场
关于prettier详细介绍可以 点击此处
在VScode 我们首先要安装Prettier插件
在根目录插件.prettierrc.js文件 内容如下,可以根据自己的需求来配置规则
module.exports = {
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 90,
tabWidth: 2,
jsxBracketSameLine: true,
endOfLine: 'auto',
};
同样创建忽略修复的文件.prettierignore
node_modules
.DS_Store
dist
dist-ssr
*.local
node_modules/*
第一步打开设置搜索format on save 如图进行设置
第二步搜索**formatter ** 选择prettier
在代码提交之前,进行代码规则检查能够确保进入git仓库的代码都是符合规范的,但是整个项目运行lint速度会很慢,lint-staged能够让lint只检测暂存区的文件,所以速度很快
yarn add husky lint-staged -D
package.json添加如下配置
"husky":{
"hooks":{
"pre-commit":"lint-staged"
}
},
"lint-staged":{
"*.js":"eslint --fix",
"*.ts":"eslint --fix"
},
当文件变化,我们git commit它们,pre-commit钩子会启动,执行lint-staged命令.
路径别名 和webpack中的alias功能一样,当项目比较复杂的时候,如果在深层的文件想引入最外层的文件 例如会出现如下情况,我们可以将src设置为跟目录,用@符来标识
import untils from '../../../../util' // 未使用别名
import untils from '@/util' // 使用别名后
使用extensions可以忽略导入时候的扩展名,例如
import App from './app.jsx // 未使用extensions
import App from './app // 使用extensions
修改vite.config.ts的文件
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
alias: {
'@': '/src',
},
},
})
关于配置中server需要关注的是proxy,请求代理,如果我们没有设置proxy,那么我们发送请求就会出现跨域的情况。
server: {
proxy: {
'/api': {
target: 'https://www.xxx.xxx',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
}
}
在vite.config.js这么配置,那么你的请求url前缀都是以**/api**开头的话,就会被转发到https://www.xxx.xxx中。
至此关于React项目基本的搭建已经完毕,下面介绍的是如何在Vite搭建的React项目中使用React-route和Antd
下面内容参照该文章
https://juejin.cn/post/6938671679153373214#heading-5
首选安装 react-router-dom
yarn add react-router-dom -D // 这么安装是react-router-dom的最新版本 我的版本6.0.2
react-router-dom的最新版本(6x之后)不提供Switch组件而是使用Routes,关于react-router-dom 5x版本和6x版本的差异请点击下方链接查看
在src文件下建立Home和About文件夹,并创建对应的文件
/src/Home/index.tsx
import React from 'react'
export default function index() {
return (
<div>
Home
</div>
)
}
/src/About/index.tsx
import React from 'react'
export default function index() {
return (
<div>
About
</div>
)
}
根目录创建route.tsx文件
这里我需要解释下为什么route文件是tsx而不是ts文件,主要是内部需要直接使用组件 ,之前我是使用导出函数,然后在Route里面执行函数的方式来,发现Vite不在热更新,很神奇,感觉这是Vite与最新的React-route-dom不兼容的问题,我补充下有问题代码的写法 如下:
// route.tsx
import Home from '../src/Home'
import About from '../src/About'
const routes = [
{
path: "/home",
component: Home
},
{
path: "/about",
component: About
}
];
export default routes
// App.tsx
import React from 'react'
import { Route, Routes } from "react-router-dom";
import routes from './route'
import './App.css'
function App() {
return (
<div className="App">
<Routes>
{
routes.map(item => {
return <Route path={item.path} key={item.path} element={item.component()}/>
})
}
</Routes>
</div>
)
}
export default App
这是使用之后Vite不在热更新的写法!!!
// route.tsx
import Home from '../src/Home'
import About from '../src/About'
const routes = [
{
path: "/home",
component: <Home/>
},
{
path: "/about",
component: <About/>
}
];
export default routes
修改App.tsx文件
import React from 'react'
import { Route, Routes } from "react-router-dom";
import routes from './route'
import './App.css'
function App() {
return (
<div className="App">
<Routes>
{
routes.map(item => {
return <Route path={item.path} key={item.path} element={item.component}/>
})
}
</Routes>
</div>
)
}
export default App
修改main.tsx文件
import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter} from 'react-router-dom'
import './index.css'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
)
分别访问 http://localhost:3000/home 和 http://localhost:3000/about 会看到自己路由生效
安装antd和 @ant-design/icons ,antd 4x版本之后图标库就和antd本身区分开了,需要单独安装
yarn add antd @ant-design/icons
在App.tsx文件引入antd
import React from 'react'
import { Route, Routes} from "react-router-dom";
import routes from './route'
import { Button } from 'antd'
import './App.css'
function App() {
return (
<div className="App">
<Button type="primary">fishfan</Button>
<Routes>
{
routes.map(item => {
return <Route path={item.path} key={item.path} element={item.component}/>
})
}
</Routes>
</div>
)
}
export default App
那是因为我们没有引入antd 的样式,在main.tsx引入antd的样式
// main.tsx
import 'antd/dist/antd.css'
样式生效了!!!
但是运行打包命令
yarn build
我们来看下打包后的css体积,有500多KB,很明显样式没有实现按需加载。
请注意我们这里使用的是less,如果你是使用css,下方配置对应的也需要修改
安装vite-plugin-imp
yarn add vite-plugin-imp -D
修改vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import vitePluginImp from 'vite-plugin-imp'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
vitePluginImp({
libList: [
{
libName: "antd",
style: (name) => `antd/lib/${name}/style/index.less`, // 这里是less,没安装less,就使用css(css使用全局样式无法生效)
},
],
})
],
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
}
}
},
})
删除main.tsx 引入antd样式的代码
import 'antd/dist/antd.css' 删除
再运行打包命令发现css的体积减小到40kb左右,体积大幅减少。
修改vite.config.js中的css配置,添加modifyVars对象,对象的变量就是antd全局的变量值
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
modifyVars:{
'primary-color': '#ff704c',
'link-color': '#ff704c',
'border-radius-base': '4px',
}
}
}
},
我们会发现设置的全局主题颜色发生了变化,说明我们的配置生效了
场景: 在终端输入code 文件名 . 回车后使用vscode打开当前文件夹
在你的命令终端执行如下命令打开bash或者zsh配置文件
# bash用户请使用
vi ~/.bash_profile
# zsh用户请使用
vi ~/.zshrc
点击i 进入编辑模式然后在文件的最后一行加入下面一行代码设置命令别名
# 设置vscode启动的命令别名
alias code="一会让你复制的内容"
查找自己vscode的code终端
会看到文件夹contents点击进入,按路径Contents/Resources/app/bin/code会找到code
拖code到终端或文件中会自动填入安装目录
将划线的复制粘贴到第二步需要粘贴的地方
退出编辑并进行保存
重新运行修改后的文件.bash_profile或者zshrc
source ~/.bash_profile // bash
source ~/.zshrc // zsh
随后你在终端运行
code 你要打开的文件
就可以直接在vscode中编辑自己的文件夹
这篇文章会讲解如何搭建一个最简单的脚手架,即通过命令生成已设定好的模板
我们先创建一个node-cli结构的文件
fishfan-cli
├─ bin
│ └─ cli.js # 启动文件
└─ package.json
// package.json
{
"name": "fishfan-cli",
"version": "0.0.1",
"description": "a good cli",
"main": "index.js",
"bin": {
"fishcli": "./bin/cli.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "fishfan",
"license": "ISC"
}
如上图所示,我们在终端执行命令的时候,统一走bin下面的cli.js文件(这里并不是一定要在bin目录下面,而是根据package.json文件的bin字段所确定)
编辑cli.js文件
#!/usr/bin/env node
console.log('hello,fishfan')
为了方便在本地进行调试,使用npm link链接到全局,mac下需要使用管理员权限
sudo npm link
友情提示:必须在你所创建的脚手架的文件下进行该命令操作,不然会出现下面的错误
我们随后可以随意创建一个文件,在该文件下执行下fishcli
看到成功打印的hello,fishfan,算是第一步成功
我们需要做的是让cli.js文件能够读懂我们的终端命令,比如fishcli create创建项目,为了能够在终端流利地操作命令行,我们引入commander模块
commander更多用法 👉 中文文档
npm install commander --save
编辑cli.js文件
#!/usr/bin/env node
const program = require('commander');
program
.command('create')
.description('create a project')
.action(() => {
console.log('欢迎使用fishcli脚手架')
});
// 解析用户执行命令传入参数
program.parse(process.argv);
在终端输入fishcli create查看命令是否创建成功
这时候我们的create命令已经创建成功,但是我们想实现和终端的交互功能,这就是要node的inquirer模块,同时我们发现很多的脚手架的文字都是彩色的,而我们的脚手架打印的都是默认颜色,我们引入chalk工具库
inquirer文档👉
npm install inquirer chalk --save
inquirer的基础用法
const inquirer = require('inquirer');
inquirer
.prompt([
/* 把你的问题传过来 */
])
.then(answers => {
/* 反馈用户内容 */
})
.catch(error => {
/* 出现错误 */
});
假设我们这里做的是一个React脚手架,我们和用户设定的问题为
上述的prompt第一参数和我们所设置的问题进行结合,得到的question配置大致是这样
const question = [
{
name:'conf', /* key */
type:'confirm', /* 确认 */
message:'是否创建新的项目?' /* 提示 */
},{
name:'name',
message:'请输入项目名称',
when: res => Boolean(res.conf) /* 是否进行 */
},{
name:'author',
message:'请输入作者',
when: res => Boolean(res.conf)
},{
type: 'list', /* 选择框 */
message: '请选择公共管理状态?',
name: 'state',
choices: ['mobx','redux'], /* 选项*/
filter: function(val) { /* 过滤 */
return val.toLowerCase()
},
when: res => Boolean(res.conf)
}
]
我们接着完善cli.js的代码
#!/usr/bin/env node
const program = require('commander');
const chalk = require('chalk')
const inquirer = require('inquirer')
const question = [
{
name:'conf', /* key */
type:'confirm', /* 确认 */
message:'是否创建新的项目?' /* 提示 */
},{
name:'name',
message:'请输入项目名称?',
when: res => Boolean(res.conf) /* 是否进行 */
},{
name:'author',
message:'请输入作者?',
when: res => Boolean(res.conf)
},{
type: 'list', /* 选择框 */
message: '请选择公共管理状态?',
name: 'state',
choices: ['mobx','redux'], /* 选项*/
filter: function(val) { /* 过滤 */
return val.toLowerCase()
},
when: res => Boolean(res.conf)
}
]
program
.command('create')
.description('create a project')
.action(() => {
console.log(chalk.green('欢迎使用fishcli,轻松构建react ts项目~🎉🎉🎉'))
inquirer.prompt(question).then(answer=>{
console.log('answer=', answer )
})
});
// 解析用户执行命令传入参数
program.parse(process.argv);
效果如下
这时候我们可以拿到用户所输入的信息,然后我们就可以根据用户输入的信息,来选择适合用户输入的模板进行创建,这里的作者名字和项目名称都是动态的,因此我们需要动态去修改模板里面的文件,将用户输入的内容进行替换,本文暂时不讲解。
在最外层我们创建template文件夹,这里模板文件就是供用户下载的文件,如果你的脚手架有多种选择比如mobx或者redux,那么这里的模板文件就不止一个。这里的template文件我使用的是webpack5搭建的React项目,代码地址,关于如何使用webpack5搭建react项目可以查看该文章链接
由于template项目模板,有可能是深层的文件结构,我们需要深拷贝项目文件,需要node的fs模块
我们在src下创建create.js文件
src/craete.js
const fs = require('fs');
const chalk = require('chalk')
const { Buffer } = require('buffer');
/* 三变量判断异步操作 */
let fileCount = 0; /* 文件数量 */
let dirCount = 0; /* 文件夹数量 */
let flat = 0; /* readir数量 */
module.exports = function (res) {
/* 创建文件 */
console.log(chalk.green('------开始构建-------'));
const sourcePath = __dirname.slice(0, -3) + 'template';
console.log(chalk.blue('当前路径:' + process.cwd()));
/* 修改package.json*/
revisePackageJson(res, sourcePath).then(() => {
copy(sourcePath, process.cwd());
});
};
const copy = (sourcePath, currentPath)=> {
flat++;
fs.readdir(sourcePath, (err, paths) => {
flat--;
if (err) {
throw err;
}
paths.forEach((path) => {
if (path !== '.git' && path !== 'package.json') fileCount++;
const newSoucePath = sourcePath + '/' + path;
const newCurrentPath = currentPath + '/' + path;
fs.stat(newSoucePath, (err, stat) => {
if (err) {
throw err;
}
if (stat.isFile() && path !== 'package.json') {
const readSteam = fs.createReadStream(newSoucePath);
const writeSteam = fs.createWriteStream(newCurrentPath);
readSteam.pipe(writeSteam);
console.log(chalk.green('创建文件:' + newCurrentPath));
fileCount--;
} else if (stat.isDirectory()) {
if (path !== '.git' && path !== 'package.json') {
dirCount++;
dirExist(newSoucePath, newCurrentPath, copy);
}
}
});
});
});
}
const dirExist =(sourcePath, currentPath, copyCallback)=> {
fs.exists(currentPath,(exist) => {
if (exist) {
copyCallback(sourcePath, currentPath);
} else {
fs.mkdir(currentPath, () => {
fileCount--;
dirCount--;
copyCallback(sourcePath, currentPath);
console.log(chalk.yellow('创建文件夹:' + currentPath));
});
}
});
}
const revisePackageJson = (res, sourcePath) => new Promise((resolve) => {
fs.readFile(sourcePath + '/package.json', (err, data) => {
if (err) throw err;
const { author, name } = res;
let json = data.toString();
json = json.replace(/demoname/g, name.trim());
json = json.replace(/demoAuthor/g, author.trim());
const path = process.cwd() + '/package.json';
const data1 = new Uint8Array(Buffer.from(json));
fs.writeFile(path, data1, () => {
console.log(chalk.green('创建文件:' + path));
resolve();
});
});
});
在cli.js引入我们创建的create文件的方法
const create = require('../src/create');
program
.command('create')
.description('create a project')
.action(() => {
console.log(chalk.green('欢迎使用fishcli,轻松构建react ts项目~🎉🎉🎉'))
inquirer.prompt(question).then(answer=>{
if (answer.conf) {
create(answer)
} else {
console.log(chalk.yellow('没关系,希望以后有合作机会!'))
}
})
});
这时候我们fishcli create命令已经完善完毕,可以利用该命令来生成自己的文件
至此我们最基本的脚手架已经构建完成。
关于文件拷贝部分涉及node的fs知识,这里建议大家抽时间看看这里的内容,我这里也是直接使用了他人写的方法,自己着实没想到啥好的方法来。这篇文章其实介绍功能就是拷贝文件生成文件,终端等命令其实都是工具而已,不过所涉及的工具是大家学习终端操作所避不开的东西。
本文大部分内容借鉴以下俩篇文章,如果大家想深入了解脚手架知识,可以参考下面两篇文章,后续我会继续完善该脚手架,并对文章进行完善,做进一步的了解。
之前创建React或者Vue项目一直是使用官方推荐的脚手架,
在公司也是一直使用的是前辈实现的脚手架,未能深入了解脚手架所涉及的知识,这次决定学习下脚手架的知识,巩固一下Webpack以及学习最新的Webpack5知识。
这篇文章我会一步一步搭建,中间会讲解各个步骤的原因,后续会推出视频版本
关于状态管理库我这里不会使用Redux或者Mobx,自从React引入hooks之后,对于Redux等依赖其实已经很少了,很多小型项目基本是不需要使用Redux,不过为了项目的完整性,我这里会使用一个小型的状态管理库(zustand)
目前接触过很多的后台管理项目,项目的简单程度完全不需要使用Redux这种非常大的状态管理库,非常影响项目的体积,各位读者可以回想下,你所接触的后台管理系统,各个页面数据交流真的多吗?
一个真正的易用的状态管理工具不需要过多复杂的概念,但这往往是设计过程中就要去思考到的,但我觉得使用者对于工具的设计感和创造者对于工具的设计感有时候会出现冲突,往往后者会沉溺于复杂的构建中,逐渐也就淡化了用户的感受。
进入项目的根目录,创建默认的package.json,
yarn init -y
或者
npm init -y
这里是可以使用npm 也可以使用yarn。我这里统一用yarn 进行安装
|--package.json
package.json
{
"name": "template",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
}
yarn add webpack webpack-cli webpack-merge -D
为了区分开发与生产环境,方便维护和特殊处理,就不把所有的内容配置到webpack.config.js这一个文件里面。区分webpack的配置还是非常有必要的,可以加快开发环境的打包速度,有时候遇到开发环境打包慢,可以排查下是否配置有问题(如开发环境开启了代码压缩等),最终的整合使用webpack-merge工具
在根目录新建workflow目录,里面有三个代码文件
|--package.json
|--workflow
| |--webpack.base.config.js 通用配置
| |--webpack.dev.config.js 开发环境配置
| |--webpack.prod.config.js 生产环境配置
每当我们对webpack进行修改的时候,都需要验证下,对package.json做下修改,
package.json
{
"name": "template",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"webpack": "^5.58.2",
"webpack-cli": "^4.9.1",
"webpack-merge": "^5.8.0"
},
"scripts": {
"build": "webpack --config ./workflow/webpack.dev.config.js"
}
}
这里有个坑 如果你的webpack的配置文件不是在最外层(或者你的名字不是webpack.config.js),而是按照我们这种写法来写,但是你的package.json 的script中的build命令写法按照webpack官网写的话(如下代码),你会发现你的webpack配置未能生效
pageage.json
....
"scripts": {
"build": "webpack"
}
当你的webpack后面没有任何配置,默认是读取webpack.config.js这个文件,但是前面我们为了区分开发和生产环境,并不推荐这种使用方式,所以你在写build命令的时候,需要加上 --config ./workflow/webpack.dev.config.js
入口文件,webpack会首先从这里开始编译
// webpack.base.config.js
const path = require('path');
module.exports = {
entry: {
main: './src/index.js',
},
};
定义打包后输出的位置以及对应的文件名,[name]是占位符,这话字段是根据entry中定义的key值来定的,即为main
// webpack.base.config.js
const path = require('path');
module.exports = {
entry: {
main: './src/index.js',
},
output:{
path: path.resolve(__dirname, '../dist'), //这里可以不写,默认生成的路径和workflow同级
filename: '[name].bundle.js',
}
};
// webpack.dev.config.js
const {merge} = require('webpack-merge');
const commonConfig = require('./webpack.base.config');
const devConfig = {
mode: 'development',
devtool:"source-map", //会生成map文件,能够清晰的展示报错信息的位置,视频会详解,后面可看source-map内容
};
module.exports = merge(commonConfig, devConfig);
在终端运行
yarn run build
即可以在根目录看到生成的dist目录
上面的代码中涉及一个字段mode,它是用来告知webpack使用相应模式的内置优化,你在开发环境和生产环境进行的操作不一定都是相同的。
mode有development和production两个数值,一般你本地启动服务的时候,mode是使用development,而我们部署到生产环境(线上环境)的时候,都是使用production。
当 webpack 打包源代码时,可能会很难追踪到 error(错误) 和 warning(警告) 在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会直接指向到 bundle.js。你可能需要准确地知道错误来自于哪个源文件,所以这种提示这通常不会提供太多帮助。
为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps 功能,可以将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你。
因此本地开发的时候我们需要定位到具体的错误地址devtool的数值为source-map,而生产环境devtool我们一般设置为cheap-module-source-map 这种不会显示源码,而且会对空格或者空行进行删除,减少文件体积大小
// webpack.prod.config.js
const {merge} = require('webpack-merge');
const commonConfig = require('./webpack.base.config');
const devConfig = {
mode: 'production',
devtool:"cheap-module-source-map",
};
module.exports = merge(commonConfig, devConfig);
webpack的插件机制使得webpack拥有很多丰富的功能
https://www.webpackjs.com/plugins/
当我们构建一个Web的时候,我们需要一个HTML,该HTML再引入JavaScript代码。HtmlWebpackPlugin简化了HTML文件的创建,以便为你的webpack包提供服务。这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。
yarn add html-webpack-plugin -D
修改webpack.base.config.js文件,添加html-webpack-plugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].bundle.js',
},
plugins: [
new HtmlWebpackPlugin(),
],
};
运行 yarn run build 命令就默认在dist生成一个index.html文件,并且自动给你引入打包好的文件
// dist/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack App</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script defer src="app.bundle.js"></script>
</head>
<body>
</body>
</html>
这个index模板是可以由我们来定义的
在根目录创建public目录,该目录下使我们存放一些公共文件的地方,创建模板index.html
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
代码中的htmlWebpackPlugin字段是在webpack中定义的
修改webpack.base.config.js
....
plugins: [
new HtmlWebpackPlugin({
title: '欢迎fishfan同学',
template: path.resolve(__dirname, '../public/index.html'),
filename: 'fish.html',
}),
],
运行yarn run build ,dist目录下回生成一个fish.html 这个文件名字就是由filename来决定的。默认为index.html,后续我们会删除这个filename
webpack5 自带有clear:true,这里可以使用也可以不使用
clear-webpack-plugin 插件打包前用来清理dist目录之前打包的文件,如果不清理每次打包,dist目录下都会存在上次打包的文件,特别是生成的文件是由hash值来命名的时候,即
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].[chunkhash].js'
},
安装
yarn add clear-webpack-plugin -D
修改webpack.base.config.js文件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
....
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: '欢迎fishfan同学',
template: path.resolve(__dirname, '../public/index.html'),
}),
],
Friendly-errors-webpack-plugin识别某些类别的webpack错误,并清理,聚合和优先级,以提供更好的开发人员体验。具体效果可以访问下面链接查看
https://www.npmjs.com/package/friendly-errors-webpack-plugin
yarn add friendly-errors-webpack-plugin -D
修改webpack.base.config.js文件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
....
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: '欢迎fishfan同学',
template: path.resolve(__dirname, '../public/index.html'),
}),
new FriendlyErrorsWebpackPlugin(),
],
为什么需要loader?
在使用webpack的时候,并不是所有类型的文件都能够处理的,因此我们需要通过loader进行转换
什么是loader?
所谓 loader 只是一个导出为函数的 JavaScript 模块,可以把它理解成不同的打包方案,针对不同的类型的文件使用不同的打包方案。
目前我们项目中只有一个HTML和一个index.js文件,是没有什么作用的,我们还需要webpack做一些事情。
我们需要能够在JavaScript中使用CSS,就需要使用css-loader和style-loader两个loader。
css-loader能够让webpack解析的时候识别css语法,而style-loader是将css注入DOM中,两者缺一不可
修改webpack.base.config.js文件
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'], // 执行顺序从右到左,因此这俩loader的顺序不能改变
},
],
},
plugins: [
...
],
添加css文件
src/css/index.css
.title {
color: red;
}
修改index.js文件
/src/index.js
import './css/index.css';
function login() {
const oH2 = document.createElement('h2');
oH2.innerHTML = 'fish-cli';
oH2.className = 'title';
return oH2;
}
document.body.appendChild(login());
现在的文件目录结构
|--package.json
|--dist
| |--index.html
| |--app.bundle.js
| |--app.bundle.js.map
|--public
| |--index.html
|--workflow
| |--webpack.base.config.js 通用配置
| |--webpack.dev.config.js 开发环境配置
| |--webpack.prod.config.js 生产环境配置
|--src
| |--index.js
| |--css
| | |--index.css
运行打包命令yarn run build 后打开dist下的index.html会发现我们配置的css生效了
在实际项目中,我们都会借助CSS预处理语言,而不是去写原生的CSS,最常用的有less
安装less-loader
yarn add less less-loader -D
修改webpack.base.config.js文件
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader'],
},
]
}
在css文件下,建立一个index.less文件
// index.less
@bgColor: '#123456';
@fontsize: 100px;
.title {
background-color: @bgColor;
font-size: @fontsize;
}
在src/index.js文件下引入index.less
import '../css/index.css';
import '../css/index.less';
function login() {
const oH2 = document.createElement('h2');
oH2.innerHTML = 'fish-cli';
oH2.className = 'title';
return oH2;
}
document.body.appendChild(login());
运行yarn run build 查看结果没有问题
我们在使用webpack对css或者less打包之后,要保证最终生成的CSS代码能够在不同的平台运行,比如不同的浏览器或者不同版本的浏览器,这时候就需要某些工具来处理
首先我们要订一个标准,让Webpack按照该标准来进行打包。
修改package.json文件,添加browserslist字段
//package.json
...
...
"browserslist": [
">1%",
"last 2 versions",
"not ie <=10"
]
针对CSS样式的补全(不同的浏览器CSS类需要加不同的前缀,例如-ms-transform的-ms-前缀),我们需要接入postcss工具
postcss是什么?
postcss就是一个运行JavaScript插件转换样式的工具,这些插件可以检查你的CSS,编译尚未被浏览器广泛支持的先进的CSS语法,内联图片等其他很多优秀功能
postcss
安装
yarn add postcss postcss-loader postcss-preset-env autoprefixer postcss-nested -D
关于postcss插件的具体含义和用法可以查看下面地址
https://github.com/postcss/postcss/blob/main/docs/README-cn.md
新建postcss文件(和src目录同级别),文件名固定必须是postcss.config.js,配置如下
module.exports = {
plugins: [
require('autoprefixer'),
require('postcss-nested'),
require('postcss-preset-env'),
],
};
修改webpack.base.config.js文件
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
{
test: /\.less/,
use: ['style-loader', 'css-loader', 'postcss-loader','less-loader'],
},
]
}
如何我们的css文件存在这样的形式
// x.css
@import './index.css'
需要对css-loader增加一些选项,修改webpack.base.config.js文件
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
'postcss-loader',
],
},
Babel是一个JavaScript编译器,能将ES7或者ES6语法的代码转换成ES5的(浏览器可识别),让你使用最新的语言特性而不用担心兼容问题
yarn add babel-loader @babel/core @babel/preset-env -D
yarn add @babel/plugin-transform-runtime @babel/runtime-corejs3 -D //在JS的情况下来使用异步
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime", {
"corejs": 3
}
]
]
}
修改webpack.base.config.js 在rules中添加
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: ['babel-loader'],
},
运行yarn run build 可以看到生成的js文件里面都是转译过后的代码
在webpack5之前,在处理图片或者文本类文件的时候都是使用file-loader或者是url-loader,在webpack5之后可以使用Asset Modules(资源模块),就不需要配置loader
修改webpack.base.config.js配置
{
test: /\.(png|svg|gif|jpe?g)$/,
type: 'asset',
generator: {
filename: 'img/[name].[hash:4]:[ext]',
},
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
现在webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择:小于 10kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
在src文件创建img文件夹,里面存放一张图片名字为fish.jpeg
修改src/index.js文件
import ImgSrc from './img/fish.jpeg';
function login() {
const IMG = document.createElement('img');
IMG.src = ImgSrc;
return IMG;
}
document.body.appendChild(login());
运行打包命令yarn run build 可以看到在打包生成后的dist目录中有img目录,图片的名称和我们设置的名字格式一致
修改webpack.base.config.js文件
{
test: /\.(eot|svg|ttf|woff|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[hash:3]:[ext]',
},
},
当我们改动代码的时候,希望能够自动重新编译,webpack-dev-server就可以帮助我们在代码发生变化的时候自动编译代码
yarn add webpack-dev-server -D
我们这里的webpack-dev-server使用过的是最新的4.4版本,v3x版本有很多不一样,3和4版本不同的地方可以查看点击此处
修改webpack.dev.config.js文件
// webpack.dev.config.js
const devConfig = {
mode: 'development',
devtool: 'source-map',
devServer: {
historyApiFallback: true, // 所有的404都会连接到index.html
open: true,
hot: 'only',// 只刷新我们更新改动的模板 在构建失败时不刷新页面作为回退
port: 8080,
},
};
注意使用webpack-dev-server的时候,HTML的模板名字一般是index.html,因此我们在使用HtmlWebpackPlugin的时候,filename不用写,如果写的话,数值为index.html
运行yarn run dev,浏览器会自动打开一个页面,这时候我们修改代码,浏览器会自动实现刷新功能
yarn add typescript esbuild-loader -D
这里为了提高性能,并没有使用传统过的ts-loader 选择最新的esbuild-loader
修改webpack.base.config.js
{
test: /\.(js|ts|jsx|tsx)$/,
include: path.appSrc,
use: [
{
loader: 'esbuild-loader',
options: {
loader: tsx,
target: '2015',
},
},
],
},
之前针对js新特的解析的babel-loader就会失效
为了兼容TypeScript文件,新增typescript配置文件tsconfig.json(和src同等级)
// tsconfig.json
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"paths": {
"@/*": [
"src/*"
]
}
}
}
yarn add react react-dom @types/react @types/react-dom -D
由于对JSX语法在支持Typescript部分已经做了配置,React这里就无须进行配置
我们这里验证一下React语法是否成功
创建index.tsx文件在src下
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
const Index = () => {
return <div id='root'>欢迎fishfan</div>;
};
ReactDOM.render(<Index />, document.getElementById('root'));
修改入口文件配置
entry: {
app: './src/index.tsx',
},
运行yarn run dev,我们所配置的代码已经成功
创建 import 或 require 的别名,来确保模块引入变得更简单,例如,一些位于 src/ 文件夹下的常用模块:
在不使用resolve的时候你会这么写
import {method} from '../../../src/utils'
使用resolve的alias之后
import {method} from '@/src/utils'
能够使用户在引入模块时不带扩展,例如
在不使用resolve的时候你会这么写
import {method} from './src/utils.js'
使用resolve的extensions之后
import {method} from './src/utils'
修改webpack.base.config.js
....
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].bundle.js',
clean: true, // webpack5 自带的清空dist目录功能
},
resolve: {
alias: {
'@': resolve('src'),
ReactDOM: path.resolve('./node_modules/react-dom'), // 保证全局只有一个react,否则会导致hook无法使用
React: path.resolve('./node_modules/react'), // 保证全局只有一个react,否则会导致hook无法使用
// https://github.com/facebook/react/issues/13991
},
extensions: ['.js', '.jsx', '.ts', '.tsx'], //没有写后缀时进行补全
},
具体webpack的环境区分代码请点击下方链接查看
修改下打包命令
"scripts": {
"build": "webpack --config ./workflow/webpack.prod.config.js",
"dev": "webpack serve --config ./workflow/webpack.dev.config.js"
},
关于zustand后续会出一个新的章节来介绍这个工具库
我差点爆仓了,哔哩哔哩第三季度的财报出来,亏损幅度加大,美股就开始暴跌,第一天先跌10%,我本来港股是赚了24%,本以为B站股票翻倍不是梦想,谁能想到一天直接跌10%,第二天也就是今天早上(昨天夜里美股跌了17%),我早早来公司,打算在9点的时候将手上的股票赶紧出售了,谁知道B站居然停牌了,也就意味着我B站11W的股票暂时算是损失,直接导致我的账户处于危险状态,维持保证金已经大于总资产总值,已经达到爆仓的地部,无奈之下,紧急割肉呷哺呷哺,才将账户回复到中等水平。
当初踏入股市,感觉赚钱很容易,中概股都是在低位,我刚好入场,这不就是抄底吗?这不就是摆明让我赚钱,哎~
我一直关注的up主,也是炒股的,莫大韭菜一直在说炒股你赚钱那是因为你运气好,和你没啥关系,要时时刻刻保持一个敬畏心。
股市有风险,投资需谨慎!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.