Skip to content

Commit 6e29641

Browse files
committed
feat: 改进 mpx 默认的 mainCssChunkMatcher
1 parent 7b2b888 commit 6e29641

File tree

13 files changed

+1618
-30
lines changed

13 files changed

+1618
-30
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"weapp-tailwindcss": patch
3+
---
4+
5+
改进 mpx 默认的 `mainCssChunkMatcher`,凡是落在 `styles/` 目录下的 CSS/WXSS 产物都会被视为主样式包。这样像 `dist/wx/styles/app364cd4a4.wxss` 这种带 hash 的入口也能自动注入 Tailwind v4 变量与预设,不必再额外配置 matcher。
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,50 @@
1+
import type { AppType } from '@/types'
2+
3+
const MPX_STYLE_RESOURCE_QUERY_RE = /(?:\?|&)type=styles\b/
4+
15
export function getCacheKey(filename: string) {
26
return filename
37
}
8+
9+
export function stripResourceQuery(resource?: string): string | undefined {
10+
if (typeof resource !== 'string') {
11+
return resource
12+
}
13+
const queryIndex = resource.indexOf('?')
14+
if (queryIndex !== -1) {
15+
return resource.slice(0, queryIndex)
16+
}
17+
const hashIndex = resource.indexOf('#')
18+
if (hashIndex !== -1) {
19+
return resource.slice(0, hashIndex)
20+
}
21+
return resource
22+
}
23+
24+
export function isCssLikeModuleResource(
25+
resource: string | undefined,
26+
cssMatcher: (file: string) => boolean,
27+
appType?: AppType,
28+
) {
29+
if (typeof resource !== 'string') {
30+
return false
31+
}
32+
const normalizedResource = stripResourceQuery(resource)
33+
if (normalizedResource && cssMatcher(normalizedResource)) {
34+
return true
35+
}
36+
if (appType === 'mpx') {
37+
return MPX_STYLE_RESOURCE_QUERY_RE.test(resource)
38+
}
39+
return false
40+
}
41+
42+
export function hasLoaderEntry(
43+
entries: Array<{ loader?: string }>,
44+
target?: string,
45+
) {
46+
if (!target) {
47+
return false
48+
}
49+
return entries.some(entry => entry.loader?.includes?.(target))
50+
}

packages/weapp-tailwindcss/src/bundlers/webpack/BaseUnifiedPlugin/v4.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { resolveOutputSpecifier, toAbsoluteOutputPath } from '../../shared/modul
1818
import { pushConcurrentTaskFactories } from '../../shared/run-tasks'
1919
import { applyTailwindcssCssImportRewrite } from '../shared/css-imports'
2020
import { createLoaderAnchorFinders } from '../shared/loader-anchors'
21-
import { getCacheKey } from './shared'
21+
import { getCacheKey, hasLoaderEntry, isCssLikeModuleResource } from './shared'
2222

2323
const debug = createDebug()
2424
export const weappTailwindcssPackageDir = resolvePackageDir('weapp-tailwindcss')
@@ -157,7 +157,7 @@ export class UnifiedWebpackPluginV4 implements IBaseWebpackPlugin {
157157
const rewriteAnchorIdx = findRewriteAnchor(loaderEntries)
158158
const classSetAnchorIdx = findClassSetAnchor(loaderEntries)
159159

160-
const isCssModule = typeof module.resource === 'string' && this.options.cssMatcher(module.resource)
160+
const isCssModule = isCssLikeModuleResource(module.resource, this.options.cssMatcher, this.appType)
161161
if (process.env.WEAPP_TW_LOADER_DEBUG && isCssModule) {
162162
debug('loader hook css module: %s loaders=%o anchors=%o', module.resource, loaderEntries.map((x: any) => x.loader), { rewriteAnchorIdx, classSetAnchorIdx })
163163
}
@@ -181,7 +181,13 @@ export class UnifiedWebpackPluginV4 implements IBaseWebpackPlugin {
181181
}
182182
}
183183

