diff --git a/packages/uni-app-vite/src/nvue/plugins/mainJs.ts b/packages/uni-app-vite/src/nvue/plugins/mainJs.ts index 3f0bd2ed19e8db1f9edd9a3087413ca3135351a4..d749ef13b28baace13a6d846b0373f7c61a46899 100644 --- a/packages/uni-app-vite/src/nvue/plugins/mainJs.ts +++ b/packages/uni-app-vite/src/nvue/plugins/mainJs.ts @@ -2,6 +2,7 @@ import { defineUniMainJsPlugin, MANIFEST_JSON_JS, PAGES_JSON_JS, + UNI_MODULES_EXPORTS, } from '@dcloudio/uni-cli-shared' import { APP_CSS_JS } from './appCss' @@ -20,7 +21,7 @@ export function uniMainJsPlugin({ if (opts.filter(id)) { if (renderer !== 'native') { return { - code: `import './${PAGES_JSON_JS}';import('${APP_CSS_JS}').then(()=>{})`, + code: `import './${PAGES_JSON_JS}';import '${UNI_MODULES_EXPORTS}';import('${APP_CSS_JS}').then(()=>{})`, map: { mappings: '' }, } } @@ -30,13 +31,13 @@ export function uniMainJsPlugin({ : createLegacyApp(code) return { code: - `import './${MANIFEST_JSON_JS}';\nimport './${PAGES_JSON_JS}';\n` + + `import './${MANIFEST_JSON_JS}';\nimport './${PAGES_JSON_JS}';\nimport '${UNI_MODULES_EXPORTS}';\n` + code, map: { mappings: '' }, } } return { - code: `import './${PAGES_JSON_JS}';`, + code: `import './${PAGES_JSON_JS}';import '${UNI_MODULES_EXPORTS}';`, map: { mappings: '' }, } } diff --git a/packages/uni-app-vite/src/vue/plugins/mainJs.ts b/packages/uni-app-vite/src/vue/plugins/mainJs.ts index d453977326a2f701d09b6da8518d59f3b6aca184..e609ae064293123207f6acab772366a455dd9441 100644 --- a/packages/uni-app-vite/src/vue/plugins/mainJs.ts +++ b/packages/uni-app-vite/src/vue/plugins/mainJs.ts @@ -1,4 +1,8 @@ -import { defineUniMainJsPlugin, PAGES_JSON_JS } from '@dcloudio/uni-cli-shared' +import { + defineUniMainJsPlugin, + PAGES_JSON_JS, + UNI_MODULES_EXPORTS, +} from '@dcloudio/uni-cli-shared' export function uniMainJsPlugin() { return defineUniMainJsPlugin((opts) => { @@ -11,7 +15,9 @@ export function uniMainJsPlugin() { ? createApp(code) : createLegacyApp(code) return { - code: `import './${PAGES_JSON_JS}';` + code, + code: + `import './${PAGES_JSON_JS}';import '${UNI_MODULES_EXPORTS}';` + + code, map: { mappings: '' }, } } diff --git a/packages/uni-cli-shared/__tests__/uniModulesExports.spec.ts b/packages/uni-cli-shared/__tests__/uniModulesExports.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..44ce85824380bfa8883a44fb829e99365ae2ad02 --- /dev/null +++ b/packages/uni-cli-shared/__tests__/uniModulesExports.spec.ts @@ -0,0 +1,81 @@ +import { parseExports } from '../src/vite/plugins/uniModules' + +describe('uni_modules:exports', () => { + test('parseExports', () => { + expect( + parseExports('app', `@/uni_modules/uni-getbatteryinfo`, { + uni: 'getBatteryInfo', + }) + ).toEqual([ + ["import getBatteryInfo from '@/uni_modules/uni-getbatteryinfo'"], + ['uni.getBatteryInfo = getBatteryInfo'], + ]) + expect( + parseExports('app', `@/uni_modules/uni-getbatteryinfo`, { + uni: ['getBatteryInfo'], + }) + ).toEqual([ + ["import { getBatteryInfo } from '@/uni_modules/uni-getbatteryinfo'"], + ['uni.getBatteryInfo = getBatteryInfo'], + ]) + expect( + parseExports('app', `@/uni_modules/uni-location`, { + uni: ['openLocation', 'chooseLocation'], + }) + ).toEqual([ + [ + "import { openLocation, chooseLocation } from '@/uni_modules/uni-location'", + ], + [ + 'uni.openLocation = openLocation', + 'uni.chooseLocation = chooseLocation', + ], + ]) + expect( + parseExports('app', `@/uni_modules/uni-capturescreen`, { + uni: { + onUserCaptureScreen: 'onCaptureScreen', + offUserCaptureScreen: 'offUserCaptureScreen', + }, + }) + ).toEqual([ + [ + "import { onCaptureScreen as onUserCaptureScreen, offUserCaptureScreen } from '@/uni_modules/uni-capturescreen'", + ], + [ + 'uni.onUserCaptureScreen = onUserCaptureScreen', + 'uni.offUserCaptureScreen = offUserCaptureScreen', + ], + ]) + }) + test('parseExports with platform', () => { + expect( + parseExports('web', `@/uni_modules/uni-getbatteryinfo`, { + uni: 'getBatteryInfo1', + web: { + uni: 'getBatteryInfo', + }, + }) + ).toEqual([ + ["import getBatteryInfo from '@/uni_modules/uni-getbatteryinfo'"], + ['uni.getBatteryInfo = getBatteryInfo'], + ]) + expect( + parseExports('web', `@/uni_modules/uni-getbatteryinfo`, { + uni: 'getBatteryInfo1', + web: false, + }) + ).toEqual([[], []]) + expect( + parseExports('web', `@/uni_modules/uni-location`, { + uni: ['openLocation'], + web: { + uni: ['chooseLocation'], + }, + }) + ).toEqual([ + ["import { chooseLocation } from '@/uni_modules/uni-location'"], + ['uni.chooseLocation = chooseLocation'], + ]) + }) +}) diff --git a/packages/uni-cli-shared/src/constants.ts b/packages/uni-cli-shared/src/constants.ts index 332b87e4a28b3336dac722ced41c18d72c8bd1da..2f1d130405d7014b4399945b541774f7cbdfc024 100644 --- a/packages/uni-cli-shared/src/constants.ts +++ b/packages/uni-cli-shared/src/constants.ts @@ -17,6 +17,7 @@ export const extensions = [ '.json', ].concat(EXTNAME_VUE) +export const UNI_MODULES_EXPORTS = '\0uni-modules-exports' export const PAGES_JSON_JS = 'pages-json-js' export const MANIFEST_JSON_JS = 'manifest-json-js' export const JSON_JS_MAP = { diff --git a/packages/uni-cli-shared/src/vite/plugins/index.ts b/packages/uni-cli-shared/src/vite/plugins/index.ts index d3be4caa966dbf33b2db1f68ce0d16ee68de931d..c76fea8ac2dfda513e73b4e7050762254696f079 100644 --- a/packages/uni-cli-shared/src/vite/plugins/index.ts +++ b/packages/uni-cli-shared/src/vite/plugins/index.ts @@ -5,6 +5,7 @@ export * from './mainJs' export * from './jsonJs' export * from './console' export * from './dynamicImportPolyfill' +export { uniModulesExportsPlugin } from './uniModules' export { assetPlugin, getAssetHash } from './vitejs/plugins/asset' export { diff --git a/packages/uni-cli-shared/src/vite/plugins/inject.ts b/packages/uni-cli-shared/src/vite/plugins/inject.ts index 1185bcb927e991e6787530e0974b659fea572dd5..90e94e7a0311a411831b05c3470927423b11e753 100644 --- a/packages/uni-cli-shared/src/vite/plugins/inject.ts +++ b/packages/uni-cli-shared/src/vite/plugins/inject.ts @@ -23,6 +23,7 @@ import { isJsFile, isAssignmentExpression, } from '../utils' +import { UNI_MODULES_EXPORTS } from '../../constants' interface Scope { parent: Scope @@ -83,8 +84,11 @@ export function uniViteInjectPlugin( // 确保在 commonjs 之后,否则会混合 es6 module 与 cjs 的代码,导致 commonjs 失效 enforce: 'post', transform(code, id) { - if (!filter(id)) return null - if (!isJsFile(id)) return null + // 硬编码支持了uni_modules_exports + if (id !== UNI_MODULES_EXPORTS) { + if (!filter(id)) return null + if (!isJsFile(id)) return null + } debugInjectTry(id) if (code.search(firstpass) === -1) return null if (sep !== '/') id = id.split(sep).join('/') diff --git a/packages/uni-cli-shared/src/vite/plugins/uniModules.ts b/packages/uni-cli-shared/src/vite/plugins/uniModules.ts new file mode 100644 index 0000000000000000000000000000000000000000..b57954144691153859ce68bdbac05911d6643dfb --- /dev/null +++ b/packages/uni-cli-shared/src/vite/plugins/uniModules.ts @@ -0,0 +1,158 @@ +import path from 'path' +import fs from 'fs-extra' +import type { Plugin } from 'vite' +import { recursive } from 'merge' +import { isArray, isPlainObject, isString } from '@vue/shared' +import { UNI_MODULES_EXPORTS } from '../../constants' +import { parseJson } from '../../json' + +export function uniModulesExportsPlugin(): Plugin { + return { + name: 'uni:modules:exports', + resolveId(id) { + if (id === UNI_MODULES_EXPORTS) { + return UNI_MODULES_EXPORTS + } + }, + load(id) { + if (id !== UNI_MODULES_EXPORTS) { + return + } + const uniModulesDir = path.resolve( + process.env.UNI_INPUT_DIR, + 'uni_modules' + ) + if (!fs.existsSync(uniModulesDir)) { + return '' + } + const importCodes: string[] = [] + const assignCodes: string[] = [] + fs.readdirSync(uniModulesDir).forEach((uniModuleDir) => { + const pkgPath = path.resolve( + uniModulesDir, + uniModuleDir, + 'package.json' + ) + if (!fs.existsSync(pkgPath)) { + return + } + const exports = parseJson(fs.readFileSync(pkgPath, 'utf8'))?.uni_modules + ?.exports as Exports | undefined + if (exports) { + const [exportsImportCodes, exportsAssignCodes] = parseExports( + process.env.UNI_PLATFORM === 'h5' + ? 'web' + : process.env.UNI_PLATFORM, + `@/uni_modules/${uniModuleDir}`, + exports + ) + importCodes.push(...exportsImportCodes) + assignCodes.push(...exportsAssignCodes) + } + }) + if (!importCodes.length) { + return '' + } + return `${importCodes.join('\n')} +${assignCodes.join('\n')}` + }, + } +} + +type Define = string | string[] | Record +type Defines = { + [name: string]: Define +} + +interface Exports { + [name: string]: Define | Defines | false +} + +export function parseExports( + platform: UniApp.PLATFORM, + source: string, + exports: Exports = {} +): [string[], string[]] { + const rootDefines: Defines = {} + Object.keys(exports).forEach((name) => { + if (name.startsWith('uni')) { + rootDefines[name] = exports[name] as Define + } + }) + const platformDefines = exports[platform] as false | Defines + // 该平台不支持 + if (platformDefines === false) { + return [[], []] + } + return parseDefines(source, recursive(true, rootDefines, platformDefines)) +} + +export function parseDefines( + source: string, + defines: Defines = {} +): [string[], string[]] { + const importCodes: string[] = [] + const assignCodes: string[] = [] + Object.keys(defines).forEach((name) => { + const [defineImportCodes, defineAssignCodes] = parseDefine( + source, + name, + defines[name] + ) + importCodes.push(...defineImportCodes) + assignCodes.push(...defineAssignCodes) + }) + return [importCodes, assignCodes] +} +/** + * uni:'getBatteryInfo' + * import getBatteryInfo from '..' + * + * uni:['getBatteryInfo'] + * import { getBatteryInfo } from '..' + * + * uni:['openLocation','chooseLocation'] + * import { openLocation, chooseLocation } from '..' + * + * uni:{ + * onUserCaptureScreen: "onCaptureScreen" + * offUserCaptureScreen: "offCaptureScreen" + * } + * + * uni.getBatteryInfo = getBatteryInfo + * @param source + * @param globalObject + * @param define + * @returns + */ +function parseDefine( + source: string, + globalObject: string, + define: Define +): [string[], string[]] { + const importCodes: string[] = [] + const assignCodes: string[] = [] + if (isString(define)) { + importCodes.push(`import ${define} from '${source}'`) + assignCodes.push(`${globalObject}.${define} = ${define}`) + } else if (isArray(define)) { + importCodes.push(`import { ${define.join(', ')} } from '${source}'`) + define.forEach((d) => { + assignCodes.push(`${globalObject}.${d} = ${d}`) + }) + } else if (isPlainObject(define)) { + const keys = Object.keys(define) + const specifiers: string[] = [] + + keys.forEach((d) => { + if (d !== define[d]) { + specifiers.push(`${define[d]} as ${d}`) + } else { + specifiers.push(d) + } + assignCodes.push(`${globalObject}.${d} = ${d}`) + }) + importCodes.push(`import { ${specifiers.join(', ')} } from '${source}'`) + } + return [importCodes, assignCodes] +} diff --git a/packages/uni-cli-shared/src/vite/utils/url.ts b/packages/uni-cli-shared/src/vite/utils/url.ts index b204abd56c6cffa4dd92bbeb86c5c310c70fa280..cff5838133baa7e6c9082900b967227821973d49 100644 --- a/packages/uni-cli-shared/src/vite/utils/url.ts +++ b/packages/uni-cli-shared/src/vite/utils/url.ts @@ -1,5 +1,9 @@ import path from 'path' -import { EXTNAME_JS_RE, EXTNAME_VUE } from '../../constants' +import { + EXTNAME_JS_RE, + EXTNAME_VUE, + UNI_MODULES_EXPORTS, +} from '../../constants' export interface VueQuery { vue?: boolean @@ -62,6 +66,10 @@ export const cleanUrl = (url: string) => url.replace(hashRE, '').replace(queryRE, '') export function isJsFile(id: string) { + // inject 使用了isJsFile 判断。uni_modules_exports 中注入使用了 uni,在小程序平台,inject 需要注入 uni 对象 + if (id === UNI_MODULES_EXPORTS) { + return true + } const isJs = EXTNAME_JS_RE.test(id) if (isJs) { return true diff --git a/packages/uni-h5-vite/src/plugins/mainJs.ts b/packages/uni-h5-vite/src/plugins/mainJs.ts index 93fd01dee6e2d433b092ab3c7df25bde5a24ca24..876e86137d1d78ac8dd72e7428248b84e7d35edd 100644 --- a/packages/uni-h5-vite/src/plugins/mainJs.ts +++ b/packages/uni-h5-vite/src/plugins/mainJs.ts @@ -2,6 +2,7 @@ import { defineUniMainJsPlugin, isSsr, PAGES_JSON_JS, + UNI_MODULES_EXPORTS, } from '@dcloudio/uni-cli-shared' import { isSSR, isSsrManifest } from '../utils' @@ -26,7 +27,7 @@ export function uniMainJsPlugin() { ? createSSRServerApp(code) : createSSRClientApp(code) } - code = `import './${PAGES_JSON_JS}';${code}` + code = `import './${PAGES_JSON_JS}';import '${UNI_MODULES_EXPORTS}';${code}` return { code, map: this.getCombinedSourcemap(), diff --git a/packages/uni-mp-vite/src/plugins/mainJs.ts b/packages/uni-mp-vite/src/plugins/mainJs.ts index dd2228ee401e7e55981791575fc04937d1f3476f..8d35037cf29d042301017504c8d122ab8ea8ea20 100644 --- a/packages/uni-mp-vite/src/plugins/mainJs.ts +++ b/packages/uni-mp-vite/src/plugins/mainJs.ts @@ -3,6 +3,7 @@ import { PAGES_JSON_JS, parseProgram, transformDynamicImports, + UNI_MODULES_EXPORTS, updateMiniProgramGlobalComponents, withSourcemap, } from '@dcloudio/uni-cli-shared' @@ -46,7 +47,7 @@ export function uniMainJsPlugin( }) return { code: - `import '\0plugin-vue:export-helper';import 'uni-mp-runtime';import './${PAGES_JSON_JS}';` + + `import '\0plugin-vue:export-helper';import 'uni-mp-runtime';import './${PAGES_JSON_JS}';import '${UNI_MODULES_EXPORTS}';` + code, map, } diff --git a/packages/vite-plugin-uni/src/index.ts b/packages/vite-plugin-uni/src/index.ts index 6bfd9208397007dc0f53b1f65aedab9c777a785d..3ba26dc8cf79cd5b46885d53d2ce7aeed8d5dac9 100644 --- a/packages/vite-plugin-uni/src/index.ts +++ b/packages/vite-plugin-uni/src/index.ts @@ -12,6 +12,7 @@ import { initModuleAlias, initPreContext, resolveSourceMapPath, + uniModulesExportsPlugin, } from '@dcloudio/uni-cli-shared' import { createConfig } from './config' @@ -85,7 +86,7 @@ export default function uniPlugin( initPreContext(options.platform, process.env.UNI_CUSTOM_CONTEXT) - const plugins: Plugin[] = [] + const plugins: Plugin[] = [uniModulesExportsPlugin()] // 仅限 h5 if (options.viteLegacyOptions && options.platform === 'h5') { plugins.push(