From e20aa5ad35f1090ae2f4fd5a03b9b4e4290247e6 Mon Sep 17 00:00:00 2001 From: xuegan Date: Tue, 23 Dec 2025 20:30:55 +0800 Subject: [PATCH 1/7] feat: rewrite fs for css --- packages/webpack-plugin/lib/index.js | 4 + .../strip-conditional-loader.js | 268 ++++-------------- 2 files changed, 65 insertions(+), 207 deletions(-) diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index 6f37142f29..2e34d1ccf9 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -79,6 +79,7 @@ const LoadAsyncChunkModule = require('./react/LoadAsyncChunkModule') const ExternalModule = require('webpack/lib/ExternalModule') const { RetryRuntimeModule, RetryRuntimeGlobal } = require('./dependencies/RetryRuntimeModule') const checkVersionCompatibility = require('./utils/check-core-version-match') +const { rewriteFsForCss } = require('./style-compiler/strip-conditional-loader') checkVersionCompatibility() @@ -323,6 +324,9 @@ class MpxWebpackPlugin { } apply (compiler) { + // 注入 fs 代理 + rewriteReadFileSyncForCss(this.options.defs, this.options.projectRoot) + if (!compiler.__mpx__) { compiler.__mpx__ = true } else { 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 24208c9dc3..c1a2200dc4 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -1,7 +1,5 @@ -const fs = require('fs/promises') -const parseRequest = require('../utils/parse-request') -const path = require('path') -const loaderUtils = require('loader-utils') +const fs = require('fs') +const Module = require('module') class Node { constructor(type, condition = null) { @@ -143,212 +141,80 @@ function traverseAndEvaluate(ast, defs) { */ function stripCondition(content, defs) { const ast = parse(content) - const result = traverseAndEvaluate(ast, defs) - return result + return traverseAndEvaluate(ast, defs) } -/** - * @typedef {Object} StripByPostcssOption - * @property {string} lang 样式语法格式 - * @property {string} resourcePath 文件路径 - * @property {string} css 源文件 - * @property {Record} defs 条件定义 - * @property {import('webpack').LoaderContext['resolve']} resolve webpack resolve 方法 - */ - -/** - * @typedef {Object} AtImportConfig - * @property {string} from 当前文件路径 - * @property {(filename: string) => Promise | string;} load 加载文件内容的函数 - * @property {(id: string, base: string) => Promise | string | null;} resolve 解析文件路径的函数 - */ - -async function atImport(options) { - let { css, load, resolve, from } = options - const fromParent = path.dirname(from) - const e1 = /\/\*[\s\S]*?\*\//g - // 匹配 // 单行注释,可能匹配到静态资源中的 http:// 的 //,不过影响不大, @import 不太可能出现在静态资源链接中 - const e2 = /\/\/.*/g - // 使用正则匹配匹配出 多行注释和单行注释 - const comments = [] - let comment - while ((comment = e1.exec(css))) { - const [content] = comment - comments.push({ - start: comment.index, - end: comment.index + content.length, - content: content - }) +function rewriteFsForCss(defs, projectRoot) { + function shouldStrip(path) { + return typeof path === 'string' && /\.(styl|scss|sass|less|css)$/.test(path) } - while ((comment = e2.exec(css))) { - const [content] = comment - comments.push({ - start: comment.index, - end: comment.index + content.length, - content: content - }) - } - - // 排序方便二分 - comments.sort((a, b) => (a.start > b.start ? 1 : -1)) - - function isInComments(index) { - let left = 0 - let right = comments.length - 1 - - while (left <= right) { - const mid = Math.floor((left + right) / 2) - const comment = comments[mid] + function patchFs(fsModule) { + const readFileSync = fsModule.readFileSync + const readFile = fsModule.readFile - if (index >= comment.start && index <= comment.end) { - return true - } else if (index < comment.start) { - right = mid - 1 - } else { - left = mid + 1 + fsModule.readFileSync = function (path, options) { + const content = readFileSync.call(fsModule, path, options) + if (shouldStrip(path)) { + try { + if (typeof content === 'string') { + return stripCondition(content, defs) + } + } catch (e) { + return content + } } + return content } - return false - } - - // 使用正则表达式匹配出所有 @import 语法,语法包含 @import "path", @import 'path', @import url("path"), @import url('path') - // 注意清理分号,否则留个分号会报错 - const importRegex = /@import\s+(url\(['"]([^'"]+)['"]\)|['"]([^'"]+)['"])(\s*;)?/g - let importList = [] - let importMatch - while ((importMatch = importRegex.exec(css))) { - const fullMatch = importMatch[0] - const importSyntax = fullMatch.trim() - importSyntax.startsWith('@import') - const importValue = importSyntax.slice(7).trim() - // 匹配 @import 后字符串格式 - const importUrlRegex = /url\s*\(['"]([^'"]+)['"]\)/g - const importStrRegexp = /^(['"])([^'"]+)\1/ - - let urlMatch = null - if (importValue.startsWith('url')) { - urlMatch = importUrlRegex.exec(importValue)?.[1] - } else { - urlMatch = importStrRegexp.exec(importValue)?.[2] - } - if (!urlMatch) { - continue - } - - importList.push({ - start: importMatch.index, - end: importMatch.index + fullMatch.length, - content: fullMatch, - url: urlMatch - }) - } - - // 过滤掉在注释中的 @import 语法 - importList = importList.filter(imp => !isInComments(imp.start)) - - // 逆序替换 import,避免修改内容导致的索引偏移问题 - importList.sort((a, b) => (a.start > b.start ? -1 : 1)) - - const result = await Promise.all( - importList.map(async imp => { - const importPath = imp.url - if (!importPath) return - // 非法路径直接报错 - const resolvedUrl = await resolve(importPath, fromParent) - const content = (await load(resolvedUrl)) ?? '' - return { - content, - start: imp.start, - end: imp.end, - resolvedUrl + fsModule.readFile = function (path, options, callback) { + // 处理参数重载 + let cb = callback + if (typeof options === 'function') { + cb = options + options = null } - }) - ) - for (const res of result) { - if (!res) continue - css = css.slice(0, res.start) + '\n' + res.content + '\n' + css.slice(res.end) - } - - return { - css, - imports: result.map(item => item.resolvedUrl) - } -} -/** - * @param {StripByPostcssOption} options - */ -async function stripByPostcss(options) { - const defs = options.defs ?? {} - - function stripContentCondition(content) { - content = stripCondition(content, defs) + const wrappedCallback = (err, data) => { + if (err) return cb(err) + if (shouldStrip(path)) { + try { + if (typeof data === 'string') { + const result = stripCondition(data, defs) + return cb(null, result) + } + } catch (e) { + return cb(null, data) + } + } + cb(null, data) + } - if (options.lang === 'stylus') { - content = content.replace(/\t/g, ' ') + if (options) { + return readFile.call(fsModule, path, options, wrappedCallback) + } + return readFile.call(fsModule, path, wrappedCallback) } - - return content } - /** - * @type {string} - */ - const afterConditionStrip = stripContentCondition(options.css, defs) - const dependencies = [] - - const atImportOptions = { - async load(filename) { - let content = await fs.readFile(filename, 'utf-8') - - content = stripContentCondition(content, defs) - - const data = await atImport({ - ...atImportOptions, - from: filename, - css: content - }) - dependencies.push(...data.imports) - return data.css - }, - resolve: (id, base) => { - return new Promise((resolve, reject) => { - // 处理 ~ 开头的路径 - options.resolve(base, id.startsWith('~') && !id.startsWith('~/') ? loaderUtils.urlToRequest(id) : id, (err, res) => { - if (err) return reject(err) - if (typeof res !== 'string') { - return reject(new Error(`[mpx-strip-conditional-loader]: Cannot resolve ${id} from ${base}`)) - } - resolve(res) - }) - }) - } + patchFs(fs) + try { + patchFs(require('graceful-fs')) + } catch (e) { + console.error('[mpx-strip-conditional-loader]: Error patching graceful-fs:', e) } - const result = await atImport({ - ...atImportOptions, - from: options.resourcePath, - css: afterConditionStrip - }) - - dependencies.push(...result.imports) - - return { - css: result.css, - dependencies + try { + const createRequire = Module.createRequire + if (createRequire) { + const lessPath = require.resolve('less', { paths: [projectRoot || process.cwd()] }) + const lessRequire = createRequire(lessPath) + patchFs(lessRequire('graceful-fs')) + } + } catch (e) { + console.error('[mpx-strip-conditional-loader]: Error patching graceful-fs used by less:', e) } } - -const createResolver = (context, extensions) => - context.getResolve({ mainFiles: ['index'], extensions: [...extensions, '.css'], preferRelative: true }) -const resolver = { - stylus: context => createResolver(context, ['.styl']), - scss: context => createResolver(context, ['.scss']), - less: context => createResolver(context, ['.styl']) -} - /** * * @this {import('webpack').LoaderContext} @@ -360,22 +226,10 @@ module.exports = async function (css) { const callback = this.async() const mpx = this.getMpx() - const { resourcePath, queryObj } = parseRequest(this.resource) - - const result = await stripByPostcss({ - lang: queryObj.lang, - resourcePath, - css, - defs: mpx.defs, - resolve: resolver[queryObj.lang] ? resolver[queryObj.lang](this) : this.resolve.bind(this) - }) - - for (const dep of result.dependencies) { - this.addDependency(path.normalize(dep)) - } + const result = stripCondition(css, mpx.defs) - callback(null, result.css, result.map) + callback(null, result) } -module.exports.stripByPostcss = stripByPostcss module.exports.stripCondition = stripCondition +module.exports.rewriteFsForCss = rewriteFsForCss From c1e6eccf5715bd0f33cff1135af1772e3c88b563 Mon Sep 17 00:00:00 2001 From: xuegan Date: Tue, 23 Dec 2025 20:35:34 +0800 Subject: [PATCH 2/7] fix: lint error --- packages/webpack-plugin/lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index 2e34d1ccf9..3faf628597 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -325,7 +325,7 @@ class MpxWebpackPlugin { apply (compiler) { // 注入 fs 代理 - rewriteReadFileSyncForCss(this.options.defs, this.options.projectRoot) + rewriteFsForCss(this.options.defs, this.options.projectRoot) if (!compiler.__mpx__) { compiler.__mpx__ = true From fa451cf969cbe676a42e1a0e32a4b18092492a85 Mon Sep 17 00:00:00 2001 From: xuegan Date: Wed, 24 Dec 2025 17:39:00 +0800 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9rewriteFs?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webpack-plugin/lib/index.js | 6 +- .../strip-conditional-loader.js | 124 ++++++++---------- 2 files changed, 56 insertions(+), 74 deletions(-) diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index 3faf628597..e6e8cc9880 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -79,8 +79,8 @@ const LoadAsyncChunkModule = require('./react/LoadAsyncChunkModule') const ExternalModule = require('webpack/lib/ExternalModule') const { RetryRuntimeModule, RetryRuntimeGlobal } = require('./dependencies/RetryRuntimeModule') const checkVersionCompatibility = require('./utils/check-core-version-match') -const { rewriteFsForCss } = require('./style-compiler/strip-conditional-loader') - +const { rewriteFSForCss, startFSStripForCss } = require('./style-compiler/strip-conditional-loader') +rewriteFSForCss() checkVersionCompatibility() const isProductionLikeMode = options => { @@ -325,7 +325,7 @@ class MpxWebpackPlugin { apply (compiler) { // 注入 fs 代理 - rewriteFsForCss(this.options.defs, this.options.projectRoot) + startFSStripForCss(this.options.defs) if (!compiler.__mpx__) { compiler.__mpx__ = true 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 c1a2200dc4..433d395b29 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -1,5 +1,4 @@ const fs = require('fs') -const Module = require('module') class Node { constructor(type, condition = null) { @@ -27,8 +26,7 @@ function tokenize(cssString) { // match[2] 为条件(如果存在) tokens.push({ type: match[1], // 'if'、'elif'、'else' 或 'endif' - condition: match[2] ? match[2].trim() : null, - rawValue: match[0] + condition: match[2] ? match[2].trim() : null }) lastIndex = regex.lastIndex } @@ -53,7 +51,6 @@ function parse(cssString) { currentChildren.push(node) } else if (token.type === 'if') { const node = new Node('If', token.condition) - node.rawValue = token.rawValue || '' currentChildren.push(node) nodeStack.push(currentChildren) currentChildren = node.children @@ -63,7 +60,6 @@ function parse(cssString) { } currentChildren = nodeStack[nodeStack.length - 1] const node = new Node('ElseIf', token.condition) - node.rawValue = token.rawValue || '' currentChildren.push(node) currentChildren = node.children } else if (token.type === 'else') { @@ -72,16 +68,12 @@ function parse(cssString) { } currentChildren = nodeStack[nodeStack.length - 1] const node = new Node('Else') - node.rawValue = token.rawValue || '' currentChildren.push(node) currentChildren = node.children } else if (token.type === 'endif') { - const node = new Node('EndIf') - node.rawValue = token.rawValue || '' if (nodeStack.length > 0) { currentChildren = nodeStack.pop() } - currentChildren.push(node) } }) return ast @@ -110,22 +102,17 @@ function traverseAndEvaluate(ast, defs) { } else if (node.type === 'If') { // 直接判断 If 节点 batchedIf = false - output += node.rawValue || '' if (evaluateCondition(node.condition, defs)) { traverse(node.children) batchedIf = true } } else if (node.type === 'ElseIf' && !batchedIf) { - output += node.rawValue || '' if (evaluateCondition(node.condition, defs)) { traverse(node.children) batchedIf = true } } else if (node.type === 'Else' && !batchedIf) { - output += node.rawValue || '' traverse(node.children) - } else if (node.type === 'EndIf') { - output += node.rawValue || '' } } } @@ -144,75 +131,69 @@ function stripCondition(content, defs) { return traverseAndEvaluate(ast, defs) } -function rewriteFsForCss(defs, projectRoot) { +let proxyReadFileSync +let proxyReadFile +const rawReadFileSync = fs.readFileSync +const rawReadFile = fs.readFile + +function rewriteFSForCss() { + proxyReadFileSync = function (path, options) { + return rawReadFileSync.call(fs, path, options) + } + proxyReadFile = function (path, options, callback) { + return rawReadFile.call(fs, path, options, callback) + } + fs.readFileSync = function (path, options) { + return proxyReadFileSync(path, options) + } + fs.readFile = function (path, options, callback) { + return proxyReadFile(path, options, callback) + } +} + +function startFSStripForCss(defs) { function shouldStrip(path) { return typeof path === 'string' && /\.(styl|scss|sass|less|css)$/.test(path) } - - function patchFs(fsModule) { - const readFileSync = fsModule.readFileSync - const readFile = fsModule.readFile - - fsModule.readFileSync = function (path, options) { - const content = readFileSync.call(fsModule, path, options) - if (shouldStrip(path)) { - try { - if (typeof content === 'string') { - return stripCondition(content, defs) - } - } catch (e) { - return content + proxyReadFileSync = function (path, options) { + const content = rawReadFileSync.call(fs, path, options) + if (shouldStrip(path)) { + try { + if (typeof content === 'string') { + return stripCondition(content, defs) } + } catch (e) { + return content } - return content } + return content + } - fsModule.readFile = function (path, options, callback) { - // 处理参数重载 - let cb = callback - if (typeof options === 'function') { - cb = options - options = null - } + proxyReadFile = function (path, options, callback) { + let cb = callback + if (typeof options === 'function') { + cb = options + options = null + } - const wrappedCallback = (err, data) => { - if (err) return cb(err) - if (shouldStrip(path)) { - try { - if (typeof data === 'string') { - const result = stripCondition(data, defs) - return cb(null, result) - } - } catch (e) { - return cb(null, data) + const wrappedCallback = (err, data) => { + if (err) return cb(err) + if (shouldStrip(path)) { + try { + if (typeof data === 'string') { + const result = stripCondition(data, defs) + return cb(null, result) } + } catch (e) { + return cb(null, data) } - cb(null, data) - } - - if (options) { - return readFile.call(fsModule, path, options, wrappedCallback) } - return readFile.call(fsModule, path, wrappedCallback) + cb(null, data) } - } - - patchFs(fs) - try { - patchFs(require('graceful-fs')) - } catch (e) { - console.error('[mpx-strip-conditional-loader]: Error patching graceful-fs:', e) - } - - try { - const createRequire = Module.createRequire - if (createRequire) { - const lessPath = require.resolve('less', { paths: [projectRoot || process.cwd()] }) - const lessRequire = createRequire(lessPath) - patchFs(lessRequire('graceful-fs')) + if (options) { + return rawReadFile.call(fs, path, options, wrappedCallback) } - } catch (e) { - console.error('[mpx-strip-conditional-loader]: Error patching graceful-fs used by less:', e) + return rawReadFile.call(fs, path, wrappedCallback) } } /** @@ -232,4 +213,5 @@ module.exports = async function (css) { } module.exports.stripCondition = stripCondition -module.exports.rewriteFsForCss = rewriteFsForCss +module.exports.rewriteFSForCss = rewriteFSForCss +module.exports.startFSStripForCss = startFSStripForCss From f3183dbc7aef6ba05ee8f8c0b53d61e4be53510b Mon Sep 17 00:00:00 2001 From: xuegan Date: Mon, 5 Jan 2026 17:55:41 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=A4=9A?= =?UTF-8?q?=E7=A7=8D=E6=A0=BC=E5=BC=8F=E6=B3=A8=E9=87=8A&=E6=94=AF?= =?UTF-8?q?=E6=8C=81Mpx=E6=96=87=E4=BB=B6=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webpack-plugin/lib/index.js | 35 --- .../strip-conditional-loader.js | 74 +++-- .../common/style-strip-condition.spec.js | 254 +++++++++++++----- 3 files changed, 229 insertions(+), 134 deletions(-) diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index e6e8cc9880..fb96ecaa83 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -58,7 +58,6 @@ 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 styleStripConditionalPath = 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') @@ -1918,42 +1917,9 @@ try { normalModuleFactory.hooks.afterResolve.tap('MpxWebpackPlugin', ({ createData }) => { const { queryObj } = parseRequest(createData.request) const loaders = createData.loaders - - // 样式 loader 类型检测和条件编译 loader 插入的工具函数 - const STYLE_LOADER_TYPES = ['stylus-loader', 'sass-loader', 'less-loader', 'css-loader', wxssLoaderPath] - const injectStyleStripLoader = (loaders) => { - // 检查是否已经存在 stripLoader - const hasStripLoader = loaders.some(loader => { - const loaderPath = toPosix(loader.loader) - return loaderPath.includes('style-compiler/strip-conditional-loader') - }) - if (hasStripLoader) { - return - } - const loaderTypes = new Map(STYLE_LOADER_TYPES.map(type => [`node_modules/${type}`, -1])) - loaders.forEach((loader, index) => { - const currentLoader = toPosix(loader.loader) - for (const [key] of loaderTypes) { - if (currentLoader.includes(key)) { - loaderTypes.set(key, index) - break - } - } - }) - const targetIndex = STYLE_LOADER_TYPES - .map(type => loaderTypes.get(`node_modules/${type}`)) - .find(index => index !== -1) - - if (targetIndex !== undefined) { - loaders.splice(targetIndex + 1, 0, { loader: styleStripConditionalPath }) - } - } if (queryObj.mpx && queryObj.mpx !== MPX_PROCESSED_FLAG) { const type = queryObj.type const extract = queryObj.extract - if (type === 'styles') { - injectStyleStripLoader(loaders) - } switch (type) { case 'styles': @@ -2006,7 +1972,6 @@ try { } // mpxStyleOptions 为 mpx style 文件的标识,避免 Vue 文件插入 styleCompiler 后导致 vue scoped 样式隔离失效 if (isWeb(mpx.mode) && queryObj.mpxStyleOptions) { - injectStyleStripLoader(loaders) const firstLoader = loaders[0] ? toPosix(loaders[0].loader) : '' const isPitcherRequest = firstLoader.includes('node_modules/vue-loader/lib/loaders/pitcher') let cssLoaderIndex = -1 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 433d395b29..e524913127 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js @@ -11,7 +11,12 @@ class Node { // 提取 css string 为 token function tokenize(cssString) { - const regex = /\/\*\s*@mpx-(if|elif|else|endif)(?:\s*\((.*?)\))?\s*\*\//g + // Support /* ... */, // ..., and styles + // 1. : /\/\*\s*@mpx-(if|elif|else|endif)(?:\s*\(([\s\S]*?)\))?\s*\*\//g + // 2. : /\/\/\s*@mpx-(if|elif|else|endif)(?:\s*\((.*?)\))?\s*$/gm + // 3. : //g + // Combined: + const regex = /(?:\/\*\s*@mpx-(if|elif|else|endif)(?:\s*\(([\s\S]*?)\))?\s*\*\/)|(?:\/\/\s*@mpx-(if|elif|else|endif)(?:\s*\((.*?)\))?\s*)|(?:)/g const tokens = [] let lastIndex = 0 let match @@ -22,11 +27,15 @@ function tokenize(cssString) { const text = cssString.substring(lastIndex, match.index) tokens.push({ type: 'text', content: text }) } - // match[1] 为关键字:if, elif, else, endif - // match[2] 为条件(如果存在) + // 1,2: (/* ... */) + // 3,4: (// ...) + // 5,6: () + const type = match[1] || match[3] || match[5] + const condition = (match[2] || match[4] || match[6]) + tokens.push({ - type: match[1], // 'if'、'elif'、'else' 或 'endif' - condition: match[2] ? match[2].trim() : null + type: type, + condition: condition ? condition.trim() : null }) lastIndex = regex.lastIndex } @@ -137,23 +146,17 @@ const rawReadFileSync = fs.readFileSync const rawReadFile = fs.readFile function rewriteFSForCss() { - proxyReadFileSync = function (path, options) { - return rawReadFileSync.call(fs, path, options) - } - proxyReadFile = function (path, options, callback) { - return rawReadFile.call(fs, path, options, callback) - } - fs.readFileSync = function (path, options) { - return proxyReadFileSync(path, options) + fs.readFileSync = function () { + return (proxyReadFileSync || rawReadFileSync).apply(fs, arguments) } - fs.readFile = function (path, options, callback) { - return proxyReadFile(path, options, callback) + fs.readFile = function () { + return (proxyReadFile || rawReadFile).apply(fs, arguments) } } function startFSStripForCss(defs) { function shouldStrip(path) { - return typeof path === 'string' && /\.(styl|scss|sass|less|css)$/.test(path) + return typeof path === 'string' && /\.(styl|scss|sass|less|css|mpx)$/.test(path) } proxyReadFileSync = function (path, options) { const content = rawReadFileSync.call(fs, path, options) @@ -161,6 +164,12 @@ function startFSStripForCss(defs) { try { if (typeof content === 'string') { return stripCondition(content, defs) + } else if (Buffer.isBuffer(content)) { + const str = content.toString('utf-8') + const result = stripCondition(str, defs) + if (result !== str) { + return Buffer.from(result, 'utf-8') + } } } catch (e) { return content @@ -169,31 +178,38 @@ function startFSStripForCss(defs) { return content } - proxyReadFile = function (path, options, callback) { - let cb = callback - if (typeof options === 'function') { - cb = options - options = null + proxyReadFile = function () { + const args = Array.from(arguments) + const callback = args[args.length - 1] + const path = args[0] + + if (typeof callback !== 'function') { + return rawReadFile.apply(fs, args) } const wrappedCallback = (err, data) => { - if (err) return cb(err) + if (err) return callback(err) if (shouldStrip(path)) { try { if (typeof data === 'string') { const result = stripCondition(data, defs) - return cb(null, result) + return callback(null, result) + } else if (Buffer.isBuffer(data)) { + const content = data.toString('utf-8') + const result = stripCondition(content, defs) + if (result !== content) { + return callback(null, Buffer.from(result, 'utf-8')) + } } } catch (e) { - return cb(null, data) + return callback(null, data) } } - cb(null, data) + callback(null, data) } - if (options) { - return rawReadFile.call(fs, path, options, wrappedCallback) - } - return rawReadFile.call(fs, path, wrappedCallback) + + args[args.length - 1] = wrappedCallback + return rawReadFile.apply(fs, args) } } /** diff --git a/packages/webpack-plugin/test/platform/common/style-strip-condition.spec.js b/packages/webpack-plugin/test/platform/common/style-strip-condition.spec.js index cc9af1bbf6..9224821afc 100644 --- a/packages/webpack-plugin/test/platform/common/style-strip-condition.spec.js +++ b/packages/webpack-plugin/test/platform/common/style-strip-condition.spec.js @@ -1,83 +1,197 @@ -const { fixture } = require('../../util/testing') -const { stripByPostcss } = require('../../../lib/style-compiler/strip-conditional-loader') -const fs = require('node:fs/promises') -const path = require('path') -function formatResult(result, config) { - return ` +const { stripCondition } = require('../../../lib/style-compiler/strip-conditional-loader') -## Config +describe('strip-conditional-loader unit tests', () => { + describe('stripCondition logic', () => { + const defs = { + platform: 'wx', + theme: 'dark', + version: 2 + } -\`\`\`json -${JSON.stringify(config, null, 4)} -\`\`\` + describe('CSS Block Comments /* ... */', () => { + it('should keep content when condition is true', () => { + const input = ` + /* @mpx-if (platform === 'wx') */ + .wx-style { color: red; } + /* @mpx-endif */ + ` + const result = stripCondition(input, defs) + expect(result).toContain('.wx-style { color: red; }') + }) -## Result + it('should remove content when condition is false', () => { + const input = ` + /* @mpx-if (platform === 'ali') */ + .ali-style { color: blue; } + /* @mpx-endif */ + ` + const result = stripCondition(input, defs) + expect(result.trim()).toBe('') + }) -\`\`\`${config.lang} -${result} -\`\`\` + it('should support elif and else', () => { + const input = ` + /* @mpx-if (platform === 'ali') */ + .ali { color: blue; } + /* @mpx-elif (platform === 'wx') */ + .wx { color: red; } + /* @mpx-else */ + .other { color: black; } + /* @mpx-endif */ + ` + const result = stripCondition(input, defs) + expect(result).not.toContain('.ali { color: blue; }') + expect(result).toContain('.wx { color: red; }') + expect(result).not.toContain('.other { color: black; }') + }) + }) - `.trim() -} + describe('Line Comments // ...', () => { + it('should work with line comments', () => { + const input = ` + // @mpx-if (theme === 'dark') + .dark-mode { background: #000; } + // @mpx-endif + ` + const result = stripCondition(input, defs) + expect(result).toContain('.dark-mode { background: #000; }') + }) -const getLangFromExtension = filename => { - const ext = path.extname(filename) - switch (ext) { - case '.styl': - return 'stylus' - case '.less': - return 'less' - case '.scss': - return 'scss' - default: - return 'css' - } -} + it('should handle false condition with line comments', () => { + const input = ` + // @mpx-if (theme === 'light') + .light-mode { background: #fff; } + // @mpx-endif + ` + const result = stripCondition(input, defs) + expect(result.trim()).toBe('') + }) + }) -describe('strip-conditional-loader', () => { - fixture( - // './fixtures/css-condition/at-import/index.styl', - './fixtures/css-condition/**/index.{styl,less,css,scss}', - async ({ filename, config = {}, cwd }) => { - const { lang = getLangFromExtension(filename), defs = {} } = config + describe('HTML Comments ', () => { + it('should work with HTML comments', () => { + const input = ` + + WeChat + + ` + const result = stripCondition(input, defs) + expect(result).toContain('WeChat') + }) - const content = await fs.readFile(filename, 'utf-8') + it('should support multiline conditions in HTML comments', () => { + const input = ` + + Multiline Condition + + ` + const result = stripCondition(input, defs) + expect(result).toContain('Multiline Condition') + }) - let result - let dependencies = [] + it('should support elif/else in HTML comments', () => { + const input = ` + + Ali + + Wx + + Other + + ` + const result = stripCondition(input, defs) + expect(result).not.toContain('Ali') + expect(result).toContain('Wx') + expect(result).not.toContain('Other') + }) + }) - try { - const output = await stripByPostcss({ - css: content, - lang, - resourcePath: filename, - defs, - resolve: (base, id, callback) => { - callback(null, path.join(base, id)) - } - }) - result = output.css - dependencies = output.dependencies || [] - } catch (error) { - console.log(error) - result = `Error: ${error?.message.trim() || error}` - } + describe('Nested Conditions', () => { + it('should handle nested conditions', () => { + const input = ` + /* @mpx-if (platform === 'wx') */ + .wx-outer { display: block; } + /* @mpx-if (theme === 'dark') */ + .wx-dark { color: #333; } + /* @mpx-endif */ + /* @mpx-if (theme === 'light') */ + .wx-light { color: #fff; } + /* @mpx-endif */ + /* @mpx-endif */ + ` + const result = stripCondition(input, defs) + expect(result).toContain('.wx-outer { display: block; }') + expect(result).toContain('.wx-dark { color: #333; }') + expect(result).not.toContain('.wx-light { color: #fff; }') + }) - return formatResult(result, { - lang, - resourcePath: path.relative(cwd, filename), - defs, - dependencies: dependencies.map(dep => path.relative(cwd, dep)) + it('should handle nested conditions with mixed comment types', () => { + const input = ` + + + // @mpx-if (theme === 'dark') + Dark Mode + // @mpx-endif + + + ` + const result = stripCondition(input, defs) + expect(result).toContain('Dark Mode') }) - }, - { - cwd: __dirname, - runner: (fn, config) => { - const name = path.basename(config.cwd) - test(`${name}`, async () => { - await fn(config) - }) - } - } - ) + }) + + describe('Complex Conditions', () => { + it('should evaluate logical operators', () => { + const input = ` + /* @mpx-if (platform === 'wx' && version >= 2) */ + .modern-wx { opacity: 1; } + /* @mpx-endif */ + ` + const result = stripCondition(input, defs) + expect(result).toContain('.modern-wx { opacity: 1; }') + }) + + it('should evaluate logical OR', () => { + const input = ` + /* @mpx-if (platform === 'ali' || theme === 'dark') */ + .hybrid { opacity: 0.5; } + /* @mpx-endif */ + ` + const result = stripCondition(input, defs) + expect(result).toContain('.hybrid { opacity: 0.5; }') + }) + }) + + describe('Edge Cases', () => { + it('should handle text outside conditions', () => { + const input = ` + .common { color: gray; } + /* @mpx-if (platform === 'wx') */ + .wx { color: green; } + /* @mpx-endif */ + .footer { margin: 0; } + ` + const result = stripCondition(input, defs) + expect(result).toContain('.common { color: gray; }') + expect(result).toContain('.wx { color: green; }') + expect(result).toContain('.footer { margin: 0; }') + }) + + it('should handle unknown variables in condition (treat as undefined/error handled)', () => { + // The implementation catches errors and returns false for the condition usually + // or logs error. Based on implementation: console.error and return false. + const input = ` + /* @mpx-if (unknownVar === true) */ + .should-not-exist {} + /* @mpx-endif */ + ` + // We expect it to not throw, but exclude the content + const result = stripCondition(input, defs) + expect(result.trim()).toBe('') + }) + }) + }) }) From 29e0a0831ee5df3079440c5b6f970a0f2f889ca4 Mon Sep 17 00:00:00 2001 From: xuegan Date: Thu, 8 Jan 2026 11:15:49 +0800 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0init-mpx=E6=96=87=E4=BB=B6=E8=BF=9B=E8=A1=8C=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E5=B7=A5=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webpack-plugin/lib/index.js | 4 ++-- packages/webpack-plugin/lib/init-mpx.js | 3 +++ ...itional-loader.js => strip-conditional.js} | 19 ++++--------------- 3 files changed, 9 insertions(+), 17 deletions(-) create mode 100644 packages/webpack-plugin/lib/init-mpx.js rename packages/webpack-plugin/lib/style-compiler/{strip-conditional-loader.js => strip-conditional.js} (95%) diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index fb96ecaa83..2c420c81f7 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -1,5 +1,6 @@ 'use strict' +require('./init-mpx') const path = require('path') const { ConcatSource, RawSource } = require('webpack').sources const ResolveDependency = require('./dependencies/ResolveDependency') @@ -78,8 +79,7 @@ const LoadAsyncChunkModule = require('./react/LoadAsyncChunkModule') const ExternalModule = require('webpack/lib/ExternalModule') const { RetryRuntimeModule, RetryRuntimeGlobal } = require('./dependencies/RetryRuntimeModule') const checkVersionCompatibility = require('./utils/check-core-version-match') -const { rewriteFSForCss, startFSStripForCss } = require('./style-compiler/strip-conditional-loader') -rewriteFSForCss() +const { startFSStripForCss } = require('./style-compiler/strip-conditional-loader') checkVersionCompatibility() const isProductionLikeMode = options => { diff --git a/packages/webpack-plugin/lib/init-mpx.js b/packages/webpack-plugin/lib/init-mpx.js new file mode 100644 index 0000000000..3d3a7828b7 --- /dev/null +++ b/packages/webpack-plugin/lib/init-mpx.js @@ -0,0 +1,3 @@ +const { rewriteFSForCss } = require('./style-compiler/strip-conditional-loader') + +rewriteFSForCss() diff --git a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js b/packages/webpack-plugin/lib/style-compiler/strip-conditional.js similarity index 95% rename from packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js rename to packages/webpack-plugin/lib/style-compiler/strip-conditional.js index e524913127..235a9deb63 100644 --- a/packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js +++ b/packages/webpack-plugin/lib/style-compiler/strip-conditional.js @@ -145,7 +145,11 @@ let proxyReadFile const rawReadFileSync = fs.readFileSync const rawReadFile = fs.readFile +let isRewritten = false + function rewriteFSForCss() { + if (isRewritten) return + isRewritten = true fs.readFileSync = function () { return (proxyReadFileSync || rawReadFileSync).apply(fs, arguments) } @@ -212,21 +216,6 @@ function startFSStripForCss(defs) { return rawReadFile.apply(fs, args) } } -/** - * - * @this {import('webpack').LoaderContext} - * @param {string} css - */ -module.exports = async function (css) { - this.cacheable() - - const callback = this.async() - - const mpx = this.getMpx() - const result = stripCondition(css, mpx.defs) - - callback(null, result) -} module.exports.stripCondition = stripCondition module.exports.rewriteFSForCss = rewriteFSForCss From 372cfc42654f27b3ad0037640b80a6a00add6de6 Mon Sep 17 00:00:00 2001 From: xuegan Date: Thu, 8 Jan 2026 11:18:55 +0800 Subject: [PATCH 6/7] fix: path error --- packages/webpack-plugin/lib/index.js | 2 +- packages/webpack-plugin/lib/init-mpx.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index 2c420c81f7..4013da37db 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -79,7 +79,7 @@ const LoadAsyncChunkModule = require('./react/LoadAsyncChunkModule') const ExternalModule = require('webpack/lib/ExternalModule') const { RetryRuntimeModule, RetryRuntimeGlobal } = require('./dependencies/RetryRuntimeModule') const checkVersionCompatibility = require('./utils/check-core-version-match') -const { startFSStripForCss } = require('./style-compiler/strip-conditional-loader') +const { startFSStripForCss } = require('./style-compiler/strip-conditional') checkVersionCompatibility() const isProductionLikeMode = options => { diff --git a/packages/webpack-plugin/lib/init-mpx.js b/packages/webpack-plugin/lib/init-mpx.js index 3d3a7828b7..8e50aaea65 100644 --- a/packages/webpack-plugin/lib/init-mpx.js +++ b/packages/webpack-plugin/lib/init-mpx.js @@ -1,3 +1,3 @@ -const { rewriteFSForCss } = require('./style-compiler/strip-conditional-loader') +const { rewriteFSForCss } = require('./style-compiler/strip-conditional') rewriteFSForCss() From e840b6b0092cdc4cc90fd515c638a4cf461ee515 Mon Sep 17 00:00:00 2001 From: xuegan Date: Thu, 8 Jan 2026 11:53:43 +0800 Subject: [PATCH 7/7] fix: unit test error --- .../test/platform/common/style-strip-condition.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webpack-plugin/test/platform/common/style-strip-condition.spec.js b/packages/webpack-plugin/test/platform/common/style-strip-condition.spec.js index 9224821afc..74a86589f7 100644 --- a/packages/webpack-plugin/test/platform/common/style-strip-condition.spec.js +++ b/packages/webpack-plugin/test/platform/common/style-strip-condition.spec.js @@ -1,6 +1,6 @@ -const { stripCondition } = require('../../../lib/style-compiler/strip-conditional-loader') +const { stripCondition } = require('../../../lib/style-compiler/strip-conditional') -describe('strip-conditional-loader unit tests', () => { +describe('strip-conditional unit tests', () => { describe('stripCondition logic', () => { const defs = { platform: 'wx',