Introduction
High level utilities for compiling Vue2 single file components, runtime helpers for @94ai/vite-plugin-vue2 .
This package contains high level utilities that you can also use if you are writing a plugin / transform for a bundler or module system that compiles Vue single file components into JavaScript.
Usage
// build.ts
import path from 'path'
import fs from 'fs'
import { fileURLToPath } from 'url'
import fsExtra from 'fs-extra'
import type { LibraryFormats } from 'vite'
import { build } from 'vite'
import type { ModuleFormat } from 'rollup'
import babel from '@rollup/plugin-babel'
import vue from '@94ai/vite-plugin-vue2'
import vueJsx from '@vitejs/plugin-vue2-jsx'
import dts from 'vite-plugin-dts'
import scriptSetup from 'unplugin-vue2-script-setup/vite'
import Components from 'unplugin-vue-components/vite'
import requireTransform from 'vite-plugin-require-transform'
import defineOptions from '@94ai/unplugin-vue-define-options/vite'
import { NfCommonUIResolver } from '@94ai/common-ui-resolver'
// import { assemble, createDefaultCompiler } from '@vue/component-compiler'
// @ts-ignore
import { output, src } from './config.ts'
// @ts-ignore
import { copyFile, getComponentDts } from './utils/index.ts'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const entryDir = path.resolve(__dirname, src)
const outputDir = path.resolve(__dirname, output)
const components: string[] = []
const themes: string[] = []
fs.readdirSync(entryDir).forEach((name) => {
const componentDir = path.resolve(entryDir, name)
const isDir = fs.lstatSync(componentDir).isDirectory()
if (isDir && fs.readdirSync(componentDir).includes('package.json')) {
if (name.includes('nf-theme-')) {
themes.push(name)
} else {
components.push(name)
}
}
})
const getAutoImport = (codepenUmd: boolean) => {
if (codepenUmd) {
return Components({
include: [
/\.vue$/,
/\.vue\?vue/,
/\.md$/,
/\.md\?vue/,
/\.jsx$/,
/\.jsx\?vue/,
/\.tsx$/,
/\.tsx\?vue/,
],
extensions: ['vue', 'md', 'jsx', 'tsx', 'mjs', 'mts'],
resolvers: [
NfCommonUIResolver({
mode: 'packages',
theme: [],
}),
],
})
}
return null
}
// @ts-ignore
// eslint-disable-next-line no-unused-vars
const getExternal = (codepenUmd: boolean) => {
const external = [
'vue',
'vue-router',
'element-ui',
'vue-demi',
'@94ai/vue2-runtime-helpers', // 👈 配置构建工具external
]
// if (!codepenUmd) {
// return [
// ...external,
// ...components.concat(themes).map((_) => `${libPrefix}/${_}`),
// ]
// }
return external
}
// @ts-ignore
// eslint-disable-next-line no-unused-vars
const getGlobals = (codepenUmd: boolean) => {
const globals = {
vue: 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
'vue-demi': 'VueDemi',
}
// if (codepenUmd) {
// return {
// ...globals,
// ...components.reduce((component, key) => {
// component[`${libPrefix}/${key}`] = key
// return component
// }, {}),
// }
// }
return globals
}
const getFormats = (codepenUmd: boolean): LibraryFormats[] => {
if (codepenUmd) {
return ['umd', 'es']
} else {
return ['es', 'cjs']
}
}
const handlePackBundle = async (name, codepenUmd = false) => {
const entryRoot = path.resolve(__dirname, `${src}/${name}`)
await build({
optimizeDeps: {
exclude: ['vue-demi'],
},
configFile: false,
publicDir: false,
resolve: {
alias: [
{
find: /^packages\//,
replacement: path.resolve('packages') + '/',
},
{
find: /^lib\//,
replacement: path.resolve('lib') + '/',
},
// {
// find: /^vue$/,
// replacement: 'vue-demi',
// },
],
},
// esbuild: {
// tsconfigRaw: {
// compilerOptions: {
// jsxFactory: 'h',
// jsxFragmentFactory: 'Fragment',
// },
// },
// jsxInject: "import { h } from 'vue-demi';",
// },
plugins: [
codepenUmd
? undefined
: dts({
entryRoot,
include: [
`${entryRoot}/*.ts`,
`${entryRoot}/*.tsx`,
`${entryRoot}/*.d.ts`,
`${entryRoot}/*.vue`,
`${entryRoot}/**/*.ts`,
`${entryRoot}/**/*.tsx`,
`${entryRoot}/**/*.d.ts`,
`${entryRoot}/**/*.vue`,
],
}),
vue({
normalizerCode: codepenUmd // 👈 指定cjs和esm构建把helpers插入的函数转成import
? ''
: `
import { normalizeComponent } from '@94ai/vue2-runtime-helpers'
export default normalizeComponent`,
include: [/\.vue$/, /\.md$/, /\.jsx$/, /\.tsx$/],
}),
defineOptions(),
scriptSetup({
importHelpersFrom: 'vue-demi',
include: [/\.vue$/, /\.md$/, /\.jsx$/, /\.tsx$/],
}),
vueJsx({
compositionAPI: 'vue-demi',
include: [/\.jsx$/, /\.tsx$/],
}),
getAutoImport(codepenUmd),
],
build: {
sourcemap: false,
minify: !!codepenUmd,
rollupOptions: {
external: getExternal(codepenUmd),
output: {
exports: 'named',
assetFileNames: ({ name: assetsName }): string => {
if (/\.(gif|jpe?g|png|svg)$/.test(assetsName ?? '')) {
return 'lib/[name]-[hash][extname]'
}
if (/\.css$/.test(assetsName ?? '')) {
return `lib/${name}[extname]`
}
return assetsName as string
},
globals: getGlobals(codepenUmd),
},
plugins: [
requireTransform({
fileRegex: /.js$|.vue$|.jsx$|.tsx$.ts$/,
}),
// {
// name: 'customize-assemble',
// async transform(code, id) {
// // 使用assemble处理SFC文件,并应用优化
// if (id.endsWith('.vue')) {
// const compiler = createDefaultCompiler()
// const filename = resolve(id)
// const descriptor = compiler.compileToDescriptor(filename, code)
// const result = await assemble(compiler, filename, descriptor, {
// normalizer: '~@94ai/vue2-runtime-helpers',
// })
// return {
// code: result.code,
// map: result.map,
// }
// }
// },
// },
codepenUmd
? babel({
babelHelpers: 'runtime',
exclude: 'node_modules/**',
extensions: [
'.ts',
'.js',
'.jsx',
'.es6',
'.es',
'.mjs',
'.tsx',
'.mtx',
'.vue',
],
})
: null,
],
},
lib: {
entry: path.resolve(entryDir, `${name}/lib/index.ts`),
name,
fileName: (format: ModuleFormat, entryName: string) => {
if (entryName.indexOf('style') > -1) {
return ''
}
if (codepenUmd) {
return `${name}.${format}.browser.js`
}
return `lib/${name}.${format === 'es' ? 'esm-bundler' : format}.js`
},
formats: getFormats(codepenUmd),
},
outDir: path.resolve(
outputDir,
codepenUmd ? `codepen/lib/${name}` : name
),
},
})
}
const handleConfigFiles = async (name) => {
const destination = outputDir + `/${name}`
const resource = entryDir + `/${name}`
const handleEslint = () => {
const eslintConfig = eval(
'(' +
fs
.readFileSync(path.join(__dirname, `.eslintrc.cjs`), {
encoding: 'utf8',
})
.replace('module.exports = ', '') +
')'
)
eslintConfig.extends.length = 1
const fileStr = JSON.stringify(eslintConfig, null, 2)
fsExtra.outputFileSync(
path.join(destination, '.eslintrc.cjs'),
'module.exports = ' + fileStr,
'utf-8'
)
}
const handlePkg = () => {
const srcPkg = JSON.parse(
fs.readFileSync(path.join(resource, `package.json`), {
encoding: 'utf8',
})
)
const libPkg: Record<string, any> = {}
;[
'name',
'version',
'description',
'keywords',
'author',
'homepage',
'license',
'publishConfig',
'repository',
].forEach((key) => {
libPkg[key] = srcPkg[key]
})
if (name !== 'codepen') {
;['devDependencies', 'dependencies', 'peerDependenciesMeta'].forEach(
(key) => {
libPkg[key] = srcPkg[key]
}
)
libPkg['peerDependencies'] = {
...(srcPkg['peerDependencies'] || {}),
'element-ui': '>=2.13.2',
}
}
libPkg.types = `lib/index.d.ts`
libPkg.main = `lib/${name}.cjs.js`
libPkg.module = `lib/${name}.esm-bundler.js`
const fileStr = JSON.stringify(libPkg, null, 2)
fsExtra.outputFileSync(
path.join(destination, `package.json`),
fileStr,
'utf-8'
)
}
const handlePostcss = () => {
fs.copyFileSync(
path.join(__dirname, 'postcss.config.mjs'),
path.join(destination, 'postcss.config.js'),
fs.constants.COPYFILE_FICLONE
)
}
const handleReadMe = () => {
fs.copyFileSync(
path.join(resource, `README.md`),
path.join(destination, 'README.md'),
fs.constants.COPYFILE_FICLONE
)
}
const handleTheme = (name) => {
if (name !== 'codepen') {
const dir = path.resolve(__dirname, output, name, 'lib', 'style')
if (!fsExtra.existsSync(dir)) {
fsExtra.mkdirsSync(dir)
}
fs.copyFileSync(
path.resolve(__dirname, src, name, output, 'style/css.ts'),
path.resolve(__dirname, output, name, output, 'style/css.js'),
fs.constants.COPYFILE_FICLONE
)
fs.copyFileSync(
path.resolve(__dirname, src, name, output, 'style/index.ts'),
path.resolve(__dirname, output, name, output, 'style/index.js'),
fs.constants.COPYFILE_FICLONE
)
}
}
const handleGlobalDts = (name) => {
if (name !== 'codepen') {
const prefix = `export {}
declare module 'vue' {
export interface GlobalComponents {
`
const after = `
}
}`
let content = ``
if (name === 'common-ui') {
for (const name of components) {
if (name !== 'common-ui' && name !== 'codepen') {
content += getComponentDts(name)
}
}
} else {
content = getComponentDts(name)
}
fsExtra.outputFileSync(
path.join(destination, `components.d.ts`),
prefix + content + after,
'utf-8'
)
}
}
handlePkg()
handleEslint()
handlePostcss()
handleReadMe()
handleTheme(name)
handleGlobalDts(name)
}
const handleThemePack = (name?: string) => {
fsExtra.ensureDirSync(path.resolve(__dirname, 'lib'))
if (name) {
copyFile(
path.resolve(__dirname, src, name),
path.resolve(__dirname, output, name)
)
handleSyncVersion(name)
} else {
themes.forEach((sigle) => {
copyFile(
path.resolve(__dirname, src, sigle),
path.resolve(__dirname, output, sigle)
)
handleSyncVersion(sigle)
})
}
console.log(`${name ?? themes.join(',')}主题构建完成`)
}
const handleSyncVersion = async (name) => {
const targetPackagePath = path.resolve(
__dirname,
output,
name,
`package.json`
)
let version
if (name !== 'common-ui') {
const mainPackagePath = path.resolve(
__dirname,
output,
`common-ui/package.json`
)
if (fs.existsSync(mainPackagePath)) {
const commonLibPkg = JSON.parse(
fs.readFileSync(mainPackagePath, {
encoding: 'utf8',
})
)
version = commonLibPkg.dependencies[`@94ai/${name}`]
}
if (!version || version?.indexOf('workspace:^') > -1) {
version = JSON.parse(
fs.readFileSync(path.resolve(__dirname, 'lerna.json'), {
encoding: 'utf8',
})
).version
}
} else {
version = JSON.parse(
fs.readFileSync(path.resolve(__dirname, 'lerna.json'), {
encoding: 'utf8',
})
).version
}
const targetLibPkg = JSON.parse(
fs.readFileSync(targetPackagePath, {
encoding: 'utf8',
})
)
targetLibPkg.version = version
.replace('^', '')
.replace('~', '')
.replace('@', '')
fsExtra.outputFileSync(
targetPackagePath,
JSON.stringify(targetLibPkg, null, 2),
'utf-8'
)
}
const sourcePackageHandler = (name) => {
const tagrgetPath = path.resolve(__dirname, output, name, 'package')
fsExtra.ensureDirSync(tagrgetPath)
copyFile(path.resolve(__dirname, src, name, 'lib'), tagrgetPath)
}
const buildLib = async () => {
const singles = process.argv.slice(2)
if (singles?.length) {
for (const single of singles) {
if (themes.includes(single)) {
handleThemePack(single)
continue
}
await handlePackBundle(single)
await handleConfigFiles(single)
if (single !== 'codepen') {
await handlePackBundle(single, true)
}
await handleSyncVersion(single)
sourcePackageHandler(single)
}
} else {
handleThemePack()
for (const name of components) {
await handlePackBundle(name)
await handleConfigFiles(name)
await handleSyncVersion(name)
sourcePackageHandler(name)
}
for (const name of components) {
if (name !== 'codepen') {
await handlePackBundle(name, true)
}
}
}
}
buildLib()
License
This content is released under the MIT License.
此内容在[MIT]下发布(http://opensource.org/licenses/MIT)许可证。