从零开始搭建一个webpack+react项目

1,698 阅读3分钟

从零开始,慢慢更新,慢慢探索~~~

简单的demo

我们先从一个简单的demo来看看具体的流程:

初始化

执行 npm init 创建一个符合node规范的项目,创建之后会成一个package.json项目。

{
  "name": "tsdemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
  },
  "author": "",
  "license": "ISC",
}

react

  1. 执行 npm i react react-dom 安装 reactreact-dom

  2. 创建页面:

// ***src/App.tsx***

import React from 'react';

const App: React.FC = () => {
  return (<div>hello, world</div>);
};

export default App;

// ***src/index.tsx***

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

// ***src/index.html***
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <meta name="viewport"
        content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <title>react-app</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

typescript

  1. 执行 npm i typescript -D 安装 typescript

  2. 创建配置文件:

// tsconfig.json
{
  "compilerOptions": {
    "outDir": "./dist",	// 输出的目录
    "module": "CommonJS",	// 指定生成哪个模块系统代码: "None""CommonJS""AMD""System""UMD""ES6""ES2015"
    "target": "ES2015",	// 指定 ES 目标版本,默认 ES3
    "jsx": "react",	// 在 .tsx 文件里支持 jsx
    "declaration": true,	// 生成相应的 .d.ts 文件
    "removeComments": true,	// 删除所有注释,除了以 /!* 开头的版权信息。
  },
  "include": [
    "src/**/*",	 // 需要编译的文件
  ],
  "exclude": [
    "node_modules", // 不编译此文件夹下的文件
  ],
}

webpack

  1. 执行 npm install webpack webpack-cli -S -D

webpack 用来打包,webpack-cliwebpack 命令可以执行

  1. 项目中有.tsx文件,所以要安装loader 进行处理:执行 npm install ts-loader --save-dev 安装 ts-loader.tsx文件进行打包。并进行如下配置。

  2. 创建配置文件:

// ***webpack.config.js***
const path = require('path')

module.exports = {
    mode: 'development',  // 模式,当前为开发模式,还有个生产模式,生产模式会自动压缩编译后代码到一行
    entry: './src/index.tsx',
    module: {
        rules: [
            {
                test: /\.tsx?$/,    // ts-loader是官方提供的处理tsx的文件
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    resolve:{
    	// 打包的时候报错:Module not found :Error : can't resolve 'App'
    	// 我们引入组件的时候,并没有加后缀(.tsx),
    	// 此配置会按顺序为我们找App.js App.jsx...找到就返回,找不到会报错
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.css', '.less', '.scss']
    },
    output:{
        filename:'bundle.js',
        path:path.resolve(__dirname,'dist')
    }
}

配置

最后在package.json文件中,配置如下

{
  "name": "tsdemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack" // 执行 npm run build 相当于 npx webpack
  },
  "author": "", 
  "license": "ISC",
  "dependencies": {
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  },
  "devDependencies": {
    "ts-loader": "^8.0.14",
    "typescript": "^4.1.3",
    "webpack": "^5.15.0",
    "webpack-cli": "^4.3.1"
  }
}

之后执行npm run build,就可以看到dist文件下打包好的文件。一个小的demo就完成了。

plugin

经过上边的一些列操作之后,我们看到了dist下打包好的文件,但是并没有看到页面。可以在 vscode 中安装插件 open in browser ,之后在 index.html 中右击选中 open in default browser,就可以看到浏览器中出现了html页面。

但是存在一个新的问题:并没有显示 App 组件中的东西(hello world)。

我们需要在 index.html中引入打包好的文件(bundle.js):

 <script src="../dist/bundle.js"></script>

删除掉dist文件夹,从新打包 npm run build

你会发现依旧没有任何东西,打开控制台,会报错:

Cannot read property 'createElement' of undefined
Consider adding an error boundary to your tree to customize error handling behavior.

这是因为 reactreact-dom 引入不规范导致的,将所有引入改为:

import * as React from 'react'
import * as ReactDom from 'react-dom'

前边的过程中,存在三个问题:

  1. 每次打包都要手动删除dist 文件夹
  2. 手动添加打包后的 bundle.js 文件
  3. html 页面并没有跑在 localhost 上,并且需要手动打开

接下来就逐步解决这些问题。

clean-webpack-plugin

clean-webpack-plugin 的作用是在打包之前删除output.path下的所有内容之后再进行打包。所以不用传递参数。

  1. 安装 :npm install --save-dev clean-webpack-plugin

  2. 引入并配置:

const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
	plugins:[        
        new CleanWebpackPlugin()
    ],
}

这样就不用每次打包前手动删除dist文件夹了。

html-webpack-plugin

自行点击标题进入官网按照教程下载就好。

HtmlWebpackPlugin的作用是当打包完成时,以src/index.html文件为模板,生成index.html文件,并将打包好的js文件注入到里边。

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
	plugins:[        
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ],
}

