Skip to content

Commit 29270ea

Browse files
committed
chore: bump version
1 parent 1b8b806 commit 29270ea

File tree

1 file changed

+203
-97
lines changed

1 file changed

+203
-97
lines changed
Lines changed: 203 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,213 @@
1-
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
2-
import path from 'pathe'
3-
import { compile, getErrors, getMemfsCompiler5, getWarnings, readAssets } from 'webpack-build-utils'
4-
import utwm from '@/webpack'
5-
6-
const context = path.resolve(__dirname, 'fixtures/webpack-repo')
7-
describe('webpack build', () => {
8-
it('common', async () => {
9-
const compiler = getMemfsCompiler5({
10-
mode: 'production',
11-
entry: path.resolve(context, './src/index.js'),
12-
context,
13-
plugins: [new MiniCssExtractPlugin()],
14-
output: {
15-
path: path.resolve(context, './dist'),
16-
filename: 'index.js',
1+
import type { Mock } from 'vitest'
2+
import { describe, expect, it, beforeEach, vi } from 'vitest'
3+
import factory from '@/core/factory'
4+
5+
const { mockCtx, mockCssHandler, mockHtmlHandler, mockJsHandler } = vi.hoisted(() => {
6+
return {
7+
mockCtx: {
8+
options: {
9+
sources: {},
1710
},
18-
module: {
19-
rules: [
20-
{
21-
test: /\.css$/i,
22-
type: 'javascript/auto',
23-
use: [
24-
MiniCssExtractPlugin.loader,
25-
{
26-
loader: 'css-loader',
27-
options: {
28-
url: false,
29-
},
30-
},
31-
'postcss-loader',
32-
],
33-
},
34-
],
11+
initConfig: vi.fn(),
12+
replaceMap: new Map(),
13+
classGenerator: {
14+
generateClassName: vi.fn(),
3515
},
36-
})
37-
const stats = await compile(compiler)
38-
39-
// get all Assets as Record<string,string>
40-
const assets = readAssets(compiler, stats)
41-
const cssAssetName = Object.keys(assets).find(name => name.endsWith('.css'))
42-
expect(cssAssetName).toBeDefined()
43-
expect(typeof assets[cssAssetName!]).toBe('string')
44-
expect(assets[cssAssetName!].length).toBeGreaterThan(0)
45-
expect(assets['index.js']).toBeDefined()
46-
// get all error
47-
expect(getErrors(stats)).toEqual([])
48-
// get all warnings
49-
expect(getWarnings(stats)).toEqual([])
16+
addToUsedBy: vi.fn(),
17+
isPreserveClass: vi.fn(() => false),
18+
isPreserveFunction: vi.fn(() => false),
19+
addPreserveClass: vi.fn(),
20+
dump: vi.fn(),
21+
},
22+
mockCssHandler: vi.fn(async (source: string) => ({ code: `/*handled*/${source}` })),
23+
mockHtmlHandler: vi.fn(() => ({ code: '<html></html>' })),
24+
mockJsHandler: vi.fn(() => ({ code: 'export {}' })),
25+
}
26+
})
27+
28+
vi.mock('@tailwindcss-mangle/core', () => {
29+
const Context = vi.fn(function ContextMock() {
30+
return mockCtx
5031
})
32+
return {
33+
Context,
34+
cssHandler: mockCssHandler,
35+
htmlHandler: mockHtmlHandler,
36+
jsHandler: mockJsHandler,
37+
}
38+
})
39+
40+
import { Context } from '@tailwindcss-mangle/core'
41+
42+
function getLatestCtx() {
43+
return (Context as unknown as Mock).mock.results.at(-1)?.value ?? mockCtx
44+
}
45+
46+
vi.mock('@/utils', async (importOriginal) => {
47+
const actual = await importOriginal<typeof import('@/utils')>()
48+
return {
49+
...actual,
50+
getGroupedEntries: vi.fn((entries: [string, any][]) => {
51+
const css = entries.filter(([name]) => name.endsWith('.css'))
52+
const js = entries.filter(([name]) => name.endsWith('.js'))
53+
const html = entries.filter(([name]) => name.endsWith('.html'))
54+
return { css, js, html }
55+
}),
56+
}
57+
})
5158

52-
it.skip('with plugin', async () => {
53-
const compiler = getMemfsCompiler5({
54-
mode: 'production',
55-
entry: path.resolve(context, './src/index.js'),
56-
context,
57-
plugins: [new MiniCssExtractPlugin()],
58-
output: {
59-
path: path.resolve(context, './dist'),
60-
filename: 'index.js',
59+
function createSyncHook<TArgs extends any[] = any[]>() {
60+
const taps: Array<(...args: TArgs) => any> = []
61+
return {
62+
tap(_name: string, fn: (...args: TArgs) => any) {
63+
taps.push(fn)
64+
},
65+
call(...args: TArgs) {
66+
taps.forEach(fn => fn(...args))
67+
},
68+
}
69+
}
70+
71+
function createAsyncHook<TArgs extends any[] = any[]>() {
72+
const taps: Array<(...args: TArgs) => Promise<any>> = []
73+
return {
74+
tapPromise(_opts: any, fn: (...args: TArgs) => Promise<any>) {
75+
taps.push(fn)
76+
},
77+
async promise(...args: TArgs) {
78+
for (const fn of taps) {
79+
await fn(...args)
80+
}
81+
},
82+
}
83+
}
84+
85+
function createFakeCompiler() {
86+
const compilationHook = createSyncHook<any[]>()
87+
const loaderHook = createSyncHook<any[]>()
88+
const processAssetsHook = createAsyncHook<any[]>()
89+
90+
const updates: Array<[string, any]> = []
91+
92+
const compiler = {
93+
webpack: {
94+
NormalModule: {
95+
getCompilationHooks: () => ({
96+
loader: loaderHook,
97+
}),
6198
},
62-
module: {
63-
rules: [
64-
{
65-
test: /\.css$/i,
66-
type: 'javascript/auto',
67-
use: [
68-
MiniCssExtractPlugin.loader,
69-
{
70-
loader: 'css-loader',
71-
options: {
72-
url: false,
73-
},
74-
},
75-
'postcss-loader',
76-
],
77-
},
78-
],
99+
Compilation: {
100+
PROCESS_ASSETS_STAGE_SUMMARIZE: 0,
79101
},
80-
})
81-
82-
utwm({
83-
registry: {
84-
file: path.resolve(context, '.tw-patch/tw-class-list.json'),
102+
sources: {
103+
ConcatSource: class {
104+
code: string
105+
constructor(code: string) {
106+
this.code = code
107+
}
108+
toString() {
109+
return this.code
110+
}
111+
source() {
112+
return this.code
113+
}
114+
},
85115
},
86-
}).apply(compiler)
87-
const stats = await compile(compiler)
88-
89-
// get all Assets as Record<string,string>
90-
const assets = readAssets(compiler, stats)
91-
const cssAssetName = Object.keys(assets).find(name => name.endsWith('.css'))
92-
expect(cssAssetName).toBeDefined()
93-
expect(assets[cssAssetName!]).toContain('.tw-')
94-
expect(assets['index.js']).toBeDefined()
95-
// get all error
96-
expect(getErrors(stats)).toEqual([])
97-
// get all warnings
98-
expect(getWarnings(stats)).toEqual([])
116+
},
117+
hooks: {
118+
compilation: compilationHook,
119+
},
120+
triggerCompilation(initial: Record<string, any> = {}) {
121+
const compilation = {
122+
hooks: {
123+
processAssets: processAssetsHook,
124+
},
125+
updateAsset: vi.fn((...args) => {
126+
updates.push(args as unknown as [string, any])
127+
}),
128+
...initial,
129+
}
130+
compilationHook.call(compilation)
131+
return { compilation, loaderHook, processAssetsHook, updates }
132+
},
133+
}
134+
135+
return compiler
136+
}
137+
138+
describe('webpack plugin integration (unit)', () => {
139+
beforeEach(() => {
140+
vi.clearAllMocks()
141+
mockCtx.replaceMap = new Map()
99142
})
100143

101-
// webpack({}, (err, stats) => {
102-
// if (err || stats.hasErrors()) {
103-
// // ...
104-
// }
105-
// // Done processing
106-
// });
144+
it('injects the webpack loader before postcss-loader', () => {
145+
const compiler = createFakeCompiler()
146+
const [, mainPlugin, postPlugin] = factory() as any[]
147+
mainPlugin.webpack?.(compiler as any)
148+
postPlugin.webpack?.(compiler as any)
149+
150+
const { loaderHook } = compiler.triggerCompilation()
151+
const module = {
152+
loaders: [
153+
{ loader: 'style-loader' },
154+
{ loader: 'postcss-loader' },
155+
],
156+
}
157+
158+
loaderHook.call({}, module)
159+
160+
expect(module.loaders).toHaveLength(3)
161+
const inserted = module.loaders[1]
162+
expect(String(inserted.loader)).toContain('loader.cjs')
163+
expect(inserted.options?.ctx).toBe(getLatestCtx())
164+
})
165+
166+
it('skips injection when postcss-loader is missing', () => {
167+
const compiler = createFakeCompiler()
168+
const [, mainPlugin, postPlugin] = factory() as any[]
169+
mainPlugin.webpack?.(compiler as any)
170+
postPlugin.webpack?.(compiler as any)
171+
172+
const { loaderHook } = compiler.triggerCompilation()
173+
const module = {
174+
loaders: [{ loader: 'css-loader' }],
175+
}
176+
177+
loaderHook.call({}, module)
178+
179+
expect(module.loaders).toHaveLength(1)
180+
})
181+
182+
it('transforms css assets during processAssets', async () => {
183+
const compiler = createFakeCompiler()
184+
const [, mainPlugin, postPlugin] = factory() as any[]
185+
mainPlugin.webpack?.(compiler as any)
186+
postPlugin.webpack?.(compiler as any)
187+
188+
const { compilation, processAssetsHook, updates } = compiler.triggerCompilation()
189+
const assets = {
190+
'style.css': {
191+
source: () => ({
192+
toString: () => 'body { color: red; }',
193+
}),
194+
},
195+
'index.js': {
196+
source: () => ({
197+
toString: () => 'console.log("noop")',
198+
}),
199+
},
200+
}
201+
202+
await processAssetsHook.promise(assets)
203+
204+
expect(mockCssHandler).toHaveBeenCalledTimes(1)
205+
expect(mockCssHandler).toHaveBeenCalledWith('body { color: red; }', {
206+
id: 'style.css',
207+
ctx: getLatestCtx(),
208+
})
209+
expect(compilation.updateAsset).toHaveBeenCalledTimes(1)
210+
expect(updates[0][0]).toBe('style.css')
211+
expect(String(updates[0][1])).toContain('/*handled*/body { color: red; }')
212+
})
107213
})

0 commit comments

Comments
 (0)