Webpack5构造React多页面应用

web前端开发

共 11565字,需浏览 24分钟

 ·

2020-12-31 19:49

来源 | https://github.com/zhedh/react-multi-page-app/

介绍

react-multi-page-app是一个基于webpack5构造的react多页面应用。
为什么建造多页面应用:
  • 多个页面之间业务互不关联,页面之间并没有共享的数据

  • 多个页面使用同一个一个服务,使用通用的组件和基础库

建造多页面应用的好处:
  • 保留了传统单页应用的开发模式:支持补充打包,你可以把每个页面看成是一个单独的单页应用

  • 独立部署:每个页面相互独立,可以单独部署,解压缩项目的复杂性,甚至可以在不同的页面选择不同的技术栈

  • 减少包的体积,优化加载渲染流程

快速上手

克隆
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编译
安装装载机
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'] }), ],}

页面编辑

第1页
index.jsx
import "./index.css";
document.querySelector("body").append("PAGE1");
index.css
body { color: blue;}
第2页
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

流程优化

分离开发生产环境

新建配置目录,并创建配置文件
mkdir configcd configtouch webpack.base.jstouch webpack.dev.jstouch 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

♡反应

以page1页面为例
约定目录
├── page1│ ├── app.jsx│ ├── index.jsx│ └── index.css└── page2 ├── app.js ├── index.jsx └── index.css
安装反应
yarn add react react-dom
代码如下
app.js
import React from 'react'
function App() { return (
我是PAGE1,Hello World
)}
export default App
index.js
import React from 'react'import ReactDOM from 'react-dom'import App from './app'import './index.css'
ReactDOM.render(, document.getElementById('root'))
index.css
body{ background-color: #ccc;}
#page1 { color: rebeccapurple;}
添加反应编译
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 srctouch template.html
template.html

Document

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

ass

以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 configtouch 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模版自动匹配,并约会反应包
以page1为例子,约会页面自定义模版目录约定如下
├── app.jsx├── index.html├── index.jsx└── index.scss
index.html

页面1

如果匹配不到自定义模版,会匹配替换模版,配置如下:
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" ]}
至此,项目配置完成

项目源码

完整代码:https://github.com/zhedh/react-multi-page-app/,喜欢给个star

问题与解答

无法读取未定义的属性“ createSnapshot”
报错:UnhandledPromiseRejectionWarning:TypeError:无法读取未定义的属性'createSnapshot'
原因:因为同时运行2个不同版本的webpack。我们项目中没有安装webpack-cli,webpack会进行交替使用的webpack-cli,webpack5和webpack-cli3不兼容
解决:升级版本webpack-cli3到webpack-cli4或在项目中安装最新版本的webpack-cli4
参考:https : //github.com/

本文完〜


浏览 33
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报