Skip to content

Commit 3a61ba2

Browse files
committed
1 parent 6986365 commit 3a61ba2

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
@@ -155,7 +155,7 @@ class VueLoaderPlugin {
155155
// get vue-loader options
156156
const vueLoaderUseIndex = vueUse.findIndex((u) => {
157157
// FIXME: this code logic is incorrect when project paths starts with `vue-loader-something`
158-
return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
158+
return /^vue-loader|^@\S+\/vue-loader/.test(u.loader)
159159
})
160160

161161
if (vueLoaderUseIndex < 0) {

test/style.spec.ts

+83
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,89 @@ test('CSS Modules', async () => {
171171
)
172172
})
173173

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

0 commit comments

Comments
 (0)