595 lines
21 KiB
TypeScript
595 lines
21 KiB
TypeScript
import { resolve } from 'path';
|
||
import type { Plugin, UserConfig } from 'vite';
|
||
import { defineConfig, loadEnv } from 'vite';
|
||
import visualizer from 'rollup-plugin-visualizer';
|
||
import vue from '@vitejs/plugin-vue';
|
||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||
import svgLoader from 'vite-svg-loader';
|
||
import AutoImport from 'unplugin-auto-import/vite';
|
||
import Components from 'unplugin-vue-components/vite';
|
||
import UnoCSS from 'unocss/vite';
|
||
import configArcoStyleImportPlugin from './config/plugin/arcoStyleImport';
|
||
import configCompressPlugin from './config/plugin/compress';
|
||
|
||
export default defineConfig(({ mode }) => {
|
||
const env = loadEnv(mode, process.cwd(), '');
|
||
const omsAdminTarget = (env.VITE_OMS_ADMIN_URL || 'http://api.nanafa.com/admin').replace(/\/$/, '');
|
||
|
||
return {
|
||
server: {
|
||
open: true,
|
||
fs: {
|
||
strict: true,
|
||
},
|
||
// 添加中间件,在代理之前拦截前端路由
|
||
middlewareMode: false,
|
||
proxy: {
|
||
// 上传静态资源:/api/upload/xxx -> http://api.nanafa.com/upload/xxx
|
||
// 必须放在通用 /api 规则前,避免被转发到 /itfx/upload 导致 404
|
||
'/api/upload': {
|
||
target: 'http://api.nanafa.com',
|
||
changeOrigin: true,
|
||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||
timeout: 30000,
|
||
proxyTimeout: 30000,
|
||
},
|
||
// 权限(admin)43.139.210.35:5281 -> http://api.nanafa.com/admin(路径保持 /rbac/xxx -> .../admin/rbac/xxx)
|
||
'/rbac': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
},
|
||
// OMS 订单等 admin 接口:/order/list、/order/detail(与 Swagger OMS-Order 一致)
|
||
'/order': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
bypass(req) {
|
||
const path = req.url?.split('?')[0] ?? '';
|
||
const orderSpaPrefixes = [
|
||
'/order/guide',
|
||
'/order/priceCheck',
|
||
'/order/placeOrder',
|
||
'/order/myOrders',
|
||
'/order/batchJobs',
|
||
'/order/templateMapping',
|
||
'/order/platformStore',
|
||
'/order/platformStoreOrder',
|
||
];
|
||
if (orderSpaPrefixes.some((p) => path === p || path.startsWith(`${p}/`))) {
|
||
return '/index.html';
|
||
}
|
||
},
|
||
},
|
||
// OMS 仓储 admin 接口:/warehouse/sku/list 等(与 Swagger OMS-Warehouse 一致)
|
||
'/warehouse': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
bypass(req) {
|
||
const path = req.url?.split('?')[0] ?? '';
|
||
const warehouseSpaExact = [
|
||
'/warehouse/parcel',
|
||
'/warehouse/inventory',
|
||
'/warehouse/sku',
|
||
'/warehouse/binLocation',
|
||
];
|
||
if (warehouseSpaExact.some((p) => path === p || path === `${p}/`)) {
|
||
return '/index.html';
|
||
}
|
||
// inboundNotice 页面无更深 API 子路径冲突;入库 API 为 /warehouse/inbound/*
|
||
if (path === '/warehouse/inboundNotice' || path === '/warehouse/inboundNotice/') {
|
||
return '/index.html';
|
||
}
|
||
},
|
||
},
|
||
'/plugin': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
bypass(req) {
|
||
const path = req.url?.split('?')[0] ?? '';
|
||
if (path === '/Plugin/AddPackage' || path.startsWith('/Plugin/AddPackage/')) {
|
||
return '/index.html';
|
||
}
|
||
},
|
||
},
|
||
'/Plugin': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
bypass(req) {
|
||
const path = req.url?.split('?')[0] ?? '';
|
||
if (path === '/Plugin/AddPackage' || path.startsWith('/Plugin/AddPackage/')) {
|
||
return '/index.html';
|
||
}
|
||
},
|
||
},
|
||
'/monitor': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
},
|
||
'/customer': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
},
|
||
'/wechat': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
// H5 页面路由与 API 同前缀:刷新 /wechat/register、/wechat/bind 须回退 index.html
|
||
bypass(req) {
|
||
const path = req.url?.split('?')[0] ?? '';
|
||
const wechatSpaPages = ['/wechat/register', '/wechat/bind'];
|
||
const isSpaPage = wechatSpaPages.some(
|
||
(p) => path === p || path.startsWith(`${p}/`),
|
||
);
|
||
if (isSpaPage) {
|
||
return '/index.html';
|
||
}
|
||
},
|
||
},
|
||
'/recharge': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
},
|
||
'/channel': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
},
|
||
'/operation': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
// 与页面同前缀的 API 代理:必须在代理层 bypass,否则刷新会先于/绕过 unshift 中间件仍被转发到后端 404
|
||
bypass(req) {
|
||
const path = req.url?.split('?')[0] ?? '';
|
||
const operationSpaPrefixes = [
|
||
'/operation/inboundScan',
|
||
'/operation/outboundScan',
|
||
'/operation/printLabel',
|
||
'/operation/parcelArrival',
|
||
];
|
||
const isSpaPage = operationSpaPrefixes.some(
|
||
(p) => path === p || path.startsWith(`${p}/`),
|
||
);
|
||
if (isSpaPage) {
|
||
return '/index.html';
|
||
}
|
||
},
|
||
},
|
||
'/system': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
bypass(req) {
|
||
const path = req.url?.split('?')[0] ?? '';
|
||
const systemSpaExact = ['/system/dict', '/system/warehouseAddress', '/system/announcement'];
|
||
if (systemSpaExact.some((p) => path === p || path === `${p}/`)) {
|
||
return '/index.html';
|
||
}
|
||
const systemSpaPrefixes = [
|
||
'/system/users',
|
||
'/system/roles',
|
||
'/system/menus',
|
||
'/system/organizations',
|
||
'/system/batchEmail',
|
||
'/system/otherConfig',
|
||
'/system/xhsAiAssistant',
|
||
'/system/dataPerm',
|
||
];
|
||
if (systemSpaPrefixes.some((p) => path === p || path.startsWith(`${p}/`))) {
|
||
return '/index.html';
|
||
}
|
||
},
|
||
},
|
||
// 小红书 OMS-Xhs 接口(开发默认走本机 Admin API,与桌面 Agent 一致)
|
||
'/xhs': {
|
||
target: omsAdminTarget,
|
||
changeOrigin: true,
|
||
secure: false,
|
||
},
|
||
// 微信公众号服务器回调(兼容 WMS 路径 /api/apiwechat/verification)
|
||
'/api/apiwechat': {
|
||
target: 'http://api.nanafa.com/admin',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||
},
|
||
// 轨迹(track)43.139.210.35:8889 -> http://api.nanafa.com/track,路径与 rbac、api 一致:/Track/xxx
|
||
'/Track': {
|
||
target: 'http://api.nanafa.com',
|
||
changeOrigin: true,
|
||
rewrite: (path) => '/track' + path,
|
||
configure: (proxy) => {
|
||
proxy.on('error', (err, req, res) => {
|
||
console.error('proxy error', err);
|
||
});
|
||
},
|
||
},
|
||
// API(itfx)43.139.210.35:8888 -> http://api.nanafa.com/itfx
|
||
// 使用正则表达式匹配,排除前端路由路径
|
||
'^/api/(?!upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface|downstreamInterface/newPort|appList|appList/createApplication|requestLog|callStatistics|apiDebugger|trackApi)': {
|
||
target: 'http://api.nanafa.com/itfx',
|
||
changeOrigin: true,
|
||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||
timeout: 30000,
|
||
proxyTimeout: 30000,
|
||
},
|
||
|
||
// 智谱 AI(开发环境代理,避免浏览器 CORS)
|
||
'/zhipu-api': {
|
||
target: 'https://open.bigmodel.cn',
|
||
changeOrigin: true,
|
||
secure: true,
|
||
rewrite: (path) => path.replace(/^\/zhipu-api/, '/api/paas/v4'),
|
||
timeout: 120000,
|
||
proxyTimeout: 120000,
|
||
},
|
||
// 外部域名(用于 ApiTest 调试,避免浏览器 CORS)
|
||
// 示例:把 https://lsp.globex.cn/rs/xxx 代理到 /external/rs/xxx
|
||
'/external': {
|
||
target: 'https://lsp.globex.cn',
|
||
changeOrigin: true,
|
||
secure: false,
|
||
rewrite: (path) => path.replace(/^\/external/, ''),
|
||
timeout: 30000,
|
||
proxyTimeout: 30000,
|
||
},
|
||
},
|
||
},
|
||
plugins: [
|
||
{
|
||
name: 'filter-known-build-warnings',
|
||
configResolved(config) {
|
||
const rawWarn = config.logger.warn;
|
||
config.logger.warn = (msg, options) => {
|
||
if (
|
||
msg.includes('[baseline-browser-mapping]') ||
|
||
msg.includes('~@/assets/logo.png referenced in ~@/assets/logo.png') ||
|
||
msg.includes('new URL("../assets/hi.png", import.meta.url) doesn\'t exist at build time')
|
||
) {
|
||
return;
|
||
}
|
||
rawWarn(msg, options);
|
||
};
|
||
},
|
||
},
|
||
// eslint({
|
||
// cache: false,
|
||
// include: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'],
|
||
// exclude: ['node_modules'],
|
||
// }),
|
||
vue(),
|
||
vueJsx(),
|
||
svgLoader({ svgoConfig: {} }),
|
||
configArcoStyleImportPlugin(),
|
||
UnoCSS(),
|
||
|
||
AutoImport({
|
||
imports: ['vue', 'vue-router', 'vue-i18n', 'pinia', '@vueuse/core'],
|
||
dts: 'src/auto-imports.d.ts',
|
||
dirs: ['src/composables', 'src/store'],
|
||
vueTemplate: true,
|
||
eslintrc: {
|
||
enabled: true,
|
||
},
|
||
}),
|
||
Components({
|
||
extensions: ['vue'],
|
||
include: [/\.vue$/, /\.vue\?vue/],
|
||
dts: 'src/components.d.ts',
|
||
dirs: ['src/components'],
|
||
resolvers: [],
|
||
// directoryAsNamespace: true,
|
||
}),
|
||
// 自定义插件:在代理之前拦截前端路由
|
||
{
|
||
name: 'exclude-frontend-routes-from-proxy',
|
||
configureServer(server) {
|
||
// 仅精确匹配的 SPA 路径(页面 URL 与后端 API 前缀相同,不能用「前缀匹配」,否则 /warehouse/parcel/list 会返回 index.html)
|
||
const frontendRoutesExact = [
|
||
'/system/dict',
|
||
'/system/warehouseAddress',
|
||
'/system/announcement',
|
||
// 仓储页面路径与 API 同段,必须精确匹配(/warehouse/parcel/list 走代理)
|
||
'/warehouse/parcel',
|
||
'/warehouse/inventory',
|
||
'/warehouse/sku',
|
||
'/warehouse/inboundNotice',
|
||
'/warehouse/binLocation',
|
||
'/Plugin/AddPackage',
|
||
];
|
||
|
||
// 前端路由路径列表(这些路径不应该被代理,应该走前端路由)
|
||
// 所有前端路由都使用驼峰命名,以这些前缀开头的路径都是前端路由
|
||
const frontendRoutes = [
|
||
// API 相关前端路由
|
||
'/api/upstreamInterface',
|
||
'/api/upstreamInterfaceConfig',
|
||
'/api/upstreamInterfaceConfigEdit',
|
||
'/api/downstreamInterface',
|
||
'/api/downstreamInterface/newPort',
|
||
'/api/appList',
|
||
'/api/appList/createApplication',
|
||
'/api/requestLog',
|
||
'/api/callStatistics',
|
||
'/api/apiDebugger',
|
||
// Channel 相关前端路由
|
||
'/channel/salesProducts',
|
||
'/channel/serviceChannels',
|
||
'/channel/priceArea',
|
||
'/channel/blacklist',
|
||
'/channel/remoteZones',
|
||
'/channel/weightRules',
|
||
'/channel/weightRounding',
|
||
'/channel/autoTrigger',
|
||
'/channel/issueTemplate',
|
||
'/channel/labelDesign',
|
||
'/channel/guide',
|
||
// Order 相关前端路由
|
||
'/order/guide',
|
||
'/order/priceCheck',
|
||
'/order/placeOrder',
|
||
'/order/myOrders',
|
||
'/order/batchJobs',
|
||
'/order/platformStore',
|
||
'/order/platformStoreOrder',
|
||
// Xhs 相关前端路由(勿用 /xhs 前缀,否则 API 会被误判为 SPA)
|
||
'/xhs/startService',
|
||
'/xhs/analysis',
|
||
'/xhs/creation',
|
||
'/xhs/publishHistory',
|
||
'/xhs/skills',
|
||
'/xhs/humanSettings',
|
||
'/xhs/agentPair',
|
||
'/track/guide',
|
||
'/track/order',
|
||
'/track/warningOrder',
|
||
'/track/state',
|
||
'/track/flag',
|
||
'/track/content',
|
||
'/track/pull',
|
||
'/track/push',
|
||
'/track/kpiRegion',
|
||
'/track/kpiEarlyWarningConfig',
|
||
'/track/query',
|
||
// Operation 相关前端路由
|
||
'/operation/inboundScan',
|
||
'/operation/outboundScan',
|
||
'/operation/printLabel',
|
||
'/operation/parcelArrival',
|
||
// System 相关前端路由(与 router system-management 子路径一致,避免刷新被 /system 代理到后端 404)
|
||
'/system/users',
|
||
'/system/roles',
|
||
'/system/menus',
|
||
'/system/organizations',
|
||
'/system/batchEmail',
|
||
'/system/xhsAiAssistant',
|
||
'/system/otherConfig',
|
||
'/system/dataPerm',
|
||
// User 相关前端路由
|
||
'/user/recharge',
|
||
// Finance 相关前端路由
|
||
'/finance/rechargeRecord',
|
||
'/finance/accountFlow',
|
||
'/finance/receivablePayable',
|
||
];
|
||
|
||
// 直接在 configureServer 中设置中间件,确保在代理之前执行
|
||
const middleware = (req: any, res: any, next: any) => {
|
||
const url = req.url?.split('?')[0] || '';
|
||
|
||
const matchesSpaExact = frontendRoutesExact.some(
|
||
(route) => url === route || url === `${route}/`,
|
||
);
|
||
|
||
// 检查是否是前端路由(前缀匹配,勿把与 API 同前缀的页面放在此列表,应放入 frontendRoutesExact)
|
||
const isFrontendRoute =
|
||
matchesSpaExact ||
|
||
frontendRoutes.some((route) => url === route || url.startsWith(`${route}/`));
|
||
|
||
if (isFrontendRoute) {
|
||
// 标记请求,让代理中间件知道不要代理
|
||
(req as any).__skipProxy = true;
|
||
(req as any).__isFrontendRoute = true;
|
||
// 关键:将 URL 重写为 /index.html,否则以 /api 开头的路径在 Vite 中不会走 SPA 回退,会直接 404
|
||
// /track/* 等非 /api 路径会正常走 SPA 回退,所以只有 /api/* 前端路由需要显式重写
|
||
req.url = '/index.html';
|
||
next();
|
||
return;
|
||
}
|
||
|
||
// 对于 /api/ 路径,检查是否是驼峰命名(前端路由的特征)
|
||
if (url.startsWith('/api/')) {
|
||
const pathAfterApi = url.substring(5); // 去掉 '/api/'
|
||
// 如果路径包含大写字母(驼峰命名),很可能是前端路由
|
||
if (/[A-Z]/.test(pathAfterApi)) {
|
||
(req as any).__skipProxy = true;
|
||
(req as any).__isFrontendRoute = true;
|
||
next();
|
||
return;
|
||
}
|
||
}
|
||
|
||
next();
|
||
};
|
||
|
||
// 使用 unshift 将中间件插入到最前面,确保优先执行
|
||
const stack = (server.middlewares as any).stack;
|
||
if (Array.isArray(stack)) {
|
||
stack.unshift({ route: '', handle: middleware });
|
||
} else {
|
||
server.middlewares.use(middleware);
|
||
}
|
||
},
|
||
},
|
||
// 生产环境启用压缩
|
||
// 注意:如果服务器不支持压缩,可以禁用此插件
|
||
// 只保留 brotli 压缩(压缩率更高,文件数量减少一半)
|
||
// 如果需要同时支持 gzip 和 brotli,取消下面的注释
|
||
...(process.env.NODE_ENV === 'production'
|
||
? ((): Plugin[] => {
|
||
const p = configCompressPlugin('brotli');
|
||
return Array.isArray(p) ? p : [p];
|
||
})()
|
||
: []),
|
||
...(process.env.REPORT === 'true'
|
||
? [
|
||
visualizer({
|
||
filename: './node_modules/.cache/visualizer/stats.html',
|
||
open: true,
|
||
gzipSize: true,
|
||
brotliSize: true,
|
||
}),
|
||
]
|
||
: []),
|
||
],
|
||
resolve: {
|
||
alias: [
|
||
// 统一走 lodash-es,避免与 lodash CJS 整包重复打入
|
||
{ find: 'lodash', replacement: 'lodash-es' },
|
||
{
|
||
find: '@',
|
||
replacement: resolve(__dirname, './src'),
|
||
},
|
||
{
|
||
find: 'assets',
|
||
replacement: resolve(__dirname, './src/assets'),
|
||
},
|
||
{
|
||
find: 'vue-i18n',
|
||
replacement: 'vue-i18n/dist/vue-i18n.cjs.js', // Resolve the i18n warning issue
|
||
},
|
||
{
|
||
find: 'vue',
|
||
replacement: 'vue/dist/vue.esm-bundler.js', // compile template
|
||
},
|
||
{
|
||
find: 'xe-utils',
|
||
replacement: resolve(
|
||
__dirname,
|
||
'./node_modules/.pnpm/xe-utils@3.7.5/node_modules/xe-utils'
|
||
), // Add xe-utils alias
|
||
},
|
||
],
|
||
extensions: ['.ts', '.js'],
|
||
},
|
||
define: {
|
||
'process.env': {},
|
||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
|
||
__VUE_OPTIONS_API__: 'true',
|
||
__VUE_PROD_DEVTOOLS__: 'false',
|
||
},
|
||
optimizeDeps: {
|
||
// 预构建常用依赖,加速开发体验
|
||
include: [
|
||
'vue',
|
||
'vue-router',
|
||
'pinia',
|
||
'@arco-design/web-vue',
|
||
'axios',
|
||
'dayjs',
|
||
'lodash-es',
|
||
],
|
||
// 确保 Vue 相关依赖的预构建顺序
|
||
esbuildOptions: {
|
||
target: 'es2017',
|
||
// 启用 tree-shaking
|
||
treeShaking: true,
|
||
},
|
||
},
|
||
build: {
|
||
// 启用压缩(使用esbuild,Vite默认,性能更好)
|
||
minify: 'esbuild',
|
||
// 生产环境移除 console 和 debugger
|
||
esbuild: {
|
||
drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
|
||
},
|
||
// 启用CSS代码分割
|
||
cssCodeSplit: true,
|
||
// 生成 source map(默认关闭,避免线上包体积变大;需要排查问题时可临时打开)
|
||
sourcemap: false,
|
||
// 构建输出配置:使用 Vite 默认的代码分割策略,避免过度自定义导致库初始化顺序问题
|
||
rollupOptions: {
|
||
onwarn(warning, warn) {
|
||
const message = warning.message || '';
|
||
if (
|
||
message.includes('~@/assets/logo.png referenced in ~@/assets/logo.png') ||
|
||
message.includes('new URL("../assets/hi.png", import.meta.url) doesn\'t exist at build time') ||
|
||
message.includes('Use of eval in "node_modules/vg-print/dist/vg-print.es.js"') ||
|
||
message.includes('dynamically imported by') && message.includes('but also statically imported by')
|
||
) {
|
||
return;
|
||
}
|
||
warn(warning);
|
||
},
|
||
output: {
|
||
manualChunks(id) {
|
||
if (!id.includes('node_modules')) return;
|
||
if (id.includes('handsontable') || id.includes('@handsontable')) {
|
||
return 'vendor-handsontable';
|
||
}
|
||
if (id.includes('codemirror') || id.includes('@codemirror')) {
|
||
return 'vendor-codemirror';
|
||
}
|
||
if (id.includes('element-plus')) {
|
||
return 'vendor-element-plus';
|
||
}
|
||
if (id.includes('@arco-design')) {
|
||
return 'vendor-arco';
|
||
}
|
||
if (id.includes('vxe-table') || id.includes('xe-utils')) {
|
||
return 'vendor-vxe';
|
||
}
|
||
if (id.includes('echarts')) {
|
||
return 'vendor-echarts';
|
||
}
|
||
if (id.includes('lodash')) {
|
||
return 'vendor-lodash';
|
||
}
|
||
},
|
||
// 确保文件名包含 hash,便于浏览器缓存
|
||
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||
entryFileNames: 'assets/js/[name]-[hash].js',
|
||
assetFileNames: (assetInfo) => {
|
||
const name = assetInfo.name ?? 'asset';
|
||
const info = name.split('.');
|
||
const ext = info[info.length - 1] ?? 'bin';
|
||
if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(name)) {
|
||
return `assets/media/[name]-[hash].${ext}`;
|
||
}
|
||
if (/\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/.test(name)) {
|
||
return `assets/img/[name]-[hash].${ext}`;
|
||
}
|
||
if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(name)) {
|
||
return `assets/fonts/[name]-[hash].${ext}`;
|
||
}
|
||
return `assets/[name]-[hash].${ext}`;
|
||
},
|
||
},
|
||
},
|
||
// 设置chunk大小警告限制(提高警告阈值,因为我们已经做了代码分割)
|
||
chunkSizeWarningLimit: 2000,
|
||
// 启用 terser 压缩以获得更好的压缩率(可选,esbuild更快但压缩率稍低)
|
||
// minify: 'terser', // 如果需要更好的压缩率,可以启用,但构建会慢一些
|
||
},
|
||
css: {
|
||
preprocessorOptions: {
|
||
less: {
|
||
modifyVars: {
|
||
hack: `true; @import (reference) "${resolve(
|
||
'./src/assets/style/breakpoint.less'
|
||
)}";`,
|
||
},
|
||
javascriptEnabled: true,
|
||
},
|
||
},
|
||
},
|
||
} as UserConfig;
|
||
});
|