diff --git a/packages/uni-app-vite/src/nvue/index.ts b/packages/uni-app-vite/src/nvue/index.ts index 9e550ccc0bd8fc69a8dc47e5fb7636f4ba63ffb5..4d550448813c94782cffd9a6ac4cfce56caa02ba 100644 --- a/packages/uni-app-vite/src/nvue/index.ts +++ b/packages/uni-app-vite/src/nvue/index.ts @@ -9,6 +9,7 @@ import { uniEasycomPlugin } from '../plugins/easycom' import { uniManifestJsonPlugin } from '../plugins/manifestJson' import { uniStatsPlugin } from '../plugins/stats' import { uniAppNVuePlugin } from './plugin' +import { uniAppCssPlugin } from './plugins/appCss' import { uniEsbuildPlugin } from './plugins/esbuild' import { uniMainJsPlugin } from './plugins/mainJs' import { uniPagesJsonPlugin } from './plugins/pagesJson' @@ -17,6 +18,7 @@ export { initNVueNodeTransforms } from './plugin' export function initNVuePlugins() { return [ + uniAppCssPlugin(), uniEasycomPlugin({ exclude: UNI_EASYCOM_EXCLUDE }), uniHBuilderXConsolePlugin(), uniMainJsPlugin(), diff --git a/packages/uni-app-vite/src/nvue/plugins/appCss.ts b/packages/uni-app-vite/src/nvue/plugins/appCss.ts new file mode 100644 index 0000000000000000000000000000000000000000..607e20f01b52a7ea5e90a1c9fbbcfc26fc16b51d --- /dev/null +++ b/packages/uni-app-vite/src/nvue/plugins/appCss.ts @@ -0,0 +1,150 @@ +import type { Plugin } from 'vite' +import type { PluginContext, RollupError } from 'rollup' +import path from 'path' +import fs from 'fs-extra' +import qs from 'querystring' +import { + CompilerError, + parse, + SFCBlock, + SFCDescriptor, +} from '@vue/compiler-sfc' +import { hash, preNVueHtml, preNVueJs } from '@dcloudio/uni-cli-shared' + +declare module '@vue/compiler-sfc' { + interface SFCDescriptor { + id: string + } +} + +export const APP_CSS_JS = './app.css.js' +export function uniAppCssPlugin(): Plugin { + const inputDir = process.env.UNI_INPUT_DIR + return { + name: 'uni:app-nvue-app-style', + resolveId(id) { + if (id === APP_CSS_JS) { + return APP_CSS_JS + } + }, + load(id) { + if (id === APP_CSS_JS) { + return genAppStylesCode(inputDir, this) + } + }, + } +} + +const defaultAppStylesCode = `export const AppStyles = []` + +async function genAppStylesCode( + inputDir: string, + pluginContext: PluginContext +) { + const filename = path.resolve(inputDir, 'App.vue') + const descriptor = createAppDescriptor(filename, pluginContext) + if (!descriptor.styles.length) { + return defaultAppStylesCode + } + let stylesCode = `` + const styleVars: string[] = [] + for (let i = 0; i < descriptor.styles.length; i++) { + const style = descriptor.styles[i] + const src = style.src || descriptor.filename + const attrsQuery = attrsToQuery(style.attrs, 'css') + const srcQuery = style.src ? `&src=${descriptor.id}` : `` + const query = `?vue&type=style&index=${i}${srcQuery}&inline` + const styleRequest = src + query + attrsQuery + stylesCode += `\nimport _style_${i} from ${JSON.stringify(styleRequest)}` + styleVars.push(`_style_${i}`) + } + return `${stylesCode} +export const AppStyles = [${styleVars.join(',')}] +` +} + +function readAppCode(filename: string) { + if (!fs.existsSync(filename)) { + return `` + } + const source = fs.readFileSync(filename, 'utf8') + if (source.includes('#endif')) { + return preNVueJs(preNVueHtml(source)) + } + return source +} + +let appDescriptor: SFCDescriptor +function createAppDescriptor( + filename: string, + pluginContext: PluginContext +): SFCDescriptor { + const source = readAppCode(filename) + const id = hash(source) + if (!appDescriptor || appDescriptor.id !== id) { + const { descriptor, errors } = parse(source, { + filename, + }) + descriptor.id = id + if (errors.length) { + errors.forEach((error) => + pluginContext.error(createRollupError(filename, error)) + ) + } + appDescriptor = descriptor + } + return appDescriptor +} + +export function createRollupError( + id: string, + error: CompilerError | SyntaxError +): RollupError { + const { message, name, stack } = error + const rollupError: RollupError = { + id, + plugin: 'vue', + message, + name, + stack, + } + + if ('code' in error && error.loc) { + rollupError.loc = { + file: id, + line: error.loc.start.line, + column: error.loc.start.column, + } + } + + return rollupError +} + +// these are built-in query parameters so should be ignored +// if the user happen to add them as attrs +const ignoreList = ['id', 'index', 'src', 'type', 'lang', 'module'] + +function attrsToQuery( + attrs: SFCBlock['attrs'], + langFallback?: string, + forceLangFallback = false +): string { + let query = `` + for (const name in attrs) { + const value = attrs[name] + if (!ignoreList.includes(name)) { + query += `&${qs.escape(name)}${ + value ? `=${qs.escape(String(value))}` : `` + }` + } + } + if (langFallback || attrs.lang) { + query += + `lang` in attrs + ? forceLangFallback + ? `&lang.${langFallback}` + : `&lang.${attrs.lang}` + : `&lang.${langFallback}` + } + return query +} diff --git a/packages/uni-app-vite/src/nvue/plugins/esbuild.ts b/packages/uni-app-vite/src/nvue/plugins/esbuild.ts index 76c32a0b7b9e80b1264429ffcdf666ae4ead3819..de687697e45482a6ae32b7ee9d35a478de719e66 100644 --- a/packages/uni-app-vite/src/nvue/plugins/esbuild.ts +++ b/packages/uni-app-vite/src/nvue/plugins/esbuild.ts @@ -1,12 +1,16 @@ +import type { Plugin } from 'vite' +import type { BuildOptions, PluginBuild } from 'esbuild' + import path from 'path' import fs from 'fs-extra' import debug from 'debug' + import { transformWithEsbuild } from '@dcloudio/uni-cli-shared' -import type { BuildOptions, PluginBuild } from 'esbuild' -import type { Plugin } from 'vite' + import { nvueOutDir } from '../../utils' const debugEsbuild = debug('uni:app-nvue-esbuild') + export function uniEsbuildPlugin(): Plugin { let buildOptions: BuildOptions const outputDir = process.env.UNI_OUTPUT_DIR @@ -50,8 +54,11 @@ export function uniEsbuildPlugin(): Plugin { function buildNVuePage(filename: string, options: BuildOptions) { return transformWithEsbuild( - `import NVuePageComponent from './${filename}' -Vue.createApp(NVuePageComponent).mount('#root')`, + `import App from './${filename}' +import { AppStyles } from './app.css.js' +const app = Vue.createApp(App) +app.provide('__appStyles', Vue.useCssStyles(AppStyles)) +app.mount('#root')`, path.join(nvueOutDir(), 'main.js'), options ).then((res) => { diff --git a/packages/uni-app-vite/src/nvue/plugins/mainJs.ts b/packages/uni-app-vite/src/nvue/plugins/mainJs.ts index ff75540fad1ac13e62e0a302972b5a05669d68f0..89ad2deb31560927094e5a6756509bd6ab4432cd 100644 --- a/packages/uni-app-vite/src/nvue/plugins/mainJs.ts +++ b/packages/uni-app-vite/src/nvue/plugins/mainJs.ts @@ -1,4 +1,5 @@ import { defineUniMainJsPlugin } from '@dcloudio/uni-cli-shared' +import { APP_CSS_JS } from './appCss' export function uniMainJsPlugin() { return defineUniMainJsPlugin((opts) => { @@ -17,7 +18,7 @@ export function uniMainJsPlugin() { } } return { - code: `import './pages.json.js'`, + code: `import './pages.json.js';import('${APP_CSS_JS}').then(()=>{})`, map: { mappings: '' }, } } diff --git a/packages/uni-app-vue/dist/nvue.runtime.esm.dev.js b/packages/uni-app-vue/dist/nvue.runtime.esm.dev.js index 5e515e9ce0413a13605380cc7d078c0acb350ddf..39492e1f8de1eb6a6c6259cd313efc734a7d268a 100644 --- a/packages/uni-app-vue/dist/nvue.runtime.esm.dev.js +++ b/packages/uni-app-vue/dist/nvue.runtime.esm.dev.js @@ -10982,39 +10982,50 @@ export function nvueFactory(exports, document) { nextSibling: node => node.nextSibling }; - function isUndef(val) { - return val === undefined || val === null; + function useCssStyles(styles) { + var normalized = {}; + + if (isArray(styles)) { + styles.forEach(style => { + Object.keys(style).forEach(name => { + if (hasOwn(normalized, name)) { + extend(normalized[name], style[name]); + } else { + normalized[name] = style[name]; + } + }); + }); + } + + return normalized; } function parseStylesheet(_ref23) { var { - type + type, + vnode: { + appContext + } } = _ref23; if (!type.__styles) { - var { - styles - } = type; - var normalizedStyles = {}; - - if (isArray(styles)) { - styles.forEach(style => { - Object.keys(style).forEach(name => { - if (hasOwn(normalizedStyles, name)) { - extend(normalizedStyles[name], style[name]); - } else { - normalizedStyles[name] = style[name]; - } - }); - }); + var styles = []; + + if (appContext) { + styles.push(appContext.provides.__appStyles); } - type.__styles = normalizedStyles; + type.styles.forEach(style => styles.push(style)); + type.__styles = useCssStyles(styles); } return type.__styles; } + function isUndef(val) { + return val === undefined || val === null; + } + function patchAttr(el, key, value) { var instance = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; @@ -11527,6 +11538,7 @@ export function nvueFactory(exports, document) { unref: unref, useAttrs: useAttrs, useCssModule: useCssModule, + useCssStyles: useCssStyles, useCssVars: useCssVars, useSSRContext: useSSRContext, useSlots: useSlots, diff --git a/packages/uni-app-vue/dist/nvue.runtime.esm.prod.js b/packages/uni-app-vue/dist/nvue.runtime.esm.prod.js index f535aef0c0f26f9c3fa5b64f2d205aeafce63c40..bed9501e2030946b1e7e0d61f87d106da52aee23 100644 --- a/packages/uni-app-vue/dist/nvue.runtime.esm.prod.js +++ b/packages/uni-app-vue/dist/nvue.runtime.esm.prod.js @@ -9157,39 +9157,50 @@ export function nvueFactory(exports, document) { nextSibling: node => node.nextSibling }; - function isUndef(val) { - return val === undefined || val === null; + function useCssStyles(styles) { + var normalized = {}; + + if (isArray(styles)) { + styles.forEach(style => { + Object.keys(style).forEach(name => { + if (hasOwn(normalized, name)) { + extend(normalized[name], style[name]); + } else { + normalized[name] = style[name]; + } + }); + }); + } + + return normalized; } function parseStylesheet(_ref23) { var { - type + type, + vnode: { + appContext + } } = _ref23; if (!type.__styles) { - var { - styles - } = type; - var normalizedStyles = {}; - - if (isArray(styles)) { - styles.forEach(style => { - Object.keys(style).forEach(name => { - if (hasOwn(normalizedStyles, name)) { - extend(normalizedStyles[name], style[name]); - } else { - normalizedStyles[name] = style[name]; - } - }); - }); + var styles = []; + + if (appContext) { + styles.push(appContext.provides.__appStyles); } - type.__styles = normalizedStyles; + type.styles.forEach(style => styles.push(style)); + type.__styles = useCssStyles(styles); } return type.__styles; } + function isUndef(val) { + return val === undefined || val === null; + } + function patchAttr(el, key, value) { var instance = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; @@ -9698,6 +9709,7 @@ export function nvueFactory(exports, document) { unref: unref, useAttrs: useAttrs, useCssModule: useCssModule, + useCssStyles: useCssStyles, useCssVars: useCssVars, useSSRContext: useSSRContext, useSlots: useSlots, diff --git a/packages/uni-app-vue/lib/nvue.runtime.esm.js b/packages/uni-app-vue/lib/nvue.runtime.esm.js index 01834880ef87e492c7eace056bb29968537eba4f..1f724f518097fc0cd82d5491fe91decff8bf4775 100644 --- a/packages/uni-app-vue/lib/nvue.runtime.esm.js +++ b/packages/uni-app-vue/lib/nvue.runtime.esm.js @@ -8875,30 +8875,38 @@ const nodeOps = { nextSibling: node => node.nextSibling }; -function isUndef(val) { - return val === undefined || val === null; +function useCssStyles(styles) { + const normalized = {}; + if (isArray(styles)) { + styles.forEach(style => { + Object.keys(style).forEach(name => { + if (hasOwn(normalized, name)) { + extend(normalized[name], style[name]); + } + else { + normalized[name] = style[name]; + } + }); + }); + } + return normalized; } -function parseStylesheet({ type }) { +function parseStylesheet({ type, vnode: { appContext } }) { if (!type.__styles) { - const { styles } = type; - const normalizedStyles = {}; - if (isArray(styles)) { - styles.forEach(style => { - Object.keys(style).forEach(name => { - if (hasOwn(normalizedStyles, name)) { - extend(normalizedStyles[name], style[name]); - } - else { - normalizedStyles[name] = style[name]; - } - }); - }); + const styles = []; + if (appContext) { + styles.push(appContext.provides.__appStyles); } - type.__styles = normalizedStyles; + type.styles.forEach(style => styles.push(style)); + type.__styles = useCssStyles(styles); } return type.__styles; } +function isUndef(val) { + return val === undefined || val === null; +} + function patchAttr(el, key, value, instance = null) { if (instance) { value = transformAttr(el, key, value, instance); @@ -9219,4 +9227,4 @@ const createApp = ((...args) => { return app; }); -export { BaseTransition, Comment, EffectScope, Fragment, KeepAlive, ReactiveEffect, Static, Suspense, Teleport, Text, callWithAsyncErrorHandling, callWithErrorHandling, cloneVNode, compatUtils, computed$1 as computed, createApp, createBlock, createCommentVNode, createElementBlock, createBaseVNode as createElementVNode, createHydrationRenderer, createPropsRestProxy, createRenderer, createSlots, createStaticVNode, createTextVNode, createVNode, customRef, defineAsyncComponent, defineComponent, defineEmits, defineExpose, defineProps, devtools, effect, effectScope, getCurrentInstance, getCurrentScope, getTransitionRawChildren, guardReactiveProps, h, handleError, initCustomFormatter, inject, injectHook, isInSSRComponentSetup, isMemoSame, isProxy, isReactive, isReadonly, isRef, isRuntimeOnly, isShallow, isVNode, markRaw, mergeDefaults, mergeProps, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onServerPrefetch, onUnmounted, onUpdated, openBlock, popScopeId, provide, proxyRefs, pushScopeId, queuePostFlushCb, reactive, readonly, ref, registerRuntimeCompiler, render, renderList, renderSlot, resolveComponent, resolveDirective, resolveDynamicComponent, resolveFilter, resolveTransitionHooks, setBlockTracking, setDevtoolsHook, setTransitionHooks, shallowReactive, shallowReadonly, shallowRef, ssrContextKey, stop, toHandlers, toRaw, toRef, toRefs, transformVNodeArgs, triggerRef, unref, useAttrs, useCssModule, useCssVars, useSSRContext, useSlots, useTransitionState, version, warn$1 as warn, watch, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withDirectives, withMemo, withScopeId }; +export { BaseTransition, Comment, EffectScope, Fragment, KeepAlive, ReactiveEffect, Static, Suspense, Teleport, Text, callWithAsyncErrorHandling, callWithErrorHandling, cloneVNode, compatUtils, computed$1 as computed, createApp, createBlock, createCommentVNode, createElementBlock, createBaseVNode as createElementVNode, createHydrationRenderer, createPropsRestProxy, createRenderer, createSlots, createStaticVNode, createTextVNode, createVNode, customRef, defineAsyncComponent, defineComponent, defineEmits, defineExpose, defineProps, devtools, effect, effectScope, getCurrentInstance, getCurrentScope, getTransitionRawChildren, guardReactiveProps, h, handleError, initCustomFormatter, inject, injectHook, isInSSRComponentSetup, isMemoSame, isProxy, isReactive, isReadonly, isRef, isRuntimeOnly, isShallow, isVNode, markRaw, mergeDefaults, mergeProps, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onServerPrefetch, onUnmounted, onUpdated, openBlock, popScopeId, provide, proxyRefs, pushScopeId, queuePostFlushCb, reactive, readonly, ref, registerRuntimeCompiler, render, renderList, renderSlot, resolveComponent, resolveDirective, resolveDynamicComponent, resolveFilter, resolveTransitionHooks, setBlockTracking, setDevtoolsHook, setTransitionHooks, shallowReactive, shallowReadonly, shallowRef, ssrContextKey, stop, toHandlers, toRaw, toRef, toRefs, transformVNodeArgs, triggerRef, unref, useAttrs, useCssModule, useCssStyles, useCssVars, useSSRContext, useSlots, useTransitionState, version, warn$1 as warn, watch, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withDirectives, withMemo, withScopeId };