From 9b87c844262178c3d277feb48ba7ccc6c82f74c6 Mon Sep 17 00:00:00 2001 From: xuegan Date: Tue, 26 Nov 2024 20:40:53 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20resolve=20plugin=20=E4=B8=AD?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=AF=B9=20css=20=E6=96=87=E4=BB=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0query=EF=BC=8C=E9=81=BF=E5=85=8D=E5=90=8E=E7=BB=AD?= =?UTF-8?q?=E7=9B=B8=E5=85=B3loader=E6=97=A0=E6=B3=95=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webpack-plugin/lib/resolver/AddEnvPlugin.js | 4 +++- packages/webpack-plugin/lib/resolver/AddModePlugin.js | 11 +++++++++-- packages/webpack-plugin/lib/utils/is-css-file-name.js | 5 +++++ 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 packages/webpack-plugin/lib/utils/is-css-file-name.js diff --git a/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js b/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js index f1d7f6678e..584496b5b1 100644 --- a/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js +++ b/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js @@ -4,6 +4,7 @@ const parseQuery = require('loader-utils').parseQuery const addInfix = require('../utils/add-infix') const { matchCondition } = require('../utils/match-condition') const { JSON_JS_EXT } = require('../utils/const') +const isCSSFileName = require('../utils/is-css-file-name') module.exports = class AddEnvPlugin { constructor (source, env, fileConditionRules, target) { @@ -34,7 +35,8 @@ module.exports = class AddEnvPlugin { if (!extname || !matchCondition(resourcePath, this.fileConditionRules)) return callback() const queryObj = parseQuery(request.query || '?') queryObj.infix = `${queryObj.infix || ''}.${env}` - obj.query = stringifyQuery(queryObj) + // css | stylus | less | sass 中 import file 过滤query,避免在对应的 loader 中无法读取到文件 + if (!isCSSFileName(extname)) obj.query = stringifyQuery(queryObj) obj.path = addInfix(resourcePath, env, extname) obj.relativePath = request.relativePath && addInfix(request.relativePath, env, extname) resolver.doResolve(target, Object.assign({}, request, obj), 'add env: ' + env, resolveContext, callback) diff --git a/packages/webpack-plugin/lib/resolver/AddModePlugin.js b/packages/webpack-plugin/lib/resolver/AddModePlugin.js index 4430a40bce..d56247bd70 100644 --- a/packages/webpack-plugin/lib/resolver/AddModePlugin.js +++ b/packages/webpack-plugin/lib/resolver/AddModePlugin.js @@ -4,6 +4,7 @@ const parseQuery = require('loader-utils').parseQuery const { matchCondition } = require('../utils/match-condition') const addInfix = require('../utils/add-infix') const { JSON_JS_EXT } = require('../utils/const') +const isCSSFileName = require('../utils/is-css-file-name') module.exports = class AddModePlugin { constructor (source, mode, options, target) { @@ -37,13 +38,19 @@ module.exports = class AddModePlugin { const queryInfix = queryObj.infix if (!implicitMode) queryObj.mode = mode queryObj.infix = `${queryInfix || ''}.${mode}` - obj.query = stringifyQuery(queryObj) + // css | stylus | less | sass 中 import file 过滤query,避免在对应的 loader 中无法读取到文件 + if (!isCSSFileName(extname)) { + obj.query = stringifyQuery(queryObj) + } obj.path = addInfix(resourcePath, mode, extname) obj.relativePath = request.relativePath && addInfix(request.relativePath, mode, extname) resolver.doResolve(target, Object.assign({}, request, obj), 'add mode: ' + mode, resolveContext, (err, result) => { if (defaultMode && !result) { queryObj.infix = `${queryInfix || ''}.${defaultMode}` - obj.query = stringifyQuery(queryObj) + // css | stylus | less | sass 中 import file 过滤query,避免在对应的 loader 中无法读取到文件 + if (!isCSSFileName(extname)) { + obj.query = stringifyQuery(queryObj) + } obj.path = addInfix(resourcePath, defaultMode, extname) resolver.doResolve(target, Object.assign({}, request, obj), 'add mode: ' + defaultMode, resolveContext, (err, result) => { callback(err, result) diff --git a/packages/webpack-plugin/lib/utils/is-css-file-name.js b/packages/webpack-plugin/lib/utils/is-css-file-name.js new file mode 100644 index 0000000000..0dbd5e0194 --- /dev/null +++ b/packages/webpack-plugin/lib/utils/is-css-file-name.js @@ -0,0 +1,5 @@ +const CSS_LANG_EXT_ARR = ['.less', '.styl', '.sass', '.scss', '.less', '.css'] + +module.exports = function isCSSFileName (extname) { + return CSS_LANG_EXT_ARR.includes(extname) +} From 977c5ad00f5a3b23caa5f2c3c5e9b28856f79c89 Mon Sep 17 00:00:00 2001 From: xuegan Date: Sat, 30 Nov 2024 12:18:06 +0800 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E5=88=A4=E6=96=ADcss=20loader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webpack-plugin/lib/index.js | 18 ++- .../lib/resolver/AddEnvPlugin.js | 2 +- .../lib/resolver/AddModePlugin.js | 4 +- .../lib/style-compiler/index.js | 6 +- .../strip-conditional-loader.js | 116 ++++++++++++++++++ 5 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index 0216e633ef..8ebfbe56df 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -54,6 +54,7 @@ const wxssLoaderPath = normalize.lib('wxss/index') const wxmlLoaderPath = normalize.lib('wxml/loader') const wxsLoaderPath = normalize.lib('wxs/loader') const styleCompilerPath = normalize.lib('style-compiler/index') +const styleStripConditaionalPath = normalize.lib('style-compiler/strip-conditional-loader') const templateCompilerPath = normalize.lib('template-compiler/index') const jsonCompilerPath = normalize.lib('json-compiler/index') const jsonThemeCompilerPath = normalize.lib('json-compiler/theme') @@ -1775,7 +1776,7 @@ try { }) const typeLoaderProcessInfo = { - styles: ['node_modules/css-loader', wxssLoaderPath, styleCompilerPath], + styles: ['node_modules/css-loader', wxssLoaderPath, styleCompilerPath, styleStripConditaionalPath], template: ['node_modules/html-loader', wxmlLoaderPath, templateCompilerPath] } @@ -1801,9 +1802,18 @@ try { } }) if (insertBeforeIndex > -1) { - loaders.splice(insertBeforeIndex + 1, 0, { - loader: info[2] - }) + if (type === 'styles') { + loaders.splice(insertBeforeIndex + 1, 0, { + loader: info[2] + }, { + loader: info[3] + }) + } else { + loaders.splice(insertBeforeIndex + 1, 0, { + loader: info[2] + }) + } + } break } diff --git a/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js b/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js index 584496b5b1..c7dd1d1db4 100644 --- a/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js +++ b/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js @@ -36,7 +36,7 @@ module.exports = class AddEnvPlugin { const queryObj = parseQuery(request.query || '?') queryObj.infix = `${queryObj.infix || ''}.${env}` // css | stylus | less | sass 中 import file 过滤query,避免在对应的 loader 中无法读取到文件 - if (!isCSSFileName(extname)) obj.query = stringifyQuery(queryObj) + obj.query = stringifyQuery(queryObj) obj.path = addInfix(resourcePath, env, extname) obj.relativePath = request.relativePath && addInfix(request.relativePath, env, extname) resolver.doResolve(target, Object.assign({}, request, obj), 'add env: ' + env, resolveContext, callback) diff --git a/packages/webpack-plugin/lib/resolver/AddModePlugin.js b/packages/webpack-plugin/lib/resolver/AddModePlugin.js index d56247bd70..26b5431703 100644 --- a/packages/webpack-plugin/lib/resolver/AddModePlugin.js +++ b/packages/webpack-plugin/lib/resolver/AddModePlugin.js @@ -48,9 +48,7 @@ module.exports = class AddModePlugin { if (defaultMode && !result) { queryObj.infix = `${queryInfix || ''}.${defaultMode}` // css | stylus | less | sass 中 import file 过滤query,避免在对应的 loader 中无法读取到文件 - if (!isCSSFileName(extname)) { - obj.query = stringifyQuery(queryObj) - } + obj.query = stringifyQuery(queryObj) obj.path = addInfix(resourcePath, defaultMode, extname) resolver.doResolve(target, Object.assign({}, request, obj), 'add mode: ' + defaultMode, resolveContext, (err, result) => { callback(err, result) diff --git a/packages/webpack-plugin/lib/style-compiler/index.js b/packages/webpack-plugin/lib/style-compiler/index.js index 586561e2a4..bb94ccee32 100644 --- a/packages/webpack-plugin/lib/style-compiler/index.js +++ b/packages/webpack-plugin/lib/style-compiler/index.js @@ -58,9 +58,9 @@ module.exports = function (css, map) { plugins.push(transSpecial({ id })) } - plugins.push(pluginCondStrip({ - defs - })) + // plugins.push(pluginCondStrip({ + // defs + // })) for (const item of transRpxRules) { const { diff --git a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js new file mode 100644 index 0000000000..fcef3fa171 --- /dev/null +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -0,0 +1,116 @@ +const MagicString = require('magic-string') + +function cssConditionalStrip(cssContent, defs) { + const ms = new MagicString(cssContent) + + // 正则匹配 @mpx-if, @mpx-elif, @mpx-else, @mpx-endif 的模式 + const ifPattern = /\/\*\s*@mpx-if\s*\((.*?)\)\s*\*\//gs + const elifPattern = /\/\*\s*@mpx-elif\s*\((.*?)\)\s*\*\//gs + const elsePattern = /\/\*\s*@mpx-else\s*\*\//gs + const endifPattern = /\/\*\s*@mpx-endif\s*\*\//gs + + function evaluateCondition(condition) { + // 替换变量 + for (const key in defs) { + condition = condition.replace(new RegExp(`\\b${key}\\b`, 'g'), JSON.stringify(defs[key])) + } + + // 解析条件表达式 + try { + return Function('"use strict";return (' + condition + ')')() + } catch (e) { + throw new Error(`Failed to evaluate condition: ${condition}`) + } + } + + let currentStart = 0 + function processCondition(start, end, condition) { + const conditionResult = evaluateCondition(condition) + let hasElse = false + let elseStart = -1 + let elseLen = 0 + currentStart = end + 1 + + while (currentStart < ms.original.length) { + elsePattern.lastIndex = currentStart + const elseMatch = elsePattern.exec(ms.original) + if (elseMatch) { + elseLen = elseMatch[0].length + } + + ifPattern.lastIndex = currentStart + const ifMatch = ifPattern.exec(ms.original) + + elifPattern.lastIndex = currentStart + const elifMatch = elifPattern.exec(ms.original) + + endifPattern.lastIndex = currentStart + const endifMatch = endifPattern.exec(ms.original) + + const nextIf = ifMatch ? ifMatch.index : Infinity + const nextElseIf = elifMatch ? elifMatch.index : Infinity + const nextElse = elseMatch ? elseMatch.index : Infinity + const nextEndif = endifMatch ? endifMatch.index : Infinity + + const nextMarker = Math.min(nextIf, nextElseIf, nextElse, nextEndif) + + if (nextMarker === Infinity) break + + if (nextMarker === nextElse) { + // 处理 @mpx-else + hasElse = true + elseStart = nextElse + currentStart = elseMatch.index + elseLen + } else if (nextMarker === nextElseIf) { + // 处理 @mpx-elif + if (!conditionResult) { + ms.remove(start, nextElseIf) + } + if (elifMatch) { + currentStart = nextElseIf + elifMatch[0].length + processCondition(nextElseIf, nextElseIf + elifMatch[0].length, elifMatch[1]) + } + } else if (nextMarker === nextIf) { + // 处理嵌套的 @mpx-if + // 如果遇到了新的 @mpx-if,则递归处理 + if (ifMatch) { + currentStart = nextIf + ifMatch[0].length + processCondition(nextIf, nextIf + ifMatch[0].length, ifMatch[1]) + } + } else if (nextMarker === nextEndif) { + // 处理 @mpx-endif block块 + if (conditionResult && hasElse) { + // 移除 @mpx-else 至 @mpx-endif 代码 + ms.remove(elseStart, endifMatch.index + endifMatch[0].length) + } else if (!conditionResult && hasElse) { + ms.remove(start, elseStart + elseLen) + } else if (!conditionResult) { + ms.remove(start, endifMatch.index + endifMatch[0].length) + } + currentStart = endifMatch.index + endifMatch[0].length + break + } + // 兜底更新当前开始位置 + if (currentStart < nextMarker) { + currentStart = nextMarker + 1 + } + } + } + + // 处理所有条件 + let match + while ((match = ifPattern.exec(ms.original)) !== null) { + processCondition(match.index, ifPattern.lastIndex, match[1]) + ifPattern.lastIndex = currentStart + } + + return ms.toString() +} + +module.exports = function (css) { + this.cacheable() + const mpx = this.getMpx() + const defs = mpx.defs + + return cssConditionalStrip(css, defs) +} From 937b4300d63694c26da7e2c554486b73ed6ebc4a Mon Sep 17 00:00:00 2001 From: xuegan Date: Thu, 5 Dec 2024 11:33:09 +0800 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=E6=89=A7=E8=A1=8C=E5=AE=8C?= =?UTF-8?q?=E6=B8=85=E9=99=A4=E6=89=80=E6=9C=89css=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strip-conditional-loader.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js index fcef3fa171..1eaf5cad0b 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -42,13 +42,13 @@ function cssConditionalStrip(cssContent, defs) { const ifMatch = ifPattern.exec(ms.original) elifPattern.lastIndex = currentStart - const elifMatch = elifPattern.exec(ms.original) + const elseIfMatch = elifPattern.exec(ms.original) endifPattern.lastIndex = currentStart const endifMatch = endifPattern.exec(ms.original) const nextIf = ifMatch ? ifMatch.index : Infinity - const nextElseIf = elifMatch ? elifMatch.index : Infinity + const nextElseIf = elseIfMatch ? elseIfMatch.index : Infinity const nextElse = elseMatch ? elseMatch.index : Infinity const nextEndif = endifMatch ? endifMatch.index : Infinity @@ -61,22 +61,22 @@ function cssConditionalStrip(cssContent, defs) { hasElse = true elseStart = nextElse currentStart = elseMatch.index + elseLen + ms.remove(elseStart, elseStart + elseLen) // 移除 @mpx-else 注释 } else if (nextMarker === nextElseIf) { // 处理 @mpx-elif if (!conditionResult) { + // 前边的if为false,则直接移除前边代码 ms.remove(start, nextElseIf) } - if (elifMatch) { - currentStart = nextElseIf + elifMatch[0].length - processCondition(nextElseIf, nextElseIf + elifMatch[0].length, elifMatch[1]) - } + currentStart = nextElseIf + elseIfMatch[0].length + ms.remove(nextElseIf, nextElseIf + elseIfMatch[0].length) // 移除 @mpx-elif 注释 + processCondition(nextElseIf, nextElseIf + elseIfMatch[0].length, elseIfMatch[1]) } else if (nextMarker === nextIf) { // 处理嵌套的 @mpx-if // 如果遇到了新的 @mpx-if,则递归处理 - if (ifMatch) { - currentStart = nextIf + ifMatch[0].length - processCondition(nextIf, nextIf + ifMatch[0].length, ifMatch[1]) - } + currentStart = nextIf + ifMatch[0].length + ms.remove(nextIf, nextIf + ifMatch[0].length) // 移除 @mpx-if 注释 + processCondition(nextIf, nextIf + ifMatch[0].length, ifMatch[1]) } else if (nextMarker === nextEndif) { // 处理 @mpx-endif block块 if (conditionResult && hasElse) { @@ -87,6 +87,7 @@ function cssConditionalStrip(cssContent, defs) { } else if (!conditionResult) { ms.remove(start, endifMatch.index + endifMatch[0].length) } + ms.remove(endifMatch.index, endifMatch.index + endifMatch[0].length) // 移除 @mpx-endif 注释 currentStart = endifMatch.index + endifMatch[0].length break } @@ -97,7 +98,6 @@ function cssConditionalStrip(cssContent, defs) { } } - // 处理所有条件 let match while ((match = ifPattern.exec(ms.original)) !== null) { processCondition(match.index, ifPattern.lastIndex, match[1]) From 9915f5dc8a53812a0f9ce05d53d377fb6321d4b7 Mon Sep 17 00:00:00 2001 From: xuegan Date: Thu, 5 Dec 2024 12:55:57 +0800 Subject: [PATCH 04/12] feat: fix eslint error --- packages/webpack-plugin/lib/index.js | 1 - packages/webpack-plugin/lib/resolver/AddEnvPlugin.js | 1 - packages/webpack-plugin/lib/style-compiler/index.js | 1 - .../lib/style-compiler/strip-conditional-loader.js | 7 ++++--- packages/webpack-plugin/lib/utils/is-css-file-name.js | 5 ----- 5 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 packages/webpack-plugin/lib/utils/is-css-file-name.js diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index 8ebfbe56df..d0685be5cc 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -1813,7 +1813,6 @@ try { loader: info[2] }) } - } break } diff --git a/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js b/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js index c7dd1d1db4..a1c6418ee6 100644 --- a/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js +++ b/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js @@ -4,7 +4,6 @@ const parseQuery = require('loader-utils').parseQuery const addInfix = require('../utils/add-infix') const { matchCondition } = require('../utils/match-condition') const { JSON_JS_EXT } = require('../utils/const') -const isCSSFileName = require('../utils/is-css-file-name') module.exports = class AddEnvPlugin { constructor (source, env, fileConditionRules, target) { diff --git a/packages/webpack-plugin/lib/style-compiler/index.js b/packages/webpack-plugin/lib/style-compiler/index.js index bb94ccee32..b35babea4c 100644 --- a/packages/webpack-plugin/lib/style-compiler/index.js +++ b/packages/webpack-plugin/lib/style-compiler/index.js @@ -4,7 +4,6 @@ const loadPostcssConfig = require('./load-postcss-config') const { MPX_ROOT_VIEW, MPX_DISABLE_EXTRACTOR_CACHE } = require('../utils/const') const rpx = require('./plugins/rpx') const vw = require('./plugins/vw') -const pluginCondStrip = require('./plugins/conditional-strip') const scopeId = require('./plugins/scope-id') const transSpecial = require('./plugins/trans-special') const cssArrayList = require('./plugins/css-array-list') diff --git a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js index 1eaf5cad0b..544b9f8e52 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -1,6 +1,6 @@ const MagicString = require('magic-string') -function cssConditionalStrip(cssContent, defs) { +function cssConditionalStrip (cssContent, defs) { const ms = new MagicString(cssContent) // 正则匹配 @mpx-if, @mpx-elif, @mpx-else, @mpx-endif 的模式 @@ -9,7 +9,7 @@ function cssConditionalStrip(cssContent, defs) { const elsePattern = /\/\*\s*@mpx-else\s*\*\//gs const endifPattern = /\/\*\s*@mpx-endif\s*\*\//gs - function evaluateCondition(condition) { + function evaluateCondition (condition) { // 替换变量 for (const key in defs) { condition = condition.replace(new RegExp(`\\b${key}\\b`, 'g'), JSON.stringify(defs[key])) @@ -17,6 +17,7 @@ function cssConditionalStrip(cssContent, defs) { // 解析条件表达式 try { + // eslint-disable-next-line no-new-func return Function('"use strict";return (' + condition + ')')() } catch (e) { throw new Error(`Failed to evaluate condition: ${condition}`) @@ -24,7 +25,7 @@ function cssConditionalStrip(cssContent, defs) { } let currentStart = 0 - function processCondition(start, end, condition) { + function processCondition (start, end, condition) { const conditionResult = evaluateCondition(condition) let hasElse = false let elseStart = -1 diff --git a/packages/webpack-plugin/lib/utils/is-css-file-name.js b/packages/webpack-plugin/lib/utils/is-css-file-name.js deleted file mode 100644 index 0dbd5e0194..0000000000 --- a/packages/webpack-plugin/lib/utils/is-css-file-name.js +++ /dev/null @@ -1,5 +0,0 @@ -const CSS_LANG_EXT_ARR = ['.less', '.styl', '.sass', '.scss', '.less', '.css'] - -module.exports = function isCSSFileName (extname) { - return CSS_LANG_EXT_ARR.includes(extname) -} From a655df34e11d5e0984109c09586951a44abd2e17 Mon Sep 17 00:00:00 2001 From: xuegan Date: Mon, 9 Dec 2024 20:53:36 +0800 Subject: [PATCH 05/12] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4strip-conditiona?= =?UTF-8?q?l=20loader=E7=9A=84=E6=8F=92=E5=85=A5=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webpack-plugin/lib/index.js | 29 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index d0685be5cc..7c7fc8d781 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -60,6 +60,7 @@ const jsonCompilerPath = normalize.lib('json-compiler/index') const jsonThemeCompilerPath = normalize.lib('json-compiler/theme') const jsonPluginCompilerPath = normalize.lib('json-compiler/plugin') const extractorPath = normalize.lib('extractor') +const selectorPath = normalize.lib('selector') const async = require('async') const { parseQuery } = require('loader-utils') const stringifyLoadersAndResource = require('./utils/stringify-loaders-resource') @@ -1802,17 +1803,9 @@ try { } }) if (insertBeforeIndex > -1) { - if (type === 'styles') { - loaders.splice(insertBeforeIndex + 1, 0, { - loader: info[2] - }, { - loader: info[3] - }) - } else { - loaders.splice(insertBeforeIndex + 1, 0, { - loader: info[2] - }) - } + loaders.splice(insertBeforeIndex + 1, 0, { + loader: info[2] + }) } break } @@ -1842,6 +1835,20 @@ try { loader: extractorPath }) } + if (type === 'styles') { + // 判断最后一个loader是否是 selectorPath, 如果是,则在sectorPath之前插入strip-conditional + const lastLoader = loaders[loaders.length - 1] + if (lastLoader.loader.includes(selectorPath)) { + loaders.splice(loaders.length - 1, 0, { + loader: styleStripConditaionalPath + }) + } else { + // 在最后一个插入strip-conditional + loaders.push({ + loader: styleStripConditaionalPath + }) + } + } createData.resource = addQuery(createData.resource, { mpx: MPX_PROCESSED_FLAG }, true) } // mpxStyleOptions 为 mpx style 文件的标识,避免 Vue 文件插入 styleCompiler 后导致 vue scoped 样式隔离失效 From 5ef88f6358f37302deb14cc2a8c0fee7fe677537 Mon Sep 17 00:00:00 2001 From: xuegan Date: Wed, 11 Dec 2024 14:11:56 +0800 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20=E5=A4=84=E7=90=86=E4=B9=8B?= =?UTF-8?q?=E5=90=8E=E5=AF=B9@mpx-if=E6=B3=A8=E9=87=8A=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/style-compiler/strip-conditional-loader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js index 544b9f8e52..e72898779d 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -102,6 +102,8 @@ function cssConditionalStrip (cssContent, defs) { let match while ((match = ifPattern.exec(ms.original)) !== null) { processCondition(match.index, ifPattern.lastIndex, match[1]) + // 移除匹配到的 @mpx-if 注释 + ms.remove(match.index, match.index + match[0].length) ifPattern.lastIndex = currentStart } @@ -112,6 +114,5 @@ module.exports = function (css) { this.cacheable() const mpx = this.getMpx() const defs = mpx.defs - return cssConditionalStrip(css, defs) } From f1dcde167a6f198934694d11e15da5be3467e722 Mon Sep 17 00:00:00 2001 From: xuegan Date: Wed, 11 Dec 2024 14:17:42 +0800 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20=E5=8E=BB=E9=99=A4isCssName?= =?UTF-8?q?=E7=9A=84=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webpack-plugin/lib/resolver/AddModePlugin.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/webpack-plugin/lib/resolver/AddModePlugin.js b/packages/webpack-plugin/lib/resolver/AddModePlugin.js index 26b5431703..9a2bff7487 100644 --- a/packages/webpack-plugin/lib/resolver/AddModePlugin.js +++ b/packages/webpack-plugin/lib/resolver/AddModePlugin.js @@ -4,7 +4,6 @@ const parseQuery = require('loader-utils').parseQuery const { matchCondition } = require('../utils/match-condition') const addInfix = require('../utils/add-infix') const { JSON_JS_EXT } = require('../utils/const') -const isCSSFileName = require('../utils/is-css-file-name') module.exports = class AddModePlugin { constructor (source, mode, options, target) { @@ -38,10 +37,7 @@ module.exports = class AddModePlugin { const queryInfix = queryObj.infix if (!implicitMode) queryObj.mode = mode queryObj.infix = `${queryInfix || ''}.${mode}` - // css | stylus | less | sass 中 import file 过滤query,避免在对应的 loader 中无法读取到文件 - if (!isCSSFileName(extname)) { - obj.query = stringifyQuery(queryObj) - } + obj.query = stringifyQuery(queryObj) obj.path = addInfix(resourcePath, mode, extname) obj.relativePath = request.relativePath && addInfix(request.relativePath, mode, extname) resolver.doResolve(target, Object.assign({}, request, obj), 'add mode: ' + mode, resolveContext, (err, result) => { From 8658fc9554b5c7995dca8282bc6b2e69d907a09a Mon Sep 17 00:00:00 2001 From: xuegan Date: Mon, 23 Dec 2024 20:52:22 +0800 Subject: [PATCH 08/12] =?UTF-8?q?fix:=20=E5=8E=BB=E9=99=A4webpack-plugin/i?= =?UTF-8?q?ndex.js=E4=B8=AD=E5=AF=B9=E4=BA=8Eloader=E6=8F=92=E5=85=A5?= =?UTF-8?q?=E7=9A=84=E6=94=B9=E5=8A=A8=EF=BC=8C=E5=AE=8C=E5=85=A8=E7=94=B1?= =?UTF-8?q?mpx-cli=E4=B8=AD=E8=BF=9B=E8=A1=8C=E7=9B=B8=E5=85=B3loader?= =?UTF-8?q?=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webpack-plugin/lib/index.js | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index 7c7fc8d781..0216e633ef 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -54,13 +54,11 @@ const wxssLoaderPath = normalize.lib('wxss/index') const wxmlLoaderPath = normalize.lib('wxml/loader') const wxsLoaderPath = normalize.lib('wxs/loader') const styleCompilerPath = normalize.lib('style-compiler/index') -const styleStripConditaionalPath = normalize.lib('style-compiler/strip-conditional-loader') const templateCompilerPath = normalize.lib('template-compiler/index') const jsonCompilerPath = normalize.lib('json-compiler/index') const jsonThemeCompilerPath = normalize.lib('json-compiler/theme') const jsonPluginCompilerPath = normalize.lib('json-compiler/plugin') const extractorPath = normalize.lib('extractor') -const selectorPath = normalize.lib('selector') const async = require('async') const { parseQuery } = require('loader-utils') const stringifyLoadersAndResource = require('./utils/stringify-loaders-resource') @@ -1777,7 +1775,7 @@ try { }) const typeLoaderProcessInfo = { - styles: ['node_modules/css-loader', wxssLoaderPath, styleCompilerPath, styleStripConditaionalPath], + styles: ['node_modules/css-loader', wxssLoaderPath, styleCompilerPath], template: ['node_modules/html-loader', wxmlLoaderPath, templateCompilerPath] } @@ -1835,20 +1833,6 @@ try { loader: extractorPath }) } - if (type === 'styles') { - // 判断最后一个loader是否是 selectorPath, 如果是,则在sectorPath之前插入strip-conditional - const lastLoader = loaders[loaders.length - 1] - if (lastLoader.loader.includes(selectorPath)) { - loaders.splice(loaders.length - 1, 0, { - loader: styleStripConditaionalPath - }) - } else { - // 在最后一个插入strip-conditional - loaders.push({ - loader: styleStripConditaionalPath - }) - } - } createData.resource = addQuery(createData.resource, { mpx: MPX_PROCESSED_FLAG }, true) } // mpxStyleOptions 为 mpx style 文件的标识,避免 Vue 文件插入 styleCompiler 后导致 vue scoped 样式隔离失效 From ae4b68df8ea5ce932cb31d60fe09faf09d2abc14 Mon Sep 17 00:00:00 2001 From: xuegan Date: Mon, 23 Dec 2024 20:55:31 +0800 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webpack-plugin/lib/resolver/AddEnvPlugin.js | 1 - packages/webpack-plugin/lib/resolver/AddModePlugin.js | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js b/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js index a1c6418ee6..f1d7f6678e 100644 --- a/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js +++ b/packages/webpack-plugin/lib/resolver/AddEnvPlugin.js @@ -34,7 +34,6 @@ module.exports = class AddEnvPlugin { if (!extname || !matchCondition(resourcePath, this.fileConditionRules)) return callback() const queryObj = parseQuery(request.query || '?') queryObj.infix = `${queryObj.infix || ''}.${env}` - // css | stylus | less | sass 中 import file 过滤query,避免在对应的 loader 中无法读取到文件 obj.query = stringifyQuery(queryObj) obj.path = addInfix(resourcePath, env, extname) obj.relativePath = request.relativePath && addInfix(request.relativePath, env, extname) diff --git a/packages/webpack-plugin/lib/resolver/AddModePlugin.js b/packages/webpack-plugin/lib/resolver/AddModePlugin.js index 9a2bff7487..4430a40bce 100644 --- a/packages/webpack-plugin/lib/resolver/AddModePlugin.js +++ b/packages/webpack-plugin/lib/resolver/AddModePlugin.js @@ -43,7 +43,6 @@ module.exports = class AddModePlugin { resolver.doResolve(target, Object.assign({}, request, obj), 'add mode: ' + mode, resolveContext, (err, result) => { if (defaultMode && !result) { queryObj.infix = `${queryInfix || ''}.${defaultMode}` - // css | stylus | less | sass 中 import file 过滤query,避免在对应的 loader 中无法读取到文件 obj.query = stringifyQuery(queryObj) obj.path = addInfix(resourcePath, defaultMode, extname) resolver.doResolve(target, Object.assign({}, request, obj), 'add mode: ' + defaultMode, resolveContext, (err, result) => { From 3b0fdebac51ace5500b7b59975b07f0092d4f5aa Mon Sep 17 00:00:00 2001 From: xuegan Date: Wed, 5 Feb 2025 16:14:26 +0800 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84css=20condition?= =?UTF-8?q?al=E5=AE=9E=E7=8E=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strip-conditional-loader.js | 219 ++++++++++-------- 1 file changed, 123 insertions(+), 96 deletions(-) diff --git a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js index e72898779d..240a5b17ff 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -1,118 +1,145 @@ -const MagicString = require('magic-string') - -function cssConditionalStrip (cssContent, defs) { - const ms = new MagicString(cssContent) - - // 正则匹配 @mpx-if, @mpx-elif, @mpx-else, @mpx-endif 的模式 - const ifPattern = /\/\*\s*@mpx-if\s*\((.*?)\)\s*\*\//gs - const elifPattern = /\/\*\s*@mpx-elif\s*\((.*?)\)\s*\*\//gs - const elsePattern = /\/\*\s*@mpx-else\s*\*\//gs - const endifPattern = /\/\*\s*@mpx-endif\s*\*\//gs +class Node { + constructor (type, condition = null) { + this.type = type // 'If', 'ElseIf', 'Else' 或 'Text' + this.condition = condition // If 或 Elif 的条件 + this.children = [] + this.value = '' + } +} - function evaluateCondition (condition) { - // 替换变量 - for (const key in defs) { - condition = condition.replace(new RegExp(`\\b${key}\\b`, 'g'), JSON.stringify(defs[key])) - } +// 提取 css string 为 token +function tokenize (cssString) { + const regex = /\/\*\s*@mpx-(if|elif|else|end)(?:\s*\(([^\)]*)\))?\s*\*\//g + const tokens = [] + let lastIndex = 0 + let match - // 解析条件表达式 - try { - // eslint-disable-next-line no-new-func - return Function('"use strict";return (' + condition + ')')() - } catch (e) { - throw new Error(`Failed to evaluate condition: ${condition}`) + while ((match = regex.exec(cssString)) !== null) { + // 如果 token 前有普通文本,生成文本 token + if (match.index > lastIndex) { + const text = cssString.substring(lastIndex, match.index) + tokens.push({ type: 'text', content: text }) } + // match[1] 为关键字:if, elif, else, end + // match[2] 为条件(如果存在) + tokens.push({ + type: match[1], // 'if'、'elif'、'else' 或 'end' + condition: match[2] ? match[2].trim() : null + }) + lastIndex = regex.lastIndex } + // 处理结尾剩余的文本 + if (lastIndex < cssString.length) { + const text = cssString.substring(lastIndex) + tokens.push({ type: 'text', content: text }) + } + return tokens +} - let currentStart = 0 - function processCondition (start, end, condition) { - const conditionResult = evaluateCondition(condition) - let hasElse = false - let elseStart = -1 - let elseLen = 0 - currentStart = end + 1 - - while (currentStart < ms.original.length) { - elsePattern.lastIndex = currentStart - const elseMatch = elsePattern.exec(ms.original) - if (elseMatch) { - elseLen = elseMatch[0].length +// parse:将生成的 token 数组构造成嵌套的 AST +function parse (cssString) { + const tokens = tokenize(cssString) + const ast = [] + const nodeStack = [] + let currentChildren = ast + function pushConditionalNode (nodeType, condition) { + // 获取父节点的 children 数组 + const parentChildren = nodeStack.length > 0 ? nodeStack[nodeStack.length - 1] : ast + const node = new Node(nodeType, condition) + parentChildren.push(node) + // 入栈 + nodeStack.push(parentChildren) + currentChildren = node.children + } + tokens.forEach(token => { + switch (token.type) { + case 'text': { + // 生成 Text 节点,保存代码文本 + const textNode = new Node('Text') + textNode.value = token.content + currentChildren.push(textNode) + break } - - ifPattern.lastIndex = currentStart - const ifMatch = ifPattern.exec(ms.original) - - elifPattern.lastIndex = currentStart - const elseIfMatch = elifPattern.exec(ms.original) - - endifPattern.lastIndex = currentStart - const endifMatch = endifPattern.exec(ms.original) - - const nextIf = ifMatch ? ifMatch.index : Infinity - const nextElseIf = elseIfMatch ? elseIfMatch.index : Infinity - const nextElse = elseMatch ? elseMatch.index : Infinity - const nextEndif = endifMatch ? endifMatch.index : Infinity - - const nextMarker = Math.min(nextIf, nextElseIf, nextElse, nextEndif) - - if (nextMarker === Infinity) break - - if (nextMarker === nextElse) { - // 处理 @mpx-else - hasElse = true - elseStart = nextElse - currentStart = elseMatch.index + elseLen - ms.remove(elseStart, elseStart + elseLen) // 移除 @mpx-else 注释 - } else if (nextMarker === nextElseIf) { - // 处理 @mpx-elif - if (!conditionResult) { - // 前边的if为false,则直接移除前边代码 - ms.remove(start, nextElseIf) + case 'if': { + pushConditionalNode('If', token.condition) + break + } + case 'elif': { + // 处理 mpx-elif:回到 if 块的父级 children 数组 + if (nodeStack.length === 0) { + throw new Error('elif without a preceding if') } - currentStart = nextElseIf + elseIfMatch[0].length - ms.remove(nextElseIf, nextElseIf + elseIfMatch[0].length) // 移除 @mpx-elif 注释 - processCondition(nextElseIf, nextElseIf + elseIfMatch[0].length, elseIfMatch[1]) - } else if (nextMarker === nextIf) { - // 处理嵌套的 @mpx-if - // 如果遇到了新的 @mpx-if,则递归处理 - currentStart = nextIf + ifMatch[0].length - ms.remove(nextIf, nextIf + ifMatch[0].length) // 移除 @mpx-if 注释 - processCondition(nextIf, nextIf + ifMatch[0].length, ifMatch[1]) - } else if (nextMarker === nextEndif) { - // 处理 @mpx-endif block块 - if (conditionResult && hasElse) { - // 移除 @mpx-else 至 @mpx-endif 代码 - ms.remove(elseStart, endifMatch.index + endifMatch[0].length) - } else if (!conditionResult && hasElse) { - ms.remove(start, elseStart + elseLen) - } else if (!conditionResult) { - ms.remove(start, endifMatch.index + endifMatch[0].length) + currentChildren = nodeStack[nodeStack.length - 1] + pushConditionalNode('Elif', token.condition) + break + } + case 'else': { + if (nodeStack.length === 0) { + throw new Error('else without a preceding if') } - ms.remove(endifMatch.index, endifMatch.index + endifMatch[0].length) // 移除 @mpx-endif 注释 - currentStart = endifMatch.index + endifMatch[0].length + currentChildren = nodeStack[nodeStack.length - 1] + pushConditionalNode('Else', null) break } - // 兜底更新当前开始位置 - if (currentStart < nextMarker) { - currentStart = nextMarker + 1 + case 'end': { + // 结束当前条件块,弹出上一级 children 指针 + if (nodeStack.length > 0) { + currentChildren = nodeStack.pop() + } else { + throw new Error('end without matching if') + } + break } + default: + break } - } + }) + return ast +} - let match - while ((match = ifPattern.exec(ms.original)) !== null) { - processCondition(match.index, ifPattern.lastIndex, match[1]) - // 移除匹配到的 @mpx-if 注释 - ms.remove(match.index, match.index + match[0].length) - ifPattern.lastIndex = currentStart +function evaluateCondition (condition, context) { + try { + const keys = Object.keys(context) + const values = keys.map(key => context[key]) + const func = new Function(...keys, `return (${condition});`) + return func(...values) + } catch (e) { + console.error(`Error evaluating condition: ${condition}`, e) + return false } +} - return ms.toString() +function traverseAndEvaluate (ast, context) { + let output = '' + + function traverse (nodes) { + for (const node of nodes) { + if (node.type === 'Rule') { + output += node.value + } else if (node.type === 'If') { + // 直接判断 If 节点 + if (evaluateCondition(node.condition, context)) { + traverse(node.children) + } + } else if (node.type === 'ElseIf') { + if (evaluateCondition(node.condition, context)) { + traverse(node.children) + return + } + } else if (node.type === 'Else') { + traverse(node.children) + return + } + } + } + traverse(ast) + return output } module.exports = function (css) { this.cacheable() const mpx = this.getMpx() const defs = mpx.defs - return cssConditionalStrip(css, defs) + const ast = parse(css) + return traverseAndEvaluate(ast, defs) } From c3a8262e8588e8c2db1de9a1a864cd521ad47f20 Mon Sep 17 00:00:00 2001 From: xuegan Date: Wed, 5 Feb 2025 16:34:52 +0800 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4regex=E8=A7=84?= =?UTF-8?q?=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../style-compiler/strip-conditional-loader.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js index 240a5b17ff..1a90700d0e 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -9,7 +9,7 @@ class Node { // 提取 css string 为 token function tokenize (cssString) { - const regex = /\/\*\s*@mpx-(if|elif|else|end)(?:\s*\(([^\)]*)\))?\s*\*\//g + const regex = /\/\*\s*@mpx-(if|elif|else|end)(?:\s*\((.*?)\))?\s*\*\//g const tokens = [] let lastIndex = 0 let match @@ -97,10 +97,11 @@ function parse (cssString) { return ast } -function evaluateCondition (condition, context) { +function evaluateCondition (condition, defs) { try { - const keys = Object.keys(context) - const values = keys.map(key => context[key]) + const keys = Object.keys(defs) + const values = keys.map(key => defs[key]) + /* eslint-disable no-new-func */ const func = new Function(...keys, `return (${condition});`) return func(...values) } catch (e) { @@ -109,7 +110,7 @@ function evaluateCondition (condition, context) { } } -function traverseAndEvaluate (ast, context) { +function traverseAndEvaluate (ast, defs) { let output = '' function traverse (nodes) { @@ -118,11 +119,11 @@ function traverseAndEvaluate (ast, context) { output += node.value } else if (node.type === 'If') { // 直接判断 If 节点 - if (evaluateCondition(node.condition, context)) { + if (evaluateCondition(node.condition, defs)) { traverse(node.children) } } else if (node.type === 'ElseIf') { - if (evaluateCondition(node.condition, context)) { + if (evaluateCondition(node.condition, defs)) { traverse(node.children) return } From e44b16276d13df6e375b867585335c72ecb0b011 Mon Sep 17 00:00:00 2001 From: xuegan Date: Wed, 5 Feb 2025 19:53:46 +0800 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8Dcssif=E9=97=AE?= =?UTF-8?q?=E9=A2=98&=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strip-conditional-loader.js | 85 +++----- .../test/platform/common/css-if.spec.js | 198 ++++++++++++++++++ 2 files changed, 231 insertions(+), 52 deletions(-) create mode 100644 packages/webpack-plugin/test/platform/common/css-if.spec.js diff --git a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js index 1a90700d0e..b852f39719 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -42,56 +42,36 @@ function parse (cssString) { const ast = [] const nodeStack = [] let currentChildren = ast - function pushConditionalNode (nodeType, condition) { - // 获取父节点的 children 数组 - const parentChildren = nodeStack.length > 0 ? nodeStack[nodeStack.length - 1] : ast - const node = new Node(nodeType, condition) - parentChildren.push(node) - // 入栈 - nodeStack.push(parentChildren) - currentChildren = node.children - } tokens.forEach(token => { - switch (token.type) { - case 'text': { - // 生成 Text 节点,保存代码文本 - const textNode = new Node('Text') - textNode.value = token.content - currentChildren.push(textNode) - break - } - case 'if': { - pushConditionalNode('If', token.condition) - break + if (token.type === 'text') { + const node = new Node('Text') + node.value = token.content + currentChildren.push(node) + } else if (token.type === 'if') { + const node = new Node('If', token.condition) + currentChildren.push(node) + nodeStack.push(currentChildren) + currentChildren = node.children + } else if (token.type === 'elif') { + if (nodeStack.length === 0) { + throw new Error('elif without a preceding if') } - case 'elif': { - // 处理 mpx-elif:回到 if 块的父级 children 数组 - if (nodeStack.length === 0) { - throw new Error('elif without a preceding if') - } - currentChildren = nodeStack[nodeStack.length - 1] - pushConditionalNode('Elif', token.condition) - break + currentChildren = nodeStack[nodeStack.length - 1] + const node = new Node('ElseIf', token.condition) + currentChildren.push(node) + currentChildren = node.children + } else if (token.type === 'else') { + if (nodeStack.length === 0) { + throw new Error('else without a preceding if') } - case 'else': { - if (nodeStack.length === 0) { - throw new Error('else without a preceding if') - } - currentChildren = nodeStack[nodeStack.length - 1] - pushConditionalNode('Else', null) - break + currentChildren = nodeStack[nodeStack.length - 1] + const node = new Node('Else') + currentChildren.push(node) + currentChildren = node.children + } else if (token.type === 'end') { + if (nodeStack.length > 0) { + currentChildren = nodeStack.pop() } - case 'end': { - // 结束当前条件块,弹出上一级 children 指针 - if (nodeStack.length > 0) { - currentChildren = nodeStack.pop() - } else { - throw new Error('end without matching if') - } - break - } - default: - break } }) return ast @@ -112,24 +92,25 @@ function evaluateCondition (condition, defs) { function traverseAndEvaluate (ast, defs) { let output = '' - + let batchedIf = false function traverse (nodes) { for (const node of nodes) { - if (node.type === 'Rule') { + if (node.type === 'Text') { output += node.value } else if (node.type === 'If') { // 直接判断 If 节点 + batchedIf = false if (evaluateCondition(node.condition, defs)) { traverse(node.children) + batchedIf = true } - } else if (node.type === 'ElseIf') { + } else if (node.type === 'ElseIf' && !batchedIf) { if (evaluateCondition(node.condition, defs)) { traverse(node.children) - return + batchedIf = true } - } else if (node.type === 'Else') { + } else if (node.type === 'Else' && !batchedIf) { traverse(node.children) - return } } } diff --git a/packages/webpack-plugin/test/platform/common/css-if.spec.js b/packages/webpack-plugin/test/platform/common/css-if.spec.js new file mode 100644 index 0000000000..6e8bfc6154 --- /dev/null +++ b/packages/webpack-plugin/test/platform/common/css-if.spec.js @@ -0,0 +1,198 @@ +const cssIfLoader = require('../../../lib/style-compiler/strip-conditional-loader') + +describe('css-if webpack loader - 测试用例', () => { + // 测试简单的 if/else 条件 + it('简单条件: 当 isMobile 为 true 时,保留 if 分支内容', () => { + const context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: true } }) + } + const inputCSS = ` +/*@mpx-if(isMobile)*/ +.mobile { display: block; } +/*@mpx-else*/ +.desktop { display: block; } +/*@mpx-end*/ + ` + const output = cssIfLoader.call(context, inputCSS) + expect(output).toContain('.mobile') + expect(output).not.toContain('.desktop') + }) + + it('简单条件: 当 isMobile 为 false 时,保留 else 分支内容', () => { + const context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: false } }) + } + const inputCSS = ` +/*@mpx-if(isMobile)*/ +.mobile { display: block; } +/*@mpx-else*/ +.desktop { display: block; } +/*@mpx-end*/ + ` + const output = cssIfLoader.call(context, inputCSS) + expect(output).toContain('.desktop') + expect(output).not.toContain('.mobile') + }) + + // 测试嵌套条件 + it('嵌套条件: 外层 isMobile 为 true 内层 hasFeature 为 true, 输出嵌套 if 分支内容', () => { + const context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: true, hasFeature: true } }) + } + const inputCSS = ` +body { margin: 0; } +/*@mpx-if(isMobile)*/ +.mobile { + display: block; + /*@mpx-if(hasFeature)*/ + .feature { color: red; } + /*@mpx-else*/ + .feature { color: blue; } + /*@mpx-end*/ +} +/*@mpx-else*/ +.desktop { display: block; } +/*@mpx-end*/ +header { color: red } + ` + const output = cssIfLoader.call(context, inputCSS) + expect(output).toContain('.mobile') + expect(output).toContain('.feature { color: red; }') + expect(output).toContain('header { color: red }') + expect(output).not.toContain('.feature { color: blue; }') + expect(output).not.toContain('.desktop') + }) + + it('嵌套条件: 外层 isMobile 为 true 内层 hasFeature 为 false, 输出内层 else 分支内容', () => { + const context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: true, hasFeature: false } }) + } + const inputCSS = ` +body { margin: 0; } +/*@mpx-if(isMobile)*/ +.mobile { + display: block; + /*@mpx-if(hasFeature)*/ + .feature { color: red; } + /*@mpx-else*/ + .feature { color: blue; } + /*@mpx-end*/ +} +/*@mpx-else*/ +.desktop { display: block; } +/*@mpx-end*/ + ` + const output = cssIfLoader.call(context, inputCSS) + expect(output).toContain('.mobile') + expect(output).toContain('.feature { color: blue; }') + expect(output).not.toContain('.feature { color: red; }') + expect(output).not.toContain('.desktop') + }) + + // 测试多个条件分支:if、elif、else 的情况 + it('多个条件分支: 优先匹配 if 分支,其次 elif,再到 else', () => { + // 测试1: isMobile 为 false,isTablet 为 true,匹配 elif 分支 + let context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: false, isTablet: true } }) + } + const inputCSS = ` +header {} +/*@mpx-if(isMobile)*/ +.mobile { display: block; } +/*@mpx-elif(isTablet)*/ +.tablet { display: block; } +/*@mpx-else*/ +.desktop { display: block; } +/*@mpx-end*/ +body {} + ` + let output = cssIfLoader.call(context, inputCSS) + expect(output).not.toContain('.mobile') + expect(output).toContain('.tablet') + expect(output).not.toContain('.desktop') + expect(output).toContain('header {}') + expect(output).toContain('body {}') + + // 测试2: isMobile 与 isTablet 均为 false,匹配 else 分支 + context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: false, isTablet: false } }) + } + output = cssIfLoader.call(context, inputCSS) + expect(output).not.toContain('.mobile') + expect(output).not.toContain('.tablet') + expect(output).toContain('.desktop') + + // 测试3: isMobile 为 true(优先匹配 if 分支),即使 isTablet 为 true + context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: true, isTablet: true } }) + } + output = cssIfLoader.call(context, inputCSS) + expect(output).toContain('.mobile') + expect(output).not.toContain('.tablet') + expect(output).not.toContain('.desktop') + }) + + // 测试多个 if 块在一起的情况 + it('多个 if 块处理: 不同 if 条件处理各自独立', () => { + const context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: true, showHeader: false } }) + } + const inputCSS = ` +/*@mpx-if(isMobile)*/ +.mobile { display: block; } +/*@mpx-end*/ + +/*@mpx-if(showHeader)*/ +.header { height: 100px; } +/*@mpx-else*/ +.header { height: 50px; } +/*@mpx-end*/ + ` + const output = cssIfLoader.call(context, inputCSS) + // 第一个 if 块:isMobile 为 true,输出 .mobile + expect(output).toContain('.mobile') + // 第二个 if 块:showHeader 为 false,应该保留 else 分支 + expect(output).not.toContain('height: 100px;') + expect(output).toContain('height: 50px;') + }) + it('多个 if 嵌套 elif 块处理', () => { + const context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: true, showHeader: true } }) + } + const inputCSS = ` +/*@mpx-if(isMobile)*/ +.mobile { display: block; } + /*@mpx-if(false)*/ + .test1 {} + /*@mpx-elif(showHeader)*/ + .test2 {} + /*@mpx-end*/ +/*@mpx-end*/ + ` + const output = cssIfLoader.call(context, inputCSS) + expect(output).toContain('.mobile') + expect(output).toContain('.test2') + }) + + it('错误处理: 缺少开始标签', () => { + const context = { + cacheable: jest.fn(), + getMpx: () => ({ defs: { isMobile: true } }) + } + const inputCSS = ` +/*@mpx-elif(isMobile)*/ +.mobile { display: block; } +` + // 预期这种情况下应该抛出异常或给出警告 + expect(() => cssIfLoader.call(context, inputCSS)).toThrow(new Error('elif without a preceding if')) + }) +})