184-
if (runtimeLoaderRewriteOptions && runtimeCssImportRewriteLoaderExists && cssImportRewriteLoaderOptions) {
184+
if (
185+
runtimeLoaderRewriteOptions
186+
&& runtimeCssImportRewriteLoaderExists
187+
&& cssImportRewriteLoaderOptions
188+
&& runtimeCssImportRewriteLoader
189+
&& !hasLoaderEntry(loaderEntries, runtimeCssImportRewriteLoader)
190+
) {
185191
const rewriteEntry = createCssImportRewriteLoaderEntry()
186192
if (rewriteEntry) {
187193
// 为了让 rewrite 在执行顺序上先于锚点 loader,需要把它插到
@@ -194,7 +200,7 @@ export class UnifiedWebpackPluginV4 implements IBaseWebpackPlugin {
194200
}
195201
}
196202
}
197-
if (runtimeClassSetLoaderExists) {
203+
if (runtimeClassSetLoaderExists && !hasLoaderEntry(loaderEntries, runtimeClassSetLoader)) {
198204
const anchorIndex = findClassSetAnchor(loaderEntries)
199205
if (anchorIndex === -1) {
200206
anchorlessInsert(createRuntimeClassSetLoaderEntry(), 'before')

packages/weapp-tailwindcss/src/bundlers/webpack/BaseUnifiedPlugin/v5.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { resolveOutputSpecifier, toAbsoluteOutputPath } from '../../shared/modul
1717
import { pushConcurrentTaskFactories } from '../../shared/run-tasks'
1818
import { applyTailwindcssCssImportRewrite } from '../shared/css-imports'
1919
import { createLoaderAnchorFinders } from '../shared/loader-anchors'
20-
import { getCacheKey } from './shared'
20+
import { getCacheKey, hasLoaderEntry, isCssLikeModuleResource } from './shared'
2121

2222
const debug = createDebug()
2323
export const weappTailwindcssPackageDir = resolvePackageDir('weapp-tailwindcss')
@@ -157,7 +157,7 @@ export class UnifiedWebpackPluginV5 implements IBaseWebpackPlugin {
157157
const loaderEntries = module.loaders || []
158158
const rewriteAnchorIdx = findRewriteAnchor(loaderEntries)
159159
const classSetAnchorIdx = findClassSetAnchor(loaderEntries)
160-
const isCssModule = typeof module.resource === 'string' && this.options.cssMatcher(module.resource)
160+
const isCssModule = isCssLikeModuleResource(module.resource, this.options.cssMatcher, this.appType)
161161
if (process.env.WEAPP_TW_LOADER_DEBUG && isCssModule) {
162162
debug('loader hook css module: %s loaders=%o anchors=%o', module.resource, loaderEntries.map((x: any) => x.loader), { rewriteAnchorIdx, classSetAnchorIdx })
163163
}
@@ -178,7 +178,12 @@ export class UnifiedWebpackPluginV5 implements IBaseWebpackPlugin {
178178
loaderEntries.unshift(entry)
179179
}
180180
}
181-
if (cssImportRewriteLoaderOptions && runtimeCssImportRewriteLoaderExists) {
181+
if (
182+
cssImportRewriteLoaderOptions
183+
&& runtimeCssImportRewriteLoaderExists
184+
&& runtimeCssImportRewriteLoader
185+
&& !hasLoaderEntry(loaderEntries, runtimeCssImportRewriteLoader)
186+
) {
182187
const rewriteLoaderEntry = createCssImportRewriteLoaderEntry()
183188
if (rewriteLoaderEntry) {
184189
// 让 rewrite 处于锚点 loader 之后(数组索引更大),这样执行时会排在锚点 loader 之前。
@@ -190,7 +195,7 @@ export class UnifiedWebpackPluginV5 implements IBaseWebpackPlugin {
190195
}
191196
}
192197
}
193-
if (runtimeClassSetLoaderExists) {
198+
if (runtimeClassSetLoaderExists && !hasLoaderEntry(loaderEntries, runtimeClassSetLoader)) {
194199
const classSetLoaderEntry = createRuntimeClassSetLoaderEntry()
195200
const anchorIndex = findClassSetAnchor(loaderEntries)
196201
if (anchorIndex === -1) {

packages/weapp-tailwindcss/src/bundlers/webpack/shared/loader-anchors.ts

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,25 @@ import { isMpx } from '@/shared/mpx'
33

44
export interface LoaderEntry { loader?: string }
55

6-
function createFinder(candidates: string[]) {
6+
const MPX_STRIP_CONDITIONAL_LOADER = '@mpxjs/webpack-plugin/lib/style-compiler/strip-conditional-loader'
7+
const MPX_STYLE_COMPILER_LOADER = '@mpxjs/webpack-plugin/lib/style-compiler/index'
8+
const MPX_REWRITE_PRECEDENCE_LOADERS = [
9+
MPX_STYLE_COMPILER_LOADER,
10+
MPX_STRIP_CONDITIONAL_LOADER,
11+
]
12+
13+
function createFinder(targets: string[]) {
14+
return (entries: LoaderEntry[]) => entries.findIndex(entry =>
15+
targets.some(target => entry?.loader?.includes?.(target)),
16+
)
17+
}
18+
19+
function createPrioritizedFinder(targets: string[]) {
720
return (entries: LoaderEntry[]) => {
8-
for (const candidate of candidates) {
9-
const index = entries.findIndex(entry => entry?.loader?.includes?.(candidate))
10-
if (index !== -1) {
11-
return index
21+
for (const target of targets) {
22+
const idx = entries.findIndex(entry => entry?.loader?.includes?.(target))
23+
if (idx !== -1) {
24+
return idx
1225
}
1326
}
1427
return -1
@@ -17,25 +30,17 @@ function createFinder(candidates: string[]) {
1730

1831
export function createLoaderAnchorFinders(appType?: AppType) {
1932
if (isMpx(appType)) {
33+
// Rewrite should run before style-compiler (and strip-conditional as fallback);
34+
// class-set should still run after style-compiler.
2035
return {
21-
// 重写需要尽量提前到 strip-conditional-loader 之前。
22-
findRewriteAnchor: createFinder([
23-
'@mpxjs/webpack-plugin/lib/style-compiler/strip-conditional-loader',
24-
'@mpxjs/webpack-plugin/lib/style-compiler/index',
25-
'postcss-loader',
26-
]),
27-
// class set 需等 style-compiler/index 跑完再做。
28-
findClassSetAnchor: createFinder([
29-
'@mpxjs/webpack-plugin/lib/style-compiler/index',
30-
'@mpxjs/webpack-plugin/lib/style-compiler/strip-conditional-loader',
31-
'postcss-loader',
32-
]),
36+
findRewriteAnchor: createPrioritizedFinder(MPX_REWRITE_PRECEDENCE_LOADERS),
37+
findClassSetAnchor: createFinder([MPX_STYLE_COMPILER_LOADER]),
3338
}
3439
}
3540

36-
const find = createFinder(['postcss-loader'])
41+
const fallbackFinder = createFinder(['postcss-loader'])
3742
return {
38-
findRewriteAnchor: find,
39-
findClassSetAnchor: find,
43+
findRewriteAnchor: fallbackFinder,
44+
findClassSetAnchor: fallbackFinder,
4045
}
4146
}

packages/weapp-tailwindcss/src/defaults.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,22 @@ const CSS_FILE_PATTERN = /.+\.(?:wx|ac|jx|tt|q|c|ty)ss$/
66
const HTML_FILE_PATTERN = /.+\.(?:(?:wx|ax|jx|ks|tt|q|ty|xhs)ml|swan)$/
77
const JS_FILE_PATTERN = /.+\.[cm]?js?$/
88

9+
function normalizePath(p: string) {
10+
return p.replace(/\\/g, '/')
11+
}
12+
13+
const MPX_STYLES_DIR_PATTERN = /(?:^|\/)styles\/.*\.(?:wx|ac|jx|tt|q|c|ty)ss$/i
14+
915
const MAIN_CSS_CHUNK_MATCHERS: Partial<Record<AppType, (file: string) => boolean>> = {
1016
'uni-app': file => file.startsWith('common/main') || file.startsWith('app'),
1117
'uni-app-vite': file => file.startsWith('app') || file.startsWith('common/main'),
12-
'mpx': file => file.startsWith('app'),
18+
'mpx': (file) => {
19+
const normalized = normalizePath(file)
20+
if (normalized.startsWith('app')) {
21+
return true
22+
}
23+
return MPX_STYLES_DIR_PATTERN.test(normalized)
24+
},
1325
'taro': file => file.startsWith('app'),
1426
'remax': file => file.startsWith('app'),
1527
'rax': file => file.startsWith('bundle'),

packages/weapp-tailwindcss/test/bundlers/webpack.v4.unit.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { createCache } from '@/cache'
55

66
interface LoaderModule {
77
loaders: Array<{ loader: string, options?: Record<string, any> }>
8+
resource?: string
89
}
910

1011
interface TestContext {
@@ -328,6 +329,59 @@ describe('bundlers/webpack UnifiedWebpackPluginV4', () => {
328329
expect(classSetIndex).toBeLessThan(anchorIndex)
329330
})
330331

332+
it('inserts rewrite loader after style compiler for mpx modules', () => {
333+
currentContext = createContext({ appType: 'mpx' })
334+
currentContext.twPatcher.majorVersion = 4
335+
getCompilerContextMock.mockImplementation(() => currentContext)
336+
const { compiler, getLoaderHandler } = createCompilerWithLoaderTracking()
337+
const plugin = new UnifiedWebpackPluginV4()
338+
plugin.apply(compiler as any)
339+
340+
const handler = getLoaderHandler()
341+
const module: LoaderModule = {
342+
loaders: [
343+
{ loader: '/abs/node_modules/@mpxjs/webpack-plugin/lib/style-compiler/strip-conditional-loader.js??ruleSet[0]' },
344+
{ loader: '/abs/node_modules/@mpxjs/webpack-plugin/lib/style-compiler/index.js??ruleSet[0]' },
345+
],
346+
}
347+
348+
handler?.({}, module)
349+
350+
const styleIndex = module.loaders.findIndex(entry =>
351+
entry.loader.includes('@mpxjs/webpack-plugin/lib/style-compiler/index'),
352+
)
353+
const rewriteIndex = module.loaders.findIndex(entry =>
354+
entry.loader === currentContext.runtimeCssImportRewriteLoaderPath,
355+
)
356+
expect(styleIndex).toBeGreaterThanOrEqual(0)
357+
expect(rewriteIndex).toBeGreaterThan(styleIndex)
358+
})
359+
360+
it('falls back to strip-conditional loader when style compiler anchor is missing', () => {
361+
currentContext = createContext({ appType: 'mpx' })
362+
currentContext.twPatcher.majorVersion = 4
363+
getCompilerContextMock.mockImplementation(() => currentContext)
364+
const { compiler, getLoaderHandler } = createCompilerWithLoaderTracking()
365+
const plugin = new UnifiedWebpackPluginV4()
366+
plugin.apply(compiler as any)
367+
368+
const handler = getLoaderHandler()
369+
const module: LoaderModule = {
370+
loaders: [{ loader: '/abs/node_modules/@mpxjs/webpack-plugin/lib/style-compiler/strip-conditional-loader.js??ruleSet[0]' }],
371+
}
372+
373+
handler?.({}, module)
374+
375+
const stripIndex = module.loaders.findIndex(entry =>
376+
entry.loader.includes('@mpxjs/webpack-plugin/lib/style-compiler/strip-conditional-loader'),
377+
)
378+
const rewriteIndex = module.loaders.findIndex(entry =>
379+
entry.loader === currentContext.runtimeCssImportRewriteLoaderPath,
380+
)
381+
expect(stripIndex).toBeGreaterThanOrEqual(0)
382+
expect(rewriteIndex).toBeGreaterThan(stripIndex)
383+
})
384+
331385
it('falls back to css matcher when anchor is missing', () => {
332386
currentContext = createContext({ appType: 'mpx' })
333387
currentContext.twPatcher.majorVersion = 4
@@ -353,6 +407,68 @@ describe('bundlers/webpack UnifiedWebpackPluginV4', () => {
353407
expect(module.loaders[0]).toBe(classSetLoaderEntry)
354408
})
355409

410+
it('treats css resources with query parameters as css modules', () => {
411+
currentContext = createContext()
412+
currentContext.twPatcher.majorVersion = 4
413+
getCompilerContextMock.mockImplementation(() => currentContext)
414+
const { compiler, getLoaderHandler } = createCompilerWithLoaderTracking()
415+
const plugin = new UnifiedWebpackPluginV4()
416+
plugin.apply(compiler as any)
417+
418+
const handler = getLoaderHandler()
419+
const module: LoaderModule = {
420+
loaders: [],
421+
resource: '/abs/app.css?type=styles',
422+
}
423+
424+
handler?.({}, module)
425+
426+
const rewriteLoaderEntry = module.loaders.find(entry => entry.loader.includes(currentContext.runtimeCssImportRewriteLoaderPath))
427+
const classSetLoaderEntry = module.loaders.find(entry => entry.loader.includes(currentContext.runtimeLoaderPath))
428+
expect(rewriteLoaderEntry).toBeDefined()
429+
expect(classSetLoaderEntry).toBeDefined()
430+
})
431+
432+
it('detects mpx style blocks via resource query when anchors missing', () => {
433+
currentContext = createContext({ appType: 'mpx' })
434+
currentContext.twPatcher.majorVersion = 4
435+
getCompilerContextMock.mockImplementation(() => currentContext)
436+
const { compiler, getLoaderHandler } = createCompilerWithLoaderTracking()
437+
const plugin = new UnifiedWebpackPluginV4()
438+
plugin.apply(compiler as any)
439+
440+
const handler = getLoaderHandler()
441+
const module: LoaderModule = {
442+
loaders: [],
443+
resource: '/abs/pages/index.mpx?type=styles&lang=css',
444+
}
445+
446+
handler?.({}, module)
447+
const classSetLoaderEntry = module.loaders.find(entry => entry.loader.includes(currentContext.runtimeLoaderPath))
448+
expect(classSetLoaderEntry).toBeDefined()
449+
})
450+
451+
it('skips inserting duplicate rewrite loaders when already present', () => {
452+
currentContext = createContext()
453+
currentContext.twPatcher.majorVersion = 4
454+
getCompilerContextMock.mockImplementation(() => currentContext)
455+
const { compiler, getLoaderHandler } = createCompilerWithLoaderTracking()
456+
const plugin = new UnifiedWebpackPluginV4()
457+
plugin.apply(compiler as any)
458+
459+
const handler = getLoaderHandler()
460+
const module: LoaderModule = {
461+
loaders: [
462+
{ loader: `${currentContext.runtimeCssImportRewriteLoaderPath}??ruleSet[0]` },
463+
],
464+
resource: '/abs/app.css',
465+
}
466+
467+
handler?.({}, module)
468+
const rewriteLoaders = module.loaders.filter(entry => entry.loader.includes(currentContext.runtimeCssImportRewriteLoaderPath))
469+
expect(rewriteLoaders).toHaveLength(1)
470+
})
471+
356472
it('does not attach runtime loader when postcss loader is missing', () => {
357473
const { compiler, getLoaderHandler } = createCompilerWithLoaderTracking()
358474
const plugin = new UnifiedWebpackPluginV4()

0 commit comments

Comments
 (0)