Webpack5構(gòu)造React多頁(yè)面應(yīng)用

介紹
多個(gè)頁(yè)面之間業(yè)務(wù)互不關(guān)聯(lián),頁(yè)面之間并沒有共享的數(shù)據(jù)
多個(gè)頁(yè)面使用同一個(gè)一個(gè)服務(wù),使用通用的組件和基礎(chǔ)庫(kù)
保留了傳統(tǒng)單頁(yè)應(yīng)用的開發(fā)模式:支持補(bǔ)充打包,你可以把每個(gè)頁(yè)面看成是一個(gè)單獨(dú)的單頁(yè)應(yīng)用
獨(dú)立部署:每個(gè)頁(yè)面相互獨(dú)立,可以單獨(dú)部署,解壓縮項(xiàng)目的復(fù)雜性,甚至可以在不同的頁(yè)面選擇不同的技術(shù)棧
減少包的體積,優(yōu)化加載渲染流程
快速上手
git clone https://github.com/zhedh/react-multi-page-app.git
yarn install
yarn start
yarn build
簡(jiǎn)易建設(shè)流程
npm初始化
yarn init
約定目錄
|____README.md|____package.json|____src| |____utils| |____components| |____pages| | |____page2| | | |____index.css| | | |____index.jsx| | |____page1| | | |____index.css| | | |____index.jsx
webpack配置
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",},};
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']}}}]},}
yarn add -D style-loader css-loader
module.exports = {...module: {...rules: [{test: /\.css$/i,use: ['style-loader','css-loader'],},]},}
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']}),],}
頁(yè)面編輯
import "./index.css";document.querySelector("body").append("PAGE1");
body {color: blue;}
import "./index.css";document.querySelector("body").append("PAGE2");
body {color: green;}
打包
webpack
├── page1│ ├── index.html│ └── index.js└── page2├── index.html└── index.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"],}),],};
{"name": "react-multi-page-app","version": "1.0.0","description": "react 多頁(yè)面應(yīng)用","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"}}
流程優(yōu)化
分離開發(fā)生產(chǎn)環(huán)境
mkdir configcd configtouch webpack.base.jstouch webpack.dev.jstouch webpack.prod.js
├── webpack.base.js├── webpack.dev.js└── webpack.prod.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,用于啟動(dòng)開發(fā)服務(wù)
yarn add -D webpack-dev-server
開發(fā)配置如下
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, //不跳轉(zhuǎn)inline: true, //實(shí)時(shí)刷新hot: true, // 開啟熱更新,port: 8000,},});
配置啟動(dòng)命令
{"scripts": {"start": "webpack serve --mode development --env development --config config/webpack.dev.js"},}
啟動(dòng)
yarn start
預(yù)覽
http:// localhost:8000 / page2
const { merge } = require('webpack-merge')const base = require('./webpack.base')module.exports = merge(base, {mode: 'production',})
{"scripts": {"start": "webpack serve --mode development --env development --config config/webpack.dev.js","build": "webpack --config config/webpack.prod.js"},}
yarn build
?反應(yīng)
├── page1│ ├── app.jsx│ ├── index.jsx│ └── index.css└── page2├── app.js├── index.jsx└── index.css
yarn add react react-dom
import React from 'react'function App() {return (我是PAGE1,Hello World)}export default App
import React from 'react'import ReactDOM from 'react-dom'import App from './app'import './index.css'ReactDOM.render(, document.getElementById('root'))
body{background-color: #ccc;}#page1 {color: rebeccapurple;}
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']}}}]},// ...}
module.exports = {// ...resolve: {extensions: ['.js', '.jsx', '.json']}}
cd srctouch template.html
Document
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
ass
body {background-color: #ccc;#page1 {color: rebeccapurple;}}
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
入口配置和模版自動(dòng)匹配
cd configtouch 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,}
const { setEntry } = require('./webpack.util')module.exports = {entry: setEntry,}
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}
├── app.jsx├── index.html├── index.jsx└── index.scss
頁(yè)面1
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}
const { setEntry, setHtmlPlugin } = require('./webpack.util')module.exports = {plugins: [...setHtmlPlugin(),]}
yarn add -D html-webpack-plugin glob
配置優(yōu)化
const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {plugins: [new CleanWebpackPlugin(),]}
yarn add -D clean-webpack-plugin
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}})]}
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"]}
項(xiàng)目源碼
問題與解答
最后
歡迎加我微信(winty230),拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...


評(píng)論
圖片
表情
