提交 68735cbc 编写于 作者: fxy060608's avatar fxy060608

fix(h5): wxs hmr

上级 88cf9b1b
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', () => {
......
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', () => {
......
......@@ -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')
......@@ -50,7 +51,8 @@ export function uniCssScopedPlugin(
if (scoped) {
code = addScoped(code)
}
return code
// 处理 block, wxs 等
return parseVueCode(code).code
}
},
}
......
export * from './transforms'
export * from './utils'
export * from './parse'
export { transformUniH5Jsx } from './babel'
export { isExternalUrl } from './transforms/templateUtils'
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 = '</block>'.length
const BLOCK_START_LEN = '<block'.length
function parseBlockNode(code: string, blocks: ElementNode[]) {
const magicString = new MagicString(code)
blocks.forEach(({ loc }) => {
const startOffset = loc.start.offset
const endOffset = loc.end.offset
magicString.overwrite(
startOffset,
startOffset + BLOCK_START_LEN,
'<template'
)
magicString.overwrite(endOffset - BLOCK_END_LEN, endOffset, '</template>')
})
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 = '</script>'.length
const SCRIPT_START_LEN = '<script'.length
function parseWxsNode(code: string, nodes: ElementNode[]) {
const magicString = new MagicString(code)
nodes.forEach(({ loc, props }) => {
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
) // <renderjs or <wxs
magicString.overwrite(
langStartOffset,
langStartOffset + ('lang="' + lang + '"').length,
''
) // remove lang="renderjs" or lang="wxs"
magicString.overwrite(
endOffset - SCRIPT_END_LEN,
endOffset,
'</' + lang + '>'
) //</renderjs> or </wxs>
if (moduleAttr) {
const moduleStartOffset = moduleAttr.loc.start.offset
magicString.overwrite(
moduleStartOffset,
moduleStartOffset + 'module'.length,
'name'
) // module="echarts" => name="echarts"
}
})
return magicString.toString()
}
......@@ -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'
......@@ -36,4 +37,5 @@ export default [
uniSetupPlugin(),
uniRenderjsPlugin(),
uniH5Plugin(),
uniPostVuePlugin(),
]
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]
}
import path from 'path'
import debug from 'debug'
import { Plugin } from 'vite'
import {
RootNode,
NodeTypes,
ParentNode,
ElementNode,
AttributeNode,
TemplateChildNode,
} from '@vue/compiler-core'
import { MagicString } from '@vue/compiler-sfc'
import {
clearMiniProgramTemplateFilter,
EXTNAME_VUE,
normalizeMiniProgramFilename,
parseVueRequest,
removeExt,
isElementNode,
parseVue,
parseVueCode,
} from '@dcloudio/uni-cli-shared'
const debugPreVue = debug('uni:pre-vue')
const BLOCK_RE = /<\/block>/
const WXS_LANG_RE = /lang=["|'](renderjs|wxs)["|']/
const WXS_ATTRS = ['wxs', 'renderjs']
export function uniPreVuePlugin(): Plugin {
return {
name: 'uni:pre-vue',
......@@ -44,156 +25,10 @@ export function uniPreVuePlugin(): Plugin {
clearMiniProgramTemplateFilter(
removeExt(normalizeMiniProgramFilename(id, process.env.UNI_INPUT_DIR))
)
const hasBlock = BLOCK_RE.test(code)
const hasWxs = WXS_LANG_RE.test(code)
if (!hasBlock && !hasWxs) {
return
}
debugPreVue(id)
const watchFiles: string[] = []
const errors: SyntaxError[] = []
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) {
const resolveId = await this.resolve(srcProp.value.content, id)
if (resolveId) {
watchFiles.push(resolveId.id)
}
}
}
}
// if (errors.length) {
// this.error(errors.join('\n'))
// }
watchFiles.forEach((file) => this.addWatchFile(file))
return {
code, // 暂不提供sourcemap,意义不大
code: parseVueCode(code).code, // 暂不提供sourcemap,意义不大
map: null,
}
},
}
}
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 = '</block>'.length
const BLOCK_START_LEN = '<block'.length
function parseBlockNode(code: string, blocks: ElementNode[]) {
const magicString = new MagicString(code)
blocks.forEach(({ loc }) => {
const startOffset = loc.start.offset
const endOffset = loc.end.offset
magicString.overwrite(
startOffset,
startOffset + BLOCK_START_LEN,
'<template'
)
magicString.overwrite(endOffset - BLOCK_END_LEN, endOffset, '</template>')
})
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 = '</script>'.length
const SCRIPT_START_LEN = '<script'.length
function parseWxsNode(code: string, nodes: ElementNode[]) {
const magicString = new MagicString(code)
nodes.forEach(({ loc, props }) => {
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
) // <renderjs or <wxs
magicString.overwrite(
langStartOffset,
langStartOffset + ('lang="' + lang + '"').length,
''
) // remove lang="renderjs" or lang="wxs"
magicString.overwrite(
endOffset - SCRIPT_END_LEN,
endOffset,
'</' + lang + '>'
) //</renderjs> or </wxs>
if (moduleAttr) {
const moduleStartOffset = moduleAttr.loc.start.offset
magicString.overwrite(
moduleStartOffset,
moduleStartOffset + 'module'.length,
'name'
) // module="echarts" => name="echarts"
}
})
return magicString.toString()
}
......@@ -20,7 +20,16 @@ interface PluginConfig {
}
}
export function initPluginUniOptions(UniVitePlugins: UniVitePlugin[]) {
export function initPluginUniOptions(UniVitePlugins: UniVitePlugin[]): {
compiler?: TemplateCompiler
copyOptions: {
assets: string[]
targets: UniViteCopyPluginTarget[]
}
transformEvent: Record<string, string>
compilerOptions: Required<Required<UniVitePlugin>['uni']>['compilerOptions']
jsxOptions: Required<Required<UniVitePlugin>['uni']>['jsxOptions']
} {
const assets: string[] = []
const targets: UniViteCopyPluginTarget[] = []
const transformEvent: Record<string, string> = Object.create(null)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册