Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0d173f5
feat: support import style strip condition
shulandmimi May 26, 2025
3776e83
feat: support url()
shulandmimi May 27, 2025
5a8c022
feat: usage postcss process strip condition
shulandmimi Jul 17, 2025
f4f9209
feat: support vue style strip condition
shulandmimi Jul 17, 2025
b88a322
chore: update unit test dep
shulandmimi Jul 17, 2025
1623608
Merge branch 'master' into fix/importStyleStripCondition
shulandmimi Jul 18, 2025
e4fea96
feat: 调整stripConditional loader在Vue时的插入处理逻辑
Blackgan3 Aug 24, 2025
8b11931
fix: insert strip condition once
shulandmimi Aug 26, 2025
9a9df5c
feat: 简化afterResolve内stripConditionalLoader的插入逻辑
Blackgan3 Aug 28, 2025
b588edc
Merge remote-tracking branch 'origin/fix/importStyleStripCondition' i…
Blackgan3 Sep 1, 2025
c2e69a2
feat: 调整loader顺序
Blackgan3 Sep 1, 2025
25d121b
feat: 添加wxssLoaderPath路径判断
Blackgan3 Sep 1, 2025
fd65dc9
feat: 调整stripLoader插入的各种边界场景
Blackgan3 Sep 1, 2025
6b54db5
feat: 调整空格格式化
Blackgan3 Sep 1, 2025
a183133
feat: 调整injectStyleStripLoader逻辑
Blackgan3 Sep 1, 2025
fcada98
feat: url lang 添加的时机
shulandmimi Sep 1, 2025
1e01883
Merge branch 'refs/heads/master' into fix/importStyleStripCondition
Blackgan3 Sep 4, 2025
7bcf9ee
feat: injectStyleStripLoader顺序调整
Blackgan3 Sep 16, 2025
d1f1534
Merge branch 'master' into fix/importStyleStripCondition
hiyuki Sep 16, 2025
b6bae92
Merge branch 'master' into fix/importStyleStripCondition
shulandmimi Sep 18, 2025
7c2d626
Merge branch 'master' into fix/importStyleStripCondition
hiyuki Oct 10, 2025
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
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ module.exports = {
camelcase: 0,
indent: 0,
'symbol-description': 0,
'node/no-callback-literal': 0
'node/no-callback-literal': 0,
'space-before-function-paren': 0
},
env: {
'jest/globals': true,
Expand Down
3 changes: 2 additions & 1 deletion jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"testPathIgnorePatterns": ["dist", "node_modules"],
"projects": [
"<rootDir>/packages/*/jest.config.json"
]
],
"testTimeout": 10000
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"lint": "eslint --ext .js,.ts,.tsx,.jsx packages/",
"fix": "eslint --fix --ext .js,.ts,.tsx,.jsx packages/",
"lint:js": "eslint --ext .js packages/",
"test": "jest",
"test": "jest --passWithNoTests",
"release": "npm run lint && npm run test && npx lerna version",
"docs:dev": "vitepress dev docs-vitepress",
"docs:build": "vitepress build docs-vitepress",
Expand Down
5 changes: 3 additions & 2 deletions packages/webpack-plugin/jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"testEnvironment": "jsdom",
"moduleNameMapper": {
"\\.(css|styl)$": "identity-obj-proxy"
}
}
},
"testTimeout": 10000
}
2 changes: 2 additions & 0 deletions packages/webpack-plugin/lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ module.exports = function createHelpers (loaderContext) {
if (part.useJSONJS) options.useJSONJS = true
// eslint-disable-next-line no-fallthrough
case 'styles':
options.lang = part.lang
// eslint-disable-next-line no-fallthrough
case 'template':
options.extract = true
}
Expand Down
46 changes: 32 additions & 14 deletions packages/webpack-plugin/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1907,24 +1907,41 @@ 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') {
let insertBeforeIndex = -1
// 单次遍历收集所有索引
loaders.forEach((loader, index) => {
const currentLoader = toPosix(loader.loader)
if (currentLoader.includes('node_modules/stylus-loader') || currentLoader.includes('node_modules/sass-loader') || currentLoader.includes('node_modules/less-loader')) {
insertBeforeIndex = index
}
})

