import vue from '@vitejs/plugin-vue' import Components from 'unplugin-vue-components/vite' import { defineConfig } from 'vite' import { fileURLToPath, URL } from 'node:url' import { VantResolver } from 'unplugin-vue-components/resolvers' const buildTimestamp = Date.now() const PRODUCTION_LIKE_MODES = new Set(['production', 'production-atu']) const STRIPPABLE_CONSOLE_METHODS = ['log', 'debug', 'info'] function isEscaped(code, index) { let backslashCount = 0 let cursor = index - 1 while (cursor >= 0 && code[cursor] === '\\') { backslashCount += 1 cursor -= 1 } return backslashCount % 2 === 1 } function findCallEnd(code, openParenIndex) { let depth = 0 let quote = null let templateExpressionDepth = 0 let inLineComment = false let inBlockComment = false for (let index = openParenIndex; index < code.length; index += 1) { const current = code[index] const next = code[index + 1] if (inLineComment) { if (current === '\n') { inLineComment = false } continue } if (inBlockComment) { if (current === '*' && next === '/') { inBlockComment = false index += 1 } continue } if (quote) { if (quote === '`') { if (current === '$' && next === '{' && !isEscaped(code, index)) { templateExpressionDepth += 1 index += 1 continue } if (current === '}' && templateExpressionDepth > 0 && !isEscaped(code, index)) { templateExpressionDepth -= 1 continue } if (current === '`' && templateExpressionDepth === 0 && !isEscaped(code, index)) { quote = null } continue } if (current === quote && !isEscaped(code, index)) { quote = null } continue } if (current === '/' && next === '/') { inLineComment = true index += 1 continue } if (current === '/' && next === '*') { inBlockComment = true index += 1 continue } if (current === '"' || current === "'" || current === '`') { quote = current continue } if (current === '(') { depth += 1 continue } if (current === ')') { depth -= 1 if (depth === 0) { let endIndex = index + 1 while (endIndex < code.length && /\s/.test(code[endIndex])) { endIndex += 1 } if (code[endIndex] === ';') { endIndex += 1 } return endIndex } } } return -1 } function findTokenOutsideLiterals(code, token, fromIndex = 0) { let quote = null let templateExpressionDepth = 0 let inLineComment = false let inBlockComment = false for (let index = fromIndex; index < code.length; index += 1) { const current = code[index] const next = code[index + 1] if (inLineComment) { if (current === '\n') { inLineComment = false } continue } if (inBlockComment) { if (current === '*' && next === '/') { inBlockComment = false index += 1 } continue } if (quote) { if (quote === '`') { if (current === '$' && next === '{' && !isEscaped(code, index)) { templateExpressionDepth += 1 index += 1 continue } if (current === '}' && templateExpressionDepth > 0 && !isEscaped(code, index)) { templateExpressionDepth -= 1 continue } if (current === '`' && templateExpressionDepth === 0 && !isEscaped(code, index)) { quote = null } continue } if (current === quote && !isEscaped(code, index)) { quote = null } continue } if (current === '/' && next === '/') { inLineComment = true index += 1 continue } if (current === '/' && next === '*') { inBlockComment = true index += 1 continue } if (current === '"' || current === "'" || current === '`') { quote = current continue } if (code.startsWith(token, index)) { return index } } return -1 } function stripSelectedConsoleCalls(code) { let output = code STRIPPABLE_CONSOLE_METHODS.forEach((method) => { let searchIndex = 0 const token = `console.${method}` while (searchIndex < output.length) { const startIndex = findTokenOutsideLiterals(output, token, searchIndex) if (startIndex === -1) { break } let cursor = startIndex + token.length while (cursor < output.length && /\s/.test(output[cursor])) { cursor += 1 } if (output[cursor] !== '(') { searchIndex = startIndex + token.length continue } const endIndex = findCallEnd(output, cursor) if (endIndex === -1) { searchIndex = cursor + 1 continue } output = `${output.slice(0, startIndex)}void 0;${output.slice(endIndex)}` searchIndex = startIndex + 'void 0;'.length } }) return output } function createBuildLogControlPlugin(stripDebugLogs) { return { name: 'likei-build-log-control', apply: 'build', enforce: 'post', transform(code, id) { if (!stripDebugLogs || id.includes('node_modules')) { return null } if (!/\.(vue|js|ts|jsx|tsx|mjs|cjs)(\?|$)/.test(id)) { return null } const transformedCode = stripSelectedConsoleCalls(code) if (transformedCode === code) { return null } return { code: transformedCode, map: null, } }, } } export default defineConfig(({ mode }) => { const keepBuildLogs = process.env.LIKEI_KEEP_BUILD_LOGS === 'true' const stripDebugLogs = PRODUCTION_LIKE_MODES.has(mode) && !keepBuildLogs return { plugins: [ vue(), Components({ resolvers: [VantResolver()], }), createBuildLogControlPlugin(stripDebugLogs), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, build: { minify: 'esbuild', sourcemap: false, cssMinify: 'esbuild', reportCompressedSize: false, esbuild: { drop: stripDebugLogs ? ['debugger'] : [], charset: 'ascii', legalComments: 'none', minifyIdentifiers: true, minifySyntax: true, minifyWhitespace: true, treeShaking: true, }, rollupOptions: { output: { entryFileNames: `js/[hash]-${buildTimestamp}.js`, chunkFileNames: `js/[hash]-${buildTimestamp}.js`, assetFileNames: (assetInfo) => { const info = assetInfo.name.split('.') const ext = info[info.length - 1] if (/\.(css)$/.test(assetInfo.name)) { return `css/[hash]-${buildTimestamp}.[ext]` } return `assets/[hash]-${buildTimestamp}.[ext]` }, }, }, }, server: { host: '0.0.0.0', port: 5173, strictPort: true, open: false, cors: true, proxy: { '/oss': { target: 'https://cdn.azizichat.com', changeOrigin: true, secure: true, rewrite: (path) => path.replace(/^\/oss/, ''), configure: (proxy) => { proxy.on('proxyRes', (proxyRes) => { delete proxyRes.headers.etag delete proxyRes.headers['last-modified'] delete proxyRes.headers['cache-control'] delete proxyRes.headers.expires }) }, }, }, }, preview: { host: '0.0.0.0', port: 4173, strictPort: true, open: false, }, } })