Skip to content

Commit cc69754

Browse files
feat: support ecma modules for the 'parser', 'stringifier' and 'syntax' options (#519)
1 parent 55630b8 commit cc69754

File tree

6 files changed

+147
-8
lines changed

6 files changed

+147
-8
lines changed

src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default async function loader(content, sourceMap, meta) {
5959
? options.sourceMap
6060
: this.sourceMap;
6161

62-
const { plugins, processOptions } = getPostcssOptions(
62+
const { plugins, processOptions } = await getPostcssOptions(
6363
this,
6464
loadedConfig,
6565
options.postcssOptions

src/utils.js

+32-7
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,35 @@ function pluginFactory() {
167167
};
168168
}
169169

170-
function getPostcssOptions(
170+
async function load(module) {
171+
let exports;
172+
173+
try {
174+
// eslint-disable-next-line import/no-dynamic-require, global-require
175+
exports = require(module);
176+
177+
return exports;
178+
} catch (requireError) {
179+
let importESM;
180+
181+
try {
182+
// eslint-disable-next-line no-new-func
183+
importESM = new Function("id", "return import(id);");
184+
} catch (e) {
185+
importESM = null;
186+
}
187+
188+
if (requireError.code === "ERR_REQUIRE_ESM" && importESM) {
189+
exports = await importESM(module);
190+
191+
return exports.default;
192+
}
193+
194+
throw requireError;
195+
}
196+
}
197+
198+
async function getPostcssOptions(
171199
loaderContext,
172200
loadedConfig = {},
173201
postcssOptions = {}
@@ -253,8 +281,7 @@ function getPostcssOptions(
253281

254282
if (typeof processOptions.parser === "string") {
255283
try {
256-
// eslint-disable-next-line import/no-dynamic-require, global-require
257-
processOptions.parser = require(processOptions.parser);
284+
processOptions.parser = await load(processOptions.parser);
258285
} catch (error) {
259286
loaderContext.emitError(
260287
new Error(
@@ -266,8 +293,7 @@ function getPostcssOptions(
266293

267294
if (typeof processOptions.stringifier === "string") {
268295
try {
269-
// eslint-disable-next-line import/no-dynamic-require, global-require
270-
processOptions.stringifier = require(processOptions.stringifier);
296+
processOptions.stringifier = await load(processOptions.stringifier);
271297
} catch (error) {
272298
loaderContext.emitError(
273299
new Error(
@@ -279,8 +305,7 @@ function getPostcssOptions(
279305

280306
if (typeof processOptions.syntax === "string") {
281307
try {
282-
// eslint-disable-next-line import/no-dynamic-require, global-require
283-
processOptions.syntax = require(processOptions.syntax);
308+
processOptions.syntax = await load(processOptions.syntax);
284309
} catch (error) {
285310
loaderContext.emitError(
286311
new Error(

test/fixtures/esparser/index.mjs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function stringify() {
2+
return "";
3+
}
4+
5+
function parse() {
6+
return "";
7+
}
8+
9+
export default { stringify, parse };

test/fixtures/esparser/package.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"type": "module",
3+
"exports": {
4+
".": {
5+
"require": "./index.js",
6+
"import": "./index.mjs"
7+
}
8+
},
9+
"name": "test",
10+
"version": "1.0.0"
11+
}

test/fixtures/esparser/runManual.mjs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import webpack from 'webpack';
2+
import path from "path";
3+
4+
const __dirname = path.resolve();
5+
const rootDir = path.resolve(__dirname, "test/helpers");
6+
7+
const compiler = webpack({
8+
target: 'node',
9+
mode: "development",
10+
devtool: false,
11+
context: path.resolve(rootDir, "../fixtures"),
12+
entry: path.resolve(rootDir, "../fixtures", "./sss/index.js"),
13+
output: {
14+
path: path.resolve(rootDir, "../outputs"),
15+
filename: "[name].bundle.js",
16+
chunkFilename: "[name].chunk.js",
17+
publicPath: "/webpack/public/path/",
18+
},
19+
module: {
20+
rules: [
21+
{
22+
test: /\.(css|sss)$/i,
23+
use: [
24+
'css-loader',
25+
{
26+
loader: path.resolve(rootDir, "../../dist"),
27+
options: {
28+
postcssOptions: {
29+
parser: path.resolve(rootDir, "../fixtures/esparser/index.mjs"),
30+
stringifier: path.resolve(rootDir, "../fixtures/esparser/index.mjs"),
31+
syntax: path.resolve(rootDir, "../fixtures/esparser/index.mjs"),
32+
},
33+
},
34+
},
35+
],
36+
},
37+
],
38+
},
39+
plugins: [],
40+
});
41+
42+
compiler.run((error) => {
43+
if (error) {
44+
throw error;
45+
}
46+
});

test/postcssOptins.test.js

+48
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,54 @@ describe('"postcssOptions" option', () => {
170170
expect(getErrors(stats)).toMatchSnapshot("errors");
171171
});
172172

173+
// TODO jest have not good support for ES modules for testing it, tested manually
174+
it.skip('should work with the "parser" option with "Object" value with ESM', async () => {
175+
const compiler = getCompiler("./sss/index.js", {
176+
postcssOptions: {
177+
parser: path.resolve(__dirname, "../fixtures/esparser/index.mjs"),
178+
},
179+
});
180+
const stats = await compile(compiler);
181+
182+
const codeFromBundle = getCodeFromBundle("style.sss", stats);
183+
184+
expect(codeFromBundle.css).toMatchSnapshot("css");
185+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
186+
expect(getErrors(stats)).toMatchSnapshot("errors");
187+
});
188+
189+
// TODO jest have not good support for ES modules for testing it, tested manually
190+
it.skip('should work with the "stringifier" option with "Object" value with ESM', async () => {
191+
const compiler = getCompiler("./sss/index.js", {
192+
postcssOptions: {
193+
stringifier: path.resolve(__dirname, "../fixtures/esparser/index.mjs"),
194+
},
195+
});
196+
const stats = await compile(compiler);
197+
198+
const codeFromBundle = getCodeFromBundle("style.sss", stats);
199+
200+
expect(codeFromBundle.css).toMatchSnapshot("css");
201+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
202+
expect(getErrors(stats)).toMatchSnapshot("errors");
203+
});
204+
205+
// TODO jest have not good support for ES modules for testing it, tested manually
206+
it.skip('should work with the "syntax" option with "Object" value with ESM', async () => {
207+
const compiler = getCompiler("./sss/index.js", {
208+
postcssOptions: {
209+
syntax: path.resolve(__dirname, "../fixtures/esparser/index.mjs"),
210+
},
211+
});
212+
const stats = await compile(compiler);
213+
214+
const codeFromBundle = getCodeFromBundle("style.sss", stats);
215+
216+
expect(codeFromBundle.css).toMatchSnapshot("css");
217+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
218+
expect(getErrors(stats)).toMatchSnapshot("errors");
219+
});
220+
173221
it('should work with the "parser" option with "Function" value', async () => {
174222
const compiler = getCompiler("./sss/index.js", {
175223
postcssOptions: {

0 commit comments

Comments
 (0)