Skip to content

Commit 34f4ecf

Browse files
committedNov 1, 2023
feat(cssModules ): namedExport support
For v17 Support css-loader : `options.modules.namedexport` https://github.com/webpack-contrib/css-loader/tree/v4.3.0#namedexport https://github.com/webpack-contrib/css-loader#namedexport
1 parent 77a1099 commit 34f4ecf

File tree

4 files changed

+100
-8
lines changed

4 files changed

+100
-8
lines changed
 

‎src/cssModules.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ export function genCSSModulesCode(
66
needsHotReload: boolean
77
): string {
88
const styleVar = `style${index}`
9-
let code = `\nimport ${styleVar} from ${request}`
9+
let code = `\nimport * as ${styleVar} from ${request}`
1010

1111
// inject variable
1212
const name = typeof moduleName === 'string' ? moduleName : '$style'
13-
code += `\ncssModules["${name}"] = ${styleVar}`
13+
14+
// omit no default export error
15+
code += `\ncssModules["${name}"] = {...${styleVar}}.default || ${styleVar}`
1416

1517
if (needsHotReload) {
1618
code += `
1719
if (module.hot) {
1820
module.hot.accept(${request}, () => {
19-
cssModules["${name}"] = ${styleVar}
21+
cssModules["${name}"] = {...${styleVar}}.default || ${styleVar}
2022
__VUE_HMR_RUNTIME__.rerender("${id}")
2123
})
2224
}`

‎src/pitcher.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,16 @@ export const pitch = function () {
105105
? [styleInlineLoaderPath]
106106
: loaders.slice(0, cssLoaderIndex + 1)
107107
const beforeLoaders = loaders.slice(cssLoaderIndex + 1)
108+
109+
const { namedExport = false } = // @ts-ignore
110+
loaders[cssLoaderIndex]?.options?.modules || {}
111+
108112
return genProxyModule(
109113
[...afterLoaders, stylePostLoaderPath, ...beforeLoaders],
110114
context,
111115
!!query.module || query.inline != null,
112-
(query.lang as string) || 'css'
116+
(query.lang as string) || 'css',
117+
namedExport
113118
)
114119
}
115120
}
@@ -134,15 +139,17 @@ function genProxyModule(
134139
loaders: (Loader | string)[],
135140
context: LoaderContext<VueLoaderOptions>,
136141
exportDefault = true,
137-
lang = 'js'
142+
lang = 'js',
143+
cssNamedExport = false
138144
) {
139145
const request = genRequest(loaders, lang, context)
140146
// return a proxy module which simply re-exports everything from the
141147
// actual request. Note for template blocks the compiled module has no
142148
// default export.
143149
return (
144-
(exportDefault ? `export { default } from ${request}; ` : ``) +
145-
`export * from ${request}`
150+
(exportDefault && !cssNamedExport
151+
? `export { default } from ${request}; `
152+
: ``) + `export * from ${request}`
146153
)
147154
}
148155

‎src/pluginWebpack5.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class VueLoaderPlugin {
154154
// get vue-loader options
155155
const vueLoaderUseIndex = vueUse.findIndex((u) => {
156156
// FIXME: this code logic is incorrect when project paths starts with `vue-loader-something`
157-
return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
157+
return /^vue-loader|^@\S+\/vue-loader/.test(u.loader)
158158
})
159159

160160
if (vueLoaderUseIndex < 0) {

‎test/style.spec.ts

+83
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,89 @@ test('CSS Modules', async () => {
166166
)
167167
})
168168

169+
test('CSS Modules namedExport', async () => {
170+
const testWithIdent = async (
171+
localIdentName: string | undefined,
172+
regexToMatch: RegExp
173+
) => {
174+
const baseLoaders = [
175+
{
176+
loader: 'style-loader',
177+
options: {
178+
modules: {
179+
namedExport: true,
180+
},
181+
},
182+
},
183+
{
184+
loader: 'css-loader',
185+
options: {
186+
modules: {
187+
localIdentName,
188+
namedExport: true,
189+
},
190+
},
191+
},
192+
]
193+
194+
const { window, instance } = await mockBundleAndRun({
195+
entry: 'css-modules.vue',
196+
modify: (config: any) => {
197+
config!.module!.rules = [
198+
{
199+
test: /\.vue$/,
200+
loader: 'vue-loader',
201+
},
202+
{
203+
test: /\.css$/,
204+
use: baseLoaders,
205+
},
206+
{
207+
test: /\.stylus$/,
208+
use: [...baseLoaders, 'stylus-loader'],
209+
},
210+
]
211+
},
212+
})
213+
214+
// get local class name
215+
const className = instance.$style.red
216+
expect(className).toMatch(regexToMatch)
217+
218+
// class name in style
219+
let style = [].slice
220+
.call(window.document.querySelectorAll('style'))
221+
.map((style: any) => {
222+
return style!.textContent
223+
})
224+
.join('\n')
225+
style = normalizeNewline(style)
226+
expect(style).toContain('.' + className + ' {\n color: red;\n}')
227+
228+
// animation name
229+
const match = style.match(/@keyframes\s+(\S+)\s+{/)
230+
expect(match).toHaveLength(2)
231+
const animationName = match[1]
232+
expect(animationName).not.toBe('fade')
233+
expect(style).toContain('animation: ' + animationName + ' 1s;')
234+
235+
// default module + pre-processor + scoped
236+
const anotherClassName = instance.$style.red
237+
expect(anotherClassName).toMatch(regexToMatch)
238+
const id = 'data-v-' + genId('css-modules.vue')
239+
expect(style).toContain('.' + anotherClassName + '[' + id + ']')
240+
}
241+
242+
// default ident
243+
await testWithIdent(undefined, /^\w{21,}/)
244+
245+
// custom ident
246+
await testWithIdent(
247+
'[path][name]---[local]---[hash:base64:5]',
248+
/css-modules---red---\w{5}/
249+
)
250+
})
251+
169252
test('CSS Modules Extend', async () => {
170253
const baseLoaders = [
171254
'style-loader',

0 commit comments

Comments
 (0)
Failed to load comments.