Webpack5构造React多页面应用


来源 | 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 (    <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;}

添加反应编译

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

<!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

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

<!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"  ]}

至此,项目配置完成

项目源码

完整代码: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/

本文完〜


最后

  • 欢迎加我微信(winty230),拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...

点个在看支持我吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值