diff --git a/src/cssModules.ts b/src/cssModules.ts index e95a27e9..9df21b60 100644 --- a/src/cssModules.ts +++ b/src/cssModules.ts @@ -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}") }) }` diff --git a/src/pitcher.ts b/src/pitcher.ts index 426dd1c7..af077345 100644 --- a/src/pitcher.ts +++ b/src/pitcher.ts @@ -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 ) } } @@ -134,15 +139,17 @@ function genProxyModule( loaders: (Loader | string)[], context: LoaderContext, 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}` ) } diff --git a/src/pluginWebpack5.ts b/src/pluginWebpack5.ts index 649cd54c..8f2ed64b 100644 --- a/src/pluginWebpack5.ts +++ b/src/pluginWebpack5.ts @@ -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) { diff --git a/test/style.spec.ts b/test/style.spec.ts index 4bb998db..93ce2c22 100644 --- a/test/style.spec.ts +++ b/test/style.spec.ts @@ -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',