背景
因为要做一个博客,前台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下文件路径是这样的 需要注意。