前言
作为一个前端开发小学生,虽然说也写过不少react的项目,但是每当自己想要用心写一个主页、博客、或者demo的时候,会发现项目开始之前的配置总是让我很烦,如果直接使用create-react-app
脚手架生成一个react项目,看起来是很方便,但是事实上一拥而来的一大堆配置文件很多都用不上,或者看不懂,而且create-react-app
本身就是基于webpack实现的,但是它的配置文件并没有暴露出来,开发者也不知到它内置了哪些功能,哪些插件,非常难受,所以从零开始配置属于自己的项目框架,并且根据自己的习惯修改或者完善它,对于学习和工作都非常有必要
初始化项目录
首先新建一个项目目录
mkdir project
进入该目录
cd project
初始化项目
备注:该步如果执行失败则说明没有安装node或者没有将node可执行文件添加到全局变量,请自行到node官网学习或下载
npm init -y
执行完该命令之后项目目录中会自动生成一个
package.josn
文件,结构如下
{
"name": "project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
这是一个宏观描述你的项目的文件,每个字段的意思分别是
字段 | 解释 |
---|---|
name | 包的名称 |
version | 包的版本 |
description | 包的描述 |
main | 包的入口文件 |
script | 可执行脚本 |
keywoeds | 搜索是的关键字 |
author | 作者 |
license | 通行证 |
其实在我们写项目的过程中,以上这些配置项几乎都不用管,之所以会自动生成这些信息,是因为npm默认将我们的项目看作是一个包;如果我们开发的是一个包,那么我们当然要写清楚自己包的名字,版本,描述等一系列信息,以方便npm统一管理,这样当你的包发布后,别人就可以下载我们的包,就像我们下载别人的包一样;
不过我们是做一个项目,并不是想发布一个包,因此我们还要做一点修改
我们将package.json
中的"main": index.js
删除,并将其改为"private": true
来告诉npm
我们是一个私人项目,不是一个待发布的包,所以不需要什么执行入口文件
{
"name": "projec",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^5.5.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0"
}
}
至此,项目构建的地基就搭建好了
创建index.js
一个项目里只有一个package.json
文件多少显得有点空虚,因此现在我们就在项目根目录下创建一个src
目录,并且在该目录下创建一个index.js
文件
里边写点什么代码呢?先就来个最简单的吧
// index.js
console.log('Hello World')
代码已经写好了,现在我们需要做的,就是将它打包
安装webpack包
node中有两个包,webpack
和webpack-cli
webpack
内置了项目文件的加载、解析、打包等一些列核心功能
webpack-cli
的作用是让开发者能够方便的使用使用命令行调用webpack
功能
所以这两个包是必须是最先下载的
npm install webpack webpack-cli -D
如果执行成功,不出意外的话,我们的package.json
文件会多出这么一段内容
没错,我们的package.josn
会记录我们项目用到的包,并且将包名添加到文件中,这也正验证了前边说过的,package.json
文件是项目的描述文件,所以通常情况下都不要轻易改动package.json
里边的内容
当然了,项目目录中也多了一个名叫
node_modules
的文件夹,它正是我们项目中安装的包所存放的地方
by the way(-D -S -g区别)
这里说一下在
npm install
的时候-D -S -g
的区别,它们都是简写,具体全拼是什么自己去搜吧,这里只说区别
-g
很简单,就是全局安装,举个例子,假如我们用了npm install typescript -g
安装了typescript
包,那么这个包就被全局安装到了你的电脑里,它的可执行文件也会被添加到环境变量中,这时我们在任意文件夹都可以执行tsc
命令来编译.ts
文件
但是-D
和-S
就不一样了,加了-D
或者-S
的安装命令,npm
会将它们安装到项目目录中,并且会记录在项目的package.json
文件里,还是拿typescript
举例子,npm install typescript-D
会将typescript
包安装到项目目录的node_module
文件夹中,这个时候直接用tsc
命令编译是会报错的,因为电脑找不到该命令,如果我们想在该项目的命令行中调用它的tsc
编译功能,可以采用npx tsc
命令,npx
命令会在本地项目目录中查看是否有可执行文件,没有的话就去网上下载,下载下来用完再删除,不占用内存,npx create-react-app
也是这个原理,因此通常我不会将任何包安装到全局当中,因为它相当于装了一个软件,会永久占用电脑内存
那-D
和-S
的区别是什么呢,我们刚才就已经看到,-D
安装的包,会被记录到package.json
文件中的devDependencies
字段,大家不妨试试-S
呢?其实它也会被记录,只不过被记录到了dependencies
字段下
有什么区别呢?
有很多网上说-D
是开发环境,-S
是生产环境,太过于肤浅,事实上,在开发项目的过程中,它俩是没有任何区别的,只有在我们发布本地项目为一个包的时候,才会有区别,-D
安装的包表示在开发中需要用到,-S
安装的包表示项目执行的依赖,具体来讲,下载一个包的时候,-S
中的包会直接同时被连带下载,而-D
安装的包需要手动执行npm i
后才会被下载,不过开发项目的时候我们通常都只使用-D
好了,废话不多说,继续
奥对了,如果刚才尝试安装了无关包,可以使用
npm uninstall package_name
删除包
配置webpack文件
上一步我们下载好了wenpack
相关的包,这里我们要给它写一个配置文件
在项目根目录下,新建命名为
webpack.config.js
的文件,然后在该文件中粘贴这些内容
// webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
filename: 'bundle.js',
path: path.join(__dirname, '/dist')
}
}
这就是一个最基本的webpack配置文件
mode
表示当前的开发模式,通常值就为development
entry
表示入口,即告诉wenpack
从哪个文件开始解析
output
表示出口,即告诉webpack
解析后的文件输出命名、路径的问题
然后执行打包命令
npx webpack
正如刚才所说,npx
会找到项目中webpack
的可执行文件,然后webpack
会在项目根目录中找到名为webpack.config.js
文件,并根据文件中的配置打包我们的项目,我们的配置文件里声明了入口文件为./src
目录下的index.js
文件,出口文件名为bundle.js
并且路径为项目目录中的./dist
目录,所以我们会看到,项目中自动生成./dist
文件夹,并且里边有个bundle.js
,它就是打包好的index.js
到这里,我们就完成了项目的第一次打包了,还是很激动的
html-webpack-plugin
这是一个自动生成html
的插件
一个前端的项目怎么可能没有.html
文件,当然了,经过前边的一番折腾我们对这个项目的原理也有了一些了解,我大可以在./dist
目录里自己创建一个index.html
,然后用script
标签引入bundle.js
文件,这样每当我们写完一波代码,一个npx webpack
命令就可以将代码打包到./dist
文件夹里,非常方便
但是这样会存在很多问题,如果bundle.js
文件命名被修改,还要修改index.html
文件重新引入,或者如果项目里新增了其它标签,也要重新手动引入,所以我们使用html-webpack-plugin
插件,来让index.html
文件自动生成并自动引入其他文件
首先安装该插件
npm install html-webpack-plugin -D
然后还是
webpack.config.js
文件,新增导入插件、声明插件
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
filename: 'bundle.js',
path: path.join(__dirname, '/dist'),
clean: true
},
plugins: [new HtmlWebpackPlugin({})]
}
plugins
字段的意思就是告诉webpack我们要用到的插件
这里还有一个需要注意的地方,即webpack
在编译的时候是基于node
的,因此导入文件只能使用node
标准的commomJS,不能使用import
,不懂得同学自行搜索二者区别
然后再次执行打包
npx webpack
可以看到打包后的文件结构就变成了这样
太好了,配了这么久终于见到index.html
文件了
我们直接在浏览器打开这个打包好的index.html
文件,F12调试发现确实输出了hello word
这也说明html-webpack-plugin
这个插件可以从webpack.config.js
中的配置识别出哪些文件是index.html
文件需要引入的,并且自动生成一个html文件
当然,这个插件的强大之处远不止这些,用到的话我们后边再说
配置ts
一个合格的前端开发人当然不会用js
写自己的react
项目,我说的没错吧嘿嘿
为了跟上时代潮流,为了用到类型检查、泛型等一些列让自己代码更健壮的强大功能,我们当然还是要用ts
来写自己的项目
所以直接将./src
中的index.js
文件改名为index.ts
或index.tsx
(.ts
与.tsx
的关系其实和.js
与.jsx
的关系是一样的,不明白的同学请查阅React官方文档)
但是!!!,直接改名是不行的,打包的时候是报错的,因为webpack
不是万能的,它并不能识别并且编译ts
文件
因此我们要下载一些新的包,并且请出webpack
配置里最好玩的配置module
首先下载两个包typescript和ts-loader
npm install typescript ts-loader -D
typescript
是编译ts的核心包,不用过多解释
ts-loader
是告诉webapck
怎么解析编译ts
的包
日后我们在webpack
的学习和使用中会经常碰到类似什么什么-loader
的形式的包,它们的功能都有类似之处,就是告诉webpack
怎么解析某种类型的文件
再回到
webpack.config.js文件
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
filename: 'bundle.js',
path: path.join(__dirname, '/dist'),
clean: true
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
plugins: [new HtmlWebpackPlugin({})]
}
我们新增了一个module.rules
,并配置了相应的配置项
这个配置的意思是,告诉webpack
除了node_modules
文件夹下的内容,所有.tsx
或者.ts
后缀的文件,统统用ts-loader
处理,我这么一说,你就知道这几个配置项是什么意思了吧
当然了
ts
的编译也是需要配置文件的,由于我们已经下载了typescript
,直接用命令行生成ts
配置文件
npx tsc --init
执行完会看到项目目录中又生成了一个tsconfig.json
文件,该文件的具体配置和相关解释也请查看tsconfig.json官方文档
不过现在,最简单的配置内容我也帮你写好了,就是这样
{
"compilerOptions": {
"module": "ES2015",
"removeComments": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
这下,再打包一次
npx webpack
完美!没有任何问题
配置react
配置了这么半天,搞得我都差点忘了我们的最终目的是配置react
,前期有多麻烦,后期就有多方便,不然我们何必费这么大劲呢
第一步还是下载包
npm install react react-dom @types/react-dom @types/react -D
react
和react-dom
还是与webpack
和wepack-cli
的原理差不多,一个集成了核心功能,一个集成了接口调用,什么?你问我那为什么不直接集成统一为一个包呢,当然不行,因为有的脚手架只用到核心包,而调用包会自己重新有自己的一套,就比如vue
中的webpack
就用的是vue-cli
至于@types/react-dom
和@types/react
也很简单啦,就是react
和react-dom
是基于js
的,想要在ts
中顺利使用,需要这两个接口类型包进行一个过滤
下载好之后我们简单搭建一个react代码框架
新建./src/pages/App.tsx
import React from 'react'
const App: React.FC = () => {
return <div className="test">Hello world</div>
}
export default App
将
./src/index.tsx
内容改为
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './pages/App'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
打包
npx webpack
成功!!!
等等,但是我明明再App组件里声明了一个节点,为什么调试浏览器里并没有显示呢?
原因很简单啦,我们再这里创建了一个id
为root
的节点
但是反过来检查我们生成的index.html
文件
压根就没有什么类似<div id="root"><div>
的东西,当然不可能显示
这里当然还要说明一下,前面说到过html-webpack-plugin
插件神通广大,但是也不可能神通广大到自动给你生成一个类似<div id="root"><div>
的东西,它并不能深入到每一个文件里去细纠哪里创建了根节点,再者说html-webpack-plugin
插件也不是为react
一家服务的
通常我们会用模板
index.html
来解决这个问题
在项目根目录新./public
目录,在该目录中新建一个index.html
文件,内容就是最基本的内容
但是要添加一个id
为root
的节点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>test</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
修改`webpack.json文件的内容
这里就是告诉webpack
我们以public
里的index.html
文件为模板来自动生成新的html
文件,同时也可以在public
下的index.html
文件中提前写好title
标签,也会自动打包到新生成的html
文件中
当然了,新建的这个public
也不是仅仅用来存放index.html
的模板文件的,我们的网站Logo以及favivon.ico
图标文件都可以放到这里,然后通过webpack打包到项目中,具体操作可以去webpack
官方文档查看
我就习惯自己先找一个favivon.ico文件放到public目录下
然后通过html-webpack-plugin
打包
这次打包后页面就成功显示了我们的hello world
!!!
支持css
其实这一步的原理之前已经讲过了,直接上代码
安装包
npm install css-loader mini-css-extract-plugin -D
webpack.config.js
文件的module
字段
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
exclude: /node_modules/
}
]
},
具体的意思网上都有,不做赘述,总的来说就是支持css,并且生成单独的css打包文件,而不是集成在js文件里
优化
讲了这么多,给大家看一下我做一个项目之前webpack.config.js文件的样子是什么样子的
// node自带的path,可以进行路径处理相关的操作
const path = require('path')
// HtmlWebpackPlugin插件,自动生成index.html文件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// MiniCssExtractPlugin插件,用来生成单独的css文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
// 模式,当前为调试模式
mode: 'development',
// 启用source-map,方便调试定位
devtool: 'source-map',
// 入口,通常只有一个
entry: {
main: './src/index.tsx'
},
// 出口,为打包好的文件配置输出路径,命名等
output: {
filename: '[name].[contenthash:6].js',
path: path.join(__dirname, '/dist'),
clean: true
},
// 插件
plugins: [
new HtmlWebpackPlugin({
favicon: path.join(__dirname, '/public/favicon.ico'),
template: './public/index.html'
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:6].css'
})
],
// 对不同模块的处理方式
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
exclude: /node_modules/
}
]
},
// 声明的后缀,引入的时候不用加后缀
resolve: {
extensions: ['.ts', '.tsx', '.js']
}
}
相比与之前讲到的内容,我还有这些优化
devtool: 'source-map'
方便调试- 输出的命名都采用内容哈希,便于刷新
clean:true
输出时先清除打包文件里之前的内容