Skip to content

Commit aa25bdc

Browse files
feat: opt-in rules for loader include/exclude (#99)
1 parent 3aa6b38 commit aa25bdc

File tree

6 files changed

+145
-30
lines changed

6 files changed

+145
-30
lines changed

CONTRIBUTING.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@
1313
### Testing workflow
1414
Have a look at `test/README.md` to see how testing works. Run `npm run watch:test` while developing. Have fun! :)
1515

16+
### Tips for working with Joi
17+
- Use `!!` at the beginning of your custom error string to get rid of the key at the beginning of the error message. Example:
18+
```js
19+
const ERROR_MSG = '!!A custom error message without a "key" in front of it'
20+
21+
// Look at node_modules/joi/lib/language.js to know
22+
// which key to override in the options.language object
23+
const schema = Joi
24+
.string()
25+
.options({ language: { string: { base: ERROR_MSG } } })
26+
```
27+
1628
## Editor setup
1729

1830
Please install [editorconfig plugin](http://editorconfig.org/#download) for your preferred editor.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ module.exports = validate(config, { schemaExtension: yourSchemaExtension })
7979
#### Rules
8080
Some validations do more than just validating your data shape, they check for best practices and do "more" which you might want to opt out of / in to. This is an overview of the available rules (we just started with this, this list will grow :)):
8181
- **no-root-files-node-modules-nameclash** (default: true): this checks that files/folders that are found in directories specified via webpacks `resolve.root` option do not nameclash with `node_modules` packages. This prevents nasty path resolving bugs (for a motivating example, have a look at [this redux issue](https://github.com/reactjs/redux/issues/1681)).
82+
- **loader-enforce-include-or-exclude** (default: false): enforce that [loader](https://webpack.github.io/docs/configuration.html#module-loaders) objects use `include` or/and `exclude`, throw when neither is supplied. Without supplying one of these conditions it is too easy to process too many files, for example your `node_modules` folder.
83+
- **loader-prefer-include** (default: false): enforce that [loader](https://webpack.github.io/docs/configuration.html#module-loaders) objects use `include` and not `exclude`. `exclude` makes it easy to match too many files, which might inadvertently slow your build down.
8284

8385
You opt in/out of rules by using the `rules` option:
8486
```js

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,14 @@
4141
"basename": "0.1.2",
4242
"chalk": "1.1.3",
4343
"commander": "2.9.0",
44+
"common-tags": "0.1.1",
4445
"cross-env": "^1.0.7",
4546
"find-node-modules": "^1.0.1",
4647
"joi": "9.0.0-0",
4748
"lodash": "4.11.1",
4849
"npmlog": "2.0.3",
49-
"yargs": "4.7.1",
50-
"shelljs": "0.7.0"
50+
"shelljs": "0.7.0",
51+
"yargs": "4.7.1"
5152
},
5253
"devDependencies": {
5354
"autoprefixer": "6.3.6",

src/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Joi from 'joi'
22
import chalk from 'chalk'
3-
import moduleSchema from './properties/module'
3+
import moduleSchemaFn from './properties/module'
44
import entrySchema from './properties/entry'
55
import contextSchema from './properties/context'
66
import devtoolSchema from './properties/devtool'
@@ -19,6 +19,7 @@ sh.config.silent = true
1919

2020
const makeSchema = (schemaOptions, schemaExtension) => {
2121
const resolveSchema = resolveSchemaFn(schemaOptions)
22+
const moduleSchema = moduleSchemaFn(schemaOptions)
2223

2324
const schema = Joi.object({
2425
amd: Joi.object(),
@@ -59,6 +60,8 @@ const makeSchema = (schemaOptions, schemaExtension) => {
5960
const defaultSchemaOptions = {
6061
rules: {
6162
'no-root-files-node-modules-nameclash': true,
63+
'loader-enforce-include-or-exclude': false,
64+
'loader-prefer-include': false,
6265
},
6366
}
6467

src/properties/module/index.js

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,86 @@
11
import Joi from 'joi'
2+
import { oneLine } from 'common-tags'
23

3-
export const CONDITION_MESSAGE =
4-
'may be a RegExp (tested against absolute path), ' +
5-
'a string containing the absolute path, a function(absPath): bool, ' +
6-
'or an array of one of these combined with “and”.'
4+
export const CONDITION_MESSAGE = oneLine`
5+
may be a RegExp (tested against absolute path),
6+
a string containing the absolute path, a function(absPath): bool,
7+
or an array of one of these combined with “and”.`
78

8-
export const LOADERS_QUERY_MESSAGE =
9-
'You can only pass the \`query\` property when you specify your ' +
10-
'loader with the singular \`loader\` property'
9+
export const LOADERS_QUERY_MESSAGE = oneLine`
10+
You can only pass the \`query\` property when you specify your
11+
loader with the singular \`loader\` property`
12+
13+
const LOADER_INCLUDE_OR_EXCLUDE_MESSAGE = oneLine`!!
14+
Please supply \`include\` or/and \`exclude\` to narrow down which
15+
files will be processed by this loader.
16+
Otherwise it's too easy
17+
to process too many files, for example your \`node_modules\` (loader-enforce-include-or-exclude).
18+
`
19+
20+
const LOADER_INCLUDE_REQUIRED = oneLine`
21+
is required. You should use \`include\` to specify which files should be processed
22+
by this loader. (loader-prefer-include)
23+
`
24+
25+
const LOADER_EXCLUDE_FORBIDDEN = oneLine`
26+
should not be used. Reason: \`exclude\` makes it easy to match too many files,
27+
which might inadvertently slow your build down. Use \`include\` instead. (loader-prefer-include)
28+
`
1129

1230
const conditionSchema = Joi.array().items([
1331
Joi.string(),
1432
Joi.object().type(RegExp),
1533
Joi.func().arity(1),
1634
]).single().options({ language: { array: { includesSingle: CONDITION_MESSAGE } } })
1735

18-
const loaderSchema = Joi.object({
19-
test: conditionSchema.required(),
20-
exclude: conditionSchema,
21-
include: conditionSchema,
22-
loader: Joi.string(),
23-
query: Joi.object(),
24-
loaders: Joi.array().items(Joi.string()),
25-
})
26-
.xor('loaders', 'loader')
27-
.nand('loaders', 'query').options({ language: { object: { nand: LOADERS_QUERY_MESSAGE } } })
28-
29-
const loadersSchema = Joi.array().items(loaderSchema)
30-
31-
export default Joi.object({
32-
loaders: loadersSchema.required(),
33-
preLoaders: loadersSchema,
34-
postLoaders: loadersSchema,
35-
noParse: Joi.array(Joi.object().type(RegExp)).single(),
36-
})
36+
const loaderSchemaFn = ({ rules }) => {
37+
let rule = Joi.object({
38+
test: conditionSchema.required(),
39+
exclude: conditionSchema,
40+
include: conditionSchema,
41+
loader: Joi.string(),
42+
query: Joi.object(),
43+
loaders: Joi.array().items(Joi.string()),
44+
})
45+
.xor('loaders', 'loader')
46+
.nand('loaders', 'query')
47+
.options({ language: { object: { nand: LOADERS_QUERY_MESSAGE } } })
48+
49+
if (rules['loader-enforce-include-or-exclude']) {
50+
rule = rule
51+
.or('exclude', 'include')
52+
.label('loader')
53+
.options({ language: { object: { missing: LOADER_INCLUDE_OR_EXCLUDE_MESSAGE } } })
54+
}
55+
56+
if (rules['loader-prefer-include']) {
57+
rule = rule.concat(
58+
Joi.object({
59+
include: conditionSchema.required(),
60+
exclude: Joi.any().forbidden(),
61+
})
62+
.options({
63+
language: {
64+
any: {
65+
required: LOADER_INCLUDE_REQUIRED,
66+
unknown: LOADER_EXCLUDE_FORBIDDEN,
67+
},
68+
},
69+
})
70+
)
71+
}
72+
73+
return rule
74+
}
75+
76+
77+
export default (options) => {
78+
const loaderSchema = loaderSchemaFn(options)
79+
const loadersSchema = Joi.array().items(loaderSchema)
80+
return Joi.object({
81+
loaders: loadersSchema.required(),
82+
preLoaders: loadersSchema,
83+
postLoaders: loadersSchema,
84+
noParse: Joi.array(Joi.object().type(RegExp)).single(),
85+
})
86+
}

src/properties/module/index.test.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import moduleSchema, { CONDITION_MESSAGE, LOADERS_QUERY_MESSAGE } from './index'
1+
import schemaFn, { CONDITION_MESSAGE, LOADERS_QUERY_MESSAGE } from './index'
22
import { allValid, allInvalid } from '../../../test/utils'
33

44
const validModuleConfigs = [
@@ -37,6 +37,20 @@ const validModuleConfigs = [
3737
],
3838
},
3939
},
40+
{
41+
// should allow both `include` and `exclude with the rule 'loader-enforce-include-or-exclude'
42+
input: {
43+
loaders: [{ test: /foo/, loader: 'foo', include: 'foo', exclude: 'bar' }],
44+
},
45+
schema: schemaFn({ rules: { 'loader-enforce-include-or-exclude': true } }),
46+
},
47+
{
48+
// should be fine with `include` with rule 'loader-enforce-include-or-exclude'
49+
input: {
50+
loaders: [{ test: /foo/, loader: 'foo', include: 'foo' }],
51+
},
52+
schema: schemaFn({ rules: { 'loader-prefer-include': true } }),
53+
},
4054
]
4155

4256
const invalidModuleConfigs = [
@@ -110,8 +124,41 @@ const invalidModuleConfigs = [
110124
},
111125
error: { message: '"query" must be an object' },
112126
},
127+
{
128+
// doesn't include `include`
129+
input: {
130+
loaders: [{ test: /foo/, loader: 'foo' }],
131+
},
132+
schema: schemaFn({ rules: { 'loader-prefer-include': true } }),
133+
},
134+
{
135+
// includes `exclude` and `include`, should only use `include`
136+
input: {
137+
loaders: [{ test: /foo/, loader: 'foo', include: 'foo', exclude: 'bar' }],
138+
},
139+
schema: schemaFn({ rules: { 'loader-prefer-include': true } }),
140+
},
141+
{
142+
// includes `exclude`, should prefer `include`
143+
input: {
144+
loaders: [{ test: /foo/, loader: 'foo', exclude: 'bar' }],
145+
},
146+
schema: schemaFn({ rules: { 'loader-prefer-include': true } }),
147+
},
148+
{
149+
// should use either `include` or `exclude
150+
input: {
151+
loaders: [{ test: /foo/, loader: 'foo' }],
152+
},
153+
schema: schemaFn({ rules: { 'loader-enforce-include-or-exclude': true } }),
154+
},
113155
]
114156

157+
const moduleSchema = schemaFn({
158+
rules: {
159+
},
160+
})
161+
115162
describe('module', () => {
116163
allValid(validModuleConfigs, moduleSchema)
117164
allInvalid(invalidModuleConfigs, moduleSchema)

0 commit comments

Comments
 (0)