webpack 帮我们处理了组件引用关系,将 jsx 转换成了 js 代码,这里我们通过实验看一下产物的跟预想的是否一样:
- 公共组件编译后是一份产物么?
- 能否手动控制公共组件一份还是多份产物?
- 如果使用 JSON 映射组件的方式,json 本身是否会打包进去?json 中未使用的组件是否会打包进去?
一、公共组件的编译结果
1.1 创建 CRA 项目
$ npx create-react-app cra-app
$ cd cra-app
$ npm start
1.2 引入 react-router-dom
$ npm i react-router-dom@5.3.0 -S
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
1.3 拆分子页面
App.js
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import Home from "./pages/home/index.tsx";
import About from "./pages/about/index.tsx";
import Dashboard from "./pages/dashboard/index.tsx";
export default function BasicExample() {
return (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/dashboard">Dashboard</Link> </li>
</ul>
<hr />
<Switch>
<Route exact path="/"><Home /></Route>
<Route path="/about"><About /></Route>
<Route path="/dashboard"><Dashboard /></Route>
</Switch>
</div>
</Router>
);
}
1.4 创建公共组件
Header.tsx
import React from "react";
const Header = () => {
return <div>
<h2>
TheHeader
</h2>
</div>;
}
export default Header;
1.5 运行效果
1.6 打包产物
$ npm run build
因为还没有配置拆包以及懒加载,所以打包产物只有 main.js
和 main.css
,我们看通过分析编译产物得出以下结论:
- TheHeader 只有 1 处,因此公共组件的产物只有 1 个
- 3 个页面生成了 3 个函数,引用相同的公共组件
二、懒加载的编译产物
2.1 路由改为懒加载
App.js 使用 lazy
和 Suspense
改造
import React, {lazy, Suspense} from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
const Home = lazy(() => import("./pages/home/index.tsx"));
const About = lazy(() => import("./pages/about/index.tsx"));
const Dashboard = lazy(() => import("./pages/dashboard/index.tsx"));
export default function BasicExample() {
return (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/dashboard">Dashboard</Link> </li>
</ul>
<hr />
<Suspense>
<Switch>
<Route exact path="/"><Home /></Route>
<Route path="/about"><About /></Route>
<Route path="/dashboard"><Dashboard /></Route>
</Switch>
</Suspense>
</div>
</Router>
);
}
2.2 打包产物
$ npm run build
可以看到多了 3 个 chunk.js
文件,对应 3 个页面
查看 3 个文件发现均有 TheHeader
公共组件
- chunk 文件 1
- chunk 文件 2
- chunk 文件 3
- main.js 没有公共组件信息
结论: 懒加载会将公共组件编译进每个子页面,当然也可以通过 splitChunk 拆包的方式将公共组件打到同一处
2.3 合并懒加载页面
App.js 中将子页面设置为相同的 webpackChunkName
const Home = lazy(() => import(/* webpackChunkName:"ChildPage" */"./pages/home/index.tsx"));
const About = lazy(() => import(/* webpackChunkName:"ChildPage" */"./pages/about/index.tsx"));
const Dashboard = lazy(() => import(/* webpackChunkName:"ChildPage" */"./pages/dashboard/index.tsx"));
打包产物
此时公共组件只存在于 ChildPage.chunk.js
三、JSON 映射组件
3.1 新增 JSON 渲染组件
Wrapper/index.tsx 纯映射 HTML 元素
import React from "react";
/**
* schema 容器
* @param props
*/
const Wrapper = (props) => {
const {schema} = props;
const elementTypeMap = ({type, inner}) => {
let res;
switch (type) {
case 'div':
res = <div>{inner}</div>
break;
case 'h1':
res = <h1>{inner}</h1>
break;
case 'p':
res = <p>{inner}</p>
break;
case 'span':
res = <span>{inner}</span>
break;
case 'abcde': res = <>{inner}</>; break;
default: res = <>{inner}</>
break;
}
return res;
}
return elementTypeMap(schema);
}
export default Wrapper;
Header/index.tsx 中引用 Wrapper 组件
import React from "react";
import Wrapper from "../wrapper/index.tsx";
const Header = () => {
return <div>
<h2>
TheHeader
</h2>
<Wrapper schema={{type: 'h1', inner: 'WrapperH1Test'}}/>
</div>;
}
export default Header;
3.2 运行效果
根据 Header
中 schema
内容正常渲染 h1
标签
3.3 打包产物
看产物大小变化 Wrapper
组件应该编译进了 ChildPage.chunk.js
看到 header
组件中的 schema
并未编译
Wrapper
组件编译结果仅仅是将最后一个 case 和 default 进行了合并
结论: Wrapper
组件会被单独打包,所以其中引用的组件也会被打包,除非组件将未使用的 schema
类型去掉
四、JSON 渲染多级结构
4.1 修改 JSON 渲染组件
- Wrapper/index.tsx 改为递归渲染
import React from "react";
/**
* schema 容器
* @param props
*/
const Wraper = (props) => {
const { schema } = props;
const elementTypeMap = (type, inner) => {
let res;
switch (type) {
case 'div':
res = <div>{inner}</div>
break;
case 'h1':
res = <h1>{inner}</h1>
break;
case 'p':
res = <p>{inner}</p>
break;
case 'span':
res = <span>{inner}</span>
break;
case 'abcde': res = <>{inner}</>; break;
default: res = <>{inner}</>
break;
}
return res;
}
const renderElement = ({ type, children, content }) => {
return <>
{elementTypeMap(type, <>
{content}
{children && children.map(item => renderElement(item))}
</>)}
</>;
}
return renderElement(schema);
}
export default Wraper;
- About/index.tsx 页面渲染多层 JSON
import React from "react";
import Header from '../../components/header/index.tsx';
import Wraper from "../../components/wrapper/index.tsx";
const About = () => {
const schema = {
type: 'div',
children:
[
{
type: 'h1',
children: [
{ type: 'text', content: 'Header1' },
{ type: 'text', content: 'Header2' }
]
},
{
type: 'div',
children: [
{
type: 'p',
content: 'p1',
children: [
{ type: 'span', content: 'span1' }
]
}
]
},
{
type: 'abcde',
content: 'abcdeContent',
}
]
};
return (
<div>
<Header />
<h2>About</h2>
<Wraper schema={schema} />
</div>
);
}
export default About;
4.2 运行效果
4.3 打包产物
-
App.js
将 3 个页面编译到 2 个chunk
文件中
const Home = lazy(() => import(/* webpackChunkName:"ChildPage1" */"./pages/home/index.tsx"));
const About = lazy(() => import(/* webpackChunkName:"ChildPage2" */"./pages/about/index.tsx"));
const Dashboard = lazy(() => import(/* webpackChunkName:"ChildPage2" */"./pages/dashboard/index.tsx"));
- 根据产物大小判断应该是
ChildPage1.chunk.js
增加了新的Wrapper
组件代码,ChildPage2.chunk.js
新增了Wrapper
组件代码和JSON
文件 - ChildPage1.chunk.js
- ChildPage2.chunk.js
- 结果跟预期一致
五、渲染组件多环境打包
5.1 添加编译环境变量
-
package.json
中添加REACT_APP_ENV
参数
"scripts": {
"start": "REACT_APP_ENV='web' react-scripts start",
"build": "REACT_APP_ENV='web' react-scripts build",
"build:mobile": "REACT_APP_ENV='mobile' react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
- header/index.tsx 中展示环境变量
import React from "react";
import Wrapper from "../wrapper/index.tsx";
const Header = () => {
return <div>
<h2>
TheHeader
</h2>
<p>ENV: {process.env.REACT_APP_ENV}</p>
<Wrapper schema={{type: 'h1', inner: 'WrapperH1Test'}}/>
</div>;
}
export default Header;
- wrapper/index.tsx 中区分环境,从而区分引入组件
import React from "react";
/**
* schema 容器
* @param props
*/
const Wrapper = (props) => {
const { schema } = props;
const elementTypeMap = (type, inner) => {
let res;
if(process.env.REACT_APP_ENV === 'web'){
switch (type) {
case 'div':
res = <div>{inner}</div>
break;
case 'h1':
res = <h1>{inner}</h1>
break;
case 'p':
res = <p>{inner}</p>
break;
case 'span':
res = <span>{inner}</span>
break;
case 'abcde': res = <>{inner}</>; break;
default: res = <>{inner}</>
break;
}
} else {
switch (type) {
case 'div':
res = <div>{inner}</div>
break;
case 'h1':
res = <h2>{inner}</h2>
break;
case 'p':
res = <text>{inner}</text>
break;
default: res = <>{inner}</>
break;
}
}
return res;
}
const renderElement = ({ type, children, content }) => {
return <>
{elementTypeMap(type, <>
{content}
{children && children.map(item => renderElement(item))}
</>)}
</>;
}
return renderElement(schema);
}
export default Wrapper;
5.2 运行效果
5.3 打包产物
- 打包
mobile
端 ChildPage1.chunk.js
中的Wrapper
组件
结论: 可以看出 switch
语句前并没有环境变量的判断,也就是说属于 web
端的组件并没有打包进来,span
作为一个组件就没有找到
- 打包
web
端 ChildPage1.chunk.js
中的Wrapper
组件
结论: 只打包了 web 端组件,text 作为移动端特有组件没有找到