UNPKG

@tarojs/plugin-html

Version:

Taro 小程序端支持使用 HTML 标签的插件

150 lines (129 loc) 4.97 kB
import * as path from 'node:path' import { babelKit } from '@tarojs/helper' import { isArray, isString } from '@tarojs/shared' import type { IPluginContext, TaroPlatformBase } from '@tarojs/service' import type { IComponentConfig } from '@tarojs/taro/types/compile/hooks' const { types: t, generate, traverse } = babelKit export interface IOptions { pxtransformBlackList?: any[] modifyElements?(inline: string[], block: string[]): void enableSizeAPIs?: boolean } interface OnParseCreateElementArgs { nodeName: string componentConfig: IComponentConfig } export default (ctx: IPluginContext, options: IOptions) => { const inlineElements = ['i', 'abbr', 'select', 'acronym', 'small', 'bdi', 'kbd', 'strong', 'big', 'sub', 'sup', 'br', 'mark', 'meter', 'template', 'cite', 'object', 'time', 'code', 'output', 'u', 'data', 'picture', 'tt', 'datalist', 'var', 'dfn', 'del', 'q', 'em', 's', 'embed', 'samp', 'b'] const blockElements = ['body', 'svg', 'address', 'fieldset', 'li', 'span', 'article', 'figcaption', 'main', 'aside', 'figure', 'nav', 'blockquote', 'footer', 'ol', 'details', 'p', 'dialog', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'dd', 'header', 'section', 'div', 'hgroup', 'table', 'dl', 'hr', 'ul', 'dt', 'view', 'view-block'] const specialElements = ['slot', 'form', 'iframe', 'img', 'audio', 'video', 'canvas', 'a', 'input', 'label', 'textarea', 'progress', 'button'] patchMappingElements(ctx, options, inlineElements, blockElements) // 默认允许使用 getBoundingClientRect 等 API ctx.modifyWebpackChain(({ chain }) => { chain .plugin('definePlugin') .tap(args => { args[0].ENABLE_SIZE_APIS = options.enableSizeAPIs ?? true return args }) }) ctx.registerMethod({ name: 'onSetupClose', fn (platform: TaroPlatformBase) { injectRuntimePath(platform) } }) // 映射、收集使用到的小程序组件 ctx.onParseCreateElement(({ nodeName, componentConfig }: OnParseCreateElementArgs) => { if (!( inlineElements.includes(nodeName) || blockElements.includes(nodeName) || specialElements.includes(nodeName) )) return const simple = ['audio', 'button', 'canvas', 'form', 'label', 'progress', 'textarea', 'video'] const special = { a: ['navigator'], iframe: ['web-view'], img: ['image'], input: ['input', 'checkbox', 'radio'] } const includes = componentConfig.includes if (simple.includes(nodeName) && !includes.has(nodeName)) { includes.add(nodeName) } else if (nodeName in special) { const maps = special[nodeName] maps.forEach(item => { !includes.has(item) && includes.add(item) }) } }) // 修改 H5 postcss options ctx.modifyRunnerOpts(({ opts }) => { if (!opts?.platform) return modifyPostcssConfigs(opts, options, opts.platform === 'h5') }) } function injectRuntimePath (platform: TaroPlatformBase) { const injectedPath = '@tarojs/plugin-html/dist/runtime' if (isArray(platform.runtimePath)) { platform.runtimePath.push(injectedPath) } else if (isString(platform.runtimePath)) { platform.runtimePath = [platform.runtimePath, injectedPath] } } function modifyPostcssConfigs (config: Record<string, any>, options: IOptions, isH5?: boolean) { config.postcss ||= {} const postcssConfig = config.postcss if (!isH5) { postcssConfig.htmltransform ||= { enable: true } } if (options.pxtransformBlackList) { postcssConfig.pxtransform ||= { enable: true } const pxtransformConfig = postcssConfig.pxtransform if (pxtransformConfig.enable) { pxtransformConfig.config ||= {} const config = pxtransformConfig.config config.selectorBlackList ||= [] config.selectorBlackList = config.selectorBlackList.concat(options.pxtransformBlackList) } } } function patchMappingElements (ctx: IPluginContext, options: IOptions, inlineElements: string[], blockElements: string[]) { const helper = ctx.helper const filePath = path.resolve(__dirname, './runtime.js') const content = helper.fs.readFileSync(filePath).toString() const ast = babelKit.parse(content, { sourceType: 'unambiguous' }) if (t.isNode(ast)) { options.modifyElements?.(inlineElements, blockElements) traverse(ast, { VariableDeclarator (path) { const node = path.node const varId = node.id if (varId.type === 'Identifier') { if (varId.name === 'inlineElements') { node.init = getNewExpression(inlineElements) } if (varId.name === 'blockElements') { node.init = getNewExpression(blockElements) } } } }) const str = generate(ast).code helper.fs.writeFileSync(filePath, str) } } function getNewExpression (elements: string[]) { return t.newExpression( t.identifier('Set'), [t.arrayExpression(elements.map(el => t.stringLiteral(el)))] ) }