From 6483e1d1ea4724cae1966a601b021035813803c7 Mon Sep 17 00:00:00 2001 From: keuby Date: Tue, 19 Dec 2023 13:18:26 +0800 Subject: [PATCH] feat: supports node native import (#5149) --- package.json | 10 +- scripts/post-build/complete-path.js | 180 ++++++++++++++++++++++++++++ scripts/post-build/index.js | 4 + 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 scripts/post-build/complete-path.js diff --git a/package.json b/package.json index 44181a83d..b33a28c2f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "2.36.0", "description": "A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast", "main": "lib/index.js", - "module": "es/index.js", + "module": "es/index.mjs", "types": "es/index.d.ts", "unpkg": "dist/index.js", "jsdelivr": "dist/index.js", @@ -46,6 +46,14 @@ "web-types.json", "README.md" ], + "exports": { + ".": { + "import": "./es/index.mjs", + "require": "./lib/index.js", + "types": "./es/index.d.ts" + }, + "./*": "./*" + }, "web-types": "./web-types.json", "lint-staged": { "*.js": [ diff --git a/scripts/post-build/complete-path.js b/scripts/post-build/complete-path.js new file mode 100644 index 000000000..4c787987a --- /dev/null +++ b/scripts/post-build/complete-path.js @@ -0,0 +1,180 @@ +const fs = require('fs-extra') +const path = require('path') +const glob = require('fast-glob') +const babel = require('@babel/core') + +/** + * @param {('es' | 'lib')[]} formats + */ +module.exports.completePath = async (formats) => { + await Promise.all( + formats.map(async (format) => { + const config = formatConfigs[format] + const files = await glob('**/*.js', { + cwd: config.root, + absolute: true, + onlyFiles: true + }) + await Promise.all( + files.map(async (filePath) => { + const code = await fs.readFile(filePath, 'utf-8') + await config.parse(code, filePath, path.dirname(filePath)) + }) + ) + }) + ) +} + +const formatConfigs = { + es: { + root: path.join(__dirname, '../../es'), + async parse (code, filePath, currentDir) { + const suffix = '.mjs' + const result = await babel.transformAsync(code, { + root: this.root, + babelrc: false, + filename: filePath, + sourceType: 'module', + plugins: [ + { + visitor: { + ImportDeclaration: ({ node }) => { + const source = node.source.value + const parsedSource = parseSource(source, currentDir, suffix) + if (parsedSource) { + node.source.value = parsedSource + } + }, + ExportNamedDeclaration: ({ node }) => { + if (node.source) { + const source = node.source.value + const parsedSource = parseSource(source, currentDir, suffix) + if (parsedSource) { + node.source.value = parsedSource + } + } + }, + ExportAllDeclaration: ({ node }) => { + const source = node.source.value + const parsedSource = parseSource(source, currentDir, suffix) + if (parsedSource) { + node.source.value = parsedSource + } + } + } + } + ] + }) + const newFilePath = replaceExtname(filePath, suffix) + await fs.writeFile(newFilePath, result.code || code) + await fs.unlink(filePath) + } + }, + lib: { + root: path.join(__dirname, '../../lib'), + async parse (code, filePath, currentDir) { + const suffix = '.js' + const result = await babel.transformAsync(code, { + root: this.root, + babelrc: false, + filename: filePath, + plugins: [ + { + visitor: { + CallExpression: ({ node }) => { + if ( + node.callee.type === 'Identifier' && + node.callee.name === 'require' + ) { + const firstArg = node.arguments[0] + if (firstArg.type === 'StringLiteral') { + const source = firstArg.value + const parsedSource = parseSource(source, currentDir, suffix) + if (parsedSource) { + firstArg.value = parsedSource + } + } + } + } + } + } + ] + }) + await fs.writeFile(filePath, result.code || code) + } + } +} + +/** + * @param {string} source + * @param {string} currentDir + * @param {string} suffix + * @returns {string | null} + */ +const parseSource = (source, currentDir, suffix) => { + if (source.startsWith('.')) { + const fullPath = joinPath(currentDir, source) + return fs.existsSync(fullPath) + ? path.extname(fullPath) + ? source + : joinPath(source, 'index' + suffix) + : source + suffix + } else { + const [pkgName, subpath] = splitSource(source) || [] + return pkgName == null || subpath == null + ? null + : guessFullPath(pkgName, subpath) + } +} + +/** + * @param {string} pkgName + * @param {string} subpath + * @return {string | null} + */ +const guessFullPath = (pkgName, subpath) => { + const pkgPath = require.resolve(path.posix.join(pkgName, 'package.json')) + const pkgRootPath = path.dirname(pkgPath) + + let parsedSource = null + const sourcePath = path.join(pkgRootPath, subpath) + if (fs.existsSync(sourcePath + '.js')) { + parsedSource = joinPath(pkgName, subpath + '.js') + } else if (fs.existsSync(sourcePath + '.mjs')) { + parsedSource = joinPath(pkgName, subpath + '.mjs') + } else if (fs.existsSync(path.join(sourcePath, 'index.js'))) { + parsedSource = joinPath(pkgName, subpath, 'index.js') + } else if (fs.existsSync(path.join(sourcePath, 'index.mjs'))) { + parsedSource = joinPath(pkgName, subpath, 'index.mjs') + } + return parsedSource +} + +const splitSource = (() => { + const splitRegex = /^([\w-]+|@[\w-]+\/[\w-]+)(?:\/(.*))?$/ + /** + * @param {string} source + * @return {[string, string] | null} + */ + return (source) => { + const matched = splitRegex.exec(source) + if (!matched) return null + return matched.slice(1) + } +})() + +const replaceExtname = (filePath, ext) => { + const oldExt = path.extname(filePath) + if (!oldExt) return filePath + ext + return joinPath(path.dirname(filePath), path.basename(filePath, oldExt) + ext) +} + +const joinPath = (firstPath, ...restPath) => { + const joinedPath = normalizePath(path.join(firstPath, ...restPath)) + return firstPath.startsWith('./') ? './' + joinedPath : joinedPath +} + +/** + * @param {string} path + */ +const normalizePath = (path) => path.replace(/\\/g, '/') diff --git a/scripts/post-build/index.js b/scripts/post-build/index.js index a4140c4eb..74a16a6ad 100644 --- a/scripts/post-build/index.js +++ b/scripts/post-build/index.js @@ -5,6 +5,7 @@ const { terseCssr } = require('./terse-cssr') const { replaceDefine, outDirs, srcDir } = require('../utils') const { genWebTypes } = require('./gen-web-types') +const { completePath } = require('./complete-path') ;(async () => { await terseCssr() @@ -18,6 +19,9 @@ const { genWebTypes } = require('./gen-web-types') "'date-fns'//": "'date-fns/esm'" }) + // complete require and import source path + await completePath(['es']) + // generate web-types.json for webstorm & vetur // web-types.json is only a very loose description for auto-complete // vscode is a much better choice