diff --git a/README.md b/README.md index 0f636bc..9f9b6d6 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ Configure the options for the plugin within your `.babelrc` as follows: |`webpackHotModuleReloading`|`boolean`|Enables hot reloading of CSS in webpack|`false`| |`handleMissingStyleName`|`"throw"`, `"warn"`, `"ignore"`|Determines what should be done for undefined CSS modules (using a `styleName` for which there is no CSS module defined). Setting this option to `"ignore"` is equivalent to setting `errorWhenNotFound: false` in [react-css-modules](https://github.com/gajus/react-css-modules#errorwhennotfound). |`"throw"`| |`attributeNames`|`?AttributeNameMapType`|Refer to [Custom Attribute Mapping](#custom-attribute-mapping)|`{"styleName": "className"}`| +|`skip`|`boolean`|Whether to apply plugin if no matching `attributeNames` found in the file|`false`| Missing a configuration? [Raise an issue](https://github.com/gajus/babel-plugin-react-css-modules/issues/new?title=New%20configuration:). diff --git a/package.json b/package.json index 1e05fae..bcc2393 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@babel/register": "^7.0.0", "babel-core": "^7.0.0-bridge.0", "babel-jest": "^23.6.0", + "babel-plugin-module-resolver": "^3.2.0", "babel-plugin-tester": "^5.5.1", "eslint": "^5.5.0", "eslint-config-canonical": "^12.0.0", diff --git a/src/attributeNameExists.js b/src/attributeNameExists.js new file mode 100644 index 0000000..6314634 --- /dev/null +++ b/src/attributeNameExists.js @@ -0,0 +1,31 @@ +// @flow + +import optionsDefaults from './schemas/optionsDefaults'; + +const attributeNameExists = (programPath: *, stats: *): boolean => { + let exists = false; + + let attributeNames = optionsDefaults.attributeNames; + + if (stats.opts && stats.opts.attributeNames) { + attributeNames = Object.assign({}, attributeNames, stats.opts.attributeNames); + } + + programPath.traverse({ + JSXAttribute (attrPath: *) { + if (exists) { + return; + } + + const attribute = attrPath.node; + + if (typeof attribute.name !== 'undefined' && typeof attributeNames[attribute.name.name] === 'string') { + exists = true; + } + } + }); + + return exists; +}; + +export default attributeNameExists; diff --git a/src/getClassName.js b/src/getClassName.js index 87e29cd..c445438 100644 --- a/src/getClassName.js +++ b/src/getClassName.js @@ -5,13 +5,12 @@ import type { StyleModuleImportMapType, HandleMissingStyleNameOptionType } from './types'; +import optionsDefaults from './schemas/optionsDefaults'; type OptionsType = {| handleMissingStyleName: HandleMissingStyleNameOptionType |}; -const DEFAULT_HANDLE_MISSING_STYLENAME_OPTION = 'throw'; - const isNamespacedStyleName = (styleName: string): boolean => { return styleName.indexOf('.') !== -1; }; @@ -28,7 +27,7 @@ const getClassNameForNamespacedStyleName = ( const importName = styleNameParts[0]; const moduleName = styleNameParts[1]; const handleMissingStyleName = handleMissingStyleNameOption || - DEFAULT_HANDLE_MISSING_STYLENAME_OPTION; + optionsDefaults.handleMissingStyleName; if (!moduleName) { if (handleMissingStyleName === 'throw') { @@ -70,7 +69,7 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM const styleModuleImportMapKeys = Object.keys(styleModuleImportMap); const handleMissingStyleName = options && options.handleMissingStyleName || - DEFAULT_HANDLE_MISSING_STYLENAME_OPTION; + optionsDefaults.handleMissingStyleName; if (!styleNameValue) { return ''; diff --git a/src/index.js b/src/index.js index 4bd892e..a584fe0 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,7 @@ import createObjectExpression from './createObjectExpression'; import requireCssModule from './requireCssModule'; import resolveStringLiteral from './resolveStringLiteral'; import replaceJsxExpressionContainer from './replaceJsxExpressionContainer'; +import attributeNameExists from './attributeNameExists'; const ajv = new Ajv({ // eslint-disable-next-line id-match @@ -31,6 +32,8 @@ export default ({ }) => { const filenameMap = {}; + let skip = false; + const setupFileForRuntimeResolution = (path, filename) => { const programPath = path.findParent((parentPath) => { return parentPath.isProgram(); @@ -147,7 +150,7 @@ export default ({ inherits: babelPluginJsxSyntax, visitor: { ImportDeclaration (path: *, stats: *): void { - if (notForPlugin(path, stats)) { + if (skip || notForPlugin(path, stats)) { return; } @@ -183,6 +186,10 @@ export default ({ } }, JSXElement (path: *, stats: *): void { + if (skip) { + return; + } + const filename = stats.file.opts.filename; if (stats.opts.exclude && isFilenameExcluded(filename, stats.opts.exclude)) { @@ -250,6 +257,10 @@ export default ({ filenameMap[filename] = { styleModuleImportMap: {} }; + + if (stats.opts.skip && !attributeNameExists(path, stats)) { + skip = true; + } } } }; diff --git a/src/requireCssModule.js b/src/requireCssModule.js index 2126dbf..0268a43 100644 --- a/src/requireCssModule.js +++ b/src/requireCssModule.js @@ -18,6 +18,7 @@ import type { GenerateScopedNameConfigurationType, StyleModuleMapType } from './types'; +import optionsDefaults from './schemas/optionsDefaults'; type FiletypeOptionsType = {| +syntax: string, @@ -102,7 +103,7 @@ export default (cssSourceFilePath: string, options: OptionsType): StyleModuleMap if (options.generateScopedName && typeof options.generateScopedName === 'function') { generateScopedName = options.generateScopedName; } else { - generateScopedName = genericNames(options.generateScopedName || '[path]___[name]__[local]___[hash:base64:5]', { + generateScopedName = genericNames(options.generateScopedName || optionsDefaults.generateScopedName, { context: options.context || process.cwd() }); } diff --git a/src/schemas/optionsDefaults.js b/src/schemas/optionsDefaults.js index 77d3e23..7daaef5 100644 --- a/src/schemas/optionsDefaults.js +++ b/src/schemas/optionsDefaults.js @@ -2,6 +2,7 @@ const optionsDefaults = { attributeNames: { styleName: 'className' }, + generateScopedName: '[path]___[name]__[local]___[hash:base64:5]', handleMissingStyleName: 'throw' }; diff --git a/src/schemas/optionsSchema.json b/src/schemas/optionsSchema.json index e14e525..2b3041f 100644 --- a/src/schemas/optionsSchema.json +++ b/src/schemas/optionsSchema.json @@ -69,6 +69,9 @@ } }, "type": "object" + }, + "skip": { + "type": "boolean" } }, "type": "object" diff --git a/test/fixtures/react-css-modules/does not apply plugin if no styleName/bar.css b/test/fixtures/react-css-modules/does not apply plugin if no styleName/bar.css new file mode 100644 index 0000000..5512dae --- /dev/null +++ b/test/fixtures/react-css-modules/does not apply plugin if no styleName/bar.css @@ -0,0 +1 @@ +.a {} diff --git a/test/fixtures/react-css-modules/does not apply plugin if no styleName/input.js b/test/fixtures/react-css-modules/does not apply plugin if no styleName/input.js new file mode 100644 index 0000000..9b3805b --- /dev/null +++ b/test/fixtures/react-css-modules/does not apply plugin if no styleName/input.js @@ -0,0 +1,3 @@ +import 'bar.css'; + +
; diff --git a/test/fixtures/react-css-modules/does not apply plugin if no styleName/options.json b/test/fixtures/react-css-modules/does not apply plugin if no styleName/options.json new file mode 100644 index 0000000..88e0d9d --- /dev/null +++ b/test/fixtures/react-css-modules/does not apply plugin if no styleName/options.json @@ -0,0 +1,11 @@ +{ + "plugins": [ + [ + "../../../../src", + { + "generateScopedName": "[name]__[local]", + "skip": true + } + ] + ] +} diff --git a/test/fixtures/react-css-modules/does not apply plugin if no styleName/output.js b/test/fixtures/react-css-modules/does not apply plugin if no styleName/output.js new file mode 100644 index 0000000..f8754b6 --- /dev/null +++ b/test/fixtures/react-css-modules/does not apply plugin if no styleName/output.js @@ -0,0 +1,5 @@ +"use strict"; + +require("bar.css"); + +
; diff --git a/test/fixtures/react-css-modules/resolves absolute path stylesheets/bar.css b/test/fixtures/react-css-modules/resolves absolute path stylesheets/bar.css new file mode 100644 index 0000000..5512dae --- /dev/null +++ b/test/fixtures/react-css-modules/resolves absolute path stylesheets/bar.css @@ -0,0 +1 @@ +.a {} diff --git a/test/fixtures/react-css-modules/resolves absolute path stylesheets/input.js b/test/fixtures/react-css-modules/resolves absolute path stylesheets/input.js new file mode 100644 index 0000000..74e3ca8 --- /dev/null +++ b/test/fixtures/react-css-modules/resolves absolute path stylesheets/input.js @@ -0,0 +1,3 @@ +import 'abs/bar.css'; + +
; diff --git a/test/fixtures/react-css-modules/resolves absolute path stylesheets/options.json b/test/fixtures/react-css-modules/resolves absolute path stylesheets/options.json new file mode 100644 index 0000000..b3f5043 --- /dev/null +++ b/test/fixtures/react-css-modules/resolves absolute path stylesheets/options.json @@ -0,0 +1,15 @@ +{ + "plugins": [ + ["module-resolver", { + "alias": { + "abs": "./test/fixtures/react-css-modules/resolves absolute path stylesheets" + } + }], + [ + "../../../../src", + { + "generateScopedName": "[name]__[local]" + } + ] + ] +} diff --git a/test/fixtures/react-css-modules/resolves absolute path stylesheets/output.js b/test/fixtures/react-css-modules/resolves absolute path stylesheets/output.js new file mode 100644 index 0000000..29a37b6 --- /dev/null +++ b/test/fixtures/react-css-modules/resolves absolute path stylesheets/output.js @@ -0,0 +1,5 @@ +"use strict"; + +require("./bar.css"); + +
;