vite-plugin-style-to-vw
Version:
一个可以将签内样式px转换vw的plugin
306 lines (280 loc) • 11 kB
text/typescript
import type { IdefaultsProp } from './index.d'
import fs from 'fs'
// import util from '@rollup/pluginutils'
let utils
// @ts-ignore
if (typeof window === "undefined") {
utils = await import('rollup-pluginutils')
}
// 默认参数
const defaultsProp: IdefaultsProp = {
allReplace: false, // 是否全局替换
unitToConvert: "px", // 需要转换的单位,默认为"px"
viewportWidth: 750, // 设计稿的视口宽度,如传入函数,函数的参数为当前处理的文件路径
unitPrecision: 5, // 单位转换后保留的精度
viewportUnit: "vw", // 希望使用的视口单位
fontViewportUnit: "vw", // 字体使用的视口单位
minPixelValue: 1, // 设置最小的转换数值,如果为 1 的话,只有大于 1 的值会被转换
attributeList: [], // 添加额外转换属性
include: [], // 包含哪些文件
exclude: [] // 排除哪些文件
};
// console.log('utils',utils.createFilter(['**/*.vue'],['node_modules/**/*']))
// 转换函数
function toFixed(number: number, precision: number) {
const multiplier = Math.pow(10, precision + 1),
wholeNumber = Math.floor(number * multiplier);
return (Math.round(wholeNumber / 10) * 10) / multiplier;
}
// px 转 vw 函数
function createPxReplace(
viewportSize: number,
minPixelValue: number,
unitPrecision: number,
viewportUnit: any
) {
return function ($0: any, $1: any) {
$1 = Number($1)
if (!$1) return $0;
const pixels = parseFloat($1);
if (pixels <= minPixelValue) return $0;
return toFixed((pixels / viewportSize) * 100, unitPrecision) + viewportUnit;
};
}
let extraOptions = defaultsProp
const templateReg = /<template>([\s\S]+)<\/template>/gi;
const pxGlobalReg = /(\d+)px/g;
const styleRegex = /style\s*(:|=)\s*(?:"([^"]*?)"|'([^']*?)'|{([^}]*)})/g;
const allReplace = /(\w+)=\s*(?:"([^"]*?)"|'([^']*?)'|{([^}]*)})/g
const styleSetPropertyReg = /style.setProperty\(.*\)/g
const dealAttributeList = (code, attributeList, customOptions) => {
if (Array.isArray(attributeList) && attributeList.length) {
for (let index = 0; index < attributeList.length; index++) {
let copyCode = code
const element = attributeList[index];
const styleRegex = new RegExp(`${element}\s*(:|=)\s*(?:"([^"]*?)"|'([^']*?)'|{([^}]*)})`, 'g')
const styleMatches = code.match(styleRegex) as string[]
if (styleMatches?.length) {
// 遍历每个 style 属性值,替换 px 为 vw
const newStyleValues: string[] = []
for (let i = 0; i < styleMatches.length; i++) {
const styleValue = styleMatches[i]
const newStyleValue = styleValue.replace(pxGlobalReg, (match) => {
return match.replace(
pxGlobalReg,
createPxReplace(
customOptions.viewportWidth,
customOptions.minPixelValue,
customOptions.unitPrecision,
customOptions.viewportUnit,
),
)
})
newStyleValues.push(newStyleValue)
}
// 将新的 style 属性值替换回原始字符串
let newStr = code
for (let i = 0; i < styleMatches.length; i++) {
newStr = newStr.replace(styleMatches[i], `${newStyleValues[i]}`)
}
code = code.replace(copyCode, newStr)
}
}
return code
} else {
return code
}
}
function vitePluginStyleToVw(customOptions: IdefaultsProp = defaultsProp) {
// @ts-ignore
if (typeof window !== "undefined") return console.warn('Please use it in node environment !!!')
// 合并
const copyDefaultsProp = Object.assign({}, defaultsProp)
// 生成自定义
customOptions = Object.assign(copyDefaultsProp, customOptions)
// 包含排除
const filter = utils.createFilter(customOptions.include,customOptions.exclude)
try {
// 异步写入文件
fs.writeFileSync(process.cwd() + "/node_modules/vite-plugin-style-to-vw/dist/file.json", JSON.stringify(customOptions), 'utf8');
// console.log('文件写入成功!');
} catch (error) {
// console.error('写入文件时出错:', error);
}
const isAllReplace = customOptions.allReplace
const replaceReg = isAllReplace ? allReplace : styleRegex
return {
// 插件名称
name: "vite-plugin-style-to-vw",
enforce: 'pre',
// 构建阶段的通用钩子:在每个传入模块请求时被调用:在每个传入模块请求时被调用,主要是用来转换单个模块
transform(code: any, id: any) {
if (filter(id)) {
if (/.vue$/.test(id)) {
let _source = ''
let _sourceCopy = ''
// template 模式
if (templateReg.test(code)) {
_source = code.match(templateReg)[0]
_sourceCopy = code.match(templateReg)[0]
} else if (code.includes('setup')) {
_source = code
_sourceCopy = code
}
if (replaceReg.test(_source)) {
const styleMatches = _source.match(replaceReg) as string[]
if (styleMatches?.length) {
// 遍历每个 style 属性值,替换 px 为 vw
const newStyleValues: string[] = []
for (let i = 0; i < styleMatches.length; i++) {
const styleValue = styleMatches[i]
const newStyleValue = styleValue.replace(pxGlobalReg, (match) => {
return match.replace(
pxGlobalReg,
createPxReplace(
customOptions.viewportWidth as number,
customOptions.minPixelValue as number,
customOptions.unitPrecision as number,
customOptions.viewportUnit as string,
),
)
})
newStyleValues.push(newStyleValue)
}
// 将新的 style 属性值替换回原始字符串
let newStr = _source
for (let i = 0; i < styleMatches.length; i++) {
newStr = newStr.replace(styleMatches[i], `${newStyleValues[i]}`)
}
code = code.replace(_sourceCopy, newStr)
}
}
if (!isAllReplace) {
// 处理属性列表
code = dealAttributeList(code, customOptions.attributeList, customOptions)
}
} else if (/\.tsx|\.jsx$/.test(id)) {
let _source = code
let _sourceCopy = code
// 匹配style
if (replaceReg.test(_source)) {
const styleMatches = _source.match(replaceReg) as string[]
if (styleMatches?.length) {
// 遍历每个 style 属性值,替换 px 为 vw
const newStyleValues: string[] = []
for (let i = 0; i < styleMatches.length; i++) {
const styleValue = styleMatches[i]
const newStyleValue = styleValue.replace(pxGlobalReg, (match) => {
return match.replace(
pxGlobalReg,
createPxReplace(
customOptions.viewportWidth as number,
customOptions.minPixelValue as number,
customOptions.unitPrecision as number,
customOptions.viewportUnit as string,
),
)
})
newStyleValues.push(newStyleValue)
}
// 将新的 style 属性值替换回原始字符串
let newStr = _source
for (let i = 0; i < styleMatches.length; i++) {
newStr = newStr.replace(styleMatches[i], `${newStyleValues[i]}`)
}
code = code.replace(_sourceCopy, newStr)
_sourceCopy = code
_source = code
}
}
// 处理属性列表
if (!isAllReplace) {
// 处理属性列表
code = dealAttributeList(code, customOptions.attributeList, customOptions)
}
// react 设置important
if (styleSetPropertyReg.test(_source)) {
const styleMatches = _source.match(styleSetPropertyReg) as string[]
if (styleMatches?.length) {
// 遍历每个 style 属性值,替换 px 为 vw
const newStyleValues: string[] = []
for (let i = 0; i < styleMatches.length; i++) {
const styleValue = styleMatches[i]
const newStyleValue = styleValue.replace(pxGlobalReg, (match) => {
return match.replace(
pxGlobalReg,
createPxReplace(
customOptions.viewportWidth as number,
customOptions.minPixelValue as number,
customOptions.unitPrecision as number,
customOptions.viewportUnit as string,
),
)
})
newStyleValues.push(newStyleValue)
}
// 将新的 style 属性值替换回原始字符串
let newStr = _source
for (let i = 0; i < styleMatches.length; i++) {
newStr = newStr.replace(styleMatches[i], `${newStyleValues[i]}`)
}
code = code.replace(code, newStr)
}
}
}
}
return {
code,
map: null, // Prevent missing sourcemap warning
}
},
};
}
export default vitePluginStyleToVw;
// @ts-ignore
if (typeof window !== "undefined") {
try {
// 读取文件,node端生成合并的配置,
// ts忽略下文
// @ts-ignore
const json = await import('/node_modules/vite-plugin-style-to-vw/dist/file.json')
extraOptions = json.default
} catch (error) {
}
}
// 手动转换成vw 100 ==> 13.33333 '100px' ==> '13.33333px
export const stylePxToVw = (code: string | number, customOptions: IdefaultsProp = {}) => {
const copyExtraOptions = Object.assign({}, extraOptions)
customOptions = Object.assign(copyExtraOptions, customOptions)
if (typeof code === 'number' || (typeof Number(code) === 'number' && !isNaN(Number(code
)))) {
const returnCode = code.toString().replace(/(\d+)/g, (match) => {
return match.replace(
/(\d+)/g,
createPxReplace(
customOptions.viewportWidth as number,
customOptions.minPixelValue as number,
customOptions.unitPrecision as number,
''
),
)
})
if (typeof code === 'number') {
return Number(returnCode)
} else {
return returnCode
}
} else {
return code.toString().replace(pxGlobalReg, (match) => {
return match.replace(
pxGlobalReg,
createPxReplace(
customOptions.viewportWidth as number,
customOptions.minPixelValue as number,
customOptions.unitPrecision as number,
customOptions.viewportUnit as string,
),
)
})
}
}