import path from 'node:path'
import type { UserConfig } from 'vite'
import { build } from 'vite'
import commonjs from '@rollup/plugin-commonjs'
import babel from '@rollup/plugin-babel'
import { logGreen } from '../../shared/utils'
import type { BuildUiOption, BaseConfig } from './build-ui'
import { pathFromPackages, getBaseConfig, requireModules } from './build-ui'
import { createProcessor } from 'tailwindcss/src/cli/build/plugin'
import { visualizer } from 'rollup-plugin-visualizer'
async function batchBuildAll({ vueVersion, tasks, message, emptyOutDir, npmScope, min, isVisualizer }) {
const rootDir = pathFromPackages('')
const runtimeDir = `vue-runtime/dist${vueVersion}`
const outDir = path.resolve(rootDir, runtimeDir)
await batchBuild({
vueVersion,
tasks,
message,
emptyOutDir,
npmScope,
min,
isVisualizer
})
function toEntry(libs) {
return libs.reduce((result, { libPath, path: file }) => {
result[libPath] = pathFromPackages(file)
return result
}, {})
}
function getExternal() {
return {
vue: 'Vue',
'@vue/composition-api': 'VueCompositionAPI',
'@opentiny/vue-locale': 'TinyVueLocale',
'@opentiny/vue-common': 'TinyVueCommon',
'@opentiny/vue-icon': 'TinyVueIcon',
'@opentiny/vue-icon-multicolor': 'TinyVueIconMulticolor',
'echarts': 'Echarts'
}
}
async function batchBuild({ vueVersion, tasks, message, emptyOutDir, npmScope, min, isVisualizer }) {
if (tasks.length === 0) return
logGreen(`====== 开始构建 ${message} ======`)
const { mode, libPath } = tasks[0]
const modeList = ['pc', 'mobile-first']
const entry = toEntry(tasks)
const baseConfig = getBaseConfig({
vueVersion,
dtsInclude: [] as string[],
dts: false,
npmScope,
isRuntime: true,
design: libPath === 'tiny-vue-saas-common' ? 'saas' : null
} as BaseConfig) as UserConfig
baseConfig.define = Object.assign(baseConfig.define || {}, {
'process.env.BUILD_TARGET': JSON.stringify(vueVersion !== '3' ? 'runtime' : 'component'),
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env.COMPONENT_VERSION': JSON.stringify(requireModules('packages/vue/package.json').version)
})
baseConfig.plugins?.push(
...([
commonjs({
include: /node_modules/,
requireReturnsDefault: true,
defaultIsModuleExports: true,
exclude: ['node_modules/echarts/**', 'node_modules/echarts']
}),
babel({
extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx'],
presets: ['@babel/preset-env']
}),
isVisualizer
? visualizer({
filename: `${tasks[0].libPath}.html`,
open: true
})
: null,
{
name: 'vite-plugin-transfer-mode',
enforce: 'pre',
transform(code, id) {
if (mode && id.includes('src/index.ts') && code.includes(`${mode}.vue`)) {
let newCode = code
modeList
.filter((value) => value !== mode)
.forEach((item) => {
newCode = newCode.replace(`./${item}.vue`, `./${mode}.vue`)
})
return {
code: newCode,
map: null
}
}
}
}
] as any[])
)
await build({
configFile: false,
...baseConfig,
build: {
emptyOutDir,
minify: true,
sourcemap: min,
rollupOptions: {
external: (source, importer, isResolved) => {
if (isResolved || !importer) return false
if (libPath === 'tiny-vue-saas-common') {
return ['@vue/composition-api', 'vue'].includes(source)
}
return Object.keys(getExternal()).includes(source)
},
output: {
strict: false,
globals: {
...getExternal()
},
assetFileNames: ({ name }) => {
if (/\.(gif|jpe?g|png|svg)$/.test(name ?? '')) {
return 'style/images/[name]-[hash][extname]'
}
if (/\.css$/.test(name ?? '')) {
return `style${min ? '.min' : ''}[extname]`
}
return 'style/[name]-[hash][extname]'
}
}
},
lib: {
entry,
formats: ['es'],
fileName: (format, entryName) => `${entryName}${min ? '.min' : ''}.${format === 'es' ? 'm' : ''}js`,
name: 'Tiny'
},
outDir
}
})
}
}
function getEntryTasks() {
const entry = [
{
path: 'vue-locale/src/index.ts',
libPath: 'tiny-vue-locale'
},
{
path: 'vue-common/src/index.ts',
libPath: 'tiny-vue-common'
},
{
path: 'vue-saas-common/src/index.ts',
libPath: 'tiny-vue-saas-common'
},
{
path: 'vue-runtime/all.ts',
libPath: 'tiny-vue-all'
},
{
path: 'vue-runtime/simple.ts',
libPath: 'tiny-vue-simple',
mode: 'pc'
},
{
path: 'vue-runtime/pc.ts',
libPath: 'tiny-vue-pc',
mode: 'pc'
},
{
path: 'vue-runtime/mobile-first.ts',
libPath: 'tiny-vue-mobile-first',
mode: 'mobile-first'
},
{
path: 'vue-icon-saas/index.ts',
libPath: 'tiny-vue-icon-saas'
},
{
path: 'vue-icon/index.ts',
libPath: 'tiny-vue-icon'
},
{
path: 'vue-icon-multicolor/index.ts',
libPath: 'tiny-vue-icon-multicolor'
},
{
path: 'vue-directive/index.ts',
libPath: 'tiny-vue-directive'
},
{
path: 'vue/src/huicharts/index.ts',
libPath: 'tiny-vue-huicharts'
}
]
return entry
}
export async function buildRuntime({
vueVersions = ['2', '3'],
clean = false,
scope = 'opentiny',
min = false,
isVisualizer = false
}: BuildUiOption) {
let emptyOutDir = clean
let tasks = [...getEntryTasks()]
for (const vueVersion of vueVersions) {
const message = `TINY for vue${vueVersion}: runtime`
for (let i = 0; i < tasks.length; i++) {
await batchBuildAll({ vueVersion, tasks: [tasks[i]], message, emptyOutDir, npmScope: scope, min, isVisualizer })
}
const rootDir = pathFromPackages('')
const runtimeDir = `vue-runtime/dist${vueVersion}`
const outDir = path.resolve(rootDir, runtimeDir)
const processor = await createProcessor(
{
'--output': path.join(outDir, 'tailwind.css'),
'--content': path.join(outDir, 'tiny-vue-all.mjs')
},
path.resolve(rootDir, 'theme-saas/tailwind.config.js')
)
await processor.build()
emptyOutDir = false
}
}