# react 部分
npm i react react-dom
# webpack 部分
npm i webpack webpack-cli
创建相应文件夹,得到最简单的项目结构
结构解析
webpack.config.dev.js
对于最简单的项目,配置文件是重点,目前只针对开发环境的配置,只有 webpack.config.dev.js
// webpack.config.dev.js
const path = require('path');
// 获取到项目的根目录路径
const rootPath = path.join(__dirname, '..');
// 获取到静态资源文件夹的路径
const publicPath = path.join(rootPath, 'public');
module.exports = {
// 入口文件路径
entry: path.join(rootPath, 'src/app.js'),
// 打包输入文件配置
output: {
filename: '[name].[hash].js',
path: path.join(rootPath, 'build'),
publicPath,
},
};
__dirname
返回当前文件的所在的文件夹路径
path.join(argv0, argv1, ...)
从 argv0 开始,不断向上回溯路径
[name].[hash].js
// package.json
...
"scripts": {
+ "build": "webpack --config config/webpack.config.dev.js --mode development",
"test": "echo \"Error: no test specified\" && exit 1"
},
...
执行 npm run build
, Webpack 会从 app.js
文件开始出发,将所有设计到的文件进行打包,最后输出到 build
文件夹中
--mode development
为开发模式下,会包含 sourceMap 信息,用于调试--mode production
为生产环境模式,会尽量简化打包后的代码
实际上,可以在 Webpack 配置文件中直接配置环境,即
// webpack.config.dev.js
const path = require('path');
const rootPath = path.join(__dirname, '..');
const publicPath = path.join(rootPath, 'public');
module.exports = {
entry: path.join(rootPath, 'src/app.js'),
output: {
filename: '[name].[hash].js',
path: path.join(rootPath, 'build'),
publicPath,
},
+ mode: 'development'
};
这样,package.json
中的命令可以简化成
// package.json
...
"scripts": {
- "build": "webpack --config config/webpack.config.dev.js --mode development",
+ "build": "webpack --config config/webpack.config.dev.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
...
使用 React 编写前端页面时,我们都是使用 JSX 语法,但浏览器本身并不支持 JSX 语法,需要我们对 JSX 代码转换为 JS 代码
在 Webpack 中,我们可以使用 loader 来进行这一步的操作
实际上,Webpack 只是一个打包工具,本身并包含代码转译的功能,在此处的语境,即 Webpack 本身并不支持将 JSX 代码转译为 JS 代码。那将 JSX 代码转译为 JS 代码,我们需要借用另一个工具 Babel 来进行实现
关于如何在 Webpack 中使用 babel-loader, Webpack 官网给出了 示范
# 安装 Babel 在 Webpack 中的 loader
# 安装 Babel 核心库
# 安装 Babel 的转译规则集
npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react
babel-core Babel 的核心 babel-cli 通过命令行使用 Babel 的话,需要安装,在这里面,我们只通过 Webpack 调用,因此不安装了
在 Webpack 中使用 Babel, 我们需要使用的是 loader
const path = require('path');
const rootPath = path.join(__dirname, '..');
const publicPath = path.join(rootPath, 'public');
module.exports = {
- entry: path.join(rootPath, 'src/app.js'),
+ entry: path.join(rootPath, 'src/index.js'),
output: {
filename: '[name].[hash].js',
path: path.join(rootPath, 'build'),
publicPath,
},
mode: 'development',
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/env', '@babel/react'],
+ },
+ },
+ exclude: [
+ path.join(rootPath, 'node_modules'),
+ ],
+ },
+ ],
+ },
};
上述 loader 配置项的意思是:
对于 .js
或 .jsx
结尾的文件,使用 babel-loader 进行转译,转译规则集为 env
, react
, 但不包括 node_modules
中的文件
loader 的配置在于 module 属性内,即
module.exports = {
...
module: {
rules: [
// rules 中添加 loader
{
test: /a regex/, // 一个正则表达式,用于确定哪些文件应用此 loader
use: {
loader: 'an loader', // loader 的名称
options: { // loader 的其他选项
...
},
// 其他配置项
},
exclude: [
// 忽略的文件,如 node_modules 中的库,就不用再转译了
],
},
{
// 另一个 loader 配置
},
],
}
...
};
在 Babel 的配置中,有一个配置项 presets
非常重要
当你决定使用 Babel 对你写的代码进行转译时,presets
配置就决定你可以使用哪种版本的 JS 进行编码,如 babel-presets-es2015
, babel-presets-react
, 就分别指明了,在你的代码中可以使用 ES6 语法,React 的 JSX 语法进行编码
这些规则集都是需要根据我们自身的需求进行安装,安装了 Babel 并不会顺带安装这些规则集
实际上,在 Babel 7 中,有一个新的规则集 babel-env
, 这个规则集涵盖了
babel-preset-es2015
babel-preset-es2016
babel-preset-es2017
具体可以见 babel-preset-es2015 -> babel-preset-env. 因此,我们可以直接使用 preset-env
就可以了
由于之前的
app.jsx
与app.js
文件名在import
的时候可能会引起误会,所以将app.js
重命名为index.js
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app.jsx';
ReactDOM.render(<App />, document.body);
import React from 'react';
export default class App extends React.Component {
render() {
return (
<div>An App</div>
);
}
}
同时,我们也会对原来 package.json
进行一点修改,使得在 Webpack 打包前,将之前的文件都删除,以便我们观察文件的变化
"scripts": {
- "build": "webpack --config config/webpack.config.dev.js",
+ "build": "rm build/* && webpack --config config/webpack.config.dev.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
执行 npm run build
, 可以观察到 build 文件夹中的内容发生了变化
前面的工作只是使用了 Webpack 成功将 JS 相关文件打包,但其实并不能在浏览器上显示出内容,要在浏览器上显示出内容,则需要一个 HTML 的骨架
在 public
文件夹下创建一个 index.html
文件,用于存放整个 App 的 HTML 骨架
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>An App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
这个模版代码,实际上是从 create-react-app 中截取过来的
之前提及过,Webpack 只支持 JS 代码,因此,对于 HTML 这类静态文件,是需要安装额外的工具进行处理的,这里的额外的工具,在 Webpack 中指的是 plugin 插件
loader 与 plugin 的区别
loader: 通常只是针对一个文件的处理 plugin: 通常是对多个文件的综合处理
这里,用来处理 HTML 文件的插件是 HtmlWebpackPlugin
HtmlWebpackPlugin 会自动对 HTML 的重复工作,如 JavaScript 资源的引用,每次我们重新编译 JS 文件后,文件名可能会发生变化,而 HtmlWebpackPlugin 则会自动对资源进行正确处理
安装 HtmlWebpackPlugin
npm install -D html-webpack-plugin
在 Webpack 配置中,我们使用 plugins
属性进行配置,注意是复数的 plugin
const path = require('path');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
const rootPath = path.join(__dirname, '..');
const publicPath = path.join(rootPath, 'public');
module.exports = {
entry: path.join(rootPath, 'src/index.js'),
output: {
filename: '[name].[hash].js',
path: path.join(rootPath, 'build'),
publicPath,
},
mode: 'development',
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/env', '@babel/react'],
},
},
exclude: [
path.join(rootPath, 'node_modules'),
],
},
],
},
+ plugins: [
+ new HtmlWebpackPlugin({
+ title: 'An App', // HTML 的 title
+ template: path.join(rootPath, 'public', 'index.html'), // 生成 HTML 的模版路径
+ inject: true, // 传入 true 时,会将 JS 资源自动插入到 <body> 的最后,默认为 true
+ }),
+ ],
};
此时,运行 npm run build
, 会发现 build
文件夹除了多出打包后的 JS 文件,还有 index.html
, 其中的内容是:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>An App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<script type="text/javascript" src="absolute/path/to/public/main.759ba7003e718a6729f3.js"></script></body>
</html>
somehow, 这个 JS 资源文件使用了绝对路径,在浏览器中直接打开 build/index.html
只会得到空白一片,但后来发现,将 Webpack 配置文件中的 publicPath
注释后,得到了一个相对路径,并且工作正常
...
entry: path.join(rootPath, 'src/index.js'),
output: {
filename: '[name].[hash].js',
path: path.join(rootPath, 'build'),
- publicPath,
+ // publicPath,
},
mode: 'development',
...
得到的结果便是
就是之前在 App.jsx
中的
export default class App extends React.Component {
render() {
return (
<div>An App</div>
);
}
}