背景
公司前端工程技术栈好处于 React+Mobx
与 Spring MVC(freemarker+jQuery)
两种技术栈共存的阶段,两种技术栈页面存在一些相同的业务功能点,如果分别开发和维护,就需要双倍的人力成本,因此,下文将尝试将 React
业务组件在 webpack
、 babel
等利器的帮助下应用于 Spring MVC
项目。
应用
一、简单封装组件挂载与卸载方法
React
业务组件就是 FunctionComponent
或者 ClassComponent
,需要利用 react-dom
中的 render
方法处理,转化成 Fiber
双向链表树,形成虚拟 DOM
,最后转成实际的 HTMLElement
追加到页面上。因此,在 Spring MVC
中使用需要抛出挂载与卸载的方法:
// 引入polyfill,后面会将为什么不用@babel/polyfill import 'react-app-polyfill/ie9'; import 'react-app-polyfill/stable'; import React from 'react'; import ReactDOM from 'react-dom'; import { MediaPreview } from './src/MediaPreview'; // 引入组件库全部样式,后面会做css tree shaking处理 import '@casstime/bricks/dist/bricks.development.css'; import './styles/index.scss'; ;(function () { window.MediaPreview = (props, container) => { return { // 卸载 close: function () { ReactDOM.unmountComponentAtNode(container); }, // 挂载 open: function (activeIndex) { ReactDOM.render(React.createElement(MediaPreview, { ...props, visible: true, activeIndex: activeIndex || 0 }), container); // 或者 // ReactDOM.render(<MediaPreview {...{ ...props, visible: true, activeIndex: activeIndex || 0 }} />, container); }, }; }; })();
二、 babel
转译成 ES5
语法规范, polyfill
处理兼容性 api
babel
在转译的时候,会将源代码分成 syntax
和 api
两部分来处理
syntax
:类似于展开对象、optional chain
、let
、const
等语法;api
:类似于[1,2,3].includes
、new URL()
,new URLSearchParams()
、new Map()
等函数、方法;
babel
很轻松就转译好 syntax
,但对于 api
并不会做任何处理,如果在不支持这些 api
的浏览器中运行,就会报错,因此需要使用 polyfill
来处理 api
,处理兼容性 api
有以下方案:
@babel/preset-env
中有一个配置选项 useBuiltIns
,用来告诉 babel
如何处理 api
。由于这个选项默认值为 false
,即不处理 api
- 设置
useBuiltIns
的值为“entry
”,同时在入口文件最上方引入@babel/polyfill
,或者不指定useBuiltIns
,也可设置useBuiltIns
的值为false
,在webpack entry
引入@babel/polyfill
。这种模式下,babel
会将所有的polyfill
全部引入,导致结果的包大小会很大,然后利用webpack tree shaking
剔除没有被使用的代码块; - 使用按需加载,将
useBuiltIns
改成“usage
”,babel
就可以按需加载polyfill
,并且不需要手动引入@babel/polyfill
,但依赖需要安装它; - 上述两种方法存在两个问题
- ①
polyfill
注入的方法会改变全局变量的原型,可能带来意想不到的问题。 - ②转译
syntax
时,会注入一些辅助函数来帮忙转译,这些helper
函数会在每个需要转译的文件中定义一份,导致最终的产物里有大量重复的helper
。引入@babel/plugin-transform-runtime
将helper
和api
都改为从一个统一的地方引入,并且引入的对象和全局变量是完全隔离的; - 在入口文件最上方或者
webpack entry
引入react-app-polyfill
,并启用webpack tree shaking
; - ~~Java思维导图获取路径~~
方案一:全量引入 @babel/polyfill
, webpack
做 tree shaking
根目录配置 babel.config.json
{ "presets": [ [ "@babel/preset-env", { "targets": { "chrome": "58", "ie": "9" } }, "useBuiltIns": "entry", "corejs": "3" // 指定core-js版本 ], "@babel/preset-react", "@babel/preset-typescript" ], "plugins": [] }
如果在执行构建时报如下警告,表示在使用 useBuiltIns
选项时没有指定 core-js
版本
webpack.config.js
配置
/* eslint-disable @typescript-eslint/no-var-requires */ const package = require('./package.json'); const path = require('path'); module.exports = { mode: 'production', entry: [ './index.tsx', ], output: { path: __dirname + '/dist', filename: `media-preview.v${package.version}.min.js`, library: { type: 'umd', }, }, module: { rules: [ { test: /\.(m?js|ts|js|tsx|jsx)$/, exclude: /(node_modules|lib|dist)/, use: [ { loader: 'babel-loader', options: { cacheDirectory: true, }, }, ], }, { test: /\.(scss|css|less)/, use: [ 'style-loader', 'css-loader', 'sass-loader', ], }, { test: /\.(png|jpg|jepg|gif)$/i, use: [ { loader: 'url-loader', options: { limit: 8 * 1024 * 1024, // 大小超过8M就不使用base64编码了 name: 'static/media/[name].[hash:8].[ext]', fallback: require.resolve('file-loader'), }, }, ], }, { test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, use: [ { loader: 'url-loader', options: { limit: 8 * 1024 * 1024, name: 'static/fonts/[name].[hash:8].[ext]', fallback: require.resolve('file-loader'), }, }, ], }, ], }, plugins: [], resolve: { extensions: ['.ts', '.tsx', '.js', '.json'], }, };
构建生成的产物含有一堆图片和字体文件,并且都重复了双份,其实期望的结果是这些资源都被base64编码在代码中,但没有生效。
原因是当在 webpack 5
中使用旧的 assets loader
(如 file-loader
/ url-loader
/ raw-loader
等)和 asset
模块时,你可能想停止当前 asset
模块的处理,并再次启动处理,这可能会导致 asset
重复,你可以通过将 asset
模块的类型设置为 'javascript/auto'
来解决。
module.exports = { module: { rules: [ { test: /\.(png|jpg|jepg|gif)$/i, use: [ { loader: 'url-loader', options: { limit: 8 * 1024 * 1024, // 大小超过8M就不使用base64编码了 name: 'static/media/[name].[hash:8].[ext]', fallback: require.resolve('file-loader'), }, }, ], type: 'javascript/auto', }, { test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, use: [ { loader: 'url-loader', options: { limit: 8 * 1024 * 1024, name: &#