Skip to content

Commit

Permalink
feat(cssModules ): namedExport support
Browse files Browse the repository at this point in the history
  • Loading branch information
Airkro committed Jun 27, 2023
1 parent 8357e07 commit 855ca04
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 8 deletions.
8 changes: 5 additions & 3 deletions src/cssModules.ts
Expand Up @@ -6,17 +6,19 @@ export function genCSSModulesCode(
needsHotReload: boolean
): string {
const styleVar = `style${index}`
let code = `\nimport ${styleVar} from ${request}`
let code = `\nimport * as ${styleVar} from ${request}`

// inject variable
const name = typeof moduleName === 'string' ? moduleName : '$style'
code += `\ncssModules["${name}"] = ${styleVar}`

// omit no default export error
code += `\ncssModules["${name}"] = {...${styleVar}}.default || ${styleVar}`

if (needsHotReload) {
code += `
if (module.hot) {
module.hot.accept(${request}, () => {
cssModules["${name}"] = ${styleVar}
cssModules["${name}"] = {...${styleVar}}.default || ${styleVar}
__VUE_HMR_RUNTIME__.rerender("${id}")
})
}`
Expand Down
15 changes: 11 additions & 4 deletions src/pitcher.ts
Expand Up @@ -105,11 +105,16 @@ export const pitch = function () {
? [styleInlineLoaderPath]
: loaders.slice(0, cssLoaderIndex + 1)
const beforeLoaders = loaders.slice(cssLoaderIndex + 1)

const { namedExport = false } = // @ts-ignore
loaders[cssLoaderIndex]?.options?.modules || {}

return genProxyModule(
[...afterLoaders, stylePostLoaderPath, ...beforeLoaders],
context,
!!query.module || query.inline != null,
(query.lang as string) || 'css'
(query.lang as string) || 'css',
namedExport
)
}
}
Expand All @@ -134,15 +139,17 @@ function genProxyModule(
loaders: (Loader | string)[],
context: LoaderContext<VueLoaderOptions>,
exportDefault = true,
lang = 'js'
lang = 'js',
cssNamedExport = false
) {
const request = genRequest(loaders, lang, context)
// return a proxy module which simply re-exports everything from the
// actual request. Note for template blocks the compiled module has no
// default export.
return (
(exportDefault ? `export { default } from ${request}; ` : ``) +
`export * from ${request}`
(exportDefault && !cssNamedExport
? `export { default } from ${request}; `
: ``) + `export * from ${request}`
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/pluginWebpack5.ts
Expand Up @@ -154,7 +154,7 @@ class VueLoaderPlugin {
// get vue-loader options
const vueLoaderUseIndex = vueUse.findIndex((u) => {
// FIXME: this code logic is incorrect when project paths starts with `vue-loader-something`
return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
return /^vue-loader|^@\S+\/vue-loader/.test(u.loader)
})

if (vueLoaderUseIndex < 0) {
Expand Down
83 changes: 83 additions & 0 deletions test/style.spec.ts
Expand Up @@ -166,6 +166,89 @@ test('CSS Modules', async () => {
)
})

test('CSS Modules namedExport', async () => {
const testWithIdent = async (
localIdentName: string | undefined,
regexToMatch: RegExp
) => {
const baseLoaders = [
{
loader: 'style-loader',
options: {
modules: {
namedExport: true,
},
},
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName,
namedExport: true,
},
},
},
]

const { window, instance } = await mockBundleAndRun({
entry: 'css-modules.vue',
modify: (config: any) => {
config!.module!.rules = [
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.css$/,
use: baseLoaders,
},
{
test: /\.stylus$/,
use: [...baseLoaders, 'stylus-loader'],
},
]
},
})

// get local class name
const className = instance.$style.red
expect(className).toMatch(regexToMatch)

// class name in style
let style = [].slice
.call(window.document.querySelectorAll('style'))
.map((style: any) => {
return style!.textContent
})
.join('\n')
style = normalizeNewline(style)
expect(style).toContain('.' + className + ' {\n color: red;\n}')

// animation name
const match = style.match(/@keyframes\s+(\S+)\s+{/)
expect(match).toHaveLength(2)
const animationName = match[1]
expect(animationName).not.toBe('fade')
expect(style).toContain('animation: ' + animationName + ' 1s;')

// default module + pre-processor + scoped
const anotherClassName = instance.$style.red
expect(anotherClassName).toMatch(regexToMatch)
const id = 'data-v-' + genId('css-modules.vue')
expect(style).toContain('.' + anotherClassName + '[' + id + ']')
}

// default ident
await testWithIdent(undefined, /^\w{21,}/)

// custom ident
await testWithIdent(
'[path][name]---[local]---[hash:base64:5]',
/css-modules---red---\w{5}/
)
})

test('CSS Modules Extend', async () => {
const baseLoaders = [
'style-loader',
Expand Down

0 comments on commit 855ca04

Please sign in to comment.