diff --git a/packages/uni-app-plus/dist/uni-app-service.es.js b/packages/uni-app-plus/dist/uni-app-service.es.js index 0e65fe56512119a5b51fdf4313e13b29f46a8973..e58f3b6aa690095435e73f1e443c1e1a63b73a43 100644 --- a/packages/uni-app-plus/dist/uni-app-service.es.js +++ b/packages/uni-app-plus/dist/uni-app-service.es.js @@ -2686,9 +2686,9 @@ var serviceContext = (function (vue) { } function parseRedirectInfo() { const weexPlus = weex.requireModule('plus'); - const { path, query, extraData, userAction } = weexPlus.getRedirectInfo() || {}; + const { path, query, extraData, userAction, fromAppid } = weexPlus.getRedirectInfo() || {}; const referrerInfo = { - appId: '', + appId: fromAppid, extraData: {}, }; if (extraData) { diff --git a/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/css.ts b/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/css.ts index 52884f36fafecc3225c076de2cd7686ef2fa9713..2e438fcc1e076402d3d0974488e8ff3538d7ce48 100644 --- a/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/css.ts +++ b/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/css.ts @@ -28,6 +28,7 @@ import type Stylus from 'stylus' import type Less from 'less' import type { Alias } from 'types/alias' import { transform, formatMessages } from 'esbuild' +import { preCss } from '../../../../preprocess' // const debug = createDebugger('vite:css') export interface CSSOptions { @@ -866,6 +867,12 @@ const sass: SassStylePreprocessor = (source, root, options, aliasResolver) => aliasResolver ) +export function preprocessCss(content: string) { + if (content.includes('#endif')) { + return preCss(content) + } + return content +} /** * relative url() inside \@imported sass and less files must be rebased to use * root file as base. @@ -876,18 +883,22 @@ async function rebaseUrls( alias: Alias[] ): Promise { file = path.resolve(file) // ensure os-specific flashes + + // 条件编译 + const contents = preprocessCss(fs.readFileSync(file, 'utf-8')) + // in the same dir, no need to rebase const fileDir = path.dirname(file) const rootDir = path.dirname(rootFile) + if (fileDir === rootDir) { - return { file } + return { file, contents } } // no url() - const content = fs.readFileSync(file, 'utf-8') - if (!cssUrlRE.test(content)) { - return { file } + if (!cssUrlRE.test(contents)) { + return { file, contents } } - const rebased = await rewriteCssUrls(content, (url) => { + const rebased = await rewriteCssUrls(contents, (url) => { if (url.startsWith('/')) return url // match alias, no need to rewrite for (const { find } of alias) { diff --git a/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/h5Asset.ts b/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/h5Asset.ts new file mode 100644 index 0000000000000000000000000000000000000000..0e8781451458ff691af2660feb27a33890b445f4 --- /dev/null +++ b/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/h5Asset.ts @@ -0,0 +1,368 @@ +import path from 'path' +import { parse as parseUrl } from 'url' +import fs, { promises as fsp } from 'fs' +import mime from 'mime/lite' +import { Plugin } from '../plugin' +import { ResolvedConfig } from '../config' +import { cleanUrl } from '../utils' +import { FS_PREFIX } from '../constants' +import { OutputOptions, PluginContext, RenderedChunk } from 'rollup' +import MagicString from 'magic-string' +import { createHash } from 'crypto' +import { normalizePath } from '../utils' + +export const assetUrlRE = /__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?/g + +const rawRE = /(\?|&)raw(?:&|$)/ +const urlRE = /(\?|&)url(?:&|$)/ + +export const chunkToEmittedAssetsMap = new WeakMap>() + +const assetCache = new WeakMap>() + +const assetHashToFilenameMap = new WeakMap< + ResolvedConfig, + Map +>() +// save hashes of the files that has been emitted in build watch +const emittedHashMap = new WeakMap>() + +/** + * Also supports loading plain strings with import text from './foo.txt?raw' + */ +export function assetPlugin(config: ResolvedConfig): Plugin { + // assetHashToFilenameMap initialization in buildStart causes getAssetFilename to return undefined + assetHashToFilenameMap.set(config, new Map()) + return { + name: 'vite:asset', + + buildStart() { + assetCache.set(config, new Map()) + emittedHashMap.set(config, new Set()) + }, + + resolveId(id) { + if (!config.assetsInclude(cleanUrl(id))) { + return + } + // imports to absolute urls pointing to files in /public + // will fail to resolve in the main resolver. handle them here. + const publicFile = checkPublicFile(id, config) + if (publicFile) { + return id + } + }, + + async load(id) { + if (id.startsWith('\0')) { + // Rollup convention, this id should be handled by the + // plugin that marked it with \0 + return + } + + // raw requests, read from disk + if (rawRE.test(id)) { + const file = checkPublicFile(id, config) || cleanUrl(id) + // raw query, read file and return as string + return `export default ${JSON.stringify( + await fsp.readFile(file, 'utf-8') + )}` + } + + if (!config.assetsInclude(cleanUrl(id)) && !urlRE.test(id)) { + return + } + + id = id.replace(urlRE, '$1').replace(/[\?&]$/, '') + const url = await fileToUrl(id, config, this) + return `export default ${JSON.stringify(url)}` + }, + + renderChunk(code, chunk) { + let match: RegExpExecArray | null + let s: MagicString | undefined + + // Urls added with JS using e.g. + // imgElement.src = "my/file.png" are using quotes + + // Urls added in CSS that is imported in JS end up like + // var inlined = ".inlined{color:green;background:url(__VITE_ASSET__5aa0ddc0__)}\n"; + + // In both cases, the wrapping should already be fine + + while ((match = assetUrlRE.exec(code))) { + s = s || (s = new MagicString(code)) + const [full, hash, postfix = ''] = match + // some internal plugins may still need to emit chunks (e.g. worker) so + // fallback to this.getFileName for that. + const file = getAssetFilename(hash, config) || this.getFileName(hash) + registerAssetToChunk(chunk, file) + const outputFilepath = config.base + file + postfix + s.overwrite(match.index, match.index + full.length, outputFilepath) + } + + if (s) { + return { + code: s.toString(), + map: config.build.sourcemap ? s.generateMap({ hires: true }) : null, + } + } else { + return null + } + }, + + generateBundle(_, bundle) { + // do not emit assets for SSR build + if (config.command === 'build' && config.build.ssr) { + for (const file in bundle) { + if ( + bundle[file].type === 'asset' && + !file.includes('ssr-manifest.json') + ) { + delete bundle[file] + } + } + } + }, + } +} + +export function registerAssetToChunk(chunk: RenderedChunk, file: string): void { + let emitted = chunkToEmittedAssetsMap.get(chunk) + if (!emitted) { + emitted = new Set() + chunkToEmittedAssetsMap.set(chunk, emitted) + } + emitted.add(cleanUrl(file)) +} + +export function checkPublicFile( + url: string, + { publicDir }: ResolvedConfig +): string | undefined { + // note if the file is in /public, the resolver would have returned it + // as-is so it's not going to be a fully resolved path. + if (!publicDir || !url.startsWith('/')) { + return + } + const publicFile = path.join(publicDir, cleanUrl(url)) + if (fs.existsSync(publicFile)) { + return publicFile + } else { + return + } +} + +export function fileToUrl( + id: string, + config: ResolvedConfig, + ctx: PluginContext +): string | Promise { + if (config.command === 'serve') { + return fileToDevUrl(id, config) + } else { + return fileToBuiltUrl(id, config, ctx) + } +} + +function fileToDevUrl(id: string, config: ResolvedConfig) { + let rtn: string + if (checkPublicFile(id, config)) { + // in public dir, keep the url as-is + rtn = id + } else if (id.startsWith(config.root)) { + // in project root, infer short public path + rtn = '/' + path.posix.relative(config.root, id) + } else { + // outside of project root, use absolute fs path + // (this is special handled by the serve static middleware + rtn = path.posix.join(FS_PREFIX + id) + } + const origin = config.server?.origin ?? '' + return origin + config.base + rtn.replace(/^\//, '') +} + +export function getAssetFilename( + hash: string, + config: ResolvedConfig +): string | undefined { + return assetHashToFilenameMap.get(config)?.get(hash) +} + +/** + * converts the source filepath of the asset to the output filename based on the assetFileNames option. \ + * this function imitates the behavior of rollup.js. \ + * https://rollupjs.org/guide/en/#outputassetfilenames + * + * @example + * ```ts + * const content = Buffer.from('text'); + * const fileName = assetFileNamesToFileName( + * 'assets/[name].[hash][extname]', + * '/path/to/file.txt', + * getAssetHash(content), + * content + * ) + * // fileName: 'assets/file.982d9e3e.txt' + * ``` + * + * @param assetFileNames filename pattern. e.g. `'assets/[name].[hash][extname]'` + * @param file filepath of the asset + * @param contentHash hash of the asset. used for `'[hash]'` placeholder + * @param content content of the asset. passed to `assetFileNames` if `assetFileNames` is a function + * @returns output filename + */ +export function assetFileNamesToFileName( + assetFileNames: Exclude, + file: string, + contentHash: string, + content: string | Buffer +): string { + const basename = path.basename(file) + + // placeholders for `assetFileNames` + // `hash` is slightly different from the rollup's one + const extname = path.extname(basename) + const ext = extname.substr(1) + const name = basename.slice(0, -extname.length) + const hash = contentHash + + if (typeof assetFileNames === 'function') { + assetFileNames = assetFileNames({ + name: file, + source: content, + type: 'asset', + }) + if (typeof assetFileNames !== 'string') { + throw new TypeError('assetFileNames must return a string') + } + } else if (typeof assetFileNames !== 'string') { + throw new TypeError('assetFileNames must be a string or a function') + } + + const fileName = assetFileNames.replace( + /\[\w+\]/g, + (placeholder: string): string => { + switch (placeholder) { + case '[ext]': + return ext + + case '[extname]': + return extname + + case '[hash]': + return hash + + case '[name]': + return name + } + throw new Error( + `invalid placeholder ${placeholder} in assetFileNames "${assetFileNames}"` + ) + } + ) + + return fileName +} + +/** + * Register an asset to be emitted as part of the bundle (if necessary) + * and returns the resolved public URL + */ +async function fileToBuiltUrl( + id: string, + config: ResolvedConfig, + pluginContext: PluginContext, + skipPublicCheck = false +): Promise { + if (!skipPublicCheck && checkPublicFile(id, config)) { + return config.base + id.slice(1) + } + + const cache = assetCache.get(config)! + const cached = cache.get(id) + if (cached) { + return cached + } + + const file = cleanUrl(id) + const content = await fsp.readFile(file) + + let url: string + if ( + config.build.lib || + (!file.endsWith('.svg') && + content.length < Number(config.build.assetsInlineLimit)) + ) { + // base64 inlined as a string + url = `data:${mime.getType(file)};base64,${content.toString('base64')}` + } else { + // emit as asset + // rollup supports `import.meta.ROLLUP_FILE_URL_*`, but it generates code + // that uses runtime url sniffing and it can be verbose when targeting + // non-module format. It also fails to cascade the asset content change + // into the chunk's hash, so we have to do our own content hashing here. + // https://bundlers.tooling.report/hashing/asset-cascade/ + // https://github.com/rollup/rollup/issues/3415 + const map = assetHashToFilenameMap.get(config)! + const contentHash = getAssetHash(content) + const { search, hash } = parseUrl(id) + const postfix = (search || '') + (hash || '') + const output = config.build?.rollupOptions?.output + const assetFileNames = + (output && !Array.isArray(output) ? output.assetFileNames : undefined) ?? + // defaults to '/[name].[hash][extname]' + // slightly different from rollup's one ('assets/[name]-[hash][extname]') + path.posix.join(config.build.assetsDir, '[name].[hash][extname]') + const fileName = assetFileNamesToFileName( + assetFileNames, + file, + contentHash, + content + ) + if (!map.has(contentHash)) { + map.set(contentHash, fileName) + } + const emittedSet = emittedHashMap.get(config)! + if (!emittedSet.has(contentHash)) { + const name = normalizePath(path.relative(config.root, file)) + pluginContext.emitFile({ + name, + fileName, + type: 'asset', + source: content, + }) + emittedSet.add(contentHash) + } + + url = `__VITE_ASSET__${contentHash}__${postfix ? `$_${postfix}__` : ``}` + } + + cache.set(id, url) + return url +} + +export function getAssetHash(content: Buffer): string { + return createHash('sha256').update(content).digest('hex').slice(0, 8) +} + +export async function urlToBuiltUrl( + url: string, + importer: string, + config: ResolvedConfig, + pluginContext: PluginContext +): Promise { + if (checkPublicFile(url, config)) { + return config.base + url.slice(1) + } + const file = url.startsWith('/') + ? path.join(config.root, url) + : path.join(path.dirname(importer), url) + return fileToBuiltUrl( + file, + config, + pluginContext, + // skip public check since we just did it above + true + ) +} diff --git a/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/h5Css.ts b/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/h5Css.ts new file mode 100644 index 0000000000000000000000000000000000000000..763305e62f8d8869bcef50d9a4ec2ab1bd9c26f0 --- /dev/null +++ b/packages/uni-cli-shared/src/vite/plugins/vitejs/plugins/h5Css.ts @@ -0,0 +1,1289 @@ +import fs from 'fs' +import path from 'path' +import glob from 'fast-glob' +import { + // createDebugger, + isExternalUrl, + asyncReplace, + cleanUrl, + generateCodeFrame, + isDataUrl, + isObject, + normalizePath, + processSrcSet, +} from '../utils' +import { Plugin } from '../plugin' +import { ResolvedConfig } from '../config' +import postcssrc from 'postcss-load-config' +import { + NormalizedOutputOptions, + OutputChunk, + RenderedChunk, + RollupError, + SourceMap, +} from 'rollup' +import { dataToEsm } from '@rollup/pluginutils' +import chalk from 'chalk' +import { CLIENT_PUBLIC_PATH } from '../constants' +import { ResolveFn, ViteDevServer } from '../' +import { + getAssetFilename, + assetUrlRE, + registerAssetToChunk, + fileToUrl, + checkPublicFile, +} from './h5Asset' +import MagicString from 'magic-string' +import * as Postcss from 'postcss' +import type Sass from 'sass' +// We need to disable check of extraneous import which is buggy for stylus, +// and causes the CI tests fail, see: https://github.com/vitejs/vite/pull/2860 +import type Stylus from 'stylus' +import type Less from 'less' +import { Alias } from 'types/alias' +import type { ModuleNode } from '../server/moduleGraph' +import { transform, formatMessages } from 'esbuild' +import { preprocessCss } from './css' + +// const debug = createDebugger('vite:css') + +export interface CSSOptions { + /** + * https://github.com/css-modules/postcss-modules + */ + modules?: CSSModulesOptions | false + preprocessorOptions?: Record + postcss?: + | string + | (Postcss.ProcessOptions & { + plugins?: Postcss.Plugin[] + }) +} + +export interface CSSModulesOptions { + getJSON?: ( + cssFileName: string, + json: Record, + outputFileName: string + ) => void + scopeBehaviour?: 'global' | 'local' + globalModulePaths?: RegExp[] + generateScopedName?: + | string + | ((name: string, filename: string, css: string) => string) + hashPrefix?: string + /** + * default: null + */ + localsConvention?: + | 'camelCase' + | 'camelCaseOnly' + | 'dashes' + | 'dashesOnly' + | null +} + +const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)` +const cssLangRE = new RegExp(cssLangs) +const cssModuleRE = new RegExp(`\\.module${cssLangs}`) +const directRequestRE = /(\?|&)direct\b/ +const commonjsProxyRE = /\?commonjs-proxy/ +const inlineRE = /(\?|&)inline\b/ +const usedRE = /(\?|&)used\b/ + +const enum PreprocessLang { + less = 'less', + sass = 'sass', + scss = 'scss', + styl = 'styl', + stylus = 'stylus', +} +const enum PureCssLang { + css = 'css', +} +type CssLang = keyof typeof PureCssLang | keyof typeof PreprocessLang + +export const isCSSRequest = (request: string): boolean => + cssLangRE.test(request) + +export const isDirectCSSRequest = (request: string): boolean => + cssLangRE.test(request) && directRequestRE.test(request) + +export const isDirectRequest = (request: string): boolean => + directRequestRE.test(request) + +const cssModulesCache = new WeakMap< + ResolvedConfig, + Map> +>() + +export const chunkToEmittedCssFileMap = new WeakMap< + RenderedChunk, + Set +>() + +export const removedPureCssFilesCache = new WeakMap< + ResolvedConfig, + Map +>() + +const postcssConfigCache = new WeakMap< + ResolvedConfig, + PostCSSConfigResult | null +>() + +/** + * Plugin applied before user plugins + */ +export function cssPlugin(config: ResolvedConfig): Plugin { + let server: ViteDevServer + let moduleCache: Map> + + const resolveUrl = config.createResolver({ + preferRelative: true, + tryIndex: false, + extensions: [], + }) + const atImportResolvers = createCSSResolvers(config) + + return { + name: 'vite:css', + + configureServer(_server) { + server = _server + }, + + buildStart() { + // Ensure a new cache for every build (i.e. rebuilding in watch mode) + moduleCache = new Map>() + cssModulesCache.set(config, moduleCache) + + removedPureCssFilesCache.set(config, new Map()) + }, + + async transform(raw, id) { + if (!isCSSRequest(id) || commonjsProxyRE.test(id)) { + return + } + + const urlReplacer: CssUrlReplacer = async (url, importer) => { + if (checkPublicFile(url, config)) { + return config.base + url.slice(1) + } + const resolved = await resolveUrl(url, importer) + if (resolved) { + return fileToUrl(resolved, config, this) + } + return url + } + + const { + code: css, + modules, + deps, + } = await compileCSS( + id, + raw, + config, + urlReplacer, + atImportResolvers, + server + ) + if (modules) { + moduleCache.set(id, modules) + } + + // track deps for build watch mode + if (config.command === 'build' && config.build.watch && deps) { + for (const file of deps) { + this.addWatchFile(file) + } + } + + // dev + if (server) { + // server only logic for handling CSS @import dependency hmr + const { moduleGraph } = server + const thisModule = moduleGraph.getModuleById(id) + if (thisModule) { + // CSS modules cannot self-accept since it exports values + const isSelfAccepting = !modules && !inlineRE.test(id) + if (deps) { + // record deps in the module graph so edits to @import css can trigger + // main import to hot update + const depModules = new Set() + for (const file of deps) { + depModules.add( + isCSSRequest(file) + ? moduleGraph.createFileOnlyEntry(file) + : await moduleGraph.ensureEntryFromUrl( + ( + await fileToUrl(file, config, this) + ).replace( + (config.server?.origin ?? '') + config.base, + '/' + ) + ) + ) + } + moduleGraph.updateModuleInfo( + thisModule, + depModules, + // The root CSS proxy module is self-accepting and should not + // have an explicit accept list + new Set(), + isSelfAccepting + ) + for (const file of deps) { + this.addWatchFile(file) + } + } else { + thisModule.isSelfAccepting = isSelfAccepting + } + } + } + + return { + code: css, + // TODO CSS source map + map: { mappings: '' }, + } + }, + } +} + +/** + * Plugin applied after user plugins + */ +export function cssPostPlugin(config: ResolvedConfig): Plugin { + // styles initialization in buildStart causes a styling loss in watch + const styles: Map = new Map() + let pureCssChunks: Set + + // when there are multiple rollup outputs and extracting CSS, only emit once, + // since output formats have no effect on the generated CSS. + let outputToExtractedCSSMap: Map + let hasEmitted = false + + return { + name: 'vite:css-post', + + buildStart() { + // Ensure new caches for every build (i.e. rebuilding in watch mode) + pureCssChunks = new Set() + outputToExtractedCSSMap = new Map() + hasEmitted = false + }, + + async transform(css, id, options) { + if (!isCSSRequest(id) || commonjsProxyRE.test(id)) { + return + } + const ssr = + typeof options === 'boolean' + ? options + : (options && (options as any).ssr) === true + const inlined = inlineRE.test(id) + const modules = cssModulesCache.get(config)!.get(id) + const modulesCode = + modules && dataToEsm(modules, { namedExports: true, preferConst: true }) + + if (config.command === 'serve') { + if (isDirectCSSRequest(id)) { + return css + } else { + // server only + if (ssr) { + return modulesCode || `export default ${JSON.stringify(css)}` + } + if (inlined) { + return `export default ${JSON.stringify(css)}` + } + return [ + `import { updateStyle, removeStyle } from ${JSON.stringify( + path.posix.join(config.base, CLIENT_PUBLIC_PATH) + )}`, + `const id = ${JSON.stringify(id)}`, + `const css = ${JSON.stringify(css)}`, + `updateStyle(id, css)`, + // css modules exports change on edit so it can't self accept + `${modulesCode || `import.meta.hot.accept()\nexport default css`}`, + `import.meta.hot.prune(() => removeStyle(id))`, + ].join('\n') + } + } + + // build CSS handling ---------------------------------------------------- + + // record css + if (!inlined) { + styles.set(id, css) + } + + return { + code: + modulesCode || + (usedRE.test(id) + ? `export default ${JSON.stringify( + inlined ? await minifyCSS(css, config) : css + )}` + : `export default ''`), + map: { mappings: '' }, + // avoid the css module from being tree-shaken so that we can retrieve + // it in renderChunk() + moduleSideEffects: inlined ? false : 'no-treeshake', + } + }, + + async renderChunk(code, chunk, opts) { + let chunkCSS = '' + let isPureCssChunk = true + const ids = Object.keys(chunk.modules) + for (const id of ids) { + if ( + !isCSSRequest(id) || + cssModuleRE.test(id) || + commonjsProxyRE.test(id) + ) { + isPureCssChunk = false + } + if (styles.has(id)) { + chunkCSS += styles.get(id) + } + } + + if (!chunkCSS) { + return null + } + + // resolve asset URL placeholders to their built file URLs and perform + // minification if necessary + const processChunkCSS = async ( + css: string, + { + inlined, + minify, + }: { + inlined: boolean + minify: boolean + } + ) => { + // replace asset url references with resolved url. + const isRelativeBase = config.base === '' || config.base.startsWith('.') + css = css.replace(assetUrlRE, (_, fileHash, postfix = '') => { + const filename = getAssetFilename(fileHash, config) + postfix + registerAssetToChunk(chunk, filename) + if (!isRelativeBase || inlined) { + // absolute base or relative base but inlined (injected as style tag into + // index.html) use the base as-is + return config.base + filename + } else { + // relative base + extracted CSS - asset file will be in the same dir + return `./${path.posix.basename(filename)}` + } + }) + // only external @imports should exist at this point - and they need to + // be hoisted to the top of the CSS chunk per spec (#1845) + if (css.includes('@import')) { + css = await hoistAtImports(css) + } + if (minify && config.build.minify) { + css = await minifyCSS(css, config) + } + return css + } + + if (config.build.cssCodeSplit) { + if (isPureCssChunk) { + // this is a shared CSS-only chunk that is empty. + pureCssChunks.add(chunk.fileName) + } + if (opts.format === 'es' || opts.format === 'cjs') { + chunkCSS = await processChunkCSS(chunkCSS, { + inlined: false, + minify: true, + }) + // emit corresponding css file + const fileHandle = this.emitFile({ + name: chunk.name + '.css', + type: 'asset', + source: chunkCSS, + }) + chunkToEmittedCssFileMap.set( + chunk, + new Set([this.getFileName(fileHandle)]) + ) + } else if (!config.build.ssr) { + // legacy build, inline css + chunkCSS = await processChunkCSS(chunkCSS, { + inlined: true, + minify: true, + }) + const style = `__vite_style__` + const injectCode = + `var ${style} = document.createElement('style');` + + `${style}.innerHTML = ${JSON.stringify(chunkCSS)};` + + `document.head.appendChild(${style});` + if (config.build.sourcemap) { + const s = new MagicString(code) + s.prepend(injectCode) + return { + code: s.toString(), + map: s.generateMap({ hires: true }), + } + } else { + return { code: injectCode + code } + } + } + } else { + // non-split extracted CSS will be minified together + chunkCSS = await processChunkCSS(chunkCSS, { + inlined: false, + minify: false, + }) + outputToExtractedCSSMap.set( + opts, + (outputToExtractedCSSMap.get(opts) || '') + chunkCSS + ) + } + return null + }, + + async generateBundle(opts, bundle) { + // remove empty css chunks and their imports + if (pureCssChunks.size) { + const emptyChunkFiles = [...pureCssChunks] + .map((file) => path.basename(file)) + .join('|') + .replace(/\./g, '\\.') + const emptyChunkRE = new RegExp( + opts.format === 'es' + ? `\\bimport\\s*"[^"]*(?:${emptyChunkFiles})";\n?` + : `\\brequire\\(\\s*"[^"]*(?:${emptyChunkFiles})"\\);\n?`, + 'g' + ) + for (const file in bundle) { + const chunk = bundle[file] + if (chunk.type === 'chunk') { + // remove pure css chunk from other chunk's imports, + // and also register the emitted CSS files under the importer + // chunks instead. + chunk.imports = chunk.imports.filter((file) => { + if (pureCssChunks.has(file)) { + const css = chunkToEmittedCssFileMap.get( + bundle[file] as OutputChunk + ) + if (css) { + let existing = chunkToEmittedCssFileMap.get(chunk) + if (!existing) { + existing = new Set() + } + css.forEach((file) => existing!.add(file)) + chunkToEmittedCssFileMap.set(chunk, existing) + } + return false + } + return true + }) + chunk.code = chunk.code.replace( + emptyChunkRE, + // remove css import while preserving source map location + (m) => `/* empty css ${''.padEnd(m.length - 15)}*/` + ) + } + } + const removedPureCssFiles = removedPureCssFilesCache.get(config)! + pureCssChunks.forEach((fileName) => { + removedPureCssFiles.set(fileName, bundle[fileName] as RenderedChunk) + delete bundle[fileName] + }) + } + + let extractedCss = outputToExtractedCSSMap.get(opts) + if (extractedCss && !hasEmitted) { + hasEmitted = true + // minify css + if (config.build.minify) { + extractedCss = await minifyCSS(extractedCss, config) + } + this.emitFile({ + name: 'style.css', + type: 'asset', + source: extractedCss, + }) + } + }, + } +} + +interface CSSAtImportResolvers { + css: ResolveFn + sass: ResolveFn + less: ResolveFn +} + +function createCSSResolvers(config: ResolvedConfig): CSSAtImportResolvers { + let cssResolve: ResolveFn | undefined + let sassResolve: ResolveFn | undefined + let lessResolve: ResolveFn | undefined + return { + get css() { + return ( + cssResolve || + (cssResolve = config.createResolver({ + extensions: ['.css'], + mainFields: ['style'], + tryIndex: false, + preferRelative: true, + })) + ) + }, + + get sass() { + return ( + sassResolve || + (sassResolve = config.createResolver({ + extensions: ['.scss', '.sass', '.css'], + mainFields: ['sass', 'style'], + tryIndex: true, + tryPrefix: '_', + preferRelative: true, + })) + ) + }, + + get less() { + return ( + lessResolve || + (lessResolve = config.createResolver({ + extensions: ['.less', '.css'], + mainFields: ['less', 'style'], + tryIndex: false, + preferRelative: true, + })) + ) + }, + } +} + +function getCssResolversKeys( + resolvers: CSSAtImportResolvers +): Array { + return Object.keys(resolvers) as unknown as Array +} + +async function compileCSS( + id: string, + code: string, + config: ResolvedConfig, + urlReplacer: CssUrlReplacer, + atImportResolvers: CSSAtImportResolvers, + server?: ViteDevServer +): Promise<{ + code: string + map?: SourceMap + ast?: Postcss.Result + modules?: Record + deps?: Set +}> { + const { modules: modulesOptions, preprocessorOptions } = config.css || {} + const isModule = modulesOptions !== false && cssModuleRE.test(id) + // although at serve time it can work without processing, we do need to + // crawl them in order to register watch dependencies. + const needInlineImport = code.includes('@import') + const hasUrl = cssUrlRE.test(code) || cssImageSetRE.test(code) + const postcssConfig = await resolvePostcssConfig(config) + const lang = id.match(cssLangRE)?.[1] as CssLang | undefined + + // 1. plain css that needs no processing + if ( + lang === 'css' && + !postcssConfig && + !isModule && + !needInlineImport && + !hasUrl + ) { + return { code } + } + + let map: SourceMap | undefined + let modules: Record | undefined + const deps = new Set() + + // 2. pre-processors: sass etc. + if (isPreProcessor(lang)) { + const preProcessor = preProcessors[lang] + let opts = (preprocessorOptions && preprocessorOptions[lang]) || {} + // support @import from node dependencies by default + switch (lang) { + case PreprocessLang.scss: + case PreprocessLang.sass: + opts = { + includePaths: ['node_modules'], + alias: config.resolve.alias, + ...opts, + } + break + case PreprocessLang.less: + case PreprocessLang.styl: + case PreprocessLang.stylus: + opts = { + paths: ['node_modules'], + alias: config.resolve.alias, + ...opts, + } + } + // important: set this for relative import resolving + opts.filename = cleanUrl(id) + const preprocessResult = await preProcessor( + code, + config.root, + opts, + atImportResolvers + ) + if (preprocessResult.errors.length) { + throw preprocessResult.errors[0] + } + + code = preprocessResult.code + map = preprocessResult.map as SourceMap + if (preprocessResult.deps) { + preprocessResult.deps.forEach((dep) => { + // sometimes sass registers the file itself as a dep + if (normalizePath(dep) !== normalizePath(opts.filename)) { + deps.add(dep) + } + }) + } + } + + // 3. postcss + const postcssOptions = (postcssConfig && postcssConfig.options) || {} + const postcssPlugins = + postcssConfig && postcssConfig.plugins ? postcssConfig.plugins.slice() : [] + + if (needInlineImport) { + postcssPlugins.unshift( + (await import('postcss-import')).default({ + async resolve(id, basedir) { + const resolved = await atImportResolvers.css( + id, + path.join(basedir, '*') + ) + if (resolved) { + return path.resolve(resolved) + } + return id + }, + }) + ) + } + postcssPlugins.push( + UrlRewritePostcssPlugin({ + replacer: urlReplacer, + }) as Postcss.Plugin + ) + + if (isModule) { + postcssPlugins.unshift( + (await import('postcss-modules')).default({ + ...modulesOptions, + getJSON( + cssFileName: string, + _modules: Record, + outputFileName: string + ) { + modules = _modules + if (modulesOptions && typeof modulesOptions.getJSON === 'function') { + modulesOptions.getJSON(cssFileName, _modules, outputFileName) + } + }, + async resolve(id: string) { + for (const key of getCssResolversKeys(atImportResolvers)) { + const resolved = await atImportResolvers[key](id) + if (resolved) { + return path.resolve(resolved) + } + } + + return id + }, + }) + ) + } + + if (!postcssPlugins.length) { + return { + code, + map, + } + } + + // postcss is an unbundled dep and should be lazy imported + const postcssResult = await (await import('postcss')) + .default(postcssPlugins) + .process(code, { + ...postcssOptions, + to: id, + from: id, + map: { + inline: false, + annotation: false, + prev: map, + }, + }) + + // record CSS dependencies from @imports + for (const message of postcssResult.messages) { + if (message.type === 'dependency') { + deps.add(message.file as string) + } else if (message.type === 'dir-dependency') { + // https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#3-dependencies + const { dir, glob: globPattern = '**' } = message + const pattern = + normalizePath(path.resolve(path.dirname(id), dir)) + `/` + globPattern + const files = glob.sync(pattern, { + ignore: ['**/node_modules/**'], + }) + for (let i = 0; i < files.length; i++) { + deps.add(files[i]) + } + if (server) { + // register glob importers so we can trigger updates on file add/remove + if (!(id in (server as any)._globImporters)) { + ;(server as any)._globImporters[id] = { + module: server.moduleGraph.getModuleById(id)!, + importGlobs: [], + } + } + ;(server as any)._globImporters[id].importGlobs.push({ + base: config.root, + pattern, + }) + } + } else if (message.type === 'warning') { + let msg = `[vite:css] ${message.text}` + if (message.line && message.column) { + msg += `\n${generateCodeFrame(code, { + line: message.line, + column: message.column, + })}` + } + config.logger.warn(chalk.yellow(msg)) + } + } + + return { + ast: postcssResult, + code: postcssResult.css, + map: postcssResult.map as any, + modules, + deps, + } +} + +interface PostCSSConfigResult { + options: Postcss.ProcessOptions + plugins: Postcss.Plugin[] +} + +async function resolvePostcssConfig( + config: ResolvedConfig +): Promise { + let result = postcssConfigCache.get(config) + if (result !== undefined) { + return result + } + + // inline postcss config via vite config + const inlineOptions = config.css?.postcss + if (isObject(inlineOptions)) { + const options = { ...inlineOptions } + + delete options.plugins + result = { + options, + plugins: inlineOptions.plugins || [], + } + } else { + try { + const searchPath = + typeof inlineOptions === 'string' ? inlineOptions : config.root + // @ts-ignore + result = await postcssrc({}, searchPath) + } catch (e: any) { + if (!/No PostCSS Config found/.test(e.message)) { + throw e + } + result = null + } + } + + postcssConfigCache.set(config, result) + return result +} + +type CssUrlReplacer = ( + url: string, + importer?: string +) => string | Promise +// https://drafts.csswg.org/css-syntax-3/#identifier-code-point +export const cssUrlRE = + /(?<=^|[^\w\-\u0080-\uffff])url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/ +const cssImageSetRE = /image-set\(([^)]+)\)/ + +const UrlRewritePostcssPlugin: Postcss.PluginCreator<{ + replacer: CssUrlReplacer +}> = (opts) => { + if (!opts) { + throw new Error('base or replace is required') + } + + return { + postcssPlugin: 'vite-url-rewrite', + Once(root) { + const promises: Promise[] = [] + root.walkDecls((declaration) => { + const isCssUrl = cssUrlRE.test(declaration.value) + const isCssImageSet = cssImageSetRE.test(declaration.value) + if (isCssUrl || isCssImageSet) { + const replacerForDeclaration = (rawUrl: string) => { + const importer = declaration.source?.input.file + return opts.replacer(rawUrl, importer) + } + const rewriterToUse = isCssUrl ? rewriteCssUrls : rewriteCssImageSet + promises.push( + rewriterToUse(declaration.value, replacerForDeclaration).then( + (url) => { + declaration.value = url + } + ) + ) + } + }) + if (promises.length) { + return Promise.all(promises) as any + } + }, + } +} +UrlRewritePostcssPlugin.postcss = true + +function rewriteCssUrls( + css: string, + replacer: CssUrlReplacer +): Promise { + return asyncReplace(css, cssUrlRE, async (match) => { + const [matched, rawUrl] = match + return await doUrlReplace(rawUrl, matched, replacer) + }) +} + +function rewriteCssImageSet( + css: string, + replacer: CssUrlReplacer +): Promise { + return asyncReplace(css, cssImageSetRE, async (match) => { + const [matched, rawUrl] = match + const url = await processSrcSet(rawUrl, ({ url }) => + doUrlReplace(url, matched, replacer) + ) + return `image-set(${url})` + }) +} +async function doUrlReplace( + rawUrl: string, + matched: string, + replacer: CssUrlReplacer +) { + let wrap = '' + const first = rawUrl[0] + if (first === `"` || first === `'`) { + wrap = first + rawUrl = rawUrl.slice(1, -1) + } + if (isExternalUrl(rawUrl) || isDataUrl(rawUrl) || rawUrl.startsWith('#')) { + return matched + } + + return `url(${wrap}${await replacer(rawUrl)}${wrap})` +} + +async function minifyCSS(css: string, config: ResolvedConfig) { + const { code, warnings } = await transform(css, { + loader: 'css', + minify: true, + target: config.build.cssTarget || undefined, + }) + if (warnings.length) { + const msgs = await formatMessages(warnings, { kind: 'warning' }) + config.logger.warn( + chalk.yellow(`warnings when minifying css:\n${msgs.join('\n')}`) + ) + } + return code +} + +// #1845 +// CSS @import can only appear at top of the file. We need to hoist all @import +// to top when multiple files are concatenated. +async function hoistAtImports(css: string) { + const postcss = await import('postcss') + return (await postcss.default([AtImportHoistPlugin]).process(css)).css +} + +const AtImportHoistPlugin: Postcss.PluginCreator = () => { + return { + postcssPlugin: 'vite-hoist-at-imports', + Once(root) { + const imports: Postcss.AtRule[] = [] + root.walkAtRules((rule) => { + if (rule.name === 'import') { + // record in reverse so that can simply prepend to preserve order + imports.unshift(rule) + } + }) + imports.forEach((i) => root.prepend(i)) + }, + } +} +AtImportHoistPlugin.postcss = true + +// Preprocessor support. This logic is largely replicated from @vue/compiler-sfc + +type PreprocessorAdditionalData = + | string + | ((source: string, filename: string) => string | Promise) + +type StylePreprocessorOptions = { + [key: string]: any + additionalData?: PreprocessorAdditionalData + filename: string + alias: Alias[] +} + +type SassStylePreprocessorOptions = StylePreprocessorOptions & Sass.Options + +type StylePreprocessor = ( + source: string, + root: string, + options: StylePreprocessorOptions, + resolvers: CSSAtImportResolvers +) => StylePreprocessorResults | Promise + +type SassStylePreprocessor = ( + source: string, + root: string, + options: SassStylePreprocessorOptions, + resolvers: CSSAtImportResolvers +) => StylePreprocessorResults | Promise + +export interface StylePreprocessorResults { + code: string + map?: object + errors: RollupError[] + deps: string[] +} + +const loadedPreprocessors: Partial> = {} + +function loadPreprocessor(lang: PreprocessLang.scss, root: string): typeof Sass +function loadPreprocessor(lang: PreprocessLang.sass, root: string): typeof Sass +function loadPreprocessor(lang: PreprocessLang.less, root: string): typeof Less +function loadPreprocessor( + lang: PreprocessLang.stylus, + root: string +): typeof Stylus +function loadPreprocessor(lang: PreprocessLang, root: string): any { + if (lang in loadedPreprocessors) { + return loadedPreprocessors[lang] + } + try { + // Search for the preprocessor in the root directory first, and fall back + // to the default require paths. + const fallbackPaths = require.resolve.paths?.(lang) || [] + const resolved = require.resolve(lang, { paths: [root, ...fallbackPaths] }) + return (loadedPreprocessors[lang] = require(resolved)) + } catch (e) { + throw new Error( + `Preprocessor dependency "${lang}" not found. Did you install it?` + ) + } +} + +// .scss/.sass processor +const scss: SassStylePreprocessor = async ( + source, + root, + options, + resolvers +) => { + const render = loadPreprocessor(PreprocessLang.sass, root).render + const internalImporter: Sass.Importer = (url, importer, done) => { + resolvers.sass(url, importer).then((resolved) => { + if (resolved) { + rebaseUrls(resolved, options.filename, options.alias) + .then((data) => done?.(data)) + .catch((data) => done?.(data)) + } else { + done?.(null) + } + }) + } + const importer = [internalImporter] + if (options.importer) { + Array.isArray(options.importer) + ? importer.push(...options.importer) + : importer.push(options.importer) + } + + const finalOptions: Sass.Options = { + ...options, + data: await getSource(source, options.filename, options.additionalData), + file: options.filename, + outFile: options.filename, + importer, + } + + try { + const result = await new Promise((resolve, reject) => { + render(finalOptions, (err, res) => { + if (err) { + reject(err) + } else { + resolve(res) + } + }) + }) + const deps = result.stats.includedFiles + + return { + code: result.css.toString(), + errors: [], + deps, + } + } catch (e: any) { + // normalize SASS error + e.id = e.file + e.frame = e.formatted + return { code: '', errors: [e], deps: [] } + } +} + +const sass: SassStylePreprocessor = (source, root, options, aliasResolver) => + scss( + source, + root, + { + ...options, + indentedSyntax: true, + }, + aliasResolver + ) + +/** + * relative url() inside \@imported sass and less files must be rebased to use + * root file as base. + */ +async function rebaseUrls( + file: string, + rootFile: string, + alias: Alias[] +): Promise { + file = path.resolve(file) // ensure os-specific flashes + + // fixed by xxxxxx 条件编译 + const contents = preprocessCss(fs.readFileSync(file, 'utf-8')) + + // in the same dir, no need to rebase + const fileDir = path.dirname(file) + const rootDir = path.dirname(rootFile) + if (fileDir === rootDir) { + return { file, contents } + } + // no url() + if (!cssUrlRE.test(contents)) { + return { file, contents } + } + const rebased = await rewriteCssUrls(contents, (url) => { + if (url.startsWith('/')) return url + // match alias, no need to rewrite + for (const { find } of alias) { + const matches = + typeof find === 'string' ? url.startsWith(find) : find.test(url) + if (matches) { + return url + } + } + const absolute = path.resolve(fileDir, url) + const relative = path.relative(rootDir, absolute) + return normalizePath(relative) + }) + return { + file, + contents: rebased, + } +} + +// .less +const less: StylePreprocessor = async (source, root, options, resolvers) => { + const nodeLess = loadPreprocessor(PreprocessLang.less, root) + const viteResolverPlugin = createViteLessPlugin( + nodeLess, + options.filename, + options.alias, + resolvers + ) + source = await getSource(source, options.filename, options.additionalData) + + let result: Less.RenderOutput | undefined + try { + result = await nodeLess.render(source, { + ...options, + plugins: [viteResolverPlugin, ...(options.plugins || [])], + }) + } catch (e) { + const error = e as Less.RenderError + // normalize error info + const normalizedError: RollupError = new Error(error.message || error.type) + normalizedError.loc = { + file: error.filename || options.filename, + line: error.line, + column: error.column, + } + return { code: '', errors: [normalizedError], deps: [] } + } + return { + code: result.css.toString(), + deps: result.imports, + errors: [], + } +} + +/** + * Less manager, lazy initialized + */ +let ViteLessManager: any + +function createViteLessPlugin( + less: typeof Less, + rootFile: string, + alias: Alias[], + resolvers: CSSAtImportResolvers +): Less.Plugin { + if (!ViteLessManager) { + ViteLessManager = class ViteManager extends less.FileManager { + resolvers + rootFile + alias + constructor( + rootFile: string, + resolvers: CSSAtImportResolvers, + alias: Alias[] + ) { + super() + this.rootFile = rootFile + this.resolvers = resolvers + this.alias = alias + } + override supports() { + return true + } + override supportsSync() { + return false + } + override async loadFile( + filename: string, + dir: string, + opts: any, + env: any + ): Promise { + const resolved = await this.resolvers.less( + filename, + path.join(dir, '*') + ) + if (resolved) { + const result = await rebaseUrls(resolved, this.rootFile, this.alias) + let contents: string + if (result && 'contents' in result) { + contents = result.contents + } else { + contents = fs.readFileSync(resolved, 'utf-8') + } + return { + filename: path.resolve(resolved), + contents, + } + } else { + return super.loadFile(filename, dir, opts, env) + } + } + } + } + + return { + install(_, pluginManager) { + pluginManager.addFileManager( + new ViteLessManager(rootFile, resolvers, alias) + ) + }, + minVersion: [3, 0, 0], + } +} + +// .styl +const styl: StylePreprocessor = async (source, root, options) => { + const nodeStylus = loadPreprocessor(PreprocessLang.stylus, root) + // Get source with preprocessor options.additionalData. Make sure a new line separator + // is added to avoid any render error, as added stylus content may not have semi-colon separators + source = await getSource( + source, + options.filename, + options.additionalData, + '\n' + ) + // Get preprocessor options.imports dependencies as stylus + // does not return them with its builtin `.deps()` method + const importsDeps = (options.imports ?? []).map((dep: string) => + path.resolve(dep) + ) + try { + const ref = nodeStylus(source, options) + + // if (map) ref.set('sourcemap', { inline: false, comment: false }) + + const result = ref.render() + + // Concat imports deps with computed deps + const deps = [...ref.deps(), ...importsDeps] + + return { code: result, errors: [], deps } + } catch (e: any) { + return { code: '', errors: [e], deps: [] } + } +} + +function getSource( + source: string, + filename: string, + additionalData?: PreprocessorAdditionalData, + sep: string = '' +): string | Promise { + if (!additionalData) return source + if (typeof additionalData === 'function') { + return additionalData(source, filename) + } + return additionalData + sep + source +} + +const preProcessors = Object.freeze({ + [PreprocessLang.less]: less, + [PreprocessLang.sass]: sass, + [PreprocessLang.scss]: scss, + [PreprocessLang.styl]: styl, + [PreprocessLang.stylus]: styl, +}) + +function isPreProcessor(lang: any): lang is PreprocessLang { + return lang && lang in preProcessors +} diff --git a/packages/uni-cli-shared/src/vite/utils/plugin.ts b/packages/uni-cli-shared/src/vite/utils/plugin.ts index 0b3cdfbba313fc00dc0c0c7f3aeac59f14b11280..8e1e124433116686a4d2b5e709d3707e2906f902 100644 --- a/packages/uni-cli-shared/src/vite/utils/plugin.ts +++ b/packages/uni-cli-shared/src/vite/utils/plugin.ts @@ -1,6 +1,11 @@ import type { Plugin, ResolvedConfig } from 'vite' import { assetPlugin } from '../plugins/vitejs/plugins/asset' import { cssPlugin, cssPostPlugin } from '../plugins/vitejs/plugins/css' +import { assetPlugin as h5AssetPlugin } from '../plugins/vitejs/plugins/h5Asset' +import { + cssPlugin as h5CssPlugin, + cssPostPlugin as h5CssPostPlugin, +} from '../plugins/vitejs/plugins/h5Css' export type CreateUniViteFilterPlugin = ( opts: UniViteFilterPluginOptions ) => Plugin @@ -9,6 +14,18 @@ export interface UniViteFilterPluginOptions { filter: (id: string) => boolean } +export function injectH5AssetPlugin(config: ResolvedConfig) { + replacePlugins([h5AssetPlugin(config)], config) +} + +export function injectH5CssPlugin(config: ResolvedConfig) { + replacePlugins([h5CssPlugin(config)], config) +} + +export function injectH5CssPostPlugin(config: ResolvedConfig) { + replacePlugins([h5CssPostPlugin(config)], config) +} + export function injectAssetPlugin(config: ResolvedConfig) { replacePlugins([assetPlugin(config)], config) } diff --git a/packages/uni-h5-vite/src/plugin/index.ts b/packages/uni-h5-vite/src/plugin/index.ts index 0dd24f6c30837937a38a5299439c3aba6f922071..fe806360f6a970ac64edb4e62643e1d358bea857 100644 --- a/packages/uni-h5-vite/src/plugin/index.ts +++ b/packages/uni-h5-vite/src/plugin/index.ts @@ -1,5 +1,10 @@ import type { ResolvedConfig } from 'vite' -import { UniVitePlugin } from '@dcloudio/uni-cli-shared' +import { + injectH5AssetPlugin, + injectH5CssPlugin, + injectH5CssPostPlugin, + UniVitePlugin, +} from '@dcloudio/uni-cli-shared' import { createHandleHotUpdate } from './handleHotUpdate' import { createTransformIndexHtml } from './transformIndexHtml' import { createConfigureServer } from './configureServer' @@ -21,6 +26,10 @@ export function uniH5PLugin(): UniVitePlugin { configOptions.resolvedConfig = config // TODO 禁止 optimizeDeps ;(config as any).cacheDir = '' + + injectH5AssetPlugin(config) + injectH5CssPlugin(config) + injectH5CssPostPlugin(config) }, configureServer: createConfigureServer(), handleHotUpdate: createHandleHotUpdate(), diff --git a/packages/uni-h5/dist/uni-h5.cjs.js b/packages/uni-h5/dist/uni-h5.cjs.js index 04b8b23fb33b2ea3346bf4b08589fc9a465eae24..8023f48b526976913532b23a4ee8fe17434591c1 100644 --- a/packages/uni-h5/dist/uni-h5.cjs.js +++ b/packages/uni-h5/dist/uni-h5.cjs.js @@ -10765,10 +10765,11 @@ function usePageHeadSearchInput({ }; } var _export_sfc = (sfc, props2) => { + const target = sfc.__vccOpts || sfc; for (const [key, val] of props2) { - sfc[key] = val; + target[key] = val; } - return sfc; + return target; }; const _sfc_main = { name: "PageRefresh", diff --git a/packages/uni-h5/dist/uni-h5.es.js b/packages/uni-h5/dist/uni-h5.es.js index 5b392f6429643e28ce765ce43791f3d1076d4a12..7cdceb99c6cd7365e6a8d257b6d348a5d281b5d3 100644 --- a/packages/uni-h5/dist/uni-h5.es.js +++ b/packages/uni-h5/dist/uni-h5.es.js @@ -21564,10 +21564,11 @@ function usePageHeadSearchInput({ }; } var _export_sfc = (sfc, props2) => { + const target = sfc.__vccOpts || sfc; for (const [key, val] of props2) { - sfc[key] = val; + target[key] = val; } - return sfc; + return target; }; const _sfc_main = { name: "PageRefresh",