From 1e9505edd8196592e8eb73a3855564f71eb90b4e Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Tue, 9 Mar 2021 22:19:41 +0300 Subject: [PATCH 1/2] feat: support ECMA modules --- src/index.js | 2 +- src/utils.js | 78 +++++++++++++++++++++------- test/fixtures/esparser/index.mjs | 9 ++++ test/fixtures/esparser/package.json | 11 ++++ test/fixtures/esparser/runManual.mjs | 46 ++++++++++++++++ test/postcssOptins.test.js | 48 +++++++++++++++++ 6 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 test/fixtures/esparser/index.mjs create mode 100644 test/fixtures/esparser/package.json create mode 100644 test/fixtures/esparser/runManual.mjs diff --git a/src/index.js b/src/index.js index f582df1e..907bc68e 100644 --- a/src/index.js +++ b/src/index.js @@ -59,7 +59,7 @@ export default async function loader(content, sourceMap, meta) { ? options.sourceMap : this.sourceMap; - const { plugins, processOptions } = getPostcssOptions( + const { plugins, processOptions } = await getPostcssOptions( this, loadedConfig, options.postcssOptions diff --git a/src/utils.js b/src/utils.js index e43becf0..76592150 100644 --- a/src/utils.js +++ b/src/utils.js @@ -167,7 +167,26 @@ function pluginFactory() { }; } -function getPostcssOptions( +async function getImportEsm(module, requireError) { + let importESM; + + try { + // eslint-disable-next-line no-new-func + importESM = new Function("id", "return import(id);"); + } catch (e) { + importESM = null; + } + + if (requireError.code === "ERR_REQUIRE_ESM" && importESM) { + const exports = await importESM(module); + + return exports.default; + } + + throw requireError; +} + +async function getPostcssOptions( loaderContext, loadedConfig = {}, postcssOptions = {} @@ -255,12 +274,19 @@ function getPostcssOptions( try { // eslint-disable-next-line import/no-dynamic-require, global-require processOptions.parser = require(processOptions.parser); - } catch (error) { - loaderContext.emitError( - new Error( - `Loading PostCSS "${processOptions.parser}" parser failed: ${error.message}\n\n(@${file})` - ) - ); + } catch (requireError) { + try { + processOptions.parser = await getImportEsm( + processOptions.parser, + requireError + ); + } catch (error) { + loaderContext.emitError( + new Error( + `Loading PostCSS "${processOptions.parser}" parser failed: ${error.message}\n\n(@${file})` + ) + ); + } } } @@ -268,12 +294,19 @@ function getPostcssOptions( try { // eslint-disable-next-line import/no-dynamic-require, global-require processOptions.stringifier = require(processOptions.stringifier); - } catch (error) { - loaderContext.emitError( - new Error( - `Loading PostCSS "${processOptions.stringifier}" stringifier failed: ${error.message}\n\n(@${file})` - ) - ); + } catch (requireError) { + try { + processOptions.stringifier = await getImportEsm( + processOptions.stringifier, + requireError + ); + } catch (error) { + loaderContext.emitError( + new Error( + `Loading PostCSS "${processOptions.stringifier}" stringifier failed: ${error.message}\n\n(@${file})` + ) + ); + } } } @@ -281,12 +314,19 @@ function getPostcssOptions( try { // eslint-disable-next-line import/no-dynamic-require, global-require processOptions.syntax = require(processOptions.syntax); - } catch (error) { - loaderContext.emitError( - new Error( - `Loading PostCSS "${processOptions.syntax}" syntax failed: ${error.message}\n\n(@${file})` - ) - ); + } catch (requireError) { + try { + processOptions.syntax = await getImportEsm( + processOptions.syntax, + requireError + ); + } catch (error) { + loaderContext.emitError( + new Error( + `Loading PostCSS "${processOptions.syntax}" syntax failed: ${error.message}\n\n(@${file})` + ) + ); + } } } diff --git a/test/fixtures/esparser/index.mjs b/test/fixtures/esparser/index.mjs new file mode 100644 index 00000000..5905c023 --- /dev/null +++ b/test/fixtures/esparser/index.mjs @@ -0,0 +1,9 @@ +function stringify() { + return ""; +} + +function parse() { + return ""; +} + +export default { stringify, parse }; diff --git a/test/fixtures/esparser/package.json b/test/fixtures/esparser/package.json new file mode 100644 index 00000000..29a16e5d --- /dev/null +++ b/test/fixtures/esparser/package.json @@ -0,0 +1,11 @@ +{ + "type": "module", + "exports": { + ".": { + "require": "./index.js", + "import": "./index.mjs" + } + }, + "name": "test", + "version": "1.0.0" +} diff --git a/test/fixtures/esparser/runManual.mjs b/test/fixtures/esparser/runManual.mjs new file mode 100644 index 00000000..2fc433fd --- /dev/null +++ b/test/fixtures/esparser/runManual.mjs @@ -0,0 +1,46 @@ +import webpack from 'webpack'; +import path from "path"; + +const __dirname = path.resolve(); +const rootDir = path.resolve(__dirname, "test/helpers"); + +const compiler = webpack({ + target: 'node', + mode: "development", + devtool: false, + context: path.resolve(rootDir, "../fixtures"), + entry: path.resolve(rootDir, "../fixtures", "./sss/index.js"), + output: { + path: path.resolve(rootDir, "../outputs"), + filename: "[name].bundle.js", + chunkFilename: "[name].chunk.js", + publicPath: "/webpack/public/path/", + }, + module: { + rules: [ + { + test: /\.(css|sss)$/i, + use: [ + 'css-loader', + { + loader: path.resolve(rootDir, "../../dist"), + options: { + postcssOptions: { + parser: path.resolve(rootDir, "../fixtures/esparser/index.mjs"), + stringifier: path.resolve(rootDir, "../fixtures/esparser/index.mjs"), + syntax: path.resolve(rootDir, "../fixtures/esparser/index.mjs"), + }, + }, + }, + ], + }, + ], + }, + plugins: [], +}); + +compiler.run((error) => { + if (error) { + throw error; + } +}); diff --git a/test/postcssOptins.test.js b/test/postcssOptins.test.js index 17c01911..b1d76dcf 100644 --- a/test/postcssOptins.test.js +++ b/test/postcssOptins.test.js @@ -170,6 +170,54 @@ describe('"postcssOptions" option', () => { expect(getErrors(stats)).toMatchSnapshot("errors"); }); + // TODO jest have not good support for ES modules for testing it, tested manually + it.skip('should work with the "parser" option with "Object" value with ESM', async () => { + const compiler = getCompiler("./sss/index.js", { + postcssOptions: { + parser: path.resolve(__dirname, "../fixtures/esparser/index.mjs"), + }, + }); + const stats = await compile(compiler); + + const codeFromBundle = getCodeFromBundle("style.sss", stats); + + expect(codeFromBundle.css).toMatchSnapshot("css"); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + + // TODO jest have not good support for ES modules for testing it, tested manually + it.skip('should work with the "stringifier" option with "Object" value with ESM', async () => { + const compiler = getCompiler("./sss/index.js", { + postcssOptions: { + stringifier: path.resolve(__dirname, "../fixtures/esparser/index.mjs"), + }, + }); + const stats = await compile(compiler); + + const codeFromBundle = getCodeFromBundle("style.sss", stats); + + expect(codeFromBundle.css).toMatchSnapshot("css"); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + + // TODO jest have not good support for ES modules for testing it, tested manually + it.skip('should work with the "syntax" option with "Object" value with ESM', async () => { + const compiler = getCompiler("./sss/index.js", { + postcssOptions: { + syntax: path.resolve(__dirname, "../fixtures/esparser/index.mjs"), + }, + }); + const stats = await compile(compiler); + + const codeFromBundle = getCodeFromBundle("style.sss", stats); + + expect(codeFromBundle.css).toMatchSnapshot("css"); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + it('should work with the "parser" option with "Function" value', async () => { const compiler = getCompiler("./sss/index.js", { postcssOptions: { From f7a68f710245284a1ef3168d33929c5793bf56a6 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Thu, 11 Mar 2021 13:25:39 +0300 Subject: [PATCH 2/2] refactor: code --- src/utils.js | 99 ++++++++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 57 deletions(-) diff --git a/src/utils.js b/src/utils.js index 76592150..4ca898ab 100644 --- a/src/utils.js +++ b/src/utils.js @@ -167,23 +167,32 @@ function pluginFactory() { }; } -async function getImportEsm(module, requireError) { - let importESM; +async function load(module) { + let exports; try { - // eslint-disable-next-line no-new-func - importESM = new Function("id", "return import(id);"); - } catch (e) { - importESM = null; - } + // eslint-disable-next-line import/no-dynamic-require, global-require + exports = require(module); - if (requireError.code === "ERR_REQUIRE_ESM" && importESM) { - const exports = await importESM(module); + return exports; + } catch (requireError) { + let importESM; - return exports.default; - } + try { + // eslint-disable-next-line no-new-func + importESM = new Function("id", "return import(id);"); + } catch (e) { + importESM = null; + } + + if (requireError.code === "ERR_REQUIRE_ESM" && importESM) { + exports = await importESM(module); - throw requireError; + return exports.default; + } + + throw requireError; + } } async function getPostcssOptions( @@ -272,61 +281,37 @@ async function getPostcssOptions( if (typeof processOptions.parser === "string") { try { - // eslint-disable-next-line import/no-dynamic-require, global-require - processOptions.parser = require(processOptions.parser); - } catch (requireError) { - try { - processOptions.parser = await getImportEsm( - processOptions.parser, - requireError - ); - } catch (error) { - loaderContext.emitError( - new Error( - `Loading PostCSS "${processOptions.parser}" parser failed: ${error.message}\n\n(@${file})` - ) - ); - } + processOptions.parser = await load(processOptions.parser); + } catch (error) { + loaderContext.emitError( + new Error( + `Loading PostCSS "${processOptions.parser}" parser failed: ${error.message}\n\n(@${file})` + ) + ); } } if (typeof processOptions.stringifier === "string") { try { - // eslint-disable-next-line import/no-dynamic-require, global-require - processOptions.stringifier = require(processOptions.stringifier); - } catch (requireError) { - try { - processOptions.stringifier = await getImportEsm( - processOptions.stringifier, - requireError - ); - } catch (error) { - loaderContext.emitError( - new Error( - `Loading PostCSS "${processOptions.stringifier}" stringifier failed: ${error.message}\n\n(@${file})` - ) - ); - } + processOptions.stringifier = await load(processOptions.stringifier); + } catch (error) { + loaderContext.emitError( + new Error( + `Loading PostCSS "${processOptions.stringifier}" stringifier failed: ${error.message}\n\n(@${file})` + ) + ); } } if (typeof processOptions.syntax === "string") { try { - // eslint-disable-next-line import/no-dynamic-require, global-require - processOptions.syntax = require(processOptions.syntax); - } catch (requireError) { - try { - processOptions.syntax = await getImportEsm( - processOptions.syntax, - requireError - ); - } catch (error) { - loaderContext.emitError( - new Error( - `Loading PostCSS "${processOptions.syntax}" syntax failed: ${error.message}\n\n(@${file})` - ) - ); - } + processOptions.syntax = await load(processOptions.syntax); + } catch (error) { + loaderContext.emitError( + new Error( + `Loading PostCSS "${processOptions.syntax}" syntax failed: ${error.message}\n\n(@${file})` + ) + ); } }