9 changed files with 576 additions and 0 deletions
After Width: | Height: | Size: 7.9 KiB |
@ -0,0 +1,32 @@ |
|||
/** |
|||
* 用于自动生成前端组件 |
|||
* 通过 src/routes/routes.json 文件构建 src/components/index.ts 文件 |
|||
*/ |
|||
const fs = require('fs'); |
|||
const Json5 =require('json5'); |
|||
|
|||
// 解析前端路由配置文件 |
|||
const routesJson = Json5.parse(fs.readFileSync('./src/routes/routes.json', 'utf8')); |
|||
|
|||
let content =''; |
|||
content +='/**\n'; |
|||
content +=' * 此文件为自动生成文件,请勿修改\n'; |
|||
content +=' */\n\n'; |
|||
for(const route of routesJson){ |
|||
const componentName =route.component.substring(route.component.lastIndexOf('.')+1); |
|||
const componentPath =route.componentPath; |
|||
content +=`import ${componentName} from '${componentPath}';\n`; |
|||
} |
|||
|
|||
content +='\n'; |
|||
content +='const localComponents = { \n'; |
|||
for(const route of routesJson){ |
|||
const componentName =route.component.substring(route.component.lastIndexOf('.')+1); |
|||
content +=`'${route.component}': ${componentName},\n`; |
|||
} |
|||
content +='}\n\n'; |
|||
content +='export default localComponents;\n'; |
|||
|
|||
fs.writeFileSync('./src/components/index.ts', content); |
|||
|
|||
console.info('components generated!'); |
@ -0,0 +1,173 @@ |
|||
/** |
|||
* 用于将前端模块注册到后端服务器 |
|||
*/ |
|||
const packageJson = require('./package.json'); |
|||
const { ModuleFederationPlugin } = require('webpack').container; |
|||
const Server = require('webpack-dev-server'); |
|||
const mf = require('./webpack.config.mf.cjs'); |
|||
const fs = require('fs'); |
|||
const http = require('http'); |
|||
const https = require('https'); |
|||
const Json5 =require('json5'); |
|||
|
|||
// 解析前端注册器配置文件 |
|||
const frontendRegisterConfigure = Json5.parse(fs.readFileSync('./frontend-register.json', 'utf8')); |
|||
// 解析前端路由配置文件 |
|||
const frontendRoutes =Json5.parse(fs.readFileSync('./src/routes/routes.json', 'utf8')); |
|||
|
|||
/** |
|||
* 远程组件注册器类 |
|||
*/ |
|||
class RemoteFrontEndModuleRegister { |
|||
/** |
|||
* 构造函数,传入配置信息, 包括远程和本地服务器配置信息 |
|||
* 配置信息定义格式如下: |
|||
* // 远程服务器配置信息 |
|||
* remoteServerConfig: { |
|||
* protocol: 'http', |
|||
* host: 'localhost', |
|||
* port: 8080, |
|||
* path: '/api/system/frontend/regist', |
|||
* }, |
|||
* // 本地服务器配置信息 |
|||
* localServerConfig: { |
|||
* protocol: devServer.options.server.type, |
|||
* host: Server.internalIPSync("v4"), |
|||
* port: devServer.options.port, |
|||
* path: '/', |
|||
* } |
|||
* @param devServer webpack dev server 对象 |
|||
*/ |
|||
constructor(devServer) { |
|||
if (!devServer) { |
|||
throw new Error('webpack-dev-server is not defined'); |
|||
} |
|||
this.devServer = devServer; |
|||
this.registSuccess = null; |
|||
this.remoteServerConfig = { |
|||
protocol: frontendRegisterConfigure.protocol, |
|||
host: frontendRegisterConfigure.host, |
|||
port: frontendRegisterConfigure.port, |
|||
path: frontendRegisterConfigure.path, |
|||
}; |
|||
this.localServerConfig = { |
|||
protocol: devServer.options.server.type, |
|||
host: Server.internalIPSync("v4"), |
|||
port: devServer.options.port, |
|||
path: '/', |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* 周期性向服务器注册前端模块 |
|||
* @param delay 延迟执行(单位:毫秒) |
|||
* @param interval 固定频率执行(单位:毫秒) |
|||
*/ |
|||
regist(delay,interval) { |
|||
if(frontendRegisterConfigure.enable){ |
|||
setTimeout(() => { |
|||
let remoteServerUrl = this.remoteServerConfig.protocol + '//' + this.remoteServerConfig.host + ':' + this.remoteServerConfig.port + this.remoteServerConfig.path; |
|||
console.info('regist frontend module to server --> ' + remoteServerUrl); |
|||
setInterval(this.doRegist.bind(this), delay); |
|||
}, delay); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 向服务器注册前端模块 |
|||
*/ |
|||
doRegist() { |
|||
const data = JSON.stringify(this.getRegistJson()); |
|||
if (data) { |
|||
let request = this.getRequest(this.remoteServerConfig.protocol); |
|||
let This = this; |
|||
request.on('error', error => { |
|||
if (This.registSuccess == null || This.registSuccess) { |
|||
This.registSuccess = false; |
|||
console.error('regist frontend module to server, Failed!', error); |
|||
} |
|||
}); |
|||
request.write(data); |
|||
request.end(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取前端模块的注册信息 |
|||
* @returns 前端模块的注册信息 |
|||
*/ |
|||
getRegistJson() { |
|||
return { |
|||
protocol: this.localServerConfig.protocol, |
|||
host: this.localServerConfig.host, |
|||
port: this.localServerConfig.port, |
|||
contextPath: this.localServerConfig.contextPath, |
|||
name: packageJson.name, |
|||
components: this.getComponents(), |
|||
routes: frontendRoutes, |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取前端模块的注册信息(组件集合) |
|||
* @returns 前端模块的注册信息(组件集合) |
|||
*/ |
|||
getComponents() { |
|||
const plugins = mf.plugins; |
|||
for (let i = 0; i < plugins.length; i++) { |
|||
const plugin = plugins[i]; |
|||
if (plugin instanceof ModuleFederationPlugin) { |
|||
const exposes = plugin._options.exposes; |
|||
if (exposes) { |
|||
const components = []; |
|||
let keyIndex = 0; |
|||
for (let key in exposes) { |
|||
components[keyIndex] = key; |
|||
keyIndex++; |
|||
} |
|||
return components; |
|||
} |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 获取 http/https 请求 |
|||
* @param {*} protocol 请求协议 |
|||
*/ |
|||
getRequest(protocol) { |
|||
let request = http; |
|||
if (protocol == 'https:') { |
|||
request = https; |
|||
} |
|||
let This = this; |
|||
return request.request({ |
|||
protocol: this.remoteServerConfig.protocol + ":", |
|||
host: this.remoteServerConfig.host, |
|||
port: this.remoteServerConfig.port, |
|||
path: this.remoteServerConfig.path, |
|||
method: 'POST', |
|||
headers: { |
|||
'Content-Type': 'application/json' |
|||
} |
|||
}, request => { |
|||
request.setEncoding('utf-8'); |
|||
request.on('data', d => { |
|||
const data = JSON.parse(d); |
|||
if (data.code === 200) { |
|||
if (This.registSuccess == null || !This.registSuccess) { |
|||
This.registSuccess = true; |
|||
console.info('regist frontend module to server, Success!'); |
|||
} |
|||
} else { |
|||
console.error('regist frontend module to server, Failed!', d); |
|||
} |
|||
}) |
|||
}); |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
RemoteFrontEndModuleRegister |
|||
} |
@ -0,0 +1,159 @@ |
|||
/** |
|||
* webpack 通用配置 |
|||
*/ |
|||
const path = require('path'); // path |
|||
const webpack = require('webpack'); // webpack |
|||
const json5 = require('json5'); // json5 |
|||
const HtmlWebpackPlugin = require('html-webpack-plugin'); // webpack html 生成插件 |
|||
const CopyWebpackPlugin = require('copy-webpack-plugin'); // webpack copy 插件 |
|||
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 抽取 css 插件 |
|||
const { VueLoaderPlugin } = require('vue-loader'); // vue loader 插件 |
|||
const ESLintPlugin = require('eslint-webpack-plugin'); // eslint 插件 |
|||
const packageJson = require('./package.json'); // package.json |
|||
const projectName =packageJson.name; // 项目名称 |
|||
|
|||
module.exports = { |
|||
// 入口文件 |
|||
entry: './src/main', |
|||
// 输出 |
|||
output: { |
|||
// 输出路径(为兼容后端和多个前端项目) |
|||
// 1. 兼容后端: 将 dist 目录作为资源目录, 其中 public 种的静态资源可以直接访问 |
|||
// 2. 兼容多个前端项目: 每个项目发布到 public 目录下的唯一项目名称目录 |
|||
path: path.resolve(__dirname, `dist/public/${projectName}`), |
|||
// 输出文件名 |
|||
filename: `javascript/[name].[contenthash:5].js`, |
|||
// 指定发布路径,使用 auto 可具有更多灵活性 |
|||
publicPath: 'auto', |
|||
// 每次构建时,首先删除 output.path 目录所有内容,保证每次得到最新的构建结果 |
|||
clean: true, |
|||
}, |
|||
|
|||
module: { |
|||
rules: [ |
|||
// babel(包含处理: typescript) |
|||
{ |
|||
test: /\.(t|j)s$/, |
|||
exclude: /node_modules/, |
|||
use: [ |
|||
{ |
|||
loader: "babel-loader", |
|||
options: { |
|||
cacheDirectory: true, |
|||
} |
|||
} |
|||
] |
|||
}, |
|||
|
|||
// css |
|||
{ |
|||
test: /\.(sa|sc|c)ss$/, |
|||
use: [{ |
|||
loader: MiniCssExtractPlugin.loader, |
|||
}, |
|||
{ |
|||
loader: 'css-loader', |
|||
}, |
|||
{ |
|||
loader: 'postcss-loader', |
|||
}] |
|||
}, |
|||
|
|||
// 字体文件 |
|||
{ |
|||
test: /\.(woff|woff2|eot|ttf|otf)(\?.*)?$/, |
|||
type: 'asset/resource', |
|||
generator: { |
|||
filename: `fonts/[name].[contenthash:5].[ext]`, |
|||
} |
|||
}, |
|||
|
|||
// json5 |
|||
{ |
|||
test: /\.json$/, |
|||
type: 'json', |
|||
parser: { |
|||
parse: json5.parse, |
|||
}, |
|||
}, |
|||
|
|||
// vue loader |
|||
{ |
|||
test: /\.vue$/, |
|||
exclude: /node_modules/, |
|||
use: [ |
|||
{ |
|||
loader: 'vue-loader', |
|||
} |
|||
] |
|||
}, |
|||
], |
|||
}, |
|||
|
|||
// 插件 |
|||
plugins: [ |
|||
new webpack.DefinePlugin({ |
|||
__VUE_OPTIONS_API__: JSON.stringify(true), |
|||
__VUE_PROD_DEVTOOLS__: JSON.stringify(false) |
|||
}), |
|||
|
|||
// 进度显示插件 |
|||
new webpack.ProgressPlugin(), |
|||
|
|||
// css 抽取插件 |
|||
new MiniCssExtractPlugin({ |
|||
filename: `css/[name].[contenthash:5].css`, |
|||
chunkFilename: `css/[name].[contenthash:5].css` |
|||
}), |
|||
|
|||
// 自动生成静态 index.html 文件 |
|||
new HtmlWebpackPlugin({ |
|||
template: 'public/index.html', |
|||
filename: `index.html`, |
|||
minify: false, |
|||
inject: 'body', |
|||
timestamp: new Date().getTime(), |
|||
}), |
|||
|
|||
// 拷贝静态资源到 output.path 指定的目录 |
|||
new CopyWebpackPlugin({ |
|||
patterns: [ |
|||
{ |
|||
from: 'public', |
|||
toType: 'dir', |
|||
filter: async (resourcePath) => { |
|||
// 不复制 index.html 因为 index.html 已经由 HtmlWebpackPlugin 插件生成了 |
|||
if (resourcePath.endsWith('index.html') || resourcePath.endsWith('.DS_Store')) { |
|||
return false; |
|||
} |
|||
return true; |
|||
}, |
|||
info: { minimized: true }, |
|||
} |
|||
] |
|||
}), |
|||
|
|||
// vue loader 插件 |
|||
new VueLoaderPlugin(), |
|||
|
|||
// eslint 插件 |
|||
new ESLintPlugin({ |
|||
fix: true, |
|||
formatter: 'stylish', |
|||
extensions: ['js', 'ts', 'vue', 'cjs'], |
|||
exclude: [ |
|||
'node_modules', |
|||
], |
|||
}), |
|||
], |
|||
|
|||
// 配置模块如何被解析, |
|||
resolve: { |
|||
// 设置模块别名,方便引用 |
|||
alias: { |
|||
'@': path.resolve(__dirname, 'src'), |
|||
}, |
|||
// 设置支持的模块扩展名,即这些扩展名的文件可以作为模块被使用 |
|||
extensions: ['.ts', '.js', '.cjs', '.vue'] |
|||
}, |
|||
}; |
@ -0,0 +1,66 @@ |
|||
/** |
|||
* webpack module federation 配置 |
|||
*/ |
|||
const fs = require('fs'); // 文件读取 |
|||
const Json5 =require('json5'); // json5 |
|||
const { ModuleFederationPlugin } = require('webpack').container; // webpack 模块联邦插件 |
|||
const packageJson = require('./package.json'); // package.json |
|||
const projectName =packageJson.name; // 项目名称 |
|||
const deps = packageJson.dependencies; // 项目依赖 |
|||
|
|||
// 读取本地路由配置, 通过其中 component 和 componentPath 两个属性构建 webpack 模块联邦的 exposes 属性值 |
|||
const data = fs.readFileSync('./src/routes/routes.json', 'utf8'); |
|||
const routes =Json5.parse(data); |
|||
const mfExposes ={}; |
|||
for(const route of routes){ |
|||
mfExposes[route.component]= route.componentPath; |
|||
} |
|||
|
|||
// 导出 webapck 配置的模块联邦部分 |
|||
module.exports = { |
|||
plugins: [ |
|||
new ModuleFederationPlugin({ |
|||
// 模块联邦的模块名称 |
|||
name: `${projectName}`, |
|||
// 模块联邦的远程入口文件 |
|||
filename: `javascript/remoteEntry.js`, |
|||
// 通过浏览器 window 对象保存模块联邦对象 |
|||
library: { type: 'window', name: `${projectName}` }, |
|||
remoteType: 'window', |
|||
// 模块联邦的导出组件 |
|||
exposes: mfExposes, |
|||
// 模块联邦共享库 |
|||
shared: { |
|||
'@codemirror/autocomplete': { requiredVersion: deps['@codemirror/autocomplete'], singleton: true }, |
|||
'@codemirror/commands': { requiredVersion: deps['@codemirror/commands'], singleton: true }, |
|||
'@codemirror/lang-html': { requiredVersion: deps['@codemirror/lang-html'], singleton: true }, |
|||
'@codemirror/lang-java': { requiredVersion: deps['@codemirror/lang-java'], singleton: true }, |
|||
'@codemirror/lang-javascript': { requiredVersion: deps['@codemirror/lang-javascript'], singleton: true }, |
|||
'@codemirror/lang-json': { requiredVersion: deps['@codemirror/lang-json'], singleton: true }, |
|||
'@codemirror/lang-sql': { requiredVersion: deps['@codemirror/lang-sql'], singleton: true }, |
|||
'@codemirror/lang-xml': { requiredVersion: deps['@codemirror/lang-xml'], singleton: true }, |
|||
'@codemirror/language': { requiredVersion: deps['@codemirror/language'], singleton: true }, |
|||
'@codemirror/search': { requiredVersion: deps['@codemirror/search'], singleton: true }, |
|||
'@codemirror/state': { requiredVersion: deps['@codemirror/state'], singleton: true }, |
|||
'@codemirror/view': { requiredVersion: deps['@codemirror/view'], singleton: true }, |
|||
'@vueuse/core': { requiredVersion: deps['@vueuse/core'], singleton: true }, |
|||
'axios': { requiredVersion: deps['axios'], singleton: true }, |
|||
'codemirror': { requiredVersion: deps['codemirror'], singleton: true }, |
|||
'dayjs': { requiredVersion: deps['dayjs'], singleton: true }, |
|||
'echarts':{ requiredVersion: deps['echarts'], singleton: true }, |
|||
'exceljs':{ requiredVersion: deps['exceljs'], singleton: true }, |
|||
'file-saver':{ requiredVersion: deps['file-saver'], singleton: true }, |
|||
'luckyexcel':{ requiredVersion: deps['luckyexcel'], singleton: true }, |
|||
"mockjs": { requiredVersion: deps['mockjs'], singleton: true }, |
|||
'pinia': { requiredVersion: deps['pinia'], singleton: true }, |
|||
'platform-core': { requiredVersion: deps['platform-core'], singleton: true }, |
|||
'quasar': { requiredVersion: deps['quasar'], singleton: true }, |
|||
'vue': { requiredVersion: deps['vue'], singleton: true }, |
|||
'vue-codemirror6': { requiredVersion: deps['vue-codemirror6'], singleton: true }, |
|||
'vue-dompurify-html':{ requiredVersion: deps['vue-dompurify-html'], singleton: true }, |
|||
'vue-i18n': { requiredVersion: deps['vue-i18n'], singleton: true }, |
|||
'vue-router': { requiredVersion: deps['vue-router'], singleton: true }, |
|||
} |
|||
}), |
|||
] |
|||
}; |
@ -0,0 +1,76 @@ |
|||
/** |
|||
* 开发环境构建 |
|||
*/ |
|||
const { merge } = require('webpack-merge'); // webpack 配置合并函数 |
|||
const common = require('./webpack.config.common.cjs'); // webpack 通用配置 |
|||
const mf = require('./webpack.config.mf.cjs'); // webpack 模块联邦配置 |
|||
|
|||
module.exports = merge(common, mf, { |
|||
mode: 'development', |
|||
// ------------------------------------------------------------------------------------------------------------------------------- |
|||
// devtool | performance | comment |
|||
// (none) | build:fastest, rebuild:fastest | Recommended choice for production builds with maximum performance. |
|||
// eval | build:fast, rebuild:fastest | Recommended choice for development builds with maximum performance. |
|||
// eval-source-map| build:slowest, rebuild:ok | Recommended choice for development builds with high quality SourceMaps. |
|||
// source-map | build:slowest, rebuild:slowest | Recommended choice for production builds with high quality SourceMaps. |
|||
// ------------------------------------------------------------------------------------------------------------------------------- |
|||
devtool: 'eval-source-map', |
|||
optimization: { |
|||
minimize: false, |
|||
moduleIds: 'named', |
|||
chunkIds: 'named', |
|||
|
|||
splitChunks: { |
|||
cacheGroups: { |
|||
'vue': { |
|||
name: 'vue', |
|||
test: /[\\/]node_modules[\\/](vue|vue-dompurify-html|vue-i18n|vue-router)[\\/]/, |
|||
priority: 20, |
|||
chunks: 'all', |
|||
enforce: true |
|||
}, |
|||
'dnd':{ |
|||
name: 'dnd', |
|||
test: /[\\/]node_modules[\\/](vue3-dnd|react-dnd-html5-backend|@vueuse[\\/]core)[\\/]/, |
|||
priority: 20, |
|||
chunks: 'all', |
|||
enforce: true |
|||
}, |
|||
'quasar': { |
|||
name: 'quasar', |
|||
test: /[\\/]node_modules[\\/](quasar)[\\/]/, |
|||
priority: 20, |
|||
chunks: 'all', |
|||
enforce: true |
|||
}, |
|||
'excel': { |
|||
name: 'excel', |
|||
test: /[\\/]node_modules[\\/](exceljs|luckyexcel|)[\\/]/, |
|||
priority: 20, |
|||
chunks: 'all', |
|||
enforce: true |
|||
}, |
|||
'platform-core': { |
|||
name: 'platform-core', |
|||
test: /[\\/]node_modules[\\/]platform-core[\\/]/, |
|||
priority: 20, |
|||
chunks: 'all', |
|||
enforce: true |
|||
}, |
|||
'view': { |
|||
name: 'view', |
|||
test: /[\\/]view[\\/]/, |
|||
priority: 20, |
|||
chunks: 'all', |
|||
enforce: true |
|||
}, |
|||
'vendors': { |
|||
name: 'vendors', |
|||
test: /[\\/]node_modules[\\/]/, |
|||
chunks: 'all', |
|||
enforce: true |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
}); |
@ -0,0 +1,35 @@ |
|||
/** |
|||
* 生产环境构建 |
|||
*/ |
|||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); // css 压缩插件 |
|||
const TerserPlugin = require("terser-webpack-plugin"); // js 压缩插件 |
|||
const { merge } = require('webpack-merge'); // webpack 配置合并函数 |
|||
const build = require('./webpack.env.build.cjs'); // 开发环境构建配置 |
|||
|
|||
module.exports = merge(build, { |
|||
mode: 'production', |
|||
// ------------------------------------------------------------------------------------------------------------------------------- |
|||
// devtool | performance | comment |
|||
// (none) | build:fastest, rebuild:fastest | Recommended choice for production builds with maximum performance. |
|||
// eval | build:fast, rebuild:fastest | Recommended choice for development builds with maximum performance. |
|||
// eval-source-map| build:slowest, rebuild:ok | Recommended choice for development builds with high quality SourceMaps. |
|||
// source-map | build:slowest, rebuild:slowest | Recommended choice for production builds with high quality SourceMaps. |
|||
// ------------------------------------------------------------------------------------------------------------------------------- |
|||
devtool: 'source-map', |
|||
optimization: { |
|||
minimize: true, |
|||
minimizer: [ |
|||
new CssMinimizerPlugin(), // css 压缩插件 |
|||
new TerserPlugin({ // js 压缩插件 |
|||
extractComments: false, |
|||
terserOptions: { |
|||
format: { |
|||
comments: false, |
|||
}, |
|||
}, |
|||
}), |
|||
], |
|||
moduleIds: 'named', |
|||
chunkIds: 'named', |
|||
}, |
|||
}); |
@ -0,0 +1,35 @@ |
|||
/** |
|||
* 开发环境下启动 webpack dev server |
|||
*/ |
|||
const path = require('path'); // path |
|||
const { merge } = require('webpack-merge'); // webpack 配置合并函数 |
|||
const common = require('./webpack.config.common.cjs'); // webpack 通用配置 |
|||
const mf = require('./webpack.config.mf.cjs'); // webpack 模块联邦配置 |
|||
const { RemoteFrontEndModuleRegister } = require('./util-frontend-register.cjs'); // 远程模块注册器 |
|||
|
|||
module.exports = (env)=> merge(common, mf,{ |
|||
mode: 'development', |
|||
devtool: 'eval', |
|||
|
|||
devServer: { |
|||
client: { |
|||
overlay: false, |
|||
}, |
|||
static: { |
|||
directory: path.join(__dirname, 'public'), |
|||
}, |
|||
compress: false, |
|||
port: 3000, |
|||
hot: true, |
|||
// 保证在出现 404 错误时,能够导航到 index.html |
|||
historyApiFallback: true, |
|||
|
|||
setupMiddlewares: (middlewares, devServer) => { |
|||
// 注册前端模块到远程服务器 |
|||
const register = new RemoteFrontEndModuleRegister(devServer); |
|||
// 延后 5 秒执行, 且每 5 秒执行一次 |
|||
register.regist(5000,5000); |
|||
return middlewares; |
|||
} |
|||
}, |
|||
}); |
Loading…
Reference in new issue