React组件应用于Spring MVC工程

背景

公司前端工程技术栈好处于 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: '
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值