## main - vite.config.js
```javascript
import path from 'path'
import vue from '@vitejs/plugin-vue'
import vueI18n from '@intlify/unplugin-vue-i18n/vite'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from 'vite-plugin-top-level-await'
import { visualizer } from 'rollup-plugin-visualizer'
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
const isProduction = mode === 'production'
return {
plugins: [
wasm(),
topLevelAwait(),
vue(),
vueI18n({
include: [path.resolve(__dirname, './src/locales/**')]
}),
visualizer({
filename: 'stats.html',
open: false,
gzipSize: true,
brotliSize: true
})
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@aa': path.resolve(__dirname, 'src/app/annotator/annotators/audio-annotator'),
'@app': path.resolve(__dirname, 'src/app'),
'@assets': path.resolve(__dirname, 'src/assets'),
'@ia': path.resolve(__dirname, 'src/app/annotator/annotators/image-annotator'),
'@pa': path.resolve(__dirname, 'src/app/annotator/annotators/pcd-annotator'),
'@pta': path.resolve(__dirname, 'src/app/annotator/annotators/prompt-annotator'),
'@services': path.resolve(__dirname, 'src/app/annotator/services'),
'@shared': path.resolve(__dirname, 'src/app/shared'),
'@ta': path.resolve(__dirname, 'src/app/annotator/annotators/text-annotator'),
'@va': path.resolve(__dirname, 'src/app/annotator/annotators/video-annotator')
},
extensions: ['.ts', '.js', '.vue', '.json'],
dedupe: ['vue']
},
define: {
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false
},
server: {
host: true,
port: 8080,
strictPort: false,
cors: true,
hmr: { overlay: true }
},
preview: { port: 4173 },
build: {
target: 'es2020',
outDir: 'dist',
assetsDir: 'assets',
sourcemap: isProduction ? 'hidden' : true,
minify: isProduction ? 'terser' : false,
chunkSizeWarningLimit: 1500,
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash][extname]',
manualChunks(id) {
if (id.includes('node_modules/three') || id.includes('three-mesh-bvh'))
return 'vendor-three'
if (
id.includes('node_modules/video.js') ||
id.includes('node_modules/@videojs') ||
id.includes('node_modules/videojs-')
)
return 'vendor-videojs'
if (id.includes('node_modules/jsoneditor') || id.includes('node_modules/ajv'))
return 'vendor-jsoneditor'
if (id.includes('@mathjax') || id.includes('mathjax')) return 'vendor-mathjax'
if (
id.includes('node_modules/vue/') ||
id.includes('node_modules/vuex/') ||
id.includes('node_modules/vue-router/') ||
id.includes('node_modules/vue-i18n/') ||
id.includes('node_modules/vue-draggable-plus') ||
id.includes('node_modules/sortablejs')
)
return 'vendor-vue'
if (id.includes('node_modules/lodash')) return 'vendor-lodash'
if (id.includes('node_modules/fabric')) return 'vendor-fabric'
if (id.includes('node_modules/@fancyapps') || id.includes('fancybox'))
return 'vendor-fancybox'
}
}
},
terserOptions: isProduction
? { compress: { drop_console: true, drop_debugger: true } }
: undefined
},
css: {
devSourcemap: true,
preprocessorOptions: {
scss: { quietDeps: true, silenceDeprecations: ['import', 'legacy-js-api'] }
}
},
optimizeDeps: {
include: [
'vue',
'vuex',
'vue-router',
'vue-i18n',
'axios',
'lodash',
'dayjs',
'chroma-js',
'nanoid'
],
exclude: ['annotator-util', 'image-annotator-util', 'wasm-utils']
},
worker: { format: 'es' },
esbuild: { supported: { 'top-level-await': true } }
}
})
```
## staging - webpack.config.cjs
```javascript
const path = require('path')
const webpack = require('webpack')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
entry: './src/main.js',
mode: 'development',
devServer: {
historyApiFallback: true,
hot: true,
liveReload: true,
allowedHosts: 'all'
},
experiments: {
syncWebAssembly: true,
topLevelAwait: true
},
module: {
rules: [
// ts-loader (Vue 파일 포함)
// vue-loader (v15 - Vue 2용)
// babel-loader
// css/sass/postcss loaders
// worker-loader
// asset/resource
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[fullhash].bundle.js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@app': path.resolve(__dirname, 'src/app'),
'@assets': path.resolve(__dirname, 'src/assets'),
'@ia': path.resolve(__dirname, 'src/app/annotator/annotators/image-annotator'),
'@pa': path.resolve(__dirname, 'src/app/annotator/annotators/pcd-annotator'),
'@pta': path.resolve(__dirname, 'src/app/annotator/annotators/prompt-annotator'),
'@ta': path.resolve(__dirname, 'src/app/annotator/annotators/text-annotator'),
'@va': path.resolve(__dirname, 'src/app/annotator/annotators/video-annotator')
},
extensions: ['.ts', '.js', '.vue', '.json']
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({ template: 'public/index.html' }),
new CopyWebpackPlugin({ patterns: [{ from: 'public', to: '.' }] }),
new webpack.DefinePlugin({
/* process.env 주입 */
})
],
cache: { type: 'filesystem' }
}
```