1.0.1 • Published 8 months ago

@94ai/vue2-runtime-helpers v1.0.1

Weekly downloads
-
License
MIT
Repository
-
Last release
8 months ago

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)许可证。

1.0.1

8 months ago

1.0.0

8 months ago