打包后你会看到dist下会有个html文件。并且将打包好的bundle.js文件自动添加。打开这个HTML,我们可以看到正常的页面。这样我们就不用逐个把打包后的文件添加到HTML文件中(所入口打包的话可能会产生很多打包后的文件,逐个添加很麻烦)。

webpack-dev-server

看官网自行安装。

module.exports = {
	devServer:{
        contentBase:'./dist', //借助devServer生成服务器放到dist目录下,但是dist目录下看不到任何东西,在内存中,这样可以提升速度
        open:true,  //可以自动打开网址不必手动打开
    },
}

配置命令:

  "scripts": {
    "build": "webpack",
    "start": "webpack serve"
    
    // "start": "webpack-dev-server" 
    // 报错Error: Cannot find module 'webpack-cli/bin/config-yargs'
    // code: 'MODULE_NOT_FOUND'
  },

运行 npm run start你会发现浏览器会自动打开窗口,并且当你修改完代码的啥时候,网页自动刷新。

loader

.jsx文件

执行npm install -D babel-loader @babel/core @babel/preset-env @babel/preset-react

根目录创建.babelrc文件并配置:

{
    "presets": ["@babel/preset-react","@babel/preset-env"]
}

配置webpack:

module:{
	rules:[
		{
          test: /\.jsx?$/,
          exclude: /node_modules/,
          use: {loader: "babel-loader"}
         }
	]
}

.css文件

安装style-loader : npm install --save-dev style-loader

安装css-loadernpm install --save-dev css-loader

配置如下:

{
	test: /\.css$/i,
    use: ['style-loader', 'css-loader'], //?modules 打开css-modules
},

css-loader负责分析各个文件之间的关系,将不同文件的代码生成一个代码块。style-loader负责将css-loader生成的代码块挂载到head中。所以loader使用顺序是从右向左,从下向上。

.less文件

安装npm i style-loader css-loader less-loader less -D

配置:

 {
	test: /\.less$/,
    use: ['style-loader', 'css-loader', 'less-loader']
},

其中less-loader就是将less语法转化为css语法,之后就和css做一样的处理。

babel

我们代码中经常会用到es6语法,但是低版本的浏览器并不支持es6,需要将其转化为es5

可以去babel官网详细的了解下。

我们主要使用到的是babel-loader,bable/core还有babel/preset-env前边我们已经安装过。

接下来就是在配置文件中配置:

rules: [
            {   test: /\.(js|jsx)$/,, // 以js和jsx结尾的文件
                exclude: /node_modules/,   //文件不在node_modules文件夹下
                loader: "babel-loader" , //babel是js和babel的桥梁,但是并不完成代码转化
                options:{
                    "presets": ["@babel/preset-env"]  //代码转化
                  }
            },
		]

低版本的浏览器中不存在某些es6的对象,比如map之类的。这时候需要将这些不存在的函数或者对象补充到浏览器中。可能会想到babelPolyfill 但是他在注入的时候很多东西都是全局注入的。所以我们用plugin-transform-runtime

plugin-transform-runtime

安装npm install --save-dev @babel/plugin-transform-runtimenpm install --save @babel/runtime 以及 npm install --save @babel/runtime-corejs2

配置如下:

		 {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                loader: "babel-loader",
                options: {
                	"presets": ["@babel/preset-env"]  //代码转化
                    "plugins": [
                        [
                            "@babel/plugin-transform-runtime",
                            {
                                "corejs": 2,
                                "helpers": true,
                                "regenerator": true,
                                "useESModules": false
                            }
                        ]
                    ]
                }

            },

babel-loader配置项options里面的内容可以单独抽出来放到根目录下.babelrc

.babelrc文件配置babel

{
    "presets": [
        "@babel/preset-react",//之前配置.jsx文件添加的
        "@babel/preset-env"
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": 2,
                "helpers": true,
                "regenerator": true,
                "useESModules": false
            }
        ]
    ]
}

antd

执行npm install antd --save安装。

首先如果会使用到icon需要先安装:npm install --save @ant-design/icons

  1. 报错
Support for the experimental syntax 'classProperties' isn't currently enable

解决:

npm install --save-dev @babel/plugin-proposal-class-properties

