From 174b42a6876d6970c9944d6e6f8c840ceb2faabe Mon Sep 17 00:00:00 2001 From: wangshaoping Date: Sun, 4 Feb 2024 14:14:42 +0800 Subject: [PATCH] update --- app.platform/x.txt | 0 .../luckysheet/2.1.13/css/waffle_sprite.png | Bin 0 -> 8116 bytes .../util-components-generator.cjs | 32 ++++ .../util-frontend-register.cjs | 173 ++++++++++++++++++ .../webpack.config.common.cjs | 159 ++++++++++++++++ .../webpack.config.mf.cjs | 66 +++++++ .../webpack.env.build.cjs | 76 ++++++++ io.sc.engine.mv.frontend/webpack.env.prod.cjs | 35 ++++ .../webpack.env.serve.cjs | 35 ++++ 9 files changed, 576 insertions(+) create mode 100644 app.platform/x.txt create mode 100644 io.sc.engine.mv.frontend/public/webjars/luckysheet/2.1.13/css/waffle_sprite.png create mode 100644 io.sc.engine.mv.frontend/util-components-generator.cjs create mode 100644 io.sc.engine.mv.frontend/util-frontend-register.cjs create mode 100644 io.sc.engine.mv.frontend/webpack.config.common.cjs create mode 100644 io.sc.engine.mv.frontend/webpack.config.mf.cjs create mode 100644 io.sc.engine.mv.frontend/webpack.env.build.cjs create mode 100644 io.sc.engine.mv.frontend/webpack.env.prod.cjs create mode 100644 io.sc.engine.mv.frontend/webpack.env.serve.cjs diff --git a/app.platform/x.txt b/app.platform/x.txt new file mode 100644 index 00000000..e69de29b diff --git a/io.sc.engine.mv.frontend/public/webjars/luckysheet/2.1.13/css/waffle_sprite.png b/io.sc.engine.mv.frontend/public/webjars/luckysheet/2.1.13/css/waffle_sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..336ce4f1de438f1da055973a7b228741f2ef916a GIT binary patch literal 8116 zcma)hc|26@`~R2?gNPYM$`&%F%poOD*>}dySkgF*7A>-rk}Ss>rtFLok|iZ;ikcW1 zLqjR*k@7^g9wkCqvh+Lpe16~8_s{P?_iN6abME`Puls$yuj_iBWJh~TG2AvB0)Y^- zwlZ@@AW#7O`?wGm9*0j=JVziDBCO4fT`vku-^2v%uzZ-9R&{*P_n6n@KQXyadwQ%B z?#5yV>x!QC+_R($y|Z>B;;Y=}s`H*dU>n!?5Kl79dj$mVv(Cu=y7}A9=dJ6&P_zE^ z{Db|Wzgrg@r0QMEj?YerV#yN^76 zW0rsU=i&kXjaWN1%Xo>*9aoc4tv>qc>B}p22gR1B#6I-ILRTkwb$s>lm}Ao${H89 z{30mfSzf*q2}lge;I^V4#aj&imAYi^$Id_evtP4r&)>q3p2BYh@B z1*5(9aCn;j#!w{tAEeY3uBf^sR^`_($=~V3l6hplQ#^On4SIdqKHNe}9vrI3v0$RI z3a$%@Dyh`qn3E!WMoK`N#>9e~3{mAi$7AbU5I6 zeeDwH0ll8Sd#HwmKV%10GC3owo|zm8lX_QB=Lr9DhX+mFCC6Qz7yGD!bb-Xv_L;g{ z!ch#s@NBNQV|dQika0psM&46`H6l%%#G5bKw!{hgOBRz@E2!2gPke#zC@s4Td&VO? zAeD3CadzO**m@D}BjmK7Vd2bnpPuuivFtgUH0@i;Ki;JUc*k{h>b@SmCOEd|cgeZK zJkQ6<Ky!Aec`SeVC4n1Y1ufB@@W54GwQ%J6mVFa zxo&#mnIsX%%*hKA=|LZjW+SScr$zeKbA&S;c;txpy$vZvUkmwx{ErecPlraNkLJ&l zCyq^=JX$zE%g@Up@ss(T*%ve{$rQIOPK(Z$@Xt|8^vZARqbhb7i+-u8@cb$(9Ax~m zE^WQ0uU($Sw^R7)&XuYNaba^tB8ZA6&y+RMW3G~h#0GW)tK#Re9^>e_-Fn%Ct;K+r4_@}Cy zCyj-e*PbklrcyTK(33-vn&w##^cWbExX zXesuFJ{bTHWuqVVch48%y>FCXuwblR5pT&8&?*)ZPE`|^SmgNEa8A5*5yd3x121lI zMiS2Mz*y)#K7x#^@%_GJyFp+nb*NK%6@i+@H1XVBz)Y+p&U%kai(M&;SxH|wn808`Gt|3-AqUVGG~o|Vfc43s}zztCMTB#J3t_Af9HQaMXCxo%fRiEApV z`1tYT(%PhMt5yqm*~h17YsQVVN*Wl9xiT;5)3Ov7KFuhF3%3B%g+%=@m81NS-9 zA_M)KoCEVELgiVvc3EWH%~AmtwXDHAHptt0WLpfDbQm;prJwYMWDnB7AZoGtAHEc} z0d3kdh~nY-cTS&Druayd??Jk#ssk$tE?pjw!1Z3T#b61WlEAraG`Lu840?wPi&@zu=5T-AQv*gaZ8fKyOJ#+Tad@b3Dfp+#U||SF7Y088}zBi^O!9jf1Guc5M5e@-FPd2 zI^ZFu;RHV)Y%R_DKG9(cev~TV#MIHi%PMMdY#j-iu2jEir;>PAC|~=i&0a5DPo2WA zOL%c8IfoTyuf!0i3Vl9G^Vyy(kQqN&Kn&M2!0QPia41Rw&sDFi3@{|Lf=uY2$9oO?g=Eb=Q)r*idMQMQM3TE~#;;2KBZeuuV zEPGkF$29HG)L|&tyewWG#(I2^=B?spUPhjjO3~)l@I=@Q49|E$??bO&W-Y^jd}d3S zS?`nN=wGG7tum6guh?F_zMLc(_NMcv%sW(;J$*Yj2@wJNcTSqv)h+w`Qod)@k|IVe zUY;fK5+%jj7#&YQnnIuO+MD}hrk>#$T6-3YY8>7ZxA@*Vj7oYf~{TuagW{)1?$!`1sMP>BV}wSFoK!zDjSv$4uqld`Xnyj_MaQeN|Yjl zZ-=_5rh$AJ7|HE`YCJM4`alAczyhIkCX-nz$!f7qoiw339;r?j0t@){o0?~3zAVxbL&_|S09#M z%}ED*&utO>m~u)(yBkKH-&+5mq$!+GVP8DHj03R*EswS=owR!3Uq*dKy7C!bsa1!j1t1zg|M!IabGb>9(nyLd; zoTeb0EEym) z*eX60TzV0998wCkNm!=P)VFW?;`hu~V&_uEMT9TnWYZt~`1#XQ);g(0A-yDEAM8U# zN(%Uw>?J=uc{g2XuAP9FS(;M`wFjp} zLC=IIcPgx0X>;m@9;D(A&gK1TVC8Yvz9$*#kkZ5YPa-gC#&qv_8DgdiCD$LSo6n?y z>RSIH(_lh<$lO24tsAh7P*uUXKI_T4PI*+x2>YgFmN#UboA;1rMgpX9)OxmxQ%h}a zvVvr$6Bsit4)Y>Twj%xQ{^K3#`vW(Z_@W8mSz~F2AK~<_Z=_6oxn9S z&6l@q!}a_~`Y4j@#7(eU%aLNuM}R`GC*T?IEKQ-F4jG@JvM!HWq!d}}=6lov%v-Ip z$;kfYC?exAUO0Jg?;$A>DIIeK!OR@me5Ou9PlgN))KG@ag=ra6e|aS&Zh1Q#VOJbM zzdvEAMhD+U_$oOb3xj=(mpOee3b9vpii)L_eU2*Z>-d1hR!s9Mh2KWPjs9a zt z+*uzen57q5?&ME}tDrZYU=@6hIF(fcptSxEGzDO24z)JA8UCI~`E5C$h#VV0Wsys( zVN@lrVscwK>C*@2vJ{IsBObp#G#u~UUlYClwG|D$9?Lu4!_q>BH*fst6NaFM;dtX5%^D7e6J~HqQMVkYLS5&PM279+Mkxl0^0o3|t62r~wdGw>g6$_Ez&I z5b4@tm5IgnlQ3Dc%3C=lA$=+&Od@gDwLiHD_7+aGk3=A5#e8e;5hLA5A1||lUdv2< zHnzFkcF~-NR;uFE3i8Crz*NBBU*UT4`=(dafvGVH<&!PdYCbm99TXB~*~`G`;T;V8 z8_U8l08Js1iC^hQWV<}kWHU`Ha?sdP70hVdlYY%g^k#%%)Shy!JdtDT2j5&HXSJ?P zP}DHaD%)12xB2Hu(mAg}Run3Xr0c9y3TlvB~gZc;c0$RgT1% z9J7q$@#hhE)}ONuN%6x)Qy8AC1EjT2nmFwfXJ=k#=`b@AKDQ>P8>kZ(7dQDSN#Ui` zpS0rW;-3e7hDWf)a0?+ zvuz1Q;$cZ4S3|5a*>9ZvYL)5Op@Bg>D>ee`%l`1H;k8D09;Jpl_SjpKC-pqz4Khn* zx4c!j1nep}pm9v`+0_~`eg5{NY~?+79&=c1G_PEea?XiF(!RK1DIbo0=7fgVa+;of z=$mD5D#szLm~$m=sZp(_0n*_ZCOd*_5#YQuOWFNnUqQ)G@j2U&wTTWBNJ;b0aln{`Q)gtU@mA`yfNE8+e)5jBN02Pc#X_@a$zi`0@&O@I`UV;l|~G!6`EEL_O(K zUdRs4=aT8nXmM&7)Yce}qxLV!-e#Y6XU^YrL}Sl(wvqe?{{xyT4*CI3mg}FJ$31{t zcv}xPkLup#0+L8n$zImNv=pqrYJi+ykyGar~u-1*lznI!6x0v17U;G3Dkd zQmlBx`i>>e|92%70~PcvW)L6o=NoGx#YSTp-|g1!I}1a>@o*Q&8&w6;a~Rk3%7b@U z7L5I3VR*#Q6Ab~3gBNCWey@Etd;ydA@onMLb)81{ru+rTZW8@kl&@mp$%>k8Q_vJG z%x&?}+ti(?3E*h-YV$ewO2I$9KDKH0hB2MhQDQzaSWku^&|>#jaho1qZ0%t{o1JYUrzMZ_XF1t zpMARTlGx{_z}}~Ap(rh$x8L>gFZXKpA1{i0nE%)LyW#)dO6ut=vlV+O@X@~OZ3|q- zssh_k+I-t8u4|cKx$P89P5p`b6D(hIG4x;BA<>||5S*-v zDv9Q=f~_|BTl5bmH|vUH;uB~2VH3;XC9nVuB<#L|qh7e6OVduJx9e4>Rl)_jazJC9 z?A~KcoXFinhQD5?ZTo!j$y?WY)Ab3}-?R4)3-_o~s=3+kP^OANJ1GK8yd5i1*ZB>qDNVd0f`IQ}vEzg&);_{mkhK5_R^Z(CluAQb~)_7FcfUrFV8wS%kGCw7>)FsU)9zCw)JBnQnO$p za-jsT4~&>gNXeA;xOM6Dl8%WUx_$XC2k7y;<|5kcKGs}+`mHP38c%vMa_+Aaho{~raO_~_R0tSw)o^~|>7sLKcE zZaZ|)K<95<1$W4J0`pny+QFFvV>FPHyXSDvlaD{Ed8JyL-vWMfK06v}iG z$hiVIQGw^S50h%w9!JBfkVX=@#*uz_E8W+yTtDZZ?58=mvj@~I$!BZF=x18hC@%Ev zo)dG5N28Axb%jzNJX@sLB(O^pec8iRL;1S6SBdpzB5{ZHH}{LdH+Ah zZ!25Dbib4>Ot{qec=#MT)7_kM`6s3-}F@3vLjtx%USS!EpBkuj{2&9 z_33P+cu8iLxy%v&>*&;jdo+Bg_qndXnQxF%BcAnp)D?(D@48?NWHR8~i-jHx4=Q+$AEt`@1x8^-y5gzd!2T)f?|uomp;Vsa+9&v3R%?e&KRcv()u{ z!b&icI`{B{qR;LUJxHV81R|00DQh!Pj;l7_*!g0+bN3FA(FHeOntUd`Fd1v&6xm7C| z>|_Wk4RY4;Pg{dpry}zRmD>NF6_pU!bu+3=Z|(uP$GYN3z>hPE2nwH(j=b|D>PR6H zg@h$eQQEI<+qON^x$oT;`Xv-4)YHcI8uS19_kFCbq3Ol2>36;*wkC|9b(RW|4?JhP zE$*|~)C@=KY?-9pNU2A|eq;#N%_q)#YA*CfZ|vQ$wcdl_WymO=uXqZxN}Xi?R%D#H z7t%~oGsO(z>Y%ZG68h5Wl+onMT271++&SOY(-CG+YXtj{uJQ%}6`4n0mfPL1cUCzk zInm*}8`*!lwS%@<5mi_hCuhlghcoG(B^lRuwSh&Ei?Zf$$0Z=d45s@dr_d=f#L=!` zOCix*zBn`Ls4K0cSn@V=v-uM^?~|~*(ozXu&rjX)m2khRr%PFjW%Dq6;qS8~XBl`U zB~x+c-t$#iut5tw#IWHaM?nZ`YBE#kHKx}Ns!NpJmxKj~n-*$H^K)s@Y+JLJnRpTn zyb|i8Dl8gReNqW{S9?Gaix&{{On!!=T0nE@te6Q5nnF=*U?YBmzur{1n*DwKTpPaO zs{KUSPHR=*8v9&1j1z-1W!#J3v*(3pnufMn(p8kTjBw@q^?^L^kmMYhbWJ?#3f?(>sd-t+KQ^lk#Ns3s?Mr>dI#BFEJOR57dX z79k!kggdf!-7|yp9rr&x+~a&J{G9)X{p-UI8~J0GT0F>Gd^{fo{zE2^u+9|@iRp?O zfSwTb%q39A9-|h?C%|QR(|l$U2dp@Cs4$O>)&$!5G4smi-cX%KaGePwt&$ATKqD+9 zz-x=Sk?8wH!CuLBdQ-kif%|-nGtl@2g1F}j;g$j$i*yQ>m8YMf ziEr_Op4-u5RJJxO>`m8Tly!t81ShDOVtCreAOS2|CqcBc7=wi=Q&M|y0s~NA#43Ex zOurlY;mo6a_=}d@cH_U_CNr(}st{m0o!9WE(Y43rHnU?D$3HLc0dmaMuk`T&0uk)}#>*Wf1G&X8OykbQ!*^vkZ{UVY zVYdh4{bjWCdBr_jlSw9^iYpv>V?R2_?M=j~om=m0%F>%@tE9@KPBdE?h8{v#wqm|d z4~`Avzv$p0-N)IZO3V70k^$yxsCd|#(-W%Uxcny~Ev+0YX-WCJ&h<3#{XeL7oM;5B zJqV*QB_ZAQ34FUPA`3+((oy2nlOcCGSH#_Z%gNkf*CMlww#*|-?cuLzuwJRM2siPW z&Y%iepv(5k<|Y?8i02s(r%_pOl|mqrq+IJ=6wfE<19Rhw6r*SHTtg!d==nPoDi}Tt z=X6ed5v(_Cewh)FR6aigS*wL&Ecm?TP8-;Ng%_$g%iSWdY!8bXFof?TQ7RNha^d0KhR8^YrQ8;S)X+-`PzBx#I@_i z)i;L`>)3MP$wpz;wz>Mq+Y19RmF2AtgFAUx9@c_Wn`)q}P1o(T1N0q0t6ReyowhXr zm)&$URR%~<=3&dX!GB8t=E}!Tj3R`Ir@`y2n=zFHN+d^!<-C=FVxSttZ(QxB&@oi) zsS<+oL;TNUJB zj|C&-hytiLbzkM+ivajy7}WqOZr*_}SkdD*mYZ8V(lD6wO~TKAbT(*s>k5FVy{d97 z1K3Qu`|)0evGI;T6K&O!4lK478btl)j(uKxzlWn^(v5WLtrK;Sfb`g8n0}O!#)41R zyS#<|q`KnbExJm1b{zZ_X*t%^#$#cl*6=~)F2;do1rgkrD^HSw(FnwyVvHyPVI&ra zL?9d!clsj`F`EP8TDri02YAZ}#E6jWe+MIHib(e^Mxa8cD*PD2+T7l(+60XKe}3Rm A;Q#;t literal 0 HcmV?d00001 diff --git a/io.sc.engine.mv.frontend/util-components-generator.cjs b/io.sc.engine.mv.frontend/util-components-generator.cjs new file mode 100644 index 00000000..dcf297d2 --- /dev/null +++ b/io.sc.engine.mv.frontend/util-components-generator.cjs @@ -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!'); \ No newline at end of file diff --git a/io.sc.engine.mv.frontend/util-frontend-register.cjs b/io.sc.engine.mv.frontend/util-frontend-register.cjs new file mode 100644 index 00000000..da69db21 --- /dev/null +++ b/io.sc.engine.mv.frontend/util-frontend-register.cjs @@ -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 +} diff --git a/io.sc.engine.mv.frontend/webpack.config.common.cjs b/io.sc.engine.mv.frontend/webpack.config.common.cjs new file mode 100644 index 00000000..25aee0c0 --- /dev/null +++ b/io.sc.engine.mv.frontend/webpack.config.common.cjs @@ -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'] + }, +}; diff --git a/io.sc.engine.mv.frontend/webpack.config.mf.cjs b/io.sc.engine.mv.frontend/webpack.config.mf.cjs new file mode 100644 index 00000000..e35ea57a --- /dev/null +++ b/io.sc.engine.mv.frontend/webpack.config.mf.cjs @@ -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 }, + } + }), + ] +}; diff --git a/io.sc.engine.mv.frontend/webpack.env.build.cjs b/io.sc.engine.mv.frontend/webpack.env.build.cjs new file mode 100644 index 00000000..dcc45fb7 --- /dev/null +++ b/io.sc.engine.mv.frontend/webpack.env.build.cjs @@ -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 + }, + } + } + }, +}); \ No newline at end of file diff --git a/io.sc.engine.mv.frontend/webpack.env.prod.cjs b/io.sc.engine.mv.frontend/webpack.env.prod.cjs new file mode 100644 index 00000000..ab9a725c --- /dev/null +++ b/io.sc.engine.mv.frontend/webpack.env.prod.cjs @@ -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', + }, +}); diff --git a/io.sc.engine.mv.frontend/webpack.env.serve.cjs b/io.sc.engine.mv.frontend/webpack.env.serve.cjs new file mode 100644 index 00000000..e3f793b4 --- /dev/null +++ b/io.sc.engine.mv.frontend/webpack.env.serve.cjs @@ -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; + } + }, +});