Skip to content

Commit 3bf7851

Browse files
authoredJun 28, 2021
Add support for ESM config files
ESM config files can use `export default` to expose their configuration. This commit also adds support for `.cjs` and `.mjs` config files. Essentially 1091e32 but for config files. Closes GH-50.
1 parent 8702f2c commit 3bf7851

File tree

10 files changed

+102
-64
lines changed

10 files changed

+102
-64
lines changed
 

‎doc/configure.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ regardless of `detectConfig` and `rcName`.
1313
Otherwise, configuration files are detected if [`detectConfig`][detect-config]
1414
is turned on, depending on the following options:
1515

16-
* If [`rcName`][rc-name] is given, `$rcName` (JSON), `$rcName.js` (CommonJS),
17-
`$rcName.yml` (YAML), and `$rcName.yaml` (YAML) are loaded
16+
* If [`rcName`][rc-name] is given, `$rcName` (JSON), `$rcName.js` (CommonJS or
17+
ESM), `$rcName.cjs` (CommonJS), `$rcName.mjs` (ESM), `$rcName.yml` (YAML),
18+
and `$rcName.yaml` (YAML) are loaded
1819
* If [`packageField`][package-field] is given, `package.json` (JSON) files
1920
are loaded and their `$packageField`s are used as configuration
2021

‎doc/options.md

+15-6
Original file line numberDiff line numberDiff line change
@@ -673,16 +673,23 @@ root[1] (1:1-2:1, 0-27)
673673
## `options.rcName`
674674

675675
Name of [configuration][configure] file to load.
676-
If given and [`detectConfig`][detect-config] is not `false`, `$rcName` files are
677-
loaded and parsed as JSON, `$rcName.js` are `require`d, and `$rcName.yml` and
678-
`$rcName.yaml` are loaded with `js-yaml` (`safeLoad`).
676+
If given and [`detectConfig`][detect-config] is not `false`, then:
677+
678+
* `$rcName` and `$rcName.json` are loaded and parsed as JSON
679+
* `$rcName.yml` and `$rcName.yaml` are loaded with `js-yaml`
680+
* `$rcName.js` are either `require`d or `import`ed
681+
* `$rcName.cjs` are `require`d
682+
* `$rcName.mjs` are `import`ed
683+
684+
<!---->
679685

680686
* Type: `string`, optional
681687

682688
###### Example
683689

684-
The following example processes `readme.md`, and allows configuration from
685-
`.remarkrc`, `.remarkrc.js`, and `.remarkrc.yaml` files.
690+
The following example processes `readme.md` and allows configuration from
691+
`.remarkrc`, `.remarkrc.yml`, `.remarkrc.yaml`, `.remarkrc.js`, `.remarkrc.cjs`,
692+
and `.remarkrc.mjs` files.
686693

687694
```js
688695
var engine = require('unified-engine')
@@ -775,7 +782,9 @@ File path to a config file to load, regardless of
775782
[`detectConfig`][detect-config] or [`rcName`][rc-name].
776783

777784
If the file’s extension is `yml` or `yaml`, it’s loaded as YAML.
778-
If the file’s extension is `js`, it’s `require`d.
785+
If it’s `js`, it’s either `require`d or `import`ed.
786+
If it’s `cjs`, it’s `require`d.
787+
If it’s `mjs`, it’s `import`ed.
779788
If the file’s basename is `package.json`, the property at
780789
[`packageField`][package-field] is used.
781790
Otherwise, the file is parsed as JSON.

‎lib/configuration.js

+44-45
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict'
22

33
var path = require('path')
4-
var Module = require('module')
54
var yaml = require('js-yaml')
65
var json = require('parse-json')
76
var debug = require('debug')('unified-engine:configuration')
@@ -16,7 +15,9 @@ var own = {}.hasOwnProperty
1615

1716
var loaders = {
1817
'.json': loadJson,
19-
'.js': loadScript,
18+
'.cjs': loadScriptOrModule,
19+
'.mjs': loadScriptOrModule,
20+
'.js': loadScriptOrModule,
2021
'.yaml': loadYaml,
2122
'.yml': loadYaml
2223
}
@@ -85,7 +86,11 @@ async function create(buf, filePath) {
8586
var fn = (filePath && loaders[path.extname(filePath)]) || defaultLoader
8687
var options = {prefix: self.pluginPrefix, cwd: self.cwd}
8788
var result = {settings: {}, plugins: []}
88-
var contents = buf ? fn.apply(self, arguments) : undefined
89+
var contents
90+
91+
if (filePath) {
92+
contents = await fn.apply(self, arguments)
93+
}
8994

9095
if (self.configTransform && contents !== undefined) {
9196
contents = self.configTransform(contents, filePath)
@@ -121,20 +126,8 @@ async function create(buf, filePath) {
121126
return result
122127
}
123128

124-
// Basically `Module.prototype.load`, but for a buffer instead of a file path.
125-
function loadScript(buf, filePath) {
126-
var submodule = Module._cache[filePath]
127-
128-
if (!submodule) {
129-
submodule = new Module(filePath, module)
130-
submodule.filename = filePath
131-
submodule.paths = Module._nodeModulePaths(path.dirname(filePath))
132-
submodule._compile(String(buf), filePath)
133-
submodule.loaded = true
134-
Module._cache[filePath] = submodule
135-
}
136-
137-
return submodule.exports
129+
function loadScriptOrModule(_, filePath) {
130+
return loadFromAbsolutePath(filePath, this.cwd)
138131
}
139132

140133
function loadYaml(buf, filePath) {
@@ -213,37 +206,10 @@ async function merge(target, raw, options) {
213206

214207
async function addModule(id, value) {
215208
var fp = loadPlugin.resolve(id, {cwd: options.root, prefix: options.prefix})
216-
var ext
217209
var result
218210

219211
if (fp) {
220-
ext = path.extname(fp)
221-
222-
/* istanbul ignore next - To do next major: Tests don’t run on Node 10 */
223-
if (ext !== '.mjs') {
224-
try {
225-
result = require(fp)
226-
} catch (error) {
227-
if (ext !== '.cjs' && error.code === 'ERR_REQUIRE_ESM') {
228-
ext = '.mjs'
229-
} else {
230-
throw fault(
231-
'Cannot parse script `%s`\n%s',
232-
path.relative(options.root, fp),
233-
error.stack
234-
)
235-
}
236-
}
237-
238-
if (result && typeof result === 'object' && result.__esModule) {
239-
result = result.default
240-
}
241-
}
242-
243-
/* istanbul ignore next - To do next major: Tests don’t run on Node 10 */
244-
if (ext === '.mjs') {
245-
result = (await import(fp)).default
246-
}
212+
result = await loadFromAbsolutePath(fp, options.root)
247213

248214
try {
249215
if (typeof result === 'function') {
@@ -308,3 +274,36 @@ function failingModule(id, error) {
308274
throw error
309275
}
310276
}
277+
278+
async function loadFromAbsolutePath(fp, base) {
279+
var ext = path.extname(fp)
280+
var result
281+
282+
/* istanbul ignore next - To do next major: Tests don’t run on Node 10 */
283+
if (ext !== '.mjs') {
284+
try {
285+
result = require(fp)
286+
} catch (error) {
287+
if (ext !== '.cjs' && error.code === 'ERR_REQUIRE_ESM') {
288+
ext = '.mjs'
289+
} else {
290+
throw fault(
291+
'Cannot parse script `%s`\n%s',
292+
path.relative(base, fp),
293+
error.stack
294+
)
295+
}
296+
}
297+
298+
if (result && typeof result === 'object' && result.__esModule) {
299+
result = result.default
300+
}
301+
}
302+
303+
/* istanbul ignore next - To do next major: Tests don’t run on Node 10 */
304+
if (ext === '.mjs') {
305+
result = (await import(fp)).default
306+
}
307+
308+
return result
309+
}

‎test/configuration.js

+35-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var join = path.join
1111
var fixtures = join(__dirname, 'fixtures')
1212

1313
test('configuration', function (t) {
14-
t.plan(13)
14+
t.plan(14)
1515

1616
t.test('should fail fatally when custom rc files are missing', function (t) {
1717
var stderr = spy()
@@ -101,15 +101,15 @@ test('configuration', function (t) {
101101
}
102102
})
103103

104-
t.test('should support `.rc.js` modules (1)', function (t) {
104+
t.test('should support `.rc.js` scripts (1)', function (t) {
105105
var stderr = spy()
106106

107107
t.plan(1)
108108

109109
engine(
110110
{
111111
processor: noop,
112-
cwd: join(fixtures, 'malformed-rc-module'),
112+
cwd: join(fixtures, 'malformed-rc-script'),
113113
streamError: stderr.stream,
114114
files: ['.'],
115115
rcName: '.foorc',
@@ -130,15 +130,15 @@ test('configuration', function (t) {
130130
}
131131
})
132132

133-
t.test('should support `.rc.js` modules (2)', function (t) {
133+
t.test('should support `.rc.js` scripts (2)', function (t) {
134134
var stderr = spy()
135135

136136
t.plan(1)
137137

138138
engine(
139139
{
140140
processor: noop,
141-
cwd: join(fixtures, 'rc-module'),
141+
cwd: join(fixtures, 'rc-script'),
142142
streamError: stderr.stream,
143143
files: ['.'],
144144
rcName: '.foorc',
@@ -151,22 +151,46 @@ test('configuration', function (t) {
151151
t.deepEqual(
152152
[error, code, stderr()],
153153
[null, 0, 'one.txt: no issues found\n'],
154-
'should support valid .rc modules'
154+
'should support valid .rc scripts'
155155
)
156156
}
157157
})
158158

159-
t.test('should support `.rc.js` modules (3)', function (t) {
159+
t.test('should support `.rc.js` scripts (3)', function (t) {
160160
var stderr = spy()
161161

162162
t.plan(1)
163163

164-
require('./fixtures/rc-module/.foorc.js') // eslint-disable-line import/no-unassigned-import
164+
engine(
165+
{
166+
processor: noop,
167+
cwd: join(fixtures, 'rc-script'),
168+
streamError: stderr.stream,
169+
files: ['.'],
170+
rcName: '.foorc',
171+
extensions: ['txt']
172+
},
173+
onrun
174+
)
175+
176+
function onrun(error, code) {
177+
t.deepEqual(
178+
[error, code, stderr()],
179+
[null, 0, 'one.txt: no issues found\n'],
180+
'should use Node’s module caching (coverage)'
181+
)
182+
}
183+
})
184+
185+
t.test('should support `.rc.mjs` module', function (t) {
186+
var stderr = spy()
187+
188+
t.plan(1)
165189

166190
engine(
167191
{
168192
processor: noop,
169-
cwd: join(fixtures, 'rc-module'),
193+
cwd: join(fixtures, 'rc-module-mjs'),
170194
streamError: stderr.stream,
171195
files: ['.'],
172196
rcName: '.foorc',
@@ -184,7 +208,7 @@ test('configuration', function (t) {
184208
}
185209
})
186210

187-
t.test('should support `.rc.yaml` modules', function (t) {
211+
t.test('should support `.rc.yaml` cpmfog files', function (t) {
188212
var stderr = spy()
189213

190214
t.plan(1)
@@ -345,7 +369,7 @@ test('configuration', function (t) {
345369
engine(
346370
{
347371
processor: noop,
348-
cwd: join(fixtures, 'malformed-rc-module'),
372+
cwd: join(fixtures, 'malformed-rc-script'),
349373
streamError: stderr.stream,
350374
files: ['.'],
351375
extensions: ['txt'],
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const config = {
2+
settings: []
3+
}
4+
5+
export default config
File renamed without changes.
File renamed without changes.

‎test/fixtures/rc-script/one.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)
Please sign in to comment.