if (insertBeforeIndex !== -1) {
loaders.splice(insertBeforeIndex, 0, { loader: styleStripConditionalPath })
}
loaders.push({ loader: styleStripConditionalPath })
injectStyleStripLoader(loaders)
}

switch (type) {
Expand Down Expand Up @@ -1978,6 +1995,7 @@ 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
Expand Down
164 changes: 152 additions & 12 deletions packages/webpack-plugin/lib/style-compiler/strip-conditional-loader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
const fs = require('fs/promises')
const parseRequest = require('../utils/parse-request')
const atImport = require('postcss-import')
const { default: postcss } = require('postcss')

class Node {
constructor (type, condition = null) {
constructor(type, condition = null) {
this.type = type // 'If', 'ElseIf', 'Else' 或 'Text'
this.condition = condition // If 或 Elif 的条件
this.children = []
Expand All @@ -8,7 +13,7 @@ class Node {
}

// 提取 css string 为 token
function tokenize (cssString) {
function tokenize(cssString) {
const regex = /\/\*\s*@mpx-(if|elif|else|endif)(?:\s*\((.*?)\))?\s*\*\//g
const tokens = []
let lastIndex = 0
Expand Down Expand Up @@ -37,12 +42,12 @@ function tokenize (cssString) {
}

// parse:将生成的 token 数组构造成嵌套的 AST
function parse (cssString) {
function parse(cssString) {
const tokens = tokenize(cssString)
const ast = []
const nodeStack = []
let currentChildren = ast
tokens.forEach(token => {
tokens.forEach((token) => {
if (token.type === 'text') {
const node = new Node('Text')
node.value = token.content
Expand Down Expand Up @@ -77,10 +82,10 @@ function parse (cssString) {
return ast
}

function evaluateCondition (condition, defs) {
function evaluateCondition(condition, defs) {
try {
const keys = Object.keys(defs)
const values = keys.map(key => defs[key])
const values = keys.map((key) => defs[key])
/* eslint-disable no-new-func */
const func = new Function(...keys, `return (${condition});`)
return func(...values)
Expand All @@ -90,10 +95,10 @@ function evaluateCondition (condition, defs) {
}
}

function traverseAndEvaluate (ast, defs) {
function traverseAndEvaluate(ast, defs) {
let output = ''
let batchedIf = false
function traverse (nodes) {
function traverse(nodes) {
for (const node of nodes) {
if (node.type === 'Text') {
output += node.value
Expand All @@ -118,10 +123,145 @@ function traverseAndEvaluate (ast, defs) {
return output
}

module.exports = function (css) {
/**
*
* @param {string} content
* @param {Record<string, any>} defs
* @returns
*/
function stripCondition(content, defs) {
const ast = parse(content)
const result = traverseAndEvaluate(ast, defs)
return result
}

/**
* @typedef {Object} StripByPostcssOption
* @property {string} lang 样式语法格式
* @property {string} resourcePath 文件路径
* @property {string} css 源文件
* @property {Record<string, any>} defs 条件定义
* @property {import('webpack').LoaderContext<any>['resolve']} resolve webpack resolve 方法
*/

/**
* @typedef {Object} AtImportConfig
* @property {string} from 当前文件路径
* @property {(filename: string) => Promise<string> | string;} load 加载文件内容的函数
* @property {(id: string, base: string) => Promise<string | null> | string | null;} resolve 解析文件路径的函数
*/

/**
*
* @param {Function} callback
* @param {string} name
* @returns
*/
const shouldInstallWarning = (callback, name) => {
return () => {
try {
return callback()
} catch (error) {
throw new Error(
`[mpx-strip-conditional-loader]: ${name} is not installed, please install it first.\norginal Error: ${
error?.message ?? error.toString()
}`,
{
cause: error
}
)
}
}
}
/**
*
* @typedef {import('postcss').ProcessOptions} ProcessOptions
* @typedef {import('postcss').Root} Root
*
* @type {Record<string, ProcessOptions['syntax']>}
*/
const styleSyntaxProcesserMap = {
stylus: shouldInstallWarning(() => require('postcss-styl'), 'postcss-styl'),
less: shouldInstallWarning(() => require('postcss-less'), 'postcss-less'),
scss: shouldInstallWarning(() => require('postcss-scss'), 'postcss-scss')
}

/**
* @param {StripByPostcssOption} options
*/
async function stripByPostcss(options) {
const syntax = styleSyntaxProcesserMap[options.lang]?.()
const defs = options.defs ?? {}

const afterConditionStrip = stripCondition(options.css, defs)

/**
* @type {import('postcss').AcceptedPlugin[]}
*/
const plugins = [
atImport({
async load(filename) {
let content = await fs.readFile(filename, 'utf-8')
const processer = postcss(plugins)

content = stripCondition(content, defs)

const { css } = await processer.process(content, {
syntax,
from: filename,
to: options.resourcePath
})
return css
},
resolve: (id, base) => {
return new Promise((resolve, reject) => {
options.resolve(base, 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)
})
})
}
})
]

const processer = postcss(plugins)

return processer.process(afterConditionStrip, {
from: options.resourcePath,
syntax
})
}

/**
*
* @this {import('webpack').LoaderContext<any>}
* @param {string} css
*/
module.exports = async function (css) {
this.cacheable()

const callback = this.async()

const mpx = this.getMpx()
const defs = mpx.defs
const ast = parse(css)
return traverseAndEvaluate(ast, defs)
const { resourcePath, queryObj } = parseRequest(this.resource)

const result = await stripByPostcss({
lang: queryObj.lang,
resourcePath,
css,
defs: mpx.defs,
resolve: this.resolve.bind(this)
})

callback(null, result.css, result.map)
}

module.exports.stripByPostcss = stripByPostcss
module.exports.stripCondition = stripCondition
9 changes: 8 additions & 1 deletion packages/webpack-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"mime": "^2.2.2",
"object-assign": "^4.1.1",
"postcss": "^8.4.5",
"postcss-import": "^16.1.1",
"postcss-load-config": "^3.1.1",
"postcss-modules-extract-imports": "^3.0.0",
"postcss-modules-local-by-default": "^4.0.0",
Expand All @@ -63,6 +64,9 @@
"webpack-virtual-modules": "^0.6.0"
},
"peerDependencies": {
"postcss-less": "^6.0.0",
"postcss-scss": "^4.0.9",
"postcss-styl": "^0.12.3",
"webpack": "^5.75.0"
},
"publishConfig": {
Expand All @@ -78,7 +82,7 @@
"url": "https://github.com/didi/mpx/issues"
},
"scripts": {
"test": "jest",
"test": "jest --passWithNoTests",
"copy-icons": "cp -r ./lib/runtime/components/react/mpx-icon/icons ./lib/runtime/components/react/dist/mpx-icon/icons",
"build": "rimraf ./lib/runtime/components/react/dist && tsc && npm run copy-icons"
},
Expand All @@ -87,7 +91,10 @@
"@mpxjs/api-proxy": "^2.10.16",
"@types/babel-traverse": "^6.25.4",
"@types/babel-types": "^7.0.4",
"@types/glob": "^8.1.0",
"@types/postcss-import": "^14.0.3",
"@types/react": "^18.2.79",
"glob": "^11.0.2",
"react-native": "^0.74.5",
"react-native-gesture-handler": "^2.18.1",
"react-native-linear-gradient": "^2.8.3",
Expand Down
Loading