diff --git a/packages/vite-plugin-uni/__tests__/__snapshots__/block.spec.ts.snap b/packages/uni-cli-shared/__tests__/__snapshots__/block.spec.ts.snap similarity index 100% rename from packages/vite-plugin-uni/__tests__/__snapshots__/block.spec.ts.snap rename to packages/uni-cli-shared/__tests__/__snapshots__/block.spec.ts.snap diff --git a/packages/vite-plugin-uni/__tests__/__snapshots__/wxs.spec.ts.snap b/packages/uni-cli-shared/__tests__/__snapshots__/wxs.spec.ts.snap similarity index 100% rename from packages/vite-plugin-uni/__tests__/__snapshots__/wxs.spec.ts.snap rename to packages/uni-cli-shared/__tests__/__snapshots__/wxs.spec.ts.snap diff --git a/packages/vite-plugin-uni/__tests__/block.spec.ts b/packages/uni-cli-shared/__tests__/block.spec.ts similarity index 82% rename from packages/vite-plugin-uni/__tests__/block.spec.ts rename to packages/uni-cli-shared/__tests__/block.spec.ts index b4af29fe4fd9ed89f77e9fd487004cf0918ec163..6c316871d1210449aa2d9d730562bafa76bd1938 100644 --- a/packages/vite-plugin-uni/__tests__/block.spec.ts +++ b/packages/uni-cli-shared/__tests__/block.spec.ts @@ -1,5 +1,5 @@ -import { parseVue } from '@dcloudio/uni-cli-shared' -import { parseBlockCode } from '../src/configResolved/plugins/preVue' +import { parseVue } from '../src/vite' +import { parseBlockCode } from '../src/vue' describe('block', () => { test('parseBlockCode', () => { diff --git a/packages/vite-plugin-uni/__tests__/wxs.spec.ts b/packages/uni-cli-shared/__tests__/wxs.spec.ts similarity index 88% rename from packages/vite-plugin-uni/__tests__/wxs.spec.ts rename to packages/uni-cli-shared/__tests__/wxs.spec.ts index 6cf8993db9919de804e1edfb79e75e68fa93829a..d6c2c68ef5899f62ffcc0e3df3c9007705e50ec1 100644 --- a/packages/vite-plugin-uni/__tests__/wxs.spec.ts +++ b/packages/uni-cli-shared/__tests__/wxs.spec.ts @@ -1,8 +1,5 @@ -import { parseVue } from '@dcloudio/uni-cli-shared' -import { - parseWxsCode, - parseWxsNodes, -} from '../src/configResolved/plugins/preVue' +import { parseVue } from '../src/vite' +import { parseWxsCode, parseWxsNodes } from '../src/vue' describe('wxs', () => { test('parseWxsCode', () => { diff --git a/packages/uni-cli-shared/src/json/app/pages/code.ts b/packages/uni-cli-shared/src/json/app/pages/code.ts index 583040bdb340ba3758c3e3645f6382db6656e33c..10393f7d9c80d23893d274943d4899a94dec8c35 100644 --- a/packages/uni-cli-shared/src/json/app/pages/code.ts +++ b/packages/uni-cli-shared/src/json/app/pages/code.ts @@ -10,6 +10,9 @@ if (typeof Promise !== 'undefined' && !Promise.prototype.finally) { ) } } +if(uni&&uni.base64ToArrayBuffer){ + ArrayBuffer = uni.base64ToArrayBuffer('').constructor +} ` export const restoreGlobalCode = ` if(uni.restoreGlobal){ diff --git a/packages/uni-cli-shared/src/vite/plugins/cssScoped.ts b/packages/uni-cli-shared/src/vite/plugins/cssScoped.ts index 6587407e7a41ddc76b2359adeaad234598f99602..76205823854619a0ddc2690d5ee750e592839428 100644 --- a/packages/uni-cli-shared/src/vite/plugins/cssScoped.ts +++ b/packages/uni-cli-shared/src/vite/plugins/cssScoped.ts @@ -3,6 +3,7 @@ import debug from 'debug' import type { Plugin } from 'vite' import { EXTNAME_VUE } from '../../constants' import { preHtml, preJs } from '../../preprocess' +import { parseVueCode } from '../../vue/parse' const debugScoped = debug('uni:scoped') @@ -74,7 +75,8 @@ export function uniCssScopedPlugin( if (scoped) { code = addScoped(code) } - return code + // 处理 block, wxs 等 + return parseVueCode(code).code } }, } diff --git a/packages/uni-cli-shared/src/vue/index.ts b/packages/uni-cli-shared/src/vue/index.ts index 216aa3a1b7dd5bfd93e083e8cc411b3439f35c5c..5710cd8b14b6be875f8b19945d8b6e0c320d62f8 100644 --- a/packages/uni-cli-shared/src/vue/index.ts +++ b/packages/uni-cli-shared/src/vue/index.ts @@ -1,4 +1,5 @@ export * from './transforms' export * from './utils' +export * from './parse' export { transformUniH5Jsx } from './babel' export { isExternalUrl } from './transforms/templateUtils' diff --git a/packages/uni-cli-shared/src/vue/parse.ts b/packages/uni-cli-shared/src/vue/parse.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c8bd9ef270d2cd265f4febd2af68e894a6f9c38 --- /dev/null +++ b/packages/uni-cli-shared/src/vue/parse.ts @@ -0,0 +1,155 @@ +import { + ElementNode, + NodeTypes, + RootNode, + ParentNode, + TemplateChildNode, + AttributeNode, +} from '@vue/compiler-core' +import MagicString from 'magic-string' +import { isElementNode, parseVue } from '../vite/utils/ast' + +const BLOCK_RE = /<\/block>/ +const WXS_LANG_RE = /lang=["|'](renderjs|wxs)["|']/ +const WXS_ATTRS = ['wxs', 'renderjs'] + +export function parseVueCode(code: string) { + const hasBlock = BLOCK_RE.test(code) + const hasWxs = WXS_LANG_RE.test(code) + if (!hasBlock && !hasWxs) { + return { code } + } + const errors: SyntaxError[] = [] + const files: string[] = [] + let ast = parseVue(code, errors) + if (hasBlock) { + code = parseBlockCode(ast, code) + // 重新解析新的 code + ast = parseVue(code, errors) + } + if (hasWxs) { + const wxsNodes = parseWxsNodes(ast) + code = parseWxsCode(wxsNodes, code) + // add watch + for (const wxsNode of wxsNodes) { + const srcProp = wxsNode.props.find( + (prop) => prop.type === NodeTypes.ATTRIBUTE && prop.name === 'src' + ) as AttributeNode | undefined + if (srcProp && srcProp.value) { + files.push(srcProp.value.content) + } + } + } + return { code, files, errors } +} + +function traverseChildren({ children }: ParentNode, blockNodes: ElementNode[]) { + children.forEach((node) => traverseNode(node, blockNodes)) +} + +function traverseNode( + node: RootNode | TemplateChildNode, + blockNodes: ElementNode[] +) { + if (isElementNode(node) && node.tag === 'block') { + blockNodes.push(node) + } + if ( + node.type === NodeTypes.IF_BRANCH || + node.type === NodeTypes.FOR || + node.type === NodeTypes.ELEMENT || + node.type === NodeTypes.ROOT + ) { + traverseChildren(node, blockNodes) + } +} + +export function parseBlockCode(ast: RootNode, code: string) { + const blockNodes: ElementNode[] = [] + traverseNode(ast, blockNodes) + if (blockNodes.length) { + return parseBlockNode(code, blockNodes) + } + return code +} +const BLOCK_END_LEN = ''.length +const BLOCK_START_LEN = ' { + const startOffset = loc.start.offset + const endOffset = loc.end.offset + magicString.overwrite( + startOffset, + startOffset + BLOCK_START_LEN, + '') + }) + return magicString.toString() +} + +export function parseWxsNodes(ast: RootNode) { + return ast.children.filter( + (node) => + node.type === NodeTypes.ELEMENT && + node.tag === 'script' && + node.props.find( + (prop) => + prop.name === 'lang' && + prop.type === NodeTypes.ATTRIBUTE && + prop.value && + WXS_ATTRS.includes(prop.value.content) + ) + ) as ElementNode[] +} + +export function parseWxsCode(wxsNodes: ElementNode[], code: string) { + if (wxsNodes.length) { + code = parseWxsNode(code, wxsNodes) + } + return code +} + +const SCRIPT_END_LEN = ''.length +const SCRIPT_START_LEN = ' { + const langAttr = props.find((prop) => prop.name === 'lang') as AttributeNode + const moduleAttr = props.find( + (prop) => prop.name === 'module' + ) as AttributeNode + const startOffset = loc.start.offset + const endOffset = loc.end.offset + const lang = langAttr.value!.content + const langStartOffset = langAttr.loc.start.offset + magicString.overwrite( + startOffset, + startOffset + SCRIPT_START_LEN, + '<' + lang + ) // ' + ) // or + + if (moduleAttr) { + const moduleStartOffset = moduleAttr.loc.start.offset + magicString.overwrite( + moduleStartOffset, + moduleStartOffset + 'module'.length, + 'name' + ) // module="echarts" => name="echarts" + } + }) + return magicString.toString() +} diff --git a/packages/uni-h5-vite/src/index.ts b/packages/uni-h5-vite/src/index.ts index 80286884a6af8b8c4185a8dc8cce60a59abc9a44..9f42a7966b998278e5a9da35620df8486b8eec49 100644 --- a/packages/uni-h5-vite/src/index.ts +++ b/packages/uni-h5-vite/src/index.ts @@ -13,6 +13,7 @@ import { uniInjectPlugin } from './plugins/inject' import { uniMainJsPlugin } from './plugins/mainJs' import { uniManifestJsonPlugin } from './plugins/manifestJson' import { uniPagesJsonPlugin } from './plugins/pagesJson' +import { uniPostVuePlugin } from './plugins/postVue' import { uniRenderjsPlugin } from './plugins/renderjs' import { uniResolveIdPlugin } from './plugins/resolveId' import { uniSetupPlugin } from './plugins/setup' @@ -39,4 +40,5 @@ export default [ uniSetupPlugin(), uniRenderjsPlugin(), uniH5Plugin(), + uniPostVuePlugin(), ] diff --git a/packages/uni-h5-vite/src/plugins/postVue.ts b/packages/uni-h5-vite/src/plugins/postVue.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d804d6240b5a8fb9e773ce1a8d6f7a1c3bd255c --- /dev/null +++ b/packages/uni-h5-vite/src/plugins/postVue.ts @@ -0,0 +1,48 @@ +import path from 'path' +import { Plugin } from 'vite' + +import { EXTNAME_VUE, parseVueRequest } from '@dcloudio/uni-cli-shared' + +const WXS_RE = /vue&type=(wxs|renderjs)/ +export function uniPostVuePlugin(): Plugin { + return { + name: 'uni:post-vue', + apply: 'serve', + enforce: 'post', + async transform(code, id) { + const { filename, query } = parseVueRequest(id) + if (query.vue) { + return + } + if (!EXTNAME_VUE.includes(path.extname(filename))) { + return + } + if (!WXS_RE.test(code)) { + return + } + const hmrId = parseHmrId(code) + if (!hmrId) { + return + } + // TODO 内部解决 @vitejs/plugin-vue 自定义块外链热刷的问题 + // https://github.com/vitejs/vite/blob/main/packages/plugin-vue/src/main.ts#L387 + // 没有增加 src=descriptor.id + // 包含外链 wxs,renderjs + code = code.replace( + /vue&type=(wxs|renderjs)&index=([0-9]+)&src&/gi, + (_, type, index) => { + return `vue&type=${type}&index=${index}&src=${hmrId}&` + } + ) + return { + code: code, // 暂不提供sourcemap,意义不大 + map: null, + } + }, + } +} + +function parseHmrId(code: string) { + const matches = code.match(/_sfc_main.__hmrId = "(.*)"/) + return matches && matches[1] +} diff --git a/packages/vite-plugin-uni/src/utils/plugin.ts b/packages/vite-plugin-uni/src/utils/plugin.ts index 7d4daca2fec41070feefff5bb81580e10e7e6024..09f01f4891bb4b8e8d4bd965c68e5000fc138194 100644 --- a/packages/vite-plugin-uni/src/utils/plugin.ts +++ b/packages/vite-plugin-uni/src/utils/plugin.ts @@ -20,7 +20,17 @@ interface PluginConfig { } } -export function initPluginUniOptions(UniVitePlugins: UniVitePlugin[]) { +export function initPluginUniOptions(UniVitePlugins: UniVitePlugin[]): { + compiler?: TemplateCompiler + copyOptions: { + assets: string[] + targets: UniViteCopyPluginTarget[] + } + transformEvent: Record + compilerOptions: Required['uni']>['compilerOptions'] + jsxOptions: Required['uni']>['jsxOptions'] + styleOptions: Required['uni']>['styleOptions'] +} { const assets: string[] = [] const targets: UniViteCopyPluginTarget[] = [] const transformEvent: Record = Object.create(null)