關於Webpack5 搭建 React 多頁面應用介紹
react-multi-page-app 是一個基於 webpack5 搭建的 react 多頁面應用。
為什麼搭建多頁面應用:
- 多個頁面之間業務互不關聯,頁面之間並沒有共享的資料
- 多個頁面使用同一個後端服務、使用通用的元件和基礎庫
搭建多頁面應用的好處:
- 保留了傳統單頁應用的開發模式:支援模組化打包,你可以把每個頁面看成是一個單獨的單頁應用
- 獨立部署:每個頁面相互獨立,可以單獨部署,解耦專案的複雜性,甚至可以在不同的頁面選擇不同的技術棧
- 減少包的體積,優化載入渲染流程
快速上手
clone
git clone https://github.com/zhedh/react-multi-page-app.git
安裝依賴
yarn install
開發
yarn start
http://localhost:8000/page1
打包
yarn build
簡易搭建流程
npm 初始化
yarn init
約定目錄
|____README.md
|____package.json
|____src
| |____utils
| |____components
| |____pages
| | |____page2
| | | |____index.css
| | | |____index.jsx
| | |____page1
| | | |____index.css
| | | |____index.jsx
Webpack 配置
安裝 Webpack
yarn add -D 可以使用 npm i --save-dev 替代
yarn add -D webpack webpack-cli
建立配置檔案
touch webpack.config.js
入口配置
module.exports = {
entry: {
page1: "./src/pages/page1/index.jsx",
page2: "./src/pages/page2/index.jsx",
// ...
},
};
輸出配置
module.exports = {
entry: {
page1: "./src/pages/page1/index.jsx",
page2: "./src/pages/page2/index.jsx",
// ...
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "[name]/index.js",
},
};
js|jsx 編譯
安裝 babel 外掛
yarn add -D babel-loader @babel/core @babel/preset-env
module.exports = {
...
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
}
css 編譯
安裝 loader
yarn add -D style-loader css-loader
module.exports = {
...
module: {
...
rules: [
{
test: /\.css$/i,
use: [
'style-loader',
'css-loader'
],
},
]
},
}
html 外掛配置
安裝 html-webpack-plugin
yarn add -D html-webpack-plugin
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
filename: 'page1/index.html',
chunks: ['page1']
}),
new HtmlWebpackPlugin({
filename: 'page2/index.html',
chunks: ['page2']
}),
],
}
頁面編輯
page1
index.jsx
import "./index.css";
document.querySelector("body").append("PAGE1");
index.css
body {
color: blue;
}
page2
index.jsx
import "./index.css";
document.querySelector("body").append("PAGE2");
index.css
body {
color: green;
}
打包
執行
webpack
輸出 dist 包產物如下:
├── page1
│ ├── index.html
│ └── index.js
└── page2
├── index.html
└── index.js
完整的配置檔案
webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
page1: "./src/pages/page1/index.jsx",
page2: "./src/pages/page2/index.jsx",
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "[name]/index.js",
},
module: {
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.m?jsx$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
filename: "page1/index.html",
chunks: ["page1"],
// chunks: ['page1', 'page1/index.css']
}),
new HtmlWebpackPlugin({
filename: "page2/index.html",
chunks: ["page2"],
}),
],
};
package.json
{
"name": "react-multi-page-app",
"version": "1.0.0",
"description": "React 多頁面應用",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/preset-env": "^7.12.7",
"babel-loader": "^8.2.2",
"css-loader": "^5.0.1",
"html-webpack-plugin": "^4.5.0",
"style-loader": "^2.0.0",
"webpack": "^5.9.0",
"webpack-cli": "^4.2.0"
}
}
去 github 檢視簡易版完整程式碼react-multi-page-app
流程優化
分離開發生產環境
新建 config 目錄,並建立配置檔案
mkdir config
cd config
touch webpack.base.js
touch webpack.dev.js
touch webpack.prod.js
├── webpack.base.js
├── webpack.dev.js
└── webpack.prod.js
基礎配置
webpack.base.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
page1: "./src/pages/page1/index.jsx",
page2: "./src/pages/page2/index.jsx",
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "[name]/index.js",
},
module: {
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
filename: "page1/index.html",
chunks: ["page1"],
}),
new HtmlWebpackPlugin({
filename: "page2/index.html",
chunks: ["page2"],
}),
],
};
開發配置
- 安裝 webpack-merge,用於合併 webpack 配置資訊
yarn add -D webpack-merge
- 安裝 webpack-dev-server,用於啟動開發服務
yarn add -D webpack-dev-server
- 開發配置如下
webpack.dev.js
const { merge } = require("webpack-merge");
const path = require("path");
const base = require("./webpack.base");
module.exports = merge(base, {
mode: "development",
devtool: "inline-source-map",
target: "web",
devServer: {
open: true,
contentBase: path.join(__dirname, "./dist"),
historyApiFallback: true, //不跳轉
inline: true, //實時重新整理
hot: true, // 開啟熱更新,
port: 8000,
},
});
- 配置啟動命令
package.json
{
"scripts": {
"start": "webpack serve --mode development --env development --config config/webpack.dev.js"
},
}
- 啟動
yarn start
- 預覽
http://localhost:8000/page1
http://localhost:8000/page2
生產配置
- 配置如下
webpack.prod.js
const { merge } = require('webpack-merge')
const base = require('./webpack.base')
module.exports = merge(base, {
mode: 'production',
})
- 配置打包命令
package.json
{
"scripts": {
"start": "webpack serve --mode development --env development --config config/webpack.dev.js",
"build": "webpack --config config/webpack.prod.js"
},
}
- 打包
yarn build
引入React
以page1頁面為例
- 約定目錄
├── page1
│ ├── app.jsx
│ ├── index.jsx
│ └── index.css
└── page2
├── app.js
├── index.jsx
└── index.css
- 安裝react
yarn add react react-dom
- 程式碼如下
app.js
import React from 'react'
function App() {
return (
<div id="page1">
我是PAGE1,Hello World
</div>
)
}
export default App
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
import './index.css'
ReactDOM.render(<App />, document.getElementById('root'))
index.css
body{
background-color: #ccc;
}
#page1 {
color: rebeccapurple;
}
- 新增react編譯
webpack.base.js
module.exports = {
module: {
// ...
rules: [
// ...
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react', '@babel/preset-env'],
plugins: ['@babel/plugin-proposal-class-properties']
}
}
}
]
},
// ...
}
- 省略獲取檔案字尾
webpack.base.js
module.exports = {
// ...
resolve: {
extensions: ['.js', '.jsx', '.json']
}
}
- 新增html預設模版,掛載 React DOM
cd src
touch template.html
template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
webpack.base.js
module.exports = {
plugins: [
new HtmlWebpackPlugin({
filename: 'page1/index.html',
chunks: ['page1'],
template: './src/template.html'
}),
new HtmlWebpackPlugin({
filename: 'page2/index.html',
chunks: ['page2'],
template: './src/template.html'
}),
],
}
- 安裝依賴
yarn add @babel/preset-react @babel/plugin-proposal-class-properties
引入Sass
以 page1 為例
- 將 index.css 變更為 index.scss
index.scss
body {
background-color: #ccc;
#page1 {
color: rebeccapurple;
}
}
- 新增scss編譯
webpack.base.js
module.exports = {
// ...
module: {
// ...
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
'css-loader',
'resolve-url-loader',
'sass-loader'
]
},
]
},
// ...
}
- 安裝依賴
yarn add -D resolve-url-loader sass-loader
到此,一個完整的 React 多頁面應用搭建完成,檢視完整程式碼react-multi-page-app
入口配置和模版自動匹配
為了不用每次新增頁面都要新增入口頁面配置,我們將入口配置改成自動匹配
- 入口檔案自動匹配
cd config
touch webpack.util.js
webpack.util.js
const glob = require('glob')
function setEntry() {
const files = glob.sync('./src/pages/**/index.jsx')
const entry = {}
files.forEach(file => {
const ret = file.match(/^\.\/src\/pages\/(\S*)\/index\.jsx$/)
if (ret) {
entry[ret[1]] = {
import: file,
}
}
})
return entry
}
module.exports = {
setEntry,
}
webpack.base.js
const { setEntry } = require('./webpack.util')
module.exports = {
entry: setEntry,
}
- 拆分 React 依賴,將 React 單獨打包出一個 bundle,作為公共依賴引入各個頁面
webpack.util.js
function setEntry() {
const files = glob.sync('./src/pages/**/index.jsx')
const entry = {}
files.forEach(file => {
const ret = file.match(/^\.\/src\/pages\/(\S*)\/index\.jsx$/)
if (ret) {
entry[ret[1]] = {
import: file,
dependOn: 'react_vendors',
}
}
})
// 拆分react依賴
entry['react_vendors'] = {
import: ['react', 'react-dom'],
filename: '_commom/[name].js'
}
return entry
}
- html 模版自動匹配,並引入react bundle
以 page1 為例子,引入頁面自定義模版目錄約定如下
├── app.jsx
├── index.html
├── index.jsx
└── index.scss
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>頁面1</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
如果匹配不到自定義模版,會匹配預設模版,配置如下:
webpack.util.js
function getTemplate(name) {
const files = glob.sync(`./src/pages/${name}/index.html`)
if (files.length > 0) {
return files[0]
}
return './src/template.html'
}
function setHtmlPlugin() {
const files = glob.sync('./src/pages/**/index.jsx')
const options = []
files.forEach(file => {
const ret = file.match(/^\.\/src\/pages\/(\S*)\/index\.jsx$/)
if (ret) {
const name = ret[1]
options.push(new HtmlWebpackPlugin({
filename: `${name}/index.html`,
template: getTemplate(name),
chunks: ['react_vendors', name,]
}))
}
})
return options
}
module.exports = {
setEntry,
setHtmlPlugin
}
webpack.base.js
const { setEntry, setHtmlPlugin } = require('./webpack.util')
module.exports = {
plugins: [
...setHtmlPlugin(),
]
}
- 安裝相關依賴
yarn add -D html-webpack-plugin glob
配置優化
- 清除之前打包檔案
webpack.base.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin(),
]
}
yarn add -D clean-webpack-plugin
- 分離並壓縮 css
webpack.base.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
// 'style-loader',
MiniCssExtractPlugin.loader,
'css-loader',
'resolve-url-loader',
'sass-loader'
]
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name]/index.css',
}),
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
})
]
}
html 中注入 css
webpack.util.js
function setHtmlPlugin() {
const files = glob.sync('./src/pages/**/index.jsx')
const options = []
files.forEach(file => {
const ret = file.match(/^\.\/src\/pages\/(\S*)\/index\.jsx$/)
if (ret) {
const name = ret[1]
options.push(new HtmlWebpackPlugin({
filename: `${name}/index.html`,
template: getTemplate(name),
chunks: ['react_vendors', name, '[name]/index.css']
}))
}
})
return options
}
yarn add -D mini-css-extract-plugin optimize-css-assets-webpack-plugin
在 package.json 配置 sideEffects,避免 webpack Tree Shaking 移除.css、.scss 檔案
package.json
```json
{
"sideEffects": [
"*.css",
"*.scss"
]
}
至此,專案配置完成
專案原始碼
完整程式碼react-multi-page-app,喜歡給個star
問題&解答
Cannot read property 'createSnapshot' of undefined
報錯:UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'createSnapshot' of undefined
原因:因為同時執行 2 個不同版本的 webpack。我們專案中沒有安裝 webpack-cli,webpack 會預設使用全域性的 webpack-cli,webpack5 和 webpack-cli3 不相容
解決:升級全域性 webpack-cli3 到 webpack-cli4 或在專案中安裝最新版本的 webpack-cli4
相關文章
- webpack4 + react 搭建多頁面應用WebReact
- 單頁面應用和多頁面應用
- React多頁面應用腳手架-v1.3.0React
- 用webpack搭建多頁面專案Web
- 關於 React Hooks 的簡單介紹ReactHook
- 前端:你要懂的單頁面應用和多頁面應用前端
- about關於頁面(僅用於MAC)Mac
- HTML頁面Meta介紹HTML
- 應用於 Hybrid App 的 Vue 多頁面構建APPVue
- [譯] 關於 React Motion 的簡要介紹React
- webpack 搭建vue多單頁應用WebVue
- 關於單頁面應用的 Token Storage 設計策略
- webpack如何打包多頁面應用(mpa)Web
- webpack 構建多頁面應用——初探Web
- 基於vue-cli的多頁面應用腳手架Vue
- webpack+react+antd單頁面應用例項WebReact
- React如何優雅地寫單頁面應用?React
- React多頁面應用3(webpack效能提升,包括打包效能、提取公共包等)ReactWeb
- 一文讀盡前端路由、後端路由、單頁面應用、多頁面應用前端路由後端
- 正交多項式介紹及應用
- 單頁面 Web 應用(Single Page Application,SPA)的工作原理介紹WebAPP
- RabbitMQ的web頁面介紹(三)MQWeb
- 基於 webpack4 搭建 vue2、vuex 多頁應用框架WebVue框架
- Render函式在Vue多頁面應用中的應用函式Vue
- 快速搭建你的 github pages 個人部落格 —— 基於 Create-React-App 的單頁面應用實踐GithubReactAPP
- React Native 頁面佈局簡介React Native
- webpack4+react多頁面架構WebReact架構
- Vue單頁及多頁應用全域性配置404頁面實踐Vue
- VUE 單頁面應用 修改頁面titleVue
- webpack+react多頁面開發架構WebReact架構
- create-react-app修改為多頁面支援ReactAPP
- 從零開始使用 webpack5 搭建 react 專案WebReact
- PWA介紹及快速上手搭建一個PWA應用
- postMessage實現頁面通訊介紹
- 《H5宣傳頁面》介紹(一)H5
- vonic單頁面應用
- 從零開始搭建React應用(二)——React應用架構React應用架構
- 關於Oracle Database Vault介紹OracleDatabase