并配置.babelrc

"plugins": [
        "@babel/plugin-proposal-class-properties",
 ]
  1. 报错:缺少处理cssloader:

npm install --save-dev css-loader

npm install --save-dev style-loader

并配置:

	{
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
     },

在这个过程中,css-loader负责分析各个文件之间的关系,将不同文件的代码生成一个代码块。style-loader负责将css-loader生成的代码块挂载到head中。

全局引入样式

在入口文件(index.tsx)中全局引入样式:

import 'antd/dist/antd.css'

在使用组件的地方直接引入组件:

import * as React from 'react';
import {Button} from 'antd'

class Header extends React.Component {
  render() {
    return (
      <div>
        <Button type="primary">Primary</Button>
        <Button>Default</Button>
        <Button type="dashed">Dashed</Button>
        <Button type="danger">Danger</Button>
        <Button type="link">Link</Button>
      </div>
    );
  }
}
export default Header

局部引入样式

方法一:

入口文件中不再引入样式,在使用组件的页面引入:

import * as React from 'react';

import {Button} from 'antd'
import "antd/lib/button/style" //引入对应的样式

class Header extends React.Component {
  render() {
    return (
      <div>
        <div>header</div>
        <Button type="primary">Primary</Button>
        <Button>Default</Button>
        <Button type="dashed">Dashed</Button>
        <Button type="danger">Danger</Button>
        <Button type="link">Link</Button>
      </div>
    );
  }
}
export default Header

但是这样比较麻烦,每次使用组件都要引入对应的样式文件。

方法二(推荐):babel-plugin-import

安装:npm install babel-plugin-import --save-dev

.babelrc文件中配置:

"pulgins":[
	["import",{"libraryName": "antd","style": true}],
]

使用组件时直接引入组件即可:

import * as React from 'react';
import {Button} from 'antd'

class Header extends React.Component {
  render() {
    return (
      <div>
        <div>header</div>
        <Button type="primary">Primary</Button>
        <Button>Default</Button>
        <Button type="dashed">Dashed</Button>
        <Button type="danger">Danger</Button>
        <Button type="link">Link</Button>
      </div>
    );
  }
}
export default Header

坑坑坑~~~

在这整个过程中,千万要保证没有开启css Modules功能,否则className会被编译,不再是原来样子,导致无论怎么配置,样式都是不起作用的。

css-modules

使css样式只在局部起作用。配置Webpackcss-loader插件,因为它对 CSS Modules 的支持最好,而且很容易使用。

{
     test: /\.css$/i,
     use: ['style-loader', 'css-loader?modules'],
},

重点是在css-loader后边加上参数 ?modules 打开css modules 功能。打开控制台你可以看到className 被编译成了类似于Cijm8iX5re4lkWOQ2tG-p 的形式。

less

现在我们项目中越来越经常使用less

安装

执行npm install -g less安装less

使用

打开css-modules

执行npm i style-loader css-loader less-loader less -D 安装要使用的loader

配置:

{
    test: /\.less$/,
	use: ['style-loader', 'css-loader?modules', 'less-loader']
}

如果运行时报错:.bezierEasingMixin();将less版本降低到3.0以下,可以安装2.7.3。

第三方库:antd

项目中我使用了组件库antd,这是时候如果开启了css modules会导致引入的样式失效。原因是:css modules会把className编码成类似于_1sNE87DmRAiZ8U-5Rbq8Zn这样的形式,到时第三方库的样式对不上。

为了解决上述问题,我们可以进行如下配置:在node_modules中关闭css modules,在src下开启。

具体配置如下:

            {
                test: /\.css$/i,
                exclude: [/node_modules/], //不包含node_modules文件夹
                use: [
                    { loader: 'style-loader' },
                    {
                        loader: 'css-loader',
                        options: {
                            modules: true, //开启css-modules模式, 默认值为flase
                        }
                    }
                ],
            }, {
                test: /\.css$/i,
                use: ['style-loader', 'css-loader'],
                exclude: [/src/],   //不包含src文件夹
            },
            {
                test: /\.less$/,
                exclude: [/node_modules/],  //不包含node_modules文件夹
                use: [
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1, //在css-loader前应用的loader的数目, 默认为0
                            modules: true, //开启css-modules模式, 默认值为flase
                        }
                    },
                    'less-loader'
                ],
            },
            {
                test: /\.less$/,
                exclude: [/src/],  //不包含src文件夹
                use: ['style-loader', 'css-loader', 'less-loader'],
            },

重点就是通过excludemodules:true进行配置。