Skip to content

Commit 1091e32

Browse files
authored
Add support for ESM plugins/presets
ESM modules can use `export default` to expose their plugin or preset. This does not add support for config files in ESM just yet. * Add support for plugins in ESM format w/ an `.mjs` extension * Add support for plugins in ESM format w/ a `.js` extension if the nearest `package.json` has a `type: 'module'` * Add support for interop bundles (CJS w/ `__esModule: true` field)
1 parent 53be150 commit 1091e32

File tree

14 files changed

+214
-67
lines changed

14 files changed

+214
-67
lines changed

lib/configuration.js

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,13 @@ function load(filePath, callback) {
7474
return callback(error, file)
7575
}
7676

77-
callback(null, self.create())
77+
self.create().then(function (result) {
78+
callback(null, result)
79+
}, callback)
7880
}
7981
}
8082

81-
function create(buf, filePath) {
83+
async function create(buf, filePath) {
8284
var self = this
8385
var fn = (filePath && loaders[path.extname(filePath)]) || defaultLoader
8486
var options = {prefix: self.pluginPrefix, cwd: self.cwd}
@@ -100,21 +102,21 @@ function create(buf, filePath) {
100102

101103
if (contents === undefined) {
102104
if (self.defaultConfig) {
103-
merge(
105+
await merge(
104106
result,
105107
self.defaultConfig,
106108
Object.assign({}, options, {root: self.cwd})
107109
)
108110
}
109111
} else {
110-
merge(
112+
await merge(
111113
result,
112114
contents,
113115
Object.assign({}, options, {root: path.dirname(filePath)})
114116
)
115117
}
116118

117-
merge(result, self.given, Object.assign({}, options, {root: self.cwd}))
119+
await merge(result, self.given, Object.assign({}, options, {root: self.cwd}))
118120

119121
return result
120122
}
@@ -149,26 +151,22 @@ function loadJson(buf, filePath) {
149151
return result
150152
}
151153

152-
function merge(target, raw, options) {
154+
async function merge(target, raw, options) {
153155
if (typeof raw === 'object' && raw !== null) {
154-
addPreset(raw)
156+
await addPreset(raw)
155157
} else {
156158
throw new Error('Expected preset, not `' + raw + '`')
157159
}
158160

159161
return target
160162

161-
function addPreset(result) {
163+
async function addPreset(result) {
162164
var plugins = result.plugins
163165

164166
if (plugins === null || plugins === undefined) {
165167
// Empty.
166168
} else if (typeof plugins === 'object' && plugins !== null) {
167-
if ('length' in plugins) {
168-
addEach(plugins)
169-
} else {
170-
addIn(plugins)
171-
}
169+
await ('length' in plugins ? addEach(plugins) : addIn(plugins))
172170
} else {
173171
throw new Error(
174172
'Expected a list or object of plugins, not `' + plugins + '`'
@@ -178,59 +176,80 @@ function merge(target, raw, options) {
178176
target.settings = Object.assign({}, target.settings, result.settings)
179177
}
180178

181-
function addEach(result) {
179+
async function addEach(result) {
182180
var index = -1
183181
var value
184182

185183
while (++index < result.length) {
186184
value = result[index]
187185

188-
if (value !== null && typeof value === 'object' && 'length' in value) {
189-
use.apply(null, value)
190-
} else {
191-
use(value)
192-
}
186+
// Keep order sequential instead of parallel.
187+
// eslint-disable-next-line no-await-in-loop
188+
await (value !== null && typeof value === 'object' && 'length' in value
189+
? use.apply(null, value)
190+
: use(value))
193191
}
194192
}
195193

196-
function addIn(result) {
194+
async function addIn(result) {
197195
var key
198196

199197
for (key in result) {
200-
use(key, result[key])
198+
// Keep order sequential instead of parallel.
199+
// eslint-disable-next-line no-await-in-loop
200+
await use(key, result[key])
201201
}
202202
}
203203

204-
function use(usable, value) {
204+
async function use(usable, value) {
205205
if (typeof usable === 'string') {
206-
addModule(usable, value)
206+
await addModule(usable, value)
207207
} else if (typeof usable === 'function') {
208208
addPlugin(usable, value)
209209
} else {
210-
merge(target, usable, options)
210+
await merge(target, usable, options)
211211
}
212212
}
213213

214-
function addModule(id, value) {
214+
async function addModule(id, value) {
215215
var fp = loadPlugin.resolve(id, {cwd: options.root, prefix: options.prefix})
216+
var ext
216217
var result
217218

218219
if (fp) {
219-
try {
220-
result = require(fp)
221-
} catch (error) {
222-
throw fault(
223-
'Cannot parse script `%s`\n%s',
224-
path.relative(options.root, fp),
225-
error.stack
226-
)
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
227246
}
228247

229248
try {
230249
if (typeof result === 'function') {
231250
addPlugin(result, value)
232251
} else {
233-
merge(
252+
await merge(
234253
target,
235254
result,
236255
Object.assign({}, options, {root: path.dirname(fp)})

lib/find-up.js

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var fs = require('fs')
44
var path = require('path')
55
var fault = require('fault')
66
var debug = require('debug')('unified-engine:find-up')
7+
var wrap = require('trough/wrap')
78

89
module.exports = FindUp
910

@@ -68,23 +69,32 @@ function load(filePath, callback) {
6869
result.code = 'ENOENT'
6970
result.path = error.path
7071
result.syscall = error.syscall
72+
loaded(result)
7173
} else {
72-
try {
73-
result = self.create(buf, self.givenFilePath)
74-
debug('Read given file `%s`', self.givenFilePath)
75-
} catch (error_) {
76-
result = fault(
77-
'Cannot parse given file `%s`\n%s',
78-
path.relative(self.cwd, self.givenFilePath),
79-
error_.stack
74+
wrap(self.create, onparse)(buf, self.givenFilePath)
75+
}
76+
77+
function onparse(error, result) {
78+
if (error) {
79+
debug(error.message)
80+
loaded(
81+
fault(
82+
'Cannot parse given file `%s`\n%s',
83+
path.relative(self.cwd, self.givenFilePath),
84+
error.stack
85+
)
8086
)
81-
debug(error_.message)
87+
} else {
88+
debug('Read given file `%s`', self.givenFilePath)
89+
loaded(result)
8290
}
8391
}
8492

85-
givenFile = result
86-
self.givenFile = result
87-
applyAll(cbs, result)
93+
function loaded(result) {
94+
givenFile = result
95+
self.givenFile = result
96+
applyAll(cbs, result)
97+
}
8898
}
8999

90100
function find(directory) {
@@ -117,41 +127,40 @@ function load(filePath, callback) {
117127

118128
function done(error, buf) {
119129
var fp = path.join(directory, self.names[index])
120-
var contents
121130

122131
/* istanbul ignore if - Hard to test. */
123132
if (error) {
124133
if (error.code === 'ENOENT') {
125134
return next()
126135
}
127136

128-
error = fault(
129-
'Cannot read file `%s`\n%s',
130-
path.relative(self.cwd, fp),
131-
error.message
132-
)
133137
debug(error.message)
134-
return found(error)
135-
}
136-
137-
try {
138-
contents = self.create(buf, fp)
139-
} catch (error_) {
140138
return found(
141139
fault(
142-
'Cannot parse file `%s`\n%s',
140+
'Cannot read file `%s`\n%s',
143141
path.relative(self.cwd, fp),
144-
error_.message
142+
error.message
145143
)
146144
)
147145
}
148146

149-
/* istanbul ignore else - maybe used in the future. */
150-
if (contents) {
151-
debug('Read file `%s`', fp)
152-
found(null, contents)
153-
} else {
154-
next()
147+
wrap(self.create, onparse)(buf, fp)
148+
149+
function onparse(error, result) {
150+
if (error) {
151+
found(
152+
fault(
153+
'Cannot parse file `%s`\n%s',
154+
path.relative(self.cwd, fp),
155+
error.message
156+
)
157+
)
158+
} else if (result) {
159+
debug('Read file `%s`', fp)
160+
found(null, result)
161+
} else {
162+
next()
163+
}
155164
}
156165
}
157166

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"remark-cli": "^9.0.0",
5555
"remark-preset-wooorm": "^8.0.0",
5656
"remark-toc": "^7.0.0",
57+
"semver": "^6.0.0",
5758
"strip-ansi": "^6.0.0",
5859
"tape": "^5.0.0",
5960
"unified": "^9.0.0",

0 commit comments

Comments
 (0)