解决webpack构建同时支持Vue jsx语法和React jsx语法问题记录

4,045 阅读3分钟

背景

因为要做一个博客,前台react后台vue,所以共用的是一套webpack构建。但是却因为jsx语法插件的问题react的jsx被vue的babel的jsx插件给处理了。

存在的问题

babel对于jsx语法的转换,vue需要使用@vue/babel-preset-jsx,react需要使用@babel/preset-react,但是这俩放在一起使,发现react的被Vue的preset转换了,报了一个错误。

这个h是个啥呢,是vue的createElemnt啊,所以这里这俩转换串台了,这可咋整。

解决方法

经过思考,我这里前后台区分是前台fe文件下下面,后台admin文件下,所以既然有了区分那这个就是一个很好的入手的地方。

可以通过更加详细的loader test配置的规则来匹配到不同的文件下的文件在选择不同的loader。

{
          test: /\.(tsx?|jsx?)$/i, //这里就是一个入手点
          exclude: /(node_modules|bower_components)/,
          use: [
            {
              loader: "babel-loader",
              options: {
                presets: ["@babel/preset-react","@vue/babel-preset-jsx"], //这里是问题的所在
                plugins: [
                  '@babel/plugin-transform-runtime',
                  '@babel/plugin-proposal-class-properties',
                  '@babel/plugin-syntax-dynamic-import',
                  "@babel/plugin-transform-react-jsx"
                ]
              }

            }
          ]
        },

所以首先看一下跟test 匹配的路径是啥

test不光可以配置正则还可以配置一个函数,这里使用一个函数把路径全部打印出来

test: function(str){
     console.log(str)
}

打印出来的如下(截取了部分片断)

可以看到匹配的路径是一个全路径。

这里还有个知识点就是vue在解析.vue文件的时候自己本身不直接处理而是生成这样一个个根据.vue块的lang属性当后缀的文件,分别交给webpack中配置的其他loader处理。

官方文档解释 点击跳转到官方文档 以下是部分节选

Vue Loader 的配置和其它的 loader 不太一样。除了通过一条规则将 vue-loader 应用到所有扩展名为 .vue 的文件上之外,请确保在你的 webpack 配置中添加 Vue Loader 的插件:

// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  module: {
    rules: [
      // ... 其它规则
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // 请确保引入这个插件!
    new VueLoaderPlugin()
  ]
}

这个插件是必须的! 它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果你有一条匹配 /.js$/ 的规则,那么它会应用到 .vue 文件里的 script块。

一个更完整的 webpack 配置示例看起来像这样:

// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      // 它会应用到普通的 `.js` 文件
      // 以及 `.vue` 文件中的 `<script>` 块
      {
        test: /\.js$/,
        loader: 'babel-loader'
      },
      // 它会应用到普通的 `.css` 文件
      // 以及 `.vue` 文件中的 `<style>` 块
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    // 请确保引入这个插件来施展魔法
    new VueLoaderPlugin()
  ]
}

好的回到主题,我们知道了每次匹配的路径之后,那么要做的就是分别匹配admin和fe路径下的文件加载不同的loader了。

这里直接贴代码:

 {
          test: /(\w+\\)+admin\\(\w+\\?)+(\w.?)+(tsx?|jsx?)$/i,//作用是匹配带有admin的文件夹 ,这里也可以直接使用方法includes,用正则是为了复习一下正则
          exclude: /(node_modules|bower_components)/,
          use: [
            {
              loader: "babel-loader",
              options: {
                presets: ["@vue/babel-preset-jsx"],//匹配的是vue
                plugins: [
                  '@babel/plugin-transform-runtime',
                  '@babel/plugin-proposal-class-properties',
                  '@babel/plugin-syntax-dynamic-import',
                  "@babel/plugin-transform-react-jsx"
                ]
              }

            }
          ]
        },
        

        {
          test: /(\w+\\)+fe\\(\w+\\?)+(\w.?)+(tsx?|jsx?)$/i, //作用是匹配带有fe的文件夹
          exclude: /(node_modules|bower_components)/,
          use: [
            {
              loader: "babel-loader",
              options: {
                presets: ["@babel/preset-react"], //匹配的是react
                plugins: [
                  '@babel/plugin-transform-runtime',
                  '@babel/plugin-proposal-class-properties',
                  '@babel/plugin-syntax-dynamic-import',
                  "@babel/plugin-transform-react-jsx"
                ]
              }

            }
          ]
        },

通过以上,不同文件夹下的文件就会走不同的loader处理了,vue和react的jsx也不会冲突了。

完整配置代码:

const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WebpackCdnPlugin = require("webpack-cdn-plugin");
const TerserWebpackPlugin = require('terser-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = function configFactory(mode) {
  const isProduction = mode === 'production'
  return {
    context: path.resolve(__dirname, '../'),
    target: "web",
    mode: mode,
    entry: {
      admin: "./src/admin/main.js",
      fe: "./src/fe/main.js"
    },
    output: {
      path: path.resolve(__dirname, "../dist"),
      filename: "[name].[hash].js",
      chunkFilename: "[name].[chunkhash].js"
    },
    optimization: {
      minimizer: [
        new TerserWebpackPlugin({
          cache: true,
          parallel: true,
        }),
        isProduction && new OptimizeCSSAssetsPlugin()
      ].filter(Boolean),
      splitChunks: {
        chunks: "all",
        cacheGroups: {
          defaultVendors: {
            test: /[\\/]node_modules[\\/]/, //符合组的要求就给构建venders
            priority: -15, //优先级用来判断打包到哪个里面去
            name: "vendors", //指定chunks名称
            reuseExistingChunk: true
          },
        }

      }
    },
    resolve: {
      extensions: [".js", ".jsx", ".json", ".vue", ".ts"],
      alias: {
      }
    },
    module: {
      rules: [
        {
          test: /\.vue/,
          use: [
            {
              loader: "vue-loader"
            }
          ]
        },
        {
          test: /\.(css|less)$/i,
          use: [
            !isProduction && {
              loader: 'style-loader'
            },
            isProduction && {
              loader: MiniCssExtractPlugin.loader,
              options: {
                // you can specify a publicPath here
                // by default it uses publicPath in webpackOptions.output
                publicPath: "./",
              },
            },
            {
              loader: 'css-loader'
            },
            {
              loader: 'less-loader'
            }
          ].filter(Boolean)
        },
        {
          test: /(\w+\\)+admin\\(\w+\\?)+(\w.?)+(tsx?|jsx?)$/i,
          exclude: /(node_modules|bower_components)/,
          use: [
            {
              loader: "babel-loader",
              options: {
                presets: ["@vue/babel-preset-jsx"], //支持vue
                plugins: [
                  '@babel/plugin-transform-runtime',
                  '@babel/plugin-proposal-class-properties',
                  '@babel/plugin-syntax-dynamic-import',
                  "@babel/plugin-transform-react-jsx"
                ]
              }

            }
          ]
        },
        

        {
          test: /(\w+\\)+fe\\(\w+\\?)+(\w.?)+(tsx?|jsx?)$/i, 
          exclude: /(node_modules|bower_components)/,
          use: [
            {
              loader: "babel-loader",
              options: {
                presets: ["@babel/preset-react"], // 支持react 
                plugins: [
                  '@babel/plugin-transform-runtime',
                  '@babel/plugin-proposal-class-properties',
                  '@babel/plugin-syntax-dynamic-import',
                  "@babel/plugin-transform-react-jsx"
                ]
              }

            }
          ]
        },

        {
          test: /\.(png|jpe?g$|gif|ttf|woff)$/i,
          use: [
            {
              loader: "file-loader",
              options: {
                esModule: false, //这里新版本默认为true  会出现object module的问题
                outputPath: 'assets',
                name: '[name]_[hash].[ext]',
              }
            }
          ]
        }
      ]
    },
    plugins: [
      new VueLoaderPlugin(),
      new HtmlWebpackPlugin({
        title: '大东的博客(建设中)',
        template: './public/index.html',
        filename:"index.html",
        chunks: ['fe', 'vendors']
      }),
      new HtmlWebpackPlugin({
        title: '后台管理',
        template: './public/index.html',
        filename:"admin.html",
        chunks: ['admin', 'vendors']
      }),
      isProduction && new CleanWebpackPlugin(),
      isProduction && new MiniCssExtractPlugin({
        filename: "[name]_[contenthash].css",
        ignoreOrder: true,
      }),
      !isProduction && new webpack.HotModuleReplacementPlugin(),
      isProduction && new MiniCssExtractPlugin({
        // Options similar to the same options in webpackOptions.output
        // both options are optional
        filename: 'static/css/[name].[contenthash:8].css',
        chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
      }),
      new CopyWebpackPlugin({
        patterns: [
          {
            from: "public/favicon.ico",
            to: ""
          }
        ]
      })
    ].filter(Boolean)
  };
};


注意问题

linux下文件路径是这样的 需要注意。