Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 80 additions & 6 deletions packages/webpack-plugin/lib/style-compiler/strip-conditional.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,90 @@ function traverseAndEvaluate(ast, defs) {
}

/**
*
*处理普通字符串的内部方法
* @param {string} content
* @param {Record<string, any>} defs
* @returns
*/
function stripCondition(content, defs) {
function doStripCondition(content, defs) {
const ast = parse(content)
return traverseAndEvaluate(ast, defs)
}

/**
* 检测内容中是否包含条件编译指令
* @param {string} content
* @returns {boolean}
*/
function hasConditionalDirective(content) {
const regex = /(?:\/\*\s*@mpx-(if|elif|else|endif)(?:\s*\([\s\S]*?\))?\s*\*\/)|(?:\/\/\s*@mpx-(if|elif|else|endif)(?:\s*\(.*?\))?\s*)|(?:<!--\s*@mpx-(if|elif|else|endif)(?:\s*\([\s\S]*?\))?\s*-->)/
return regex.test(content)
}

/**
* 处理 .mpx 文件内容:
* 1. 仅对 <style> 块中的内容进行条件编译裁剪
* 2. 非 <style> 区域出现条件指令时通过 logStripError 报错并返回原始内容
* @param {string} content .mpx 文件完整内容
* @param {Record<string, any>} defs 条件变量定义
* @param {string=} path 文件路径
* @returns {string} 处理后的内容
*/
function stripConditionMpx(content, defs, path) {
// 匹配 <style ...> ... </style> 块(支持多个 style 块)
const styleRegex = /(<style[^>]*>)([\s\S]*?)(<\/style>)/gi
let lastIndex = 0
let result = ''
let match

while ((match = styleRegex.exec(content)) !== null) {
// match.index 到 match[1] 结束前的内容为非 style 区域
const beforeStyle = content.substring(lastIndex, match.index)
// 检测非 style 区域是否包含条件指令
if (hasConditionalDirective(beforeStyle)) {
logStripError(path, new Error('@mpx conditional directives are only allowed inside <style> blocks in .mpx files'))
return content
}
result += beforeStyle
// 处理 style 块内容
const styleOpen = match[1]
const styleContent = match[2]
const styleClose = match[3]
const strippedStyle = doStripCondition(styleContent, defs)
result += styleOpen + strippedStyle + styleClose
lastIndex = styleRegex.lastIndex
}

// 处理最后一个 style 块之后的剩余内容
const remaining = content.substring(lastIndex)
if (hasConditionalDirective(remaining)) {
logStripError(path, new Error('@mpx conditional directives are only allowed inside <style> blocks in .mpx files'))
return content
}
result += remaining
return result
}

function isMpxFile(path) {
return typeof path === 'string' && /\.mpx$/.test(path)
}

/**
* 统一条件编译裁剪入口:
* - 传入 .mpx 文件路径时,仅裁剪 <style> 块中的条件编译
* - 未传入 path 或传入普通样式文件路径时,按普通样式字符串裁剪
* @param {string} content
* @param {Record<string, any>} defs
* @param {string=} path
* @returns {string}
*/
function stripCondition(content, defs, path) {
if (isMpxFile(path)) {
return stripConditionMpx(content, defs, path)
}
return doStripCondition(content, defs)
}

let proxyReadFileSync
let proxyReadFile
const rawReadFileSync = fs.readFileSync
Expand Down Expand Up @@ -199,10 +273,10 @@ function startFSStripForCss(defs) {
if (shouldStrip(path)) {
try {
if (typeof content === 'string') {
return stripCondition(content, defs)
return stripCondition(content, defs, path)
} else if (Buffer.isBuffer(content)) {
const str = content.toString('utf-8')
const result = stripCondition(str, defs)
const result = stripCondition(str, defs, path)
if (result !== str) {
return Buffer.from(result, 'utf-8')
}
Expand All @@ -229,11 +303,11 @@ function startFSStripForCss(defs) {
if (shouldStrip(path)) {
try {
if (typeof data === 'string') {
const result = stripCondition(data, defs)
const result = stripCondition(data, defs, path)
return callback(null, result)
} else if (Buffer.isBuffer(data)) {
const content = data.toString('utf-8')
const result = stripCondition(content, defs)
const result = stripCondition(content, defs, path)
if (result !== content) {
return callback(null, Buffer.from(result, 'utf-8'))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const postcss = require('postcss')
const stylus = require('stylus')
const { SourceMapConsumer } = require('source-map')
const { stripCondition } = require('../../../lib/style-compiler/strip-conditional')
const removeStripConditionalComments = require('../../../lib/style-compiler/plugins/remove-strip-conditional-comments')
const { parseComponent } = require('../../../lib/template-compiler/compiler')
const { STYLE_PAD_PLACEHOLDER } = require('../../../lib/utils/const')
const { stripCondition, registerStripCompilation } = require('../../../lib/style-compiler/strip-conditional')

describe('strip-conditional unit tests', () => {
describe('stripCondition logic', () => {
Expand Down Expand Up @@ -359,4 +359,143 @@ describe('strip-conditional unit tests', () => {
})
})
})

describe('stripCondition for .mpx', () => {
const defs = {
__mpx_mode__: 'wx',
platform: 'wx',
theme: 'dark'
}

it('should only strip conditions inside <style> blocks', () => {
const input = `<template>
<view class="container">Hello</view>
</template>
<style>
/* @mpx-if (platform === 'wx') */
.wx-only { color: red; }
/* @mpx-endif */
/* @mpx-if (platform === 'ali') */
.ali-only { color: blue; }
/* @mpx-endif */
</style>`
const result = stripCondition(input, defs, '/test/app.mpx')
expect(result).toContain('<view class="container">Hello</view>')
expect(result).toContain('.wx-only { color: red; }')
expect(result).not.toContain('.ali-only { color: blue; }')
})

it('should handle multiple <style> blocks', () => {
const input = `<template>
<view>Test</view>
</template>
<style>
/* @mpx-if (platform === 'wx') */
.block1 { color: red; }
/* @mpx-endif */
</style>
<style lang="less">
/* @mpx-if (theme === 'dark') */
.block2 { background: #000; }
/* @mpx-endif */
/* @mpx-if (theme === 'light') */
.block3 { background: #fff; }
/* @mpx-endif */
</style>`
const result = stripCondition(input, defs, '/test/app.mpx')
expect(result).toContain('.block1 { color: red; }')
expect(result).toContain('.block2 { background: #000; }')
expect(result).not.toContain('.block3 { background: #fff; }')
})

it('should return original content and log error when conditional directive appears in <template> section', () => {
const compilation = { errors: [] }
registerStripCompilation(compilation)
const input = `<template>
<!-- @mpx-if (platform === 'wx') -->
<view>Wx Only</view>
<!-- @mpx-endif -->
</template>
<style>
.container { color: red; }
</style>`
const result = stripCondition(input, defs, '/test/app.mpx')
expect(result).toBe(input)
expect(compilation.errors).toHaveLength(1)
expect(compilation.errors[0].file).toBe('/test/app.mpx')
expect(compilation.errors[0].message).toContain('@mpx conditional directives are only allowed inside <style> blocks in .mpx files')
registerStripCompilation(null)
})

it('should return original content and log error when conditional directive appears in <script> section', () => {
const input = `<template>
<view>Test</view>
</template>
<script>
// @mpx-if (platform === 'wx')
console.log('wx')
// @mpx-endif
</script>
<style>
.container { color: red; }
</style>`
const result = stripCondition(input, defs, '/test/app.mpx')
expect(result).toBe(input)
})

it('should return original content and log error when conditional directive appears after last </style>', () => {
const input = `<style>
.a { color: red; }
</style>
/* @mpx-if (platform === 'wx') */
.orphan { color: green; }
/* @mpx-endif */`
const result = stripCondition(input, defs, '/test/app.mpx')
expect(result).toBe(input)
})

it('should preserve non-style content unchanged', () => {
const input = `<template>
<view class="box">{{message}}</view>
</template>
<script>
import { createComponent } from '@mpxjs/core'
createComponent({ data: { message: 'hi' } })
</script>
<style>
/* @mpx-if (platform === 'wx') */
.box { padding: 10px; }
/* @mpx-endif */
</style>`
const result = stripCondition(input, defs, '/test/app.mpx')
expect(result).toContain('<view class="box">{{message}}</view>')
expect(result).toContain("import { createComponent } from '@mpxjs/core'")
expect(result).toContain('.box { padding: 10px; }')
})

it('should work with .mpx file that has no <style> and no directives', () => {
const input = `<template>
<view>Hello</view>
</template>
<script>
console.log('test')
</script>`
const result = stripCondition(input, defs, '/test/app.mpx')
expect(result).toBe(input)
})

it('should handle style block with attributes correctly', () => {
const input = `<template>
<view>Test</view>
</template>
<style lang="stylus" scoped>
/* @mpx-if (platform === 'wx') */
.styled { font-size: 14px; }
/* @mpx-endif */
</style>`
const result = stripCondition(input, defs, '/test/app.mpx')
expect(result).toContain('.styled { font-size: 14px; }')
expect(result).toContain('<style lang="stylus" scoped>')
})
})
})
Loading