From 8d909f6ecb88cde9a8040909e95b4d882aba01cb Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Fri, 5 Jun 2020 17:08:50 +0300 Subject: [PATCH 01/15] refactor: code --- src/index.js | 91 ++++----- src/options.json | 112 ++++++++++- src/utils.js | 118 +++++++++++ test/__snapshots__/loader.test.js.snap | 156 ++++++++++++++- .../validate-options.test.js.snap | 152 ++++++++++++++ test/cjs.test.js | 8 + test/fixtures/lib_1.js | 1 + test/fixtures/lib_2.js | 1 + test/fixtures/lib_3.js | 1 + test/fixtures/lib_4.js | 1 + test/helpers/getCompiler.js | 8 + test/loader.test.js | 186 +++++++++++++++++- test/validate-options.test.js | 123 ++++++++++++ 13 files changed, 900 insertions(+), 58 deletions(-) create mode 100644 src/utils.js create mode 100644 test/__snapshots__/validate-options.test.js.snap create mode 100644 test/cjs.test.js create mode 100644 test/fixtures/lib_1.js create mode 100644 test/fixtures/lib_2.js create mode 100644 test/fixtures/lib_3.js create mode 100644 test/fixtures/lib_4.js create mode 100644 test/validate-options.test.js diff --git a/src/index.js b/src/index.js index cb51833..8636a06 100644 --- a/src/index.js +++ b/src/index.js @@ -4,66 +4,63 @@ */ import { getOptions, getCurrentRequest } from 'loader-utils'; -// import validateOptions from 'schema-utils'; -// -// import schema from './options.json'; +import validateOptions from 'schema-utils'; + +import schema from './options.json'; + +import getImportString from './utils'; const { SourceNode } = require('source-map'); const { SourceMapConsumer } = require('source-map'); -const HEADER = '/*** IMPORTS FROM imports-loader ***/\n'; - export default function loader(content, sourceMap) { const options = getOptions(this) || {}; - // validateOptions(schema, options, 'Loader'); + validateOptions(schema, options, { + name: 'Imports loader', + baseDataPath: 'options', + }); - const callback = this.async(); + const moduleImport = options.import; + const { wrapper, additionalCode } = options; - if (this.cacheable) this.cacheable(); - const query = options; - const imports = []; + const HEADER = '/*** IMPORTS FROM imports-loader ***/\n'; + const prefixes = []; const postfixes = []; - Object.keys(query).forEach((name) => { - let value; - if (typeof query[name] === 'string' && query[name].substr(0, 1) === '>') { - value = query[name].substr(1); - } else { - let mod = name; - if (typeof query[name] === 'string') { - mod = query[name]; - } - value = `require(${JSON.stringify(mod)})`; - } - if (name === 'this') { - imports.push('(function() {'); - postfixes.unshift(`}.call(${value}));`); - } else if (name.indexOf('.') !== -1) { - name.split('.').reduce((previous, current, index, names) => { - const expr = previous + current; - - if (previous.length === 0) { - imports.push(`var ${expr} = (${current} || {});`); - } else if (index < names.length - 1) { - imports.push(`${expr} = ${expr} || {};`); - } else { - imports.push(`${expr} = ${value};`); - } - - return `${previous}${current}.`; - }, ''); - } else { - imports.push(`var ${name} = ${value};`); - } - }); - const prefix = `${HEADER}${imports.join('\n')}\n\n`; - const postfix = `\n${postfixes.join('\n')}`; + const imports = []; + + let moduleImports; + + if (moduleImport) { + moduleImports = Array.isArray(moduleImport) ? moduleImport : [moduleImport]; + + moduleImports.forEach((importEntry) => { + imports.push(getImportString(importEntry)); + }); + } + + if (wrapper) { + prefixes.push(`(function() {`); + postfixes.unshift(`}.call(${wrapper}));`); + } + + if (additionalCode) { + prefixes.push(`${additionalCode}\n`); + } + + const prefix = prefixes.join('\n'); + const postfix = postfixes.join('\n'); + const importString = imports.join('\n'); + + const callback = this.async(); + if (sourceMap) { const node = SourceNode.fromStringWithSourceMap( content, new SourceMapConsumer(sourceMap) ); node.prepend(prefix); + node.prepend(HEADER); node.add(postfix); const result = node.toStringWithSourceMap({ file: getCurrentRequest(this), @@ -72,5 +69,9 @@ export default function loader(content, sourceMap) { return; } - callback(null, `${prefix}${content}${postfix}`, sourceMap); + callback( + null, + `${HEADER}\n${importString}\n${prefix}\n${content}\n${postfix}`, + sourceMap + ); } diff --git a/src/options.json b/src/options.json index 4a7c07d..f002791 100644 --- a/src/options.json +++ b/src/options.json @@ -1,5 +1,115 @@ { + "definitions": { + "ObjectPattern": { + "type": "object", + "additionalProperties": false, + "properties": { + "moduleName": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "names": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "nameSpace": { + "type": "boolean" + }, + "default": { + "type": "boolean" + } + } + }, + { + "type": "array", + "minItems": 1, + "items": { + "anyOf": [ + { + "name": { + "type": "string" + } + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "nameSpace": { + "type": "boolean" + }, + "default": { + "type": "boolean" + } + } + } + ] + } + } + ] + } + } + }, + "StringPattern": { + "type": "string", + "minLength": 1 + } + }, "type": "object", - "properties": {}, + "properties": { + "import": { + "anyOf": [ + { + "$ref": "#/definitions/StringPattern" + }, + { + "$ref": "#/definitions/ObjectPattern" + }, + { + "type": "array", + "minItems": 1, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/StringPattern" + }, + { + "$ref": "#/definitions/ObjectPattern" + } + ] + } + } + ] + }, + "wrapper": { + "type": "string" + }, + "additionalCode": { + "type": "string" + } + }, "additionalProperties": false } diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..e68dff1 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,118 @@ +function getImportProfile(params) { + const importEntry = + typeof params === 'string' + ? { moduleName: params, names: { name: params, default: true } } + : { ...params }; + + const result = { + /* + import-side-effect + import-default + name-space-import + named-imports + import-default , name-space-import + import-default , named-imports + */ + type: { + default: false, + sideEffect: false, + nameSpaceImport: false, + namedImports: false, + }, + moduleName: importEntry.moduleName, + importDefault: { + // name: "defaultExport" + }, + nameSpaceImport: { + // alias: "ns" + }, + namedImports: [ + // { + // name: "exportName", + // alias: "shortName" + // } + ], + }; + let { names } = importEntry; + + if (names === false) { + result.type.sideEffect = true; + return result; + } + + names = Array.isArray(names) + ? names + : typeof names === 'string' + ? [{ name: names, default: true }] + : [names]; + + names.forEach((entry) => { + const entryNormalized = + typeof entry === 'string' ? { name: entry, alias: entry } : entry; + + if (entryNormalized.default) { + result.type.default = true; + result.importDefault.name = entryNormalized.name; + return; + } + + if (entryNormalized.nameSpace) { + result.type.nameSpace = true; + result.nameSpaceImport.alias = entryNormalized.alias; + return; + } + + result.type.namedImports = true; + result.namedImports.push({ + name: entryNormalized.name, + alias: entryNormalized.alias, + }); + }); + + return result; +} + +function getImportString(importEntry) { + const importProfile = getImportProfile(importEntry); + + const result = ['import']; + + const quantityImportsType = Object.keys(importProfile.type).filter( + (key) => importProfile.type[key] + ).length; + + if (importProfile.type.sideEffect) { + result.push(`"${importProfile.moduleName}";`); + return result.join(' '); + } + + if (importProfile.type.default) { + result.push(importProfile.importDefault.name); + } + + if (quantityImportsType > 1) { + result.push(','); + } + + if (importProfile.type.namedImports) { + const namedImportString = importProfile.namedImports.map((entry) => { + if (entry.alias) { + return `${entry.name} as ${entry.alias}`; + } + + return entry.name; + }); + + result.push(`{ ${namedImportString.join(', ')} }`); + } + + if (importProfile.type.nameSpace) { + result.push(`* as ${importProfile.nameSpaceImport.alias}`); + } + + result.push(`from "${importProfile.moduleName}";`); + + return result.join(' '); +} + +export default getImportString; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 7496454..4abfe8d 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -1,14 +1,83 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`loader should set a variable: errors 1`] = `Array []`; +exports[`loader should require when import option is array: errors 1`] = `Array []`; -exports[`loader should set a variable: result 1`] = ` +exports[`loader should require when import option is array: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ -var someVariable = 1; -var anotherVariable = 2; -var someVariable = (someVariable || {}); -someVariable.someProperty = someVariable.someProperty || {}; -someVariable.someProperty.someSubProperty = 1; + +import lib_1 from \\"lib_1\\"; +import lib_2 from \\"lib_2\\"; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should require when import option is array: warnings 1`] = `Array []`; + +exports[`loader should require when import option is filePath: errors 1`] = `Array []`; + +exports[`loader should require when import option is filePath: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +import $ from \\"./lib_1\\"; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should require when import option is filePath: warnings 1`] = `Array []`; + +exports[`loader should require when import option is object: errors 1`] = `Array []`; + +exports[`loader should require when import option is object: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +import $ from \\"./lib_1\\"; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should require when import option is object: warnings 1`] = `Array []`; + +exports[`loader should require when import option is string: errors 1`] = `Array []`; + +exports[`loader should require when import option is string: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +import lib_1 from \\"lib_1\\"; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should require when import option is string: warnings 1`] = `Array []`; + +exports[`loader should require when import-default: errors 1`] = `Array []`; + +exports[`loader should require when import-default: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +import lib_1 from \\"lib_1\\"; +import lib_2 from \\"./lib_2.js\\"; +import defaultExport , { lib_3_method as method } from \\"./lib_3.js\\"; +import lib_4 , * as lib_4_all from \\"./lib_4\\"; var someCode = { number: 123, @@ -18,4 +87,75 @@ var someCode = { " `; -exports[`loader should set a variable: warnings 1`] = `Array []`; +exports[`loader should require when import-default: warnings 1`] = `Array []`; + +exports[`loader should require when import-side-effect: errors 1`] = `Array []`; + +exports[`loader should require when import-side-effect: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +import \\"./lib_1\\"; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should require when import-side-effect: warnings 1`] = `Array []`; + +exports[`loader should require when name-space-import: errors 1`] = `Array []`; + +exports[`loader should require when name-space-import: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +import * as lib_1_all from \\"./lib_1\\"; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should require when name-space-import: warnings 1`] = `Array []`; + +exports[`loader should require when named-imports: errors 1`] = `Array []`; + +exports[`loader should require when named-imports: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +import { lib1_method } from \\"./lib_1\\"; +import { lib2_method_1, lib2_method_2 as lib_2_method_2_short } from \\"./lib_2\\"; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should require when named-imports: warnings 1`] = `Array []`; + +exports[`loader should work wrapper and additionalCode option: errors 1`] = `Array []`; + +exports[`loader should work wrapper and additionalCode option: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + + +(function() { +var someVariable = 1; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +}.call(windows));" +`; + +exports[`loader should work wrapper and additionalCode option: warnings 1`] = `Array []`; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap new file mode 100644 index 0000000..2237dd6 --- /dev/null +++ b/test/__snapshots__/validate-options.test.js.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`validate options should throw an error on the "additionalCode" option with "/test/" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.additionalCode should be a string." +`; + +exports[`validate options should throw an error on the "additionalCode" option with "[""]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.additionalCode should be a string." +`; + +exports[`validate options should throw an error on the "additionalCode" option with "[]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.additionalCode should be a string." +`; + +exports[`validate options should throw an error on the "additionalCode" option with "{}" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.additionalCode should be a string." +`; + +exports[`validate options should throw an error on the "additionalCode" option with "false" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.additionalCode should be a string." +`; + +exports[`validate options should throw an error on the "additionalCode" option with "true" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.additionalCode should be a string." +`; + +exports[`validate options should throw an error on the "import" option with "" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.import should be an non-empty string." +`; + +exports[`validate options should throw an error on the "import" option with "/test/" value 1`] = `"Cannot read property 'default' of undefined"`; + +exports[`validate options should throw an error on the "import" option with "[""]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.import[0] should be an non-empty string." +`; + +exports[`validate options should throw an error on the "import" option with "[]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.import should be an non-empty array." +`; + +exports[`validate options should throw an error on the "import" option with "{}" value 1`] = `"Cannot read property 'default' of undefined"`; + +exports[`validate options should throw an error on the "import" option with "false" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.import should be one of these: + non-empty string | object { moduleName?, names? } | [non-empty string | object { moduleName?, names? }, ...] (should not have fewer than 1 item) + Details: + * options.import should be a non-empty string. + * options.import should be an object: + object { moduleName?, names? } + * options.import should be an array: + [non-empty string | object { moduleName?, names? }, ...] (should not have fewer than 1 item)" +`; + +exports[`validate options should throw an error on the "import" option with "true" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.import should be one of these: + non-empty string | object { moduleName?, names? } | [non-empty string | object { moduleName?, names? }, ...] (should not have fewer than 1 item) + Details: + * options.import should be a non-empty string. + * options.import should be an object: + object { moduleName?, names? } + * options.import should be an array: + [non-empty string | object { moduleName?, names? }, ...] (should not have fewer than 1 item)" +`; + +exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { import?, wrapper?, additionalCode? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { import?, wrapper?, additionalCode? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { import?, wrapper?, additionalCode? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { import?, wrapper?, additionalCode? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { import?, wrapper?, additionalCode? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { import?, wrapper?, additionalCode? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { import?, wrapper?, additionalCode? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { import?, wrapper?, additionalCode? }" +`; + +exports[`validate options should throw an error on the "wrapper" option with "/test/" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.wrapper should be a string." +`; + +exports[`validate options should throw an error on the "wrapper" option with "[""]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.wrapper should be a string." +`; + +exports[`validate options should throw an error on the "wrapper" option with "[]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.wrapper should be a string." +`; + +exports[`validate options should throw an error on the "wrapper" option with "{}" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.wrapper should be a string." +`; + +exports[`validate options should throw an error on the "wrapper" option with "false" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.wrapper should be a string." +`; + +exports[`validate options should throw an error on the "wrapper" option with "true" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.wrapper should be a string." +`; diff --git a/test/cjs.test.js b/test/cjs.test.js new file mode 100644 index 0000000..8aba6ba --- /dev/null +++ b/test/cjs.test.js @@ -0,0 +1,8 @@ +import src from '../src'; +import cjs from '../src/cjs'; + +describe('cjs', () => { + it('should exported', () => { + expect(cjs).toEqual(src); + }); +}); diff --git a/test/fixtures/lib_1.js b/test/fixtures/lib_1.js new file mode 100644 index 0000000..b77dac7 --- /dev/null +++ b/test/fixtures/lib_1.js @@ -0,0 +1 @@ +export default function lib() {} diff --git a/test/fixtures/lib_2.js b/test/fixtures/lib_2.js new file mode 100644 index 0000000..b77dac7 --- /dev/null +++ b/test/fixtures/lib_2.js @@ -0,0 +1 @@ +export default function lib() {} diff --git a/test/fixtures/lib_3.js b/test/fixtures/lib_3.js new file mode 100644 index 0000000..b77dac7 --- /dev/null +++ b/test/fixtures/lib_3.js @@ -0,0 +1 @@ +export default function lib() {} diff --git a/test/fixtures/lib_4.js b/test/fixtures/lib_4.js new file mode 100644 index 0000000..b77dac7 --- /dev/null +++ b/test/fixtures/lib_4.js @@ -0,0 +1 @@ +export default function lib() {} diff --git a/test/helpers/getCompiler.js b/test/helpers/getCompiler.js index 0ba5363..f0752bb 100644 --- a/test/helpers/getCompiler.js +++ b/test/helpers/getCompiler.js @@ -30,6 +30,14 @@ export default (fixture, loaderOptions = {}, config = {}) => { ], }, plugins: [], + resolve: { + alias: { + lib_1: path.resolve(__dirname, '../', 'fixtures', 'lib_1'), + lib_2: path.resolve(__dirname, '../', 'fixtures', 'lib_2'), + lib_3: path.resolve(__dirname, '../', 'fixtures', 'lib_3'), + lib_4: path.resolve(__dirname, '../', 'fixtures', 'lib_4'), + }, + }, ...config, }; diff --git a/test/loader.test.js b/test/loader.test.js index 25ffa69..2cd8e8a 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -7,11 +7,9 @@ import { } from './helpers'; describe('loader', () => { - it.only('should set a variable', async () => { + it('should require when import option is string', async () => { const compiler = getCompiler('some-library.js', { - someVariable: '>1', - anotherVariable: '>2', - 'someVariable.someProperty.someSubProperty': '>1', + import: 'lib_1', }); const stats = await compile(compiler); @@ -21,4 +19,184 @@ describe('loader', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + + it('should require when import option is object', async () => { + const compiler = getCompiler('some-library.js', { + import: { + moduleName: './lib_1', + names: '$', + }, + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should require when import option is filePath', async () => { + const compiler = getCompiler('some-library.js', { + import: { + moduleName: './lib_1', + names: '$', + }, + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should require when import option is array', async () => { + const compiler = getCompiler('some-library.js', { + import: ['lib_1', 'lib_2'], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should require when import-default', async () => { + const compiler = getCompiler('some-library.js', { + import: [ + 'lib_1', + { + moduleName: './lib_2.js', + names: { + name: 'lib_2', + default: true, + }, + }, + { + moduleName: './lib_3.js', + names: [ + { + name: 'defaultExport', + default: true, + }, + { + name: 'lib_3_method', + alias: 'method', + }, + ], + }, + { + moduleName: './lib_4', + names: [ + { + name: 'lib_4', + default: true, + }, + { + alias: 'lib_4_all', + nameSpace: true, + }, + ], + }, + ], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should require when name-space-import', async () => { + const compiler = getCompiler('some-library.js', { + import: [ + { + moduleName: './lib_1', + names: [ + { + alias: 'lib_1_all', + nameSpace: true, + }, + ], + }, + ], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should require when named-imports', async () => { + const compiler = getCompiler('some-library.js', { + import: [ + { + moduleName: './lib_1', + names: [ + { + name: 'lib1_method', + }, + ], + }, + { + moduleName: './lib_2', + names: [ + { + name: 'lib2_method_1', + }, + { + name: 'lib2_method_2', + alias: 'lib_2_method_2_short', + }, + ], + }, + ], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should require when import-side-effect', async () => { + const compiler = getCompiler('some-library.js', { + import: { + moduleName: './lib_1', + names: false, + }, + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should work wrapper and additionalCode option', async () => { + const compiler = getCompiler('some-library.js', { + wrapper: 'windows', + additionalCode: 'var someVariable = 1;', + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js new file mode 100644 index 0000000..207fb1a --- /dev/null +++ b/test/validate-options.test.js @@ -0,0 +1,123 @@ +import { getCompiler, compile } from './helpers'; + +describe('validate options', () => { + const tests = { + import: { + success: [ + 'lib_1', + 'globalObject1.foo', + ['globalObject1'], + ['globalObject1.foo'], + { + names: false, + }, + { + names: [ + 'lib', + { + name: 'lib', + }, + { + name: 'lib', + alias: 'lib', + }, + { + name: 'lib', + default: true, + }, + { + alias: 'lib', + nameSpace: true, + }, + [ + 'lib', + { + name: 'lib', + }, + { + name: 'lib', + alias: 'lib', + }, + { + name: 'lib', + default: true, + }, + { + alias: 'lib', + nameSpace: true, + }, + ], + ], + }, + ], + failure: [false, true, /test/, '', [], [''], {}], + }, + wrapper: { + success: ['windows'], + failure: [false, true, /test/, [], [''], {}], + }, + additionalCode: { + success: ['var x = 2;'], + failure: [false, true, /test/, [], [''], {}], + }, + unknown: { + success: [], + failure: [1, true, false, 'test', /test/, [], {}, { foo: 'bar' }], + }, + }; + + function stringifyValue(value) { + if ( + Array.isArray(value) || + (value && typeof value === 'object' && value.constructor === Object) + ) { + return JSON.stringify(value); + } + + return value; + } + + async function createTestCase(key, value, type) { + it(`should ${ + type === 'success' ? 'successfully validate' : 'throw an error on' + } the "${key}" option with "${stringifyValue(value)}" value`, async () => { + const compiler = getCompiler('some-library.js', { + [key]: value, + }); + + let stats; + + try { + stats = await compile(compiler); + } finally { + if (type === 'success') { + const validationErrors = []; + + stats.compilation.errors.forEach((error) => { + if (error.message.indexOf('ValidationError') !== -1) { + validationErrors.push(error); + } + }); + expect(validationErrors.length).toBe(0); + } else if (type === 'failure') { + const { + compilation: { errors }, + } = stats; + + expect(errors).toHaveLength(1); + expect(() => { + throw new Error(errors[0].error.message); + }).toThrowErrorMatchingSnapshot(); + } + } + }); + } + + for (const [key, values] of Object.entries(tests)) { + for (const type of Object.keys(values)) { + for (const value of values[type]) { + createTestCase(key, value, type); + } + } + } +}); From dc34853a3fdb5ad1d1d48836843fef0862fbd270 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Fri, 5 Jun 2020 20:35:34 +0300 Subject: [PATCH 02/15] refactor: code --- src/index.js | 19 +++-- src/options.json | 56 +++++---------- src/utils.js | 72 ++++++++++--------- test/__snapshots__/loader.test.js.snap | 47 ++++++++++-- .../validate-options.test.js.snap | 16 ++--- test/helpers/getCompiler.js | 2 +- test/loader.test.js | 68 +++++++++++++----- test/validate-options.test.js | 29 ++------ 8 files changed, 173 insertions(+), 136 deletions(-) diff --git a/src/index.js b/src/index.js index 8636a06..39ef903 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,7 @@ export default function loader(content, sourceMap) { baseDataPath: 'options', }); + const callback = this.async(); const moduleImport = options.import; const { wrapper, additionalCode } = options; @@ -34,9 +35,16 @@ export default function loader(content, sourceMap) { if (moduleImport) { moduleImports = Array.isArray(moduleImport) ? moduleImport : [moduleImport]; - moduleImports.forEach((importEntry) => { - imports.push(getImportString(importEntry)); - }); + try { + moduleImports.forEach((importEntry) => { + imports.push(getImportString(importEntry)); + }); + } catch (error) { + this.emitError(error); + + callback(null, content, sourceMap); + return; + } } if (wrapper) { @@ -52,15 +60,12 @@ export default function loader(content, sourceMap) { const postfix = postfixes.join('\n'); const importString = imports.join('\n'); - const callback = this.async(); - if (sourceMap) { const node = SourceNode.fromStringWithSourceMap( content, new SourceMapConsumer(sourceMap) ); - node.prepend(prefix); - node.prepend(HEADER); + node.prepend(`${HEADER}\\n${importString}\\n${prefix}`); node.add(postfix); const result = node.toStringWithSourceMap({ file: getCurrentRequest(this), diff --git a/src/options.json b/src/options.json index f002791..99eeefa 100644 --- a/src/options.json +++ b/src/options.json @@ -1,5 +1,19 @@ { "definitions": { + "NamesObjectPattern": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "nameType": { + "enum": ["namespace", "default"] + } + } + }, "ObjectPattern": { "type": "object", "additionalProperties": false, @@ -8,13 +22,10 @@ "anyOf": [ { "type": "string" - }, - { - "type": "boolean" } ] }, - "names": { + "list": { "anyOf": [ { "type": "boolean" @@ -23,21 +34,7 @@ "type": "string" }, { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "alias": { - "type": "string" - }, - "nameSpace": { - "type": "boolean" - }, - "default": { - "type": "boolean" - } - } + "$ref": "#/definitions/NamesObjectPattern" }, { "type": "array", @@ -45,26 +42,7 @@ "items": { "anyOf": [ { - "name": { - "type": "string" - } - }, - { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "alias": { - "type": "string" - }, - "nameSpace": { - "type": "boolean" - }, - "default": { - "type": "boolean" - } - } + "$ref": "#/definitions/NamesObjectPattern" } ] } diff --git a/src/utils.js b/src/utils.js index e68dff1..db01f83 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,7 @@ function getImportProfile(params) { const importEntry = typeof params === 'string' - ? { moduleName: params, names: { name: params, default: true } } + ? { moduleName: params, list: { name: params, nameType: 'default' } } : { ...params }; const result = { @@ -16,15 +16,15 @@ function getImportProfile(params) { type: { default: false, sideEffect: false, - nameSpaceImport: false, + namespaceImport: false, namedImports: false, }, moduleName: importEntry.moduleName, importDefault: { // name: "defaultExport" }, - nameSpaceImport: { - // alias: "ns" + namespaceImport: { + // name: "ns" }, namedImports: [ // { @@ -33,65 +33,71 @@ function getImportProfile(params) { // } ], }; - let { names } = importEntry; + let { list } = importEntry; - if (names === false) { + if (list === false) { result.type.sideEffect = true; return result; } - names = Array.isArray(names) - ? names - : typeof names === 'string' - ? [{ name: names, default: true }] - : [names]; + list = Array.isArray(list) + ? list + : typeof list === 'string' + ? [{ name: list, nameType: 'default' }] + : [list]; - names.forEach((entry) => { + list.forEach((entry) => { const entryNormalized = typeof entry === 'string' ? { name: entry, alias: entry } : entry; - if (entryNormalized.default) { + if (entryNormalized.nameType === 'default' && entryNormalized.name) { result.type.default = true; result.importDefault.name = entryNormalized.name; return; } - if (entryNormalized.nameSpace) { - result.type.nameSpace = true; - result.nameSpaceImport.alias = entryNormalized.alias; + if (entryNormalized.nameType === 'namespace' && entryNormalized.name) { + result.type.namespace = true; + result.namespaceImport.name = entryNormalized.name; return; } - result.type.namedImports = true; - result.namedImports.push({ - name: entryNormalized.name, - alias: entryNormalized.alias, - }); + if (entryNormalized.name) { + result.type.namedImports = true; + result.namedImports.push({ + name: entryNormalized.name, + alias: entryNormalized.alias, + }); + } }); return result; } -function getImportString(importEntry) { +function renderImport(importEntry) { const importProfile = getImportProfile(importEntry); - const result = ['import']; + let result = 'import'; const quantityImportsType = Object.keys(importProfile.type).filter( (key) => importProfile.type[key] ).length; + if (quantityImportsType === 0) { + throw new Error('No valid data for import'); + } + if (importProfile.type.sideEffect) { - result.push(`"${importProfile.moduleName}";`); - return result.join(' '); + result += ` "${importProfile.moduleName}";`; + return result; } if (importProfile.type.default) { - result.push(importProfile.importDefault.name); + result += ` ${importProfile.importDefault.name}`; } if (quantityImportsType > 1) { - result.push(','); + result += ', '; } if (importProfile.type.namedImports) { @@ -103,16 +109,16 @@ function getImportString(importEntry) { return entry.name; }); - result.push(`{ ${namedImportString.join(', ')} }`); + result += ` { ${namedImportString.join(', ')} }`; } - if (importProfile.type.nameSpace) { - result.push(`* as ${importProfile.nameSpaceImport.alias}`); + if (importProfile.type.namespace) { + result += ` * as ${importProfile.namespaceImport.name}`; } - result.push(`from "${importProfile.moduleName}";`); + result += ` from "${importProfile.moduleName}";`; - return result.join(' '); + return result; } -export default getImportString; +export default renderImport; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 4abfe8d..6bbcf49 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -76,8 +76,8 @@ exports[`loader should require when import-default: result 1`] = ` import lib_1 from \\"lib_1\\"; import lib_2 from \\"./lib_2.js\\"; -import defaultExport , { lib_3_method as method } from \\"./lib_3.js\\"; -import lib_4 , * as lib_4_all from \\"./lib_4\\"; +import defaultExport, { lib_3_method as method } from \\"./lib_3.js\\"; +import lib_4, * as lib_4_all from \\"./lib_4\\"; var someCode = { number: 123, @@ -141,12 +141,30 @@ var someCode = { exports[`loader should require when named-imports: warnings 1`] = `Array []`; -exports[`loader should work wrapper and additionalCode option: errors 1`] = `Array []`; +exports[`loader should work additionalCode option: errors 1`] = `Array []`; -exports[`loader should work wrapper and additionalCode option: result 1`] = ` +exports[`loader should work additionalCode option: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ +var someVariable = 1; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should work additionalCode option: warnings 1`] = `Array []`; + +exports[`loader should work import, wrapper and additionalCode option: errors 1`] = `Array []`; + +exports[`loader should work import, wrapper and additionalCode option: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +import \\"./lib_1\\"; (function() { var someVariable = 1; @@ -155,7 +173,24 @@ var someCode = { object: { existingSubProperty: 123 } }; -}.call(windows));" +}.call(window));" +`; + +exports[`loader should work import, wrapper and additionalCode option: warnings 1`] = `Array []`; + +exports[`loader should work wrapper: errors 1`] = `Array []`; + +exports[`loader should work wrapper: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + + +(function() { +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +}.call(window));" `; -exports[`loader should work wrapper and additionalCode option: warnings 1`] = `Array []`; +exports[`loader should work wrapper: warnings 1`] = `Array []`; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 2237dd6..e37aa17 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -35,7 +35,7 @@ exports[`validate options should throw an error on the "import" option with "" v - options.import should be an non-empty string." `; -exports[`validate options should throw an error on the "import" option with "/test/" value 1`] = `"Cannot read property 'default' of undefined"`; +exports[`validate options should throw an error on the "import" option with "/test/" value 1`] = `"Cannot read property 'nameType' of undefined"`; exports[`validate options should throw an error on the "import" option with "[""]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. @@ -47,30 +47,30 @@ exports[`validate options should throw an error on the "import" option with "[]" - options.import should be an non-empty array." `; -exports[`validate options should throw an error on the "import" option with "{}" value 1`] = `"Cannot read property 'default' of undefined"`; +exports[`validate options should throw an error on the "import" option with "{}" value 1`] = `"Cannot read property 'nameType' of undefined"`; exports[`validate options should throw an error on the "import" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options.import should be one of these: - non-empty string | object { moduleName?, names? } | [non-empty string | object { moduleName?, names? }, ...] (should not have fewer than 1 item) + non-empty string | object { moduleName?, list? } | [non-empty string | object { moduleName?, list? }, ...] (should not have fewer than 1 item) Details: * options.import should be a non-empty string. * options.import should be an object: - object { moduleName?, names? } + object { moduleName?, list? } * options.import should be an array: - [non-empty string | object { moduleName?, names? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { moduleName?, list? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "import" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options.import should be one of these: - non-empty string | object { moduleName?, names? } | [non-empty string | object { moduleName?, names? }, ...] (should not have fewer than 1 item) + non-empty string | object { moduleName?, list? } | [non-empty string | object { moduleName?, list? }, ...] (should not have fewer than 1 item) Details: * options.import should be a non-empty string. * options.import should be an object: - object { moduleName?, names? } + object { moduleName?, list? } * options.import should be an array: - [non-empty string | object { moduleName?, names? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { moduleName?, list? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` diff --git a/test/helpers/getCompiler.js b/test/helpers/getCompiler.js index f0752bb..60810fa 100644 --- a/test/helpers/getCompiler.js +++ b/test/helpers/getCompiler.js @@ -19,7 +19,7 @@ export default (fixture, loaderOptions = {}, config = {}) => { module: { rules: [ { - test: /.js/i, + test: path.resolve(__dirname, '../fixtures', fixture), rules: [ { loader: path.resolve(__dirname, '../../src'), diff --git a/test/loader.test.js b/test/loader.test.js index 2cd8e8a..75a7823 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -24,7 +24,7 @@ describe('loader', () => { const compiler = getCompiler('some-library.js', { import: { moduleName: './lib_1', - names: '$', + list: '$', }, }); const stats = await compile(compiler); @@ -40,7 +40,7 @@ describe('loader', () => { const compiler = getCompiler('some-library.js', { import: { moduleName: './lib_1', - names: '$', + list: '$', }, }); const stats = await compile(compiler); @@ -71,17 +71,17 @@ describe('loader', () => { 'lib_1', { moduleName: './lib_2.js', - names: { + list: { name: 'lib_2', - default: true, + nameType: 'default', }, }, { moduleName: './lib_3.js', - names: [ + list: [ { name: 'defaultExport', - default: true, + nameType: 'default', }, { name: 'lib_3_method', @@ -91,14 +91,14 @@ describe('loader', () => { }, { moduleName: './lib_4', - names: [ + list: [ { name: 'lib_4', - default: true, + nameType: 'default', }, { - alias: 'lib_4_all', - nameSpace: true, + name: 'lib_4_all', + nameType: 'namespace', }, ], }, @@ -118,10 +118,10 @@ describe('loader', () => { import: [ { moduleName: './lib_1', - names: [ + list: [ { - alias: 'lib_1_all', - nameSpace: true, + name: 'lib_1_all', + nameType: 'namespace', }, ], }, @@ -141,7 +141,7 @@ describe('loader', () => { import: [ { moduleName: './lib_1', - names: [ + list: [ { name: 'lib1_method', }, @@ -149,7 +149,7 @@ describe('loader', () => { }, { moduleName: './lib_2', - names: [ + list: [ { name: 'lib2_method_1', }, @@ -174,7 +174,7 @@ describe('loader', () => { const compiler = getCompiler('some-library.js', { import: { moduleName: './lib_1', - names: false, + list: false, }, }); const stats = await compile(compiler); @@ -186,9 +186,21 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); - it('should work wrapper and additionalCode option', async () => { + it('should work wrapper', async () => { + const compiler = getCompiler('some-library.js', { + wrapper: 'window', + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should work additionalCode option', async () => { const compiler = getCompiler('some-library.js', { - wrapper: 'windows', additionalCode: 'var someVariable = 1;', }); const stats = await compile(compiler); @@ -196,7 +208,25 @@ describe('loader', () => { expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( 'result' ); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should work import, wrapper and additionalCode option', async () => { + const compiler = getCompiler('some-library.js', { + import: { + moduleName: './lib_1', + list: false, + }, + wrapper: 'window', + additionalCode: 'var someVariable = 1;', + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 207fb1a..e782b8d 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -9,11 +9,12 @@ describe('validate options', () => { ['globalObject1'], ['globalObject1.foo'], { - names: false, + moduleName: 'jQuery', + list: false, }, { - names: [ - 'lib', + moduleName: 'jQuery', + list: [ { name: 'lib', }, @@ -23,30 +24,12 @@ describe('validate options', () => { }, { name: 'lib', - default: true, + nameType: 'default', }, { alias: 'lib', - nameSpace: true, + nameType: 'namespace', }, - [ - 'lib', - { - name: 'lib', - }, - { - name: 'lib', - alias: 'lib', - }, - { - name: 'lib', - default: true, - }, - { - alias: 'lib', - nameSpace: true, - }, - ], ], }, ], From 0f170b9187a5eb5d64cccb842ddafb1474925686 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Sun, 7 Jun 2020 15:09:10 +0300 Subject: [PATCH 03/15] refactor: code --- src/options.json | 3 + src/utils.js | 59 ++++++++++++------- test/__snapshots__/loader.test.js.snap | 51 ++++++++++++++++ .../validate-options.test.js.snap | 18 ++++-- test/loader.test.js | 56 ++++++++++++++++++ test/validate-options.test.js | 19 +++++- 6 files changed, 176 insertions(+), 30 deletions(-) diff --git a/src/options.json b/src/options.json index 99eeefa..7cafaa5 100644 --- a/src/options.json +++ b/src/options.json @@ -18,6 +18,9 @@ "type": "object", "additionalProperties": false, "properties": { + "type": { + "enum": ["module", "commonjs"] + }, "moduleName": { "anyOf": [ { diff --git a/src/utils.js b/src/utils.js index db01f83..8fd9406 100644 --- a/src/utils.js +++ b/src/utils.js @@ -13,6 +13,8 @@ function getImportProfile(params) { import-default , name-space-import import-default , named-imports */ + quantityImportsType: 0, + moduleType: params.type || 'module', type: { default: false, sideEffect: false, @@ -47,46 +49,41 @@ function getImportProfile(params) { : [list]; list.forEach((entry) => { - const entryNormalized = - typeof entry === 'string' ? { name: entry, alias: entry } : entry; - - if (entryNormalized.nameType === 'default' && entryNormalized.name) { + if (entry.nameType === 'default' && entry.name) { result.type.default = true; - result.importDefault.name = entryNormalized.name; + result.importDefault.name = entry.name; return; } - if (entryNormalized.nameType === 'namespace' && entryNormalized.name) { + if (entry.nameType === 'namespace' && entry.name) { result.type.namespace = true; - result.namespaceImport.name = entryNormalized.name; + result.namespaceImport.name = entry.name; return; } - if (entryNormalized.name) { + if (entry.name) { result.type.namedImports = true; result.namedImports.push({ - name: entryNormalized.name, - alias: entryNormalized.alias, + name: entry.name, + alias: entry.alias, }); } }); + result.quantityImportsType = Object.keys(result.type).filter( + (key) => result.type[key] + ).length; + + if (result.quantityImportsType === 0) { + throw new Error('Not enough data to import'); + } + return result; } -function renderImport(importEntry) { - const importProfile = getImportProfile(importEntry); - +function renderImportModule(importProfile) { let result = 'import'; - const quantityImportsType = Object.keys(importProfile.type).filter( - (key) => importProfile.type[key] - ).length; - - if (quantityImportsType === 0) { - throw new Error('No valid data for import'); - } - if (importProfile.type.sideEffect) { result += ` "${importProfile.moduleName}";`; return result; @@ -96,7 +93,7 @@ function renderImport(importEntry) { result += ` ${importProfile.importDefault.name}`; } - if (quantityImportsType > 1) { + if (importProfile.quantityImportsType > 1) { result += ', '; } @@ -121,4 +118,22 @@ function renderImport(importEntry) { return result; } +function renderImportCommonjs(importProfile) { + if (!importProfile.type.default) { + throw new Error('Not enough data to commonjs import'); + } + + return `var ${importProfile.importDefault.name} = require("${importProfile.moduleName}");`; +} + +function renderImport(importEntry) { + const importProfile = getImportProfile(importEntry); + + if (importProfile.moduleType === 'module') { + return renderImportModule(importProfile); + } + + return renderImportCommonjs(importProfile); +} + export default renderImport; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 6bbcf49..b1c5e5a 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -1,5 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`loader should emit error when invalid arguments for import commonjs: errors 1`] = ` +Array [ + "ModuleError: Module Error (from \`replaced original path\`): +Not enough data to commonjs import", +] +`; + +exports[`loader should emit error when invalid arguments for import commonjs: result 1`] = ` +"var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; + +exports[`loader should emit error when invalid arguments for import commonjs: warnings 1`] = `Array []`; + +exports[`loader should emit error when invalid arguments for import: errors 1`] = ` +Array [ + "ModuleError: Module Error (from \`replaced original path\`): +Not enough data to import", +] +`; + +exports[`loader should emit error when invalid arguments for import: result 1`] = ` +"var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; + +exports[`loader should emit error when invalid arguments for import: warnings 1`] = `Array []`; + exports[`loader should require when import option is array: errors 1`] = `Array []`; exports[`loader should require when import option is array: result 1`] = ` @@ -178,6 +212,23 @@ var someCode = { exports[`loader should work import, wrapper and additionalCode option: warnings 1`] = `Array []`; +exports[`loader should work require: errors 1`] = `Array []`; + +exports[`loader should work require: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +var $ = require(\\"./lib_1\\"); + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should work require: warnings 1`] = `Array []`; + exports[`loader should work wrapper: errors 1`] = `Array []`; exports[`loader should work wrapper: result 1`] = ` diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index e37aa17..95ff17d 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -47,30 +47,36 @@ exports[`validate options should throw an error on the "import" option with "[]" - options.import should be an non-empty array." `; +exports[`validate options should throw an error on the "import" option with "{"type":"string","moduleName":"jQuery","list":false}" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.import.type should be one of these: + \\"module\\" | \\"commonjs\\"" +`; + exports[`validate options should throw an error on the "import" option with "{}" value 1`] = `"Cannot read property 'nameType' of undefined"`; exports[`validate options should throw an error on the "import" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options.import should be one of these: - non-empty string | object { moduleName?, list? } | [non-empty string | object { moduleName?, list? }, ...] (should not have fewer than 1 item) + non-empty string | object { type?, moduleName?, list? } | [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item) Details: * options.import should be a non-empty string. * options.import should be an object: - object { moduleName?, list? } + object { type?, moduleName?, list? } * options.import should be an array: - [non-empty string | object { moduleName?, list? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "import" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options.import should be one of these: - non-empty string | object { moduleName?, list? } | [non-empty string | object { moduleName?, list? }, ...] (should not have fewer than 1 item) + non-empty string | object { type?, moduleName?, list? } | [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item) Details: * options.import should be a non-empty string. * options.import should be an object: - object { moduleName?, list? } + object { type?, moduleName?, list? } * options.import should be an array: - [non-empty string | object { moduleName?, list? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` diff --git a/test/loader.test.js b/test/loader.test.js index 75a7823..07a345a 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -229,4 +229,60 @@ describe('loader', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + + it('should work require', async () => { + const compiler = getCompiler('some-library.js', { + import: { + type: 'commonjs', + moduleName: './lib_1', + list: '$', + }, + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should emit error when invalid arguments for import commonjs', async () => { + const compiler = getCompiler('some-library.js', { + import: { + type: 'commonjs', + moduleName: './lib_1', + list: false, + }, + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should emit error when invalid arguments for import', async () => { + const compiler = getCompiler('some-library.js', { + import: [ + { + moduleName: './lib_2', + list: [ + { + alias: 'lib_2_method_2_short', + }, + ], + }, + ], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index e782b8d..2f01bd6 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -9,10 +9,12 @@ describe('validate options', () => { ['globalObject1'], ['globalObject1.foo'], { + type: 'commonjs', moduleName: 'jQuery', - list: false, + list: '$', }, { + type: 'module', moduleName: 'jQuery', list: [ { @@ -33,7 +35,20 @@ describe('validate options', () => { ], }, ], - failure: [false, true, /test/, '', [], [''], {}], + failure: [ + false, + true, + /test/, + '', + [], + [''], + {}, + { + type: 'string', + moduleName: 'jQuery', + list: false, + }, + ], }, wrapper: { success: ['windows'], From 39eaa9b5640d5aa004cda3cdd78fcf351a531659 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Sun, 7 Jun 2020 15:26:10 +0300 Subject: [PATCH 04/15] refactor: code --- src/utils.js | 20 +++++++-- test/__snapshots__/loader.test.js.snap | 35 +++++++++++++++ test/loader.test.js | 59 ++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/utils.js b/src/utils.js index 8fd9406..5b2f51a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -119,11 +119,25 @@ function renderImportModule(importProfile) { } function renderImportCommonjs(importProfile) { - if (!importProfile.type.default) { - throw new Error('Not enough data to commonjs import'); + if (importProfile.type.default) { + return `var ${importProfile.importDefault.name} = require("${importProfile.moduleName}");`; + } + + if (importProfile.type.namedImports) { + const namedImportString = importProfile.namedImports.map((entry) => { + if (entry.alias) { + return `${entry.name}: ${entry.alias}`; + } + + return entry.name; + }); + + return `var { ${namedImportString.join(', ')} } = require("${ + importProfile.moduleName + }");`; } - return `var ${importProfile.importDefault.name} = require("${importProfile.moduleName}");`; + throw new Error('Not enough data to commonjs import'); } function renderImport(importEntry) { diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index b1c5e5a..ae83d3b 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -193,6 +193,41 @@ var someCode = { exports[`loader should work additionalCode option: warnings 1`] = `Array []`; +exports[`loader should work destructuring require: errors 1`] = `Array []`; + +exports[`loader should work destructuring require: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +var { lib2_method_1, lib2_method_2: lib_2_method_2_short } = require(\\"./lib_2\\"); + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should work destructuring require: warnings 1`] = `Array []`; + +exports[`loader should work few require: errors 1`] = `Array []`; + +exports[`loader should work few require: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +var $ = require(\\"./lib_1\\"); +var { lib2_method_1, lib2_method_2: lib_2_method_2_short } = require(\\"./lib_2\\"); + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +" +`; + +exports[`loader should work few require: warnings 1`] = `Array []`; + exports[`loader should work import, wrapper and additionalCode option: errors 1`] = `Array []`; exports[`loader should work import, wrapper and additionalCode option: result 1`] = ` diff --git a/test/loader.test.js b/test/loader.test.js index 07a345a..dc92355 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -247,6 +247,65 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + it('should work destructuring require', async () => { + const compiler = getCompiler('some-library.js', { + import: [ + { + type: 'commonjs', + moduleName: './lib_2', + list: [ + { + name: 'lib2_method_1', + }, + { + name: 'lib2_method_2', + alias: 'lib_2_method_2_short', + }, + ], + }, + ], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should work few require', async () => { + const compiler = getCompiler('some-library.js', { + import: [ + { + type: 'commonjs', + moduleName: './lib_1', + list: '$', + }, + { + type: 'commonjs', + moduleName: './lib_2', + list: [ + { + name: 'lib2_method_1', + }, + { + name: 'lib2_method_2', + alias: 'lib_2_method_2_short', + }, + ], + }, + ], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + it('should emit error when invalid arguments for import commonjs', async () => { const compiler = getCompiler('some-library.js', { import: { From b89765e1b335d1962c53976f63640139927e2e12 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Sun, 7 Jun 2020 16:28:59 +0300 Subject: [PATCH 05/15] refactor: code --- src/index.js | 7 ++- src/options.json | 7 +++ test/__snapshots__/loader.test.js.snap | 36 +++++++++++++++ .../validate-options.test.js.snap | 46 +++++++++++++++---- test/loader.test.js | 27 +++++++++++ test/validate-options.test.js | 4 ++ 6 files changed, 118 insertions(+), 9 deletions(-) diff --git a/src/index.js b/src/index.js index 39ef903..f843cbc 100644 --- a/src/index.js +++ b/src/index.js @@ -23,7 +23,7 @@ export default function loader(content, sourceMap) { const callback = this.async(); const moduleImport = options.import; - const { wrapper, additionalCode } = options; + const { wrapper, additionalCode, IIFE } = options; const HEADER = '/*** IMPORTS FROM imports-loader ***/\n'; const prefixes = []; @@ -47,6 +47,11 @@ export default function loader(content, sourceMap) { } } + if (IIFE) { + prefixes.push(`(function() {`); + postfixes.unshift(`}(${IIFE}));`); + } + if (wrapper) { prefixes.push(`(function() {`); postfixes.unshift(`}.call(${wrapper}));`); diff --git a/src/options.json b/src/options.json index 7cafaa5..04dff5e 100644 --- a/src/options.json +++ b/src/options.json @@ -90,6 +90,13 @@ }, "additionalCode": { "type": "string" + }, + "IIFE": { + "anyOf": [ + { + "type": "string" + } + ] } }, "additionalProperties": false diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index ae83d3b..78876a9 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -175,6 +175,42 @@ var someCode = { exports[`loader should require when named-imports: warnings 1`] = `Array []`; +exports[`loader should work IIFE and additionalCode option: errors 1`] = `Array []`; + +exports[`loader should work IIFE and additionalCode option: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + + +(function() { +const [ window, document ] = [...arguments]; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +}(window, document));" +`; + +exports[`loader should work IIFE and additionalCode option: warnings 1`] = `Array []`; + +exports[`loader should work IIFE if string: errors 1`] = `Array []`; + +exports[`loader should work IIFE if string: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + + +(function() { +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +}(window));" +`; + +exports[`loader should work IIFE if string: warnings 1`] = `Array []`; + exports[`loader should work additionalCode option: errors 1`] = `Array []`; exports[`loader should work additionalCode option: result 1`] = ` diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 95ff17d..4cbca45 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -1,5 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`validate options should throw an error on the "IIFE" option with "/test/" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.IIFE should be a string." +`; + +exports[`validate options should throw an error on the "IIFE" option with "[""]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.IIFE should be a string." +`; + +exports[`validate options should throw an error on the "IIFE" option with "[]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.IIFE should be a string." +`; + +exports[`validate options should throw an error on the "IIFE" option with "{}" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.IIFE should be a string." +`; + +exports[`validate options should throw an error on the "IIFE" option with "false" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.IIFE should be a string." +`; + +exports[`validate options should throw an error on the "IIFE" option with "true" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.IIFE should be a string." +`; + exports[`validate options should throw an error on the "additionalCode" option with "/test/" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options.additionalCode should be a string." @@ -82,49 +112,49 @@ exports[`validate options should throw an error on the "import" option with "tru exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { import?, wrapper?, additionalCode?, IIFE? }" `; exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { import?, wrapper?, additionalCode?, IIFE? }" `; exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { import?, wrapper?, additionalCode?, IIFE? }" `; exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { import?, wrapper?, additionalCode?, IIFE? }" `; exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { import?, wrapper?, additionalCode?, IIFE? }" `; exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { import?, wrapper?, additionalCode?, IIFE? }" `; exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { import?, wrapper?, additionalCode?, IIFE? }" `; exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { import?, wrapper?, additionalCode?, IIFE? }" `; exports[`validate options should throw an error on the "wrapper" option with "/test/" value 1`] = ` diff --git a/test/loader.test.js b/test/loader.test.js index dc92355..4a4d50a 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -186,6 +186,33 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + it('should work IIFE if string', async () => { + const compiler = getCompiler('some-library.js', { + IIFE: 'window', + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should work IIFE and additionalCode option', async () => { + const compiler = getCompiler('some-library.js', { + IIFE: 'window, document', + additionalCode: 'const [ window, document ] = [...arguments];', + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + it('should work wrapper', async () => { const compiler = getCompiler('some-library.js', { wrapper: 'window', diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 2f01bd6..7b44d78 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -54,6 +54,10 @@ describe('validate options', () => { success: ['windows'], failure: [false, true, /test/, [], [''], {}], }, + IIFE: { + success: ['windows'], + failure: [false, true, /test/, [], [''], {}], + }, additionalCode: { success: ['var x = 2;'], failure: [false, true, /test/, [], [''], {}], From 6881c4ff8daefb88ae41d39a69e72b2a466832d5 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Sun, 7 Jun 2020 18:04:09 +0300 Subject: [PATCH 06/15] refactor: code --- src/index.js | 22 ++--- src/options.json | 27 ++++--- test/__snapshots__/loader.test.js.snap | 49 +++--------- .../validate-options.test.js.snap | 80 +++++-------------- test/loader.test.js | 28 +++---- test/validate-options.test.js | 8 +- 6 files changed, 69 insertions(+), 145 deletions(-) diff --git a/src/index.js b/src/index.js index f843cbc..8d76e4a 100644 --- a/src/index.js +++ b/src/index.js @@ -22,8 +22,6 @@ export default function loader(content, sourceMap) { }); const callback = this.async(); - const moduleImport = options.import; - const { wrapper, additionalCode, IIFE } = options; const HEADER = '/*** IMPORTS FROM imports-loader ***/\n'; const prefixes = []; @@ -32,6 +30,8 @@ export default function loader(content, sourceMap) { let moduleImports; + const moduleImport = options.import; + if (moduleImport) { moduleImports = Array.isArray(moduleImport) ? moduleImport : [moduleImport]; @@ -40,23 +40,25 @@ export default function loader(content, sourceMap) { imports.push(getImportString(importEntry)); }); } catch (error) { - this.emitError(error); - - callback(null, content, sourceMap); + callback(error, content, sourceMap); return; } } - if (IIFE) { + const { wrapper } = options; + + if (wrapper && wrapper.IIFE) { prefixes.push(`(function() {`); - postfixes.unshift(`}(${IIFE}));`); + postfixes.unshift(`}(${wrapper.IIFE}));`); } - if (wrapper) { + if (wrapper && wrapper.call) { prefixes.push(`(function() {`); - postfixes.unshift(`}.call(${wrapper}));`); + postfixes.unshift(`}.call(${wrapper.call}));`); } + const { additionalCode } = options; + if (additionalCode) { prefixes.push(`${additionalCode}\n`); } @@ -70,7 +72,7 @@ export default function loader(content, sourceMap) { content, new SourceMapConsumer(sourceMap) ); - node.prepend(`${HEADER}\\n${importString}\\n${prefix}`); + node.prepend(`${HEADER}\n${importString}\n${prefix}`); node.add(postfix); const result = node.toStringWithSourceMap({ file: getCurrentRequest(this), diff --git a/src/options.json b/src/options.json index 04dff5e..be10fb3 100644 --- a/src/options.json +++ b/src/options.json @@ -4,10 +4,10 @@ "type": "object", "properties": { "name": { - "type": "string" + "$ref": "#/definitions/StringPattern" }, "alias": { - "type": "string" + "$ref": "#/definitions/StringPattern" }, "nameType": { "enum": ["namespace", "default"] @@ -24,7 +24,7 @@ "moduleName": { "anyOf": [ { - "type": "string" + "$ref": "#/definitions/StringPattern" } ] }, @@ -34,7 +34,7 @@ "type": "boolean" }, { - "type": "string" + "$ref": "#/definitions/StringPattern" }, { "$ref": "#/definitions/NamesObjectPattern" @@ -86,17 +86,18 @@ ] }, "wrapper": { - "type": "string" + "type": "object", + "properties": { + "call": { + "$ref": "#/definitions/StringPattern" + }, + "IIFE": { + "$ref": "#/definitions/StringPattern" + } + } }, "additionalCode": { - "type": "string" - }, - "IIFE": { - "anyOf": [ - { - "type": "string" - } - ] + "$ref": "#/definitions/StringPattern" } }, "additionalProperties": false diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 78876a9..4cf6c44 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -2,35 +2,23 @@ exports[`loader should emit error when invalid arguments for import commonjs: errors 1`] = ` Array [ - "ModuleError: Module Error (from \`replaced original path\`): -Not enough data to commonjs import", + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: Not enough data to commonjs import", ] `; -exports[`loader should emit error when invalid arguments for import commonjs: result 1`] = ` -"var someCode = { - number: 123, - object: { existingSubProperty: 123 } -}; -" -`; +exports[`loader should emit error when invalid arguments for import commonjs: result 1`] = `"throw new Error(\\"Module build failed (from /media/veracrypt1/OS/imports-loader/src/index.js):\\\\nError: Not enough data to commonjs import\\\\n at renderImportCommonjs (/media/veracrypt1/OS/imports-loader/src/utils.js:140:9)\\\\n at renderImport (/media/veracrypt1/OS/imports-loader/src/utils.js:150:10)\\\\n at forEach (/media/veracrypt1/OS/imports-loader/src/index.js:40:22)\\\\n at Array.forEach ()\\\\n at Object.loader (/media/veracrypt1/OS/imports-loader/src/index.js:39:21)\\");"`; exports[`loader should emit error when invalid arguments for import commonjs: warnings 1`] = `Array []`; exports[`loader should emit error when invalid arguments for import: errors 1`] = ` Array [ - "ModuleError: Module Error (from \`replaced original path\`): -Not enough data to import", + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: Not enough data to import", ] `; -exports[`loader should emit error when invalid arguments for import: result 1`] = ` -"var someCode = { - number: 123, - object: { existingSubProperty: 123 } -}; -" -`; +exports[`loader should emit error when invalid arguments for import: result 1`] = `"throw new Error(\\"Module build failed (from /media/veracrypt1/OS/imports-loader/src/index.js):\\\\nError: Not enough data to import\\\\n at getImportProfile (/media/veracrypt1/OS/imports-loader/src/utils.js:78:11)\\\\n at renderImport (/media/veracrypt1/OS/imports-loader/src/utils.js:144:25)\\\\n at forEach (/media/veracrypt1/OS/imports-loader/src/index.js:40:22)\\\\n at Array.forEach ()\\\\n at Object.loader (/media/veracrypt1/OS/imports-loader/src/index.js:39:21)\\");"`; exports[`loader should emit error when invalid arguments for import: warnings 1`] = `Array []`; @@ -175,28 +163,9 @@ var someCode = { exports[`loader should require when named-imports: warnings 1`] = `Array []`; -exports[`loader should work IIFE and additionalCode option: errors 1`] = `Array []`; - -exports[`loader should work IIFE and additionalCode option: result 1`] = ` -"/*** IMPORTS FROM imports-loader ***/ - - -(function() { -const [ window, document ] = [...arguments]; - -var someCode = { - number: 123, - object: { existingSubProperty: 123 } -}; - -}(window, document));" -`; - -exports[`loader should work IIFE and additionalCode option: warnings 1`] = `Array []`; - -exports[`loader should work IIFE if string: errors 1`] = `Array []`; +exports[`loader should work IIFE: errors 1`] = `Array []`; -exports[`loader should work IIFE if string: result 1`] = ` +exports[`loader should work IIFE: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ @@ -209,7 +178,7 @@ var someCode = { }(window));" `; -exports[`loader should work IIFE if string: warnings 1`] = `Array []`; +exports[`loader should work IIFE: warnings 1`] = `Array []`; exports[`loader should work additionalCode option: errors 1`] = `Array []`; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 4cbca45..2550463 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -1,63 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`validate options should throw an error on the "IIFE" option with "/test/" value 1`] = ` -"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.IIFE should be a string." -`; - -exports[`validate options should throw an error on the "IIFE" option with "[""]" value 1`] = ` -"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.IIFE should be a string." -`; - -exports[`validate options should throw an error on the "IIFE" option with "[]" value 1`] = ` -"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.IIFE should be a string." -`; - -exports[`validate options should throw an error on the "IIFE" option with "{}" value 1`] = ` -"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.IIFE should be a string." -`; - -exports[`validate options should throw an error on the "IIFE" option with "false" value 1`] = ` -"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.IIFE should be a string." -`; - -exports[`validate options should throw an error on the "IIFE" option with "true" value 1`] = ` -"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.IIFE should be a string." -`; - exports[`validate options should throw an error on the "additionalCode" option with "/test/" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.additionalCode should be a string." + - options.additionalCode should be a non-empty string." `; exports[`validate options should throw an error on the "additionalCode" option with "[""]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.additionalCode should be a string." + - options.additionalCode should be a non-empty string." `; exports[`validate options should throw an error on the "additionalCode" option with "[]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.additionalCode should be a string." + - options.additionalCode should be a non-empty string." `; exports[`validate options should throw an error on the "additionalCode" option with "{}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.additionalCode should be a string." + - options.additionalCode should be a non-empty string." `; exports[`validate options should throw an error on the "additionalCode" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.additionalCode should be a string." + - options.additionalCode should be a non-empty string." `; exports[`validate options should throw an error on the "additionalCode" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.additionalCode should be a string." + - options.additionalCode should be a non-empty string." `; exports[`validate options should throw an error on the "import" option with "" value 1`] = ` @@ -112,77 +82,71 @@ exports[`validate options should throw an error on the "import" option with "tru exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode?, IIFE? }" + object { import?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode?, IIFE? }" + object { import?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode?, IIFE? }" + object { import?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode?, IIFE? }" + object { import?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode?, IIFE? }" + object { import?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode?, IIFE? }" + object { import?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode?, IIFE? }" + object { import?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode?, IIFE? }" -`; - -exports[`validate options should throw an error on the "wrapper" option with "/test/" value 1`] = ` -"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be a string." + object { import?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "wrapper" option with "[""]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be a string." + - options.wrapper should be an object: + object { call?, IIFE?, … }" `; exports[`validate options should throw an error on the "wrapper" option with "[]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be a string." -`; - -exports[`validate options should throw an error on the "wrapper" option with "{}" value 1`] = ` -"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be a string." + - options.wrapper should be an object: + object { call?, IIFE?, … }" `; exports[`validate options should throw an error on the "wrapper" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be a string." + - options.wrapper should be an object: + object { call?, IIFE?, … }" `; exports[`validate options should throw an error on the "wrapper" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be a string." + - options.wrapper should be an object: + object { call?, IIFE?, … }" `; diff --git a/test/loader.test.js b/test/loader.test.js index 4a4d50a..c5606b1 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -186,23 +186,11 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); - it('should work IIFE if string', async () => { + it('should work IIFE', async () => { const compiler = getCompiler('some-library.js', { - IIFE: 'window', - }); - const stats = await compile(compiler); - - expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( - 'result' - ); - expect(getErrors(stats)).toMatchSnapshot('errors'); - expect(getWarnings(stats)).toMatchSnapshot('warnings'); - }); - - it('should work IIFE and additionalCode option', async () => { - const compiler = getCompiler('some-library.js', { - IIFE: 'window, document', - additionalCode: 'const [ window, document ] = [...arguments];', + wrapper: { + IIFE: 'window', + }, }); const stats = await compile(compiler); @@ -215,7 +203,9 @@ describe('loader', () => { it('should work wrapper', async () => { const compiler = getCompiler('some-library.js', { - wrapper: 'window', + wrapper: { + call: 'window', + }, }); const stats = await compile(compiler); @@ -245,7 +235,9 @@ describe('loader', () => { moduleName: './lib_1', list: false, }, - wrapper: 'window', + wrapper: { + call: 'window', + }, additionalCode: 'var someVariable = 1;', }); const stats = await compile(compiler); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 7b44d78..6c4a7c5 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -51,12 +51,8 @@ describe('validate options', () => { ], }, wrapper: { - success: ['windows'], - failure: [false, true, /test/, [], [''], {}], - }, - IIFE: { - success: ['windows'], - failure: [false, true, /test/, [], [''], {}], + success: [{ call: 'windows' }, { IIFE: 'windows' }], + failure: [false, true, [], ['']], }, additionalCode: { success: ['var x = 2;'], From 9faed2bf054691e3f8374faea6502e2825502281 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Sun, 7 Jun 2020 18:38:59 +0300 Subject: [PATCH 07/15] refactor: code --- src/index.js | 40 ++++++------- src/options.json | 16 ++---- src/utils.js | 8 +-- test/__snapshots__/loader.test.js.snap | 46 +-------------- .../validate-options.test.js.snap | 56 +++++++++---------- test/loader.test.js | 38 ++++++------- test/validate-options.test.js | 6 +- 7 files changed, 78 insertions(+), 132 deletions(-) diff --git a/src/index.js b/src/index.js index 8d76e4a..64bca26 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,7 @@ import validateOptions from 'schema-utils'; import schema from './options.json'; -import getImportString from './utils'; +import renderImport from './utils'; const { SourceNode } = require('source-map'); const { SourceMapConsumer } = require('source-map'); @@ -23,21 +23,17 @@ export default function loader(content, sourceMap) { const callback = this.async(); - const HEADER = '/*** IMPORTS FROM imports-loader ***/\n'; - const prefixes = []; - const postfixes = []; - const imports = []; + const moduleImport = options.imports; let moduleImports; - - const moduleImport = options.import; + const imports = []; if (moduleImport) { moduleImports = Array.isArray(moduleImport) ? moduleImport : [moduleImport]; try { moduleImports.forEach((importEntry) => { - imports.push(getImportString(importEntry)); + imports.push(renderImport(importEntry)); }); } catch (error) { callback(error, content, sourceMap); @@ -45,34 +41,36 @@ export default function loader(content, sourceMap) { } } + let prefix = ''; + let postfix = ''; const { wrapper } = options; if (wrapper && wrapper.IIFE) { - prefixes.push(`(function() {`); - postfixes.unshift(`}(${wrapper.IIFE}));`); + prefix += '\n(function() {'; + postfix += `\n}(${wrapper.IIFE}));`; } if (wrapper && wrapper.call) { - prefixes.push(`(function() {`); - postfixes.unshift(`}.call(${wrapper.call}));`); + prefix += '\n(function() {'; + postfix += `\n}.call(${wrapper.call}));`; } + let importString = `/*** IMPORTS FROM imports-loader ***/\n${imports.join( + '\n' + )}`; + const { additionalCode } = options; if (additionalCode) { - prefixes.push(`${additionalCode}\n`); + importString += `\n${additionalCode}`; } - const prefix = prefixes.join('\n'); - const postfix = postfixes.join('\n'); - const importString = imports.join('\n'); - if (sourceMap) { const node = SourceNode.fromStringWithSourceMap( content, new SourceMapConsumer(sourceMap) ); - node.prepend(`${HEADER}\n${importString}\n${prefix}`); + node.prepend(`${importString}${prefix}`); node.add(postfix); const result = node.toStringWithSourceMap({ file: getCurrentRequest(this), @@ -81,9 +79,5 @@ export default function loader(content, sourceMap) { return; } - callback( - null, - `${HEADER}\n${importString}\n${prefix}\n${content}\n${postfix}`, - sourceMap - ); + callback(null, `${importString}${prefix}\n${content}${postfix}`, sourceMap); } diff --git a/src/options.json b/src/options.json index be10fb3..12085df 100644 --- a/src/options.json +++ b/src/options.json @@ -9,7 +9,7 @@ "alias": { "$ref": "#/definitions/StringPattern" }, - "nameType": { + "type": { "enum": ["namespace", "default"] } } @@ -22,11 +22,7 @@ "enum": ["module", "commonjs"] }, "moduleName": { - "anyOf": [ - { - "$ref": "#/definitions/StringPattern" - } - ] + "$ref": "#/definitions/StringPattern" }, "list": { "anyOf": [ @@ -43,11 +39,7 @@ "type": "array", "minItems": 1, "items": { - "anyOf": [ - { - "$ref": "#/definitions/NamesObjectPattern" - } - ] + "$ref": "#/definitions/NamesObjectPattern" } } ] @@ -61,7 +53,7 @@ }, "type": "object", "properties": { - "import": { + "imports": { "anyOf": [ { "$ref": "#/definitions/StringPattern" diff --git a/src/utils.js b/src/utils.js index 5b2f51a..13ae1ec 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,7 @@ function getImportProfile(params) { const importEntry = typeof params === 'string' - ? { moduleName: params, list: { name: params, nameType: 'default' } } + ? { moduleName: params, list: { name: params, type: 'default' } } : { ...params }; const result = { @@ -45,17 +45,17 @@ function getImportProfile(params) { list = Array.isArray(list) ? list : typeof list === 'string' - ? [{ name: list, nameType: 'default' }] + ? [{ name: list, type: 'default' }] : [list]; list.forEach((entry) => { - if (entry.nameType === 'default' && entry.name) { + if (entry.type === 'default' && entry.name) { result.type.default = true; result.importDefault.name = entry.name; return; } - if (entry.nameType === 'namespace' && entry.name) { + if (entry.type === 'namespace' && entry.name) { result.type.namespace = true; result.namespaceImport.name = entry.name; return; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 4cf6c44..0343496 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -7,7 +7,7 @@ Error: Not enough data to commonjs import", ] `; -exports[`loader should emit error when invalid arguments for import commonjs: result 1`] = `"throw new Error(\\"Module build failed (from /media/veracrypt1/OS/imports-loader/src/index.js):\\\\nError: Not enough data to commonjs import\\\\n at renderImportCommonjs (/media/veracrypt1/OS/imports-loader/src/utils.js:140:9)\\\\n at renderImport (/media/veracrypt1/OS/imports-loader/src/utils.js:150:10)\\\\n at forEach (/media/veracrypt1/OS/imports-loader/src/index.js:40:22)\\\\n at Array.forEach ()\\\\n at Object.loader (/media/veracrypt1/OS/imports-loader/src/index.js:39:21)\\");"`; +exports[`loader should emit error when invalid arguments for import commonjs: result 1`] = `"throw new Error(\\"Module build failed (from /media/veracrypt1/OS/imports-loader/src/index.js):\\\\nError: Not enough data to commonjs import\\\\n at renderImportCommonjs (/media/veracrypt1/OS/imports-loader/src/utils.js:140:9)\\\\n at renderImport (/media/veracrypt1/OS/imports-loader/src/utils.js:150:10)\\\\n at forEach (/media/veracrypt1/OS/imports-loader/src/index.js:36:22)\\\\n at Array.forEach ()\\\\n at Object.loader (/media/veracrypt1/OS/imports-loader/src/index.js:35:21)\\");"`; exports[`loader should emit error when invalid arguments for import commonjs: warnings 1`] = `Array []`; @@ -18,7 +18,7 @@ Error: Not enough data to import", ] `; -exports[`loader should emit error when invalid arguments for import: result 1`] = `"throw new Error(\\"Module build failed (from /media/veracrypt1/OS/imports-loader/src/index.js):\\\\nError: Not enough data to import\\\\n at getImportProfile (/media/veracrypt1/OS/imports-loader/src/utils.js:78:11)\\\\n at renderImport (/media/veracrypt1/OS/imports-loader/src/utils.js:144:25)\\\\n at forEach (/media/veracrypt1/OS/imports-loader/src/index.js:40:22)\\\\n at Array.forEach ()\\\\n at Object.loader (/media/veracrypt1/OS/imports-loader/src/index.js:39:21)\\");"`; +exports[`loader should emit error when invalid arguments for import: result 1`] = `"throw new Error(\\"Module build failed (from /media/veracrypt1/OS/imports-loader/src/index.js):\\\\nError: Not enough data to import\\\\n at getImportProfile (/media/veracrypt1/OS/imports-loader/src/utils.js:78:11)\\\\n at renderImport (/media/veracrypt1/OS/imports-loader/src/utils.js:144:25)\\\\n at forEach (/media/veracrypt1/OS/imports-loader/src/index.js:36:22)\\\\n at Array.forEach ()\\\\n at Object.loader (/media/veracrypt1/OS/imports-loader/src/index.js:35:21)\\");"`; exports[`loader should emit error when invalid arguments for import: warnings 1`] = `Array []`; @@ -26,15 +26,12 @@ exports[`loader should require when import option is array: errors 1`] = `Array exports[`loader should require when import option is array: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - import lib_1 from \\"lib_1\\"; import lib_2 from \\"lib_2\\"; - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -44,14 +41,11 @@ exports[`loader should require when import option is filePath: errors 1`] = `Arr exports[`loader should require when import option is filePath: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - import $ from \\"./lib_1\\"; - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -61,14 +55,11 @@ exports[`loader should require when import option is object: errors 1`] = `Array exports[`loader should require when import option is object: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - import $ from \\"./lib_1\\"; - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -78,14 +69,11 @@ exports[`loader should require when import option is string: errors 1`] = `Array exports[`loader should require when import option is string: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - import lib_1 from \\"lib_1\\"; - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -95,17 +83,14 @@ exports[`loader should require when import-default: errors 1`] = `Array []`; exports[`loader should require when import-default: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - import lib_1 from \\"lib_1\\"; import lib_2 from \\"./lib_2.js\\"; import defaultExport, { lib_3_method as method } from \\"./lib_3.js\\"; import lib_4, * as lib_4_all from \\"./lib_4\\"; - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -115,14 +100,11 @@ exports[`loader should require when import-side-effect: errors 1`] = `Array []`; exports[`loader should require when import-side-effect: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - import \\"./lib_1\\"; - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -132,14 +114,11 @@ exports[`loader should require when name-space-import: errors 1`] = `Array []`; exports[`loader should require when name-space-import: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - import * as lib_1_all from \\"./lib_1\\"; - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -149,15 +128,12 @@ exports[`loader should require when named-imports: errors 1`] = `Array []`; exports[`loader should require when named-imports: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - import { lib1_method } from \\"./lib_1\\"; import { lib2_method_1, lib2_method_2 as lib_2_method_2_short } from \\"./lib_2\\"; - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -168,7 +144,6 @@ exports[`loader should work IIFE: errors 1`] = `Array []`; exports[`loader should work IIFE: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - (function() { var someCode = { number: 123, @@ -185,14 +160,11 @@ exports[`loader should work additionalCode option: errors 1`] = `Array []`; exports[`loader should work additionalCode option: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - var someVariable = 1; - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -202,14 +174,11 @@ exports[`loader should work destructuring require: errors 1`] = `Array []`; exports[`loader should work destructuring require: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - var { lib2_method_1, lib2_method_2: lib_2_method_2_short } = require(\\"./lib_2\\"); - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -219,15 +188,12 @@ exports[`loader should work few require: errors 1`] = `Array []`; exports[`loader should work few require: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - var $ = require(\\"./lib_1\\"); var { lib2_method_1, lib2_method_2: lib_2_method_2_short } = require(\\"./lib_2\\"); - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -237,11 +203,9 @@ exports[`loader should work import, wrapper and additionalCode option: errors 1` exports[`loader should work import, wrapper and additionalCode option: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - import \\"./lib_1\\"; -(function() { var someVariable = 1; - +(function() { var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -256,14 +220,11 @@ exports[`loader should work require: errors 1`] = `Array []`; exports[`loader should work require: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - var $ = require(\\"./lib_1\\"); - var someCode = { number: 123, object: { existingSubProperty: 123 } }; - " `; @@ -274,7 +235,6 @@ exports[`loader should work wrapper: errors 1`] = `Array []`; exports[`loader should work wrapper: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ - (function() { var someCode = { number: 123, diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 2550463..28b3a91 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -30,101 +30,101 @@ exports[`validate options should throw an error on the "additionalCode" option w - options.additionalCode should be a non-empty string." `; -exports[`validate options should throw an error on the "import" option with "" value 1`] = ` +exports[`validate options should throw an error on the "imports" option with "" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.import should be an non-empty string." + - options.imports should be an non-empty string." `; -exports[`validate options should throw an error on the "import" option with "/test/" value 1`] = `"Cannot read property 'nameType' of undefined"`; +exports[`validate options should throw an error on the "imports" option with "/test/" value 1`] = `"Cannot read property 'type' of undefined"`; -exports[`validate options should throw an error on the "import" option with "[""]" value 1`] = ` +exports[`validate options should throw an error on the "imports" option with "[""]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.import[0] should be an non-empty string." + - options.imports[0] should be an non-empty string." `; -exports[`validate options should throw an error on the "import" option with "[]" value 1`] = ` +exports[`validate options should throw an error on the "imports" option with "[]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.import should be an non-empty array." + - options.imports should be an non-empty array." `; -exports[`validate options should throw an error on the "import" option with "{"type":"string","moduleName":"jQuery","list":false}" value 1`] = ` +exports[`validate options should throw an error on the "imports" option with "{"type":"string","moduleName":"jQuery","list":false}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.import.type should be one of these: + - options.imports.type should be one of these: \\"module\\" | \\"commonjs\\"" `; -exports[`validate options should throw an error on the "import" option with "{}" value 1`] = `"Cannot read property 'nameType' of undefined"`; +exports[`validate options should throw an error on the "imports" option with "{}" value 1`] = `"Cannot read property 'type' of undefined"`; -exports[`validate options should throw an error on the "import" option with "false" value 1`] = ` +exports[`validate options should throw an error on the "imports" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.import should be one of these: + - options.imports should be one of these: non-empty string | object { type?, moduleName?, list? } | [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item) Details: - * options.import should be a non-empty string. - * options.import should be an object: + * options.imports should be a non-empty string. + * options.imports should be an object: object { type?, moduleName?, list? } - * options.import should be an array: + * options.imports should be an array: [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item)" `; -exports[`validate options should throw an error on the "import" option with "true" value 1`] = ` +exports[`validate options should throw an error on the "imports" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.import should be one of these: + - options.imports should be one of these: non-empty string | object { type?, moduleName?, list? } | [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item) Details: - * options.import should be a non-empty string. - * options.import should be an object: + * options.imports should be a non-empty string. + * options.imports should be an object: object { type?, moduleName?, list? } - * options.import should be an array: + * options.imports should be an array: [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { import?, wrapper?, additionalCode? }" + object { imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "wrapper" option with "[""]" value 1`] = ` diff --git a/test/loader.test.js b/test/loader.test.js index c5606b1..d891742 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -9,7 +9,7 @@ import { describe('loader', () => { it('should require when import option is string', async () => { const compiler = getCompiler('some-library.js', { - import: 'lib_1', + imports: 'lib_1', }); const stats = await compile(compiler); @@ -22,7 +22,7 @@ describe('loader', () => { it('should require when import option is object', async () => { const compiler = getCompiler('some-library.js', { - import: { + imports: { moduleName: './lib_1', list: '$', }, @@ -38,7 +38,7 @@ describe('loader', () => { it('should require when import option is filePath', async () => { const compiler = getCompiler('some-library.js', { - import: { + imports: { moduleName: './lib_1', list: '$', }, @@ -54,7 +54,7 @@ describe('loader', () => { it('should require when import option is array', async () => { const compiler = getCompiler('some-library.js', { - import: ['lib_1', 'lib_2'], + imports: ['lib_1', 'lib_2'], }); const stats = await compile(compiler); @@ -67,13 +67,13 @@ describe('loader', () => { it('should require when import-default', async () => { const compiler = getCompiler('some-library.js', { - import: [ + imports: [ 'lib_1', { moduleName: './lib_2.js', list: { name: 'lib_2', - nameType: 'default', + type: 'default', }, }, { @@ -81,7 +81,7 @@ describe('loader', () => { list: [ { name: 'defaultExport', - nameType: 'default', + type: 'default', }, { name: 'lib_3_method', @@ -94,11 +94,11 @@ describe('loader', () => { list: [ { name: 'lib_4', - nameType: 'default', + type: 'default', }, { name: 'lib_4_all', - nameType: 'namespace', + type: 'namespace', }, ], }, @@ -115,13 +115,13 @@ describe('loader', () => { it('should require when name-space-import', async () => { const compiler = getCompiler('some-library.js', { - import: [ + imports: [ { moduleName: './lib_1', list: [ { name: 'lib_1_all', - nameType: 'namespace', + type: 'namespace', }, ], }, @@ -138,7 +138,7 @@ describe('loader', () => { it('should require when named-imports', async () => { const compiler = getCompiler('some-library.js', { - import: [ + imports: [ { moduleName: './lib_1', list: [ @@ -172,7 +172,7 @@ describe('loader', () => { it('should require when import-side-effect', async () => { const compiler = getCompiler('some-library.js', { - import: { + imports: { moduleName: './lib_1', list: false, }, @@ -231,7 +231,7 @@ describe('loader', () => { it('should work import, wrapper and additionalCode option', async () => { const compiler = getCompiler('some-library.js', { - import: { + imports: { moduleName: './lib_1', list: false, }, @@ -251,7 +251,7 @@ describe('loader', () => { it('should work require', async () => { const compiler = getCompiler('some-library.js', { - import: { + imports: { type: 'commonjs', moduleName: './lib_1', list: '$', @@ -268,7 +268,7 @@ describe('loader', () => { it('should work destructuring require', async () => { const compiler = getCompiler('some-library.js', { - import: [ + imports: [ { type: 'commonjs', moduleName: './lib_2', @@ -295,7 +295,7 @@ describe('loader', () => { it('should work few require', async () => { const compiler = getCompiler('some-library.js', { - import: [ + imports: [ { type: 'commonjs', moduleName: './lib_1', @@ -327,7 +327,7 @@ describe('loader', () => { it('should emit error when invalid arguments for import commonjs', async () => { const compiler = getCompiler('some-library.js', { - import: { + imports: { type: 'commonjs', moduleName: './lib_1', list: false, @@ -344,7 +344,7 @@ describe('loader', () => { it('should emit error when invalid arguments for import', async () => { const compiler = getCompiler('some-library.js', { - import: [ + imports: [ { moduleName: './lib_2', list: [ diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 6c4a7c5..a79571b 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -2,7 +2,7 @@ import { getCompiler, compile } from './helpers'; describe('validate options', () => { const tests = { - import: { + imports: { success: [ 'lib_1', 'globalObject1.foo', @@ -26,11 +26,11 @@ describe('validate options', () => { }, { name: 'lib', - nameType: 'default', + type: 'default', }, { alias: 'lib', - nameType: 'namespace', + type: 'namespace', }, ], }, From b59eac06a382a8d79e0953120fd0dc8d5bc8f81b Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Mon, 8 Jun 2020 12:22:25 +0300 Subject: [PATCH 08/15] refactor: code --- src/index.js | 6 +- src/utils.js | 165 +++++++------------------ test/__snapshots__/loader.test.js.snap | 59 +++++++-- test/loader.test.js | 44 +++++++ 4 files changed, 145 insertions(+), 129 deletions(-) diff --git a/src/index.js b/src/index.js index 64bca26..b455528 100644 --- a/src/index.js +++ b/src/index.js @@ -36,7 +36,9 @@ export default function loader(content, sourceMap) { imports.push(renderImport(importEntry)); }); } catch (error) { - callback(error, content, sourceMap); + this.emitError(error); + + callback(null, content, sourceMap); return; } } @@ -70,7 +72,7 @@ export default function loader(content, sourceMap) { content, new SourceMapConsumer(sourceMap) ); - node.prepend(`${importString}${prefix}`); + node.prepend(`${importString}${prefix}\n`); node.add(postfix); const result = node.toStringWithSourceMap({ file: getCurrentRequest(this), diff --git a/src/utils.js b/src/utils.js index 13ae1ec..f156e8a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,153 +1,80 @@ -function getImportProfile(params) { +function renderImport(params) { + const isCommonJs = params.type === 'commonjs'; + const importEntry = typeof params === 'string' ? { moduleName: params, list: { name: params, type: 'default' } } : { ...params }; - const result = { - /* - import-side-effect - import-default - name-space-import - named-imports - import-default , name-space-import - import-default , named-imports - */ - quantityImportsType: 0, - moduleType: params.type || 'module', - type: { - default: false, - sideEffect: false, - namespaceImport: false, - namedImports: false, - }, - moduleName: importEntry.moduleName, - importDefault: { - // name: "defaultExport" - }, - namespaceImport: { - // name: "ns" - }, - namedImports: [ - // { - // name: "exportName", - // alias: "shortName" - // } - ], - }; let { list } = importEntry; + const { moduleName } = importEntry; + // 1. Import-side-effect if (list === false) { - result.type.sideEffect = true; - return result; + if (isCommonJs) { + throw new Error('Not enough data to commonjs import'); + } + + return `import "${moduleName}";`; } - list = Array.isArray(list) - ? list - : typeof list === 'string' - ? [{ name: list, type: 'default' }] - : [list]; + list = Array.isArray(list) ? list : [list]; + + let defaultImport = ''; + let namespaceImport = ''; + let namedImports = ''; list.forEach((entry) => { - if (entry.type === 'default' && entry.name) { - result.type.default = true; - result.importDefault.name = entry.name; + const normalizedEntry = + typeof entry === 'string' ? { name: entry, type: 'default' } : entry; + + // 2. Default import + if (normalizedEntry.type === 'default' && normalizedEntry.name) { + defaultImport += `${normalizedEntry.name}`; return; } - if (entry.type === 'namespace' && entry.name) { - result.type.namespace = true; - result.namespaceImport.name = entry.name; + // 3. Namespace import + if (normalizedEntry.type === 'namespace' && normalizedEntry.name) { + if (isCommonJs) { + throw new Error('Commonjs not support namespace import'); + } + namespaceImport += `* as ${normalizedEntry.name}`; return; } - if (entry.name) { - result.type.namedImports = true; - result.namedImports.push({ - name: entry.name, - alias: entry.alias, - }); + // 4. Named import + if (normalizedEntry.name) { + const sep = isCommonJs ? ': ' : ' as '; + const comma = namedImports ? ', ' : ''; + + namedImports += normalizedEntry.alias + ? `${comma}${normalizedEntry.name}${sep}${normalizedEntry.alias}` + : `${comma}${normalizedEntry.name}`; } }); - result.quantityImportsType = Object.keys(result.type).filter( - (key) => result.type[key] - ).length; - - if (result.quantityImportsType === 0) { - throw new Error('Not enough data to import'); - } - - return result; -} - -function renderImportModule(importProfile) { - let result = 'import'; - - if (importProfile.type.sideEffect) { - result += ` "${importProfile.moduleName}";`; - return result; - } - - if (importProfile.type.default) { - result += ` ${importProfile.importDefault.name}`; - } + let notDefaultImport = namespaceImport; - if (importProfile.quantityImportsType > 1) { - result += ', '; + if (namedImports) { + notDefaultImport = `{ ${namedImports} }`; } - if (importProfile.type.namedImports) { - const namedImportString = importProfile.namedImports.map((entry) => { - if (entry.alias) { - return `${entry.name} as ${entry.alias}`; - } - - return entry.name; - }); - - result += ` { ${namedImportString.join(', ')} }`; - } - - if (importProfile.type.namespace) { - result += ` * as ${importProfile.namespaceImport.name}`; - } - - result += ` from "${importProfile.moduleName}";`; - - return result; -} - -function renderImportCommonjs(importProfile) { - if (importProfile.type.default) { - return `var ${importProfile.importDefault.name} = require("${importProfile.moduleName}");`; + if (!defaultImport && !notDefaultImport) { + throw new Error('Not enough data to import'); } - if (importProfile.type.namedImports) { - const namedImportString = importProfile.namedImports.map((entry) => { - if (entry.alias) { - return `${entry.name}: ${entry.alias}`; - } - - return entry.name; - }); + if (!isCommonJs) { + const comma = defaultImport && notDefaultImport ? ', ' : ''; - return `var { ${namedImportString.join(', ')} } = require("${ - importProfile.moduleName - }");`; + return `import ${defaultImport}${comma}${notDefaultImport} from "${moduleName}";`; } - throw new Error('Not enough data to commonjs import'); -} - -function renderImport(importEntry) { - const importProfile = getImportProfile(importEntry); - - if (importProfile.moduleType === 'module') { - return renderImportModule(importProfile); + if (defaultImport) { + return `var ${defaultImport} = require("${moduleName}");`; } - return renderImportCommonjs(importProfile); + return `var { ${namedImports} } = require("${moduleName}");`; } export default renderImport; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 0343496..8945fd5 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -2,26 +2,55 @@ exports[`loader should emit error when invalid arguments for import commonjs: errors 1`] = ` Array [ - "ModuleBuildError: Module build failed (from \`replaced original path\`): -Error: Not enough data to commonjs import", + "ModuleError: Module Error (from \`replaced original path\`): +Not enough data to commonjs import", ] `; -exports[`loader should emit error when invalid arguments for import commonjs: result 1`] = `"throw new Error(\\"Module build failed (from /media/veracrypt1/OS/imports-loader/src/index.js):\\\\nError: Not enough data to commonjs import\\\\n at renderImportCommonjs (/media/veracrypt1/OS/imports-loader/src/utils.js:140:9)\\\\n at renderImport (/media/veracrypt1/OS/imports-loader/src/utils.js:150:10)\\\\n at forEach (/media/veracrypt1/OS/imports-loader/src/index.js:36:22)\\\\n at Array.forEach ()\\\\n at Object.loader (/media/veracrypt1/OS/imports-loader/src/index.js:35:21)\\");"`; +exports[`loader should emit error when invalid arguments for import commonjs: result 1`] = ` +"var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; exports[`loader should emit error when invalid arguments for import commonjs: warnings 1`] = `Array []`; exports[`loader should emit error when invalid arguments for import: errors 1`] = ` Array [ - "ModuleBuildError: Module build failed (from \`replaced original path\`): -Error: Not enough data to import", + "ModuleError: Module Error (from \`replaced original path\`): +Not enough data to import", ] `; -exports[`loader should emit error when invalid arguments for import: result 1`] = `"throw new Error(\\"Module build failed (from /media/veracrypt1/OS/imports-loader/src/index.js):\\\\nError: Not enough data to import\\\\n at getImportProfile (/media/veracrypt1/OS/imports-loader/src/utils.js:78:11)\\\\n at renderImport (/media/veracrypt1/OS/imports-loader/src/utils.js:144:25)\\\\n at forEach (/media/veracrypt1/OS/imports-loader/src/index.js:36:22)\\\\n at Array.forEach ()\\\\n at Object.loader (/media/veracrypt1/OS/imports-loader/src/index.js:35:21)\\");"`; +exports[`loader should emit error when invalid arguments for import: result 1`] = ` +"var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; exports[`loader should emit error when invalid arguments for import: warnings 1`] = `Array []`; +exports[`loader should emit error when try namespace import to commonjs: errors 1`] = ` +Array [ + "ModuleError: Module Error (from \`replaced original path\`): +Commonjs not support namespace import", +] +`; + +exports[`loader should emit error when try namespace import to commonjs: result 1`] = ` +"var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; + +exports[`loader should emit error when try namespace import to commonjs: warnings 1`] = `Array []`; + exports[`loader should require when import option is array: errors 1`] = `Array []`; exports[`loader should require when import option is array: result 1`] = ` @@ -85,8 +114,8 @@ exports[`loader should require when import-default: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import lib_1 from \\"lib_1\\"; import lib_2 from \\"./lib_2.js\\"; -import defaultExport, { lib_3_method as method } from \\"./lib_3.js\\"; -import lib_4, * as lib_4_all from \\"./lib_4\\"; +import defaultExport, { lib_3_method as method } from \\"./lib_3.js\\"; +import lib_4, * as lib_4_all from \\"./lib_4\\"; var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -216,6 +245,20 @@ var someCode = { exports[`loader should work import, wrapper and additionalCode option: warnings 1`] = `Array []`; +exports[`loader should work require default: errors 1`] = `Array []`; + +exports[`loader should work require default: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ +var $ = require(\\"./lib_1\\"); +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; + +exports[`loader should work require default: warnings 1`] = `Array []`; + exports[`loader should work require: errors 1`] = `Array []`; exports[`loader should work require: result 1`] = ` diff --git a/test/loader.test.js b/test/loader.test.js index d891742..78046b6 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -266,6 +266,26 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + it('should work require default', async () => { + const compiler = getCompiler('some-library.js', { + imports: { + type: 'commonjs', + moduleName: './lib_1', + list: { + name: '$', + type: 'default', + }, + }, + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + it('should work destructuring require', async () => { const compiler = getCompiler('some-library.js', { imports: [ @@ -342,6 +362,30 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + it('should emit error when try namespace import to commonjs', async () => { + const compiler = getCompiler('some-library.js', { + imports: [ + { + type: 'commonjs', + moduleName: './lib_4', + list: [ + { + name: 'lib_4_all', + type: 'namespace', + }, + ], + }, + ], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + it('should emit error when invalid arguments for import', async () => { const compiler = getCompiler('some-library.js', { imports: [ From 00096758a9a25ef26bd3d1bfe9fac824a7193a74 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Mon, 8 Jun 2020 20:31:35 +0300 Subject: [PATCH 09/15] refactor: code --- README.md | 520 ++++++++++++++++-- src/index.js | 24 +- src/options.json | 24 +- src/utils.js | 12 +- test/__snapshots__/loader.test.js.snap | 89 ++- .../validate-options.test.js.snap | 22 +- test/helpers/getCompiler.js | 2 +- test/loader.test.js | 25 +- test/validate-options.test.js | 3 +- 9 files changed, 558 insertions(+), 163 deletions(-) diff --git a/README.md b/README.md index 07e4d0f..e289d6a 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,31 @@ -[![npm][npm]][npm-url] -[![deps][deps]][deps-url] -[![test][test]][test-url] -[![chat][chat]][chat-url] -
- - + -

Imports Loader

-

The imports loader allows you to use modules that depend on specific global variables.

+[![npm][npm]][npm-url] +[![node][node]][node-url] +[![deps][deps]][deps-url] +[![tests][tests]][tests-url] +[![cover][cover]][cover-url] +[![chat][chat]][chat-url] +[![size][size]][size-url] + +# Imports Loader + +The imports loader allows you to use modules that depend on specific global variables. + This is useful for third-party modules that rely on global variables like `$` or `this` being the `window` object. The imports loader can add the necessary `require('whatever')` calls, so those modules work with webpack. -

Install

+## Getting Started -```bash -npm install imports-loader -``` +To begin, you'll need to install `copy-webpack-plugin`: -

Usage

+```console +$ npm install imports-loader +``` Given you have this file `example.js` @@ -29,70 +33,470 @@ Given you have this file `example.js` $('img').doSomeAwesomeJqueryPluginStuff(); ``` -then you can inject the `$` variable into the module by configuring the imports-loader like this: +then you can inject the `jquery` variable into the module by configuring the imports-loader like this: -```javascript +**index.js** + +```js require('imports-loader?$=jquery!./example.js'); ``` -This simply prepends `var $ = require("jquery");` to `example.js`. +This simply prepends `import jquery from "jquery";` to `example.js`. + +**index.js** + +```js +require(`imports-loader?imports[]=jquery&imports[]=angular!./example.js`); +``` + +Result: -### Syntax +```js +import jquery from 'jquery'; +import angular from 'angular'; +// code form example.js +``` -| Query value | Equals | -| ------------------- | ------------------------------------- | -| `angular` |  `var angular = require("angular");` | -| `$=jquery` | `var $ = require("jquery");` | -| `define=>false` | `var define = false;` | -| `config=>{size:50}` | `var config = {size:50};` | -| `this=>window` | `(function () { ... }).call(window);` | +**index.js** -### Multiple values +```js +require(`imports-loader?wrapper[]=window&wrapper[]=document!./example.js`); +``` -Multiple values are separated by comma `,`: +Result: -```javascript -require('imports-loader?$=jquery,angular,config=>{size:50}!./file.js'); +```js +(function () { + // code from example.js +}.call(window, document)); ``` -### webpack.config.js +To configure imports, use `webpack.config.js`: -As always, you should rather configure this in your `webpack.config.js`: +## Options -```javascript -// ./webpack.config.js +The loader's signature: + +**webpack.config.js** +```js module.exports = { - ... - module: { - rules: [ - { - test: require.resolve("some-module"), - use: "imports-loader?this=>window" - } - ] - } + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + type: 'commonjs', + moduleName: 'jquery', + list: '$', + }, + wrapper: { + call: 'window', + }, + additionalCode: 'var someVariable = 1;', + }, + }, + ], + }, + ], + }, }; ``` -

Typical Use Cases

+### Imports -### jQuery plugins +Type: `String|Object|Array` +Default: `undefined` -`imports-loader?$=jquery` +- `String` -### Custom Angular modules +**webpack.config.js** -`imports-loader?angular` +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: 'jquery', + }, + }, + ], + }, + ], + }, +}; +``` -### Disable AMD +Result: `import jquery from "jquery";`. -There are many modules that check for a `define` function before using CommonJS. Since webpack is capable of both, they default to AMD in this case, which can be a problem if the implementation is quirky. +- `Array` -Then you can easily disable the AMD path by writing +**webpack.config.js** -```javascript -imports-loader?define=>false +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: [ + 'angular', + { + moduleName: 'jquery', + list: { name: '$', type: 'default' }, + }, + ], + }, + }, + ], + }, + ], + }, +}; +``` + +Result: + +```js +import angular from 'angular'; +import $ from 'jquery'; +``` + +- `Object` + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + moduleName: 'jquery', + list: { name: '$', type: 'default' }, + }, + }, + }, + ], + }, + ], + }, +}; +``` + +Result: `import $ from "jquery";`. + +##### Type + +Type: `String` +Default: `module` + +The type of the module to import (`import` or `require`). + +Possible values: + +- `module`, +- `commonjs` + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + type: 'commonjs', + moduleName: 'jquery', + list: '$', + }, + }, + }, + ], + }, + ], + }, +}; +``` + +Result: `var $ = require("jquery");` + +##### ModuleName + +Type: `String` +Default: `undefined` + +The name of the module to import (`jquery`, `lodash`, `./example-file.js`). + +##### List + +Type: `String|Boolean|Object|Array` +Default: `undefined` + +Сonfigures import string. + +- `Boolean` + +Lets you make a namespace import or pure require + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + moduleName: 'some-module', + list: false, + }, + }, + }, + ], + }, + ], + }, +}; +``` + +Result: `import "some-module";` + +- `String` + +Reduced to the object `{name: passedValue, type: 'default'}` + +- `Array` + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + moduleName: 'some-module', + list: [ + 'nameDefaultImport', + { + name: 'method_1', + }, + { + name: 'method_2', + alias: 'method_2_alias', + }, + ], + }, + }, + }, + ], + }, + ], + }, +}; +``` + +Result: `import nameDefaultImport, { method_1, method_2 as method_2_alias } from "some-module"`. + +- `Object` + +###### name + +Type: `String` +Default: `undefined` + +Export name. + +`import { name as alias } from "some-module"`. +`import { name } from "some-module"`. + +###### alias + +Type: `String` +Default: `undefined` + +Alias for export name. + +`import { name as alias } from "some-module"`. + +###### type + +Type: `String` +Default: `undefined` + +Type export name + +Possible values: + +- `default`, +- `namespace` + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + moduleName: 'some-module', + list: { + name: 'all', + type: 'default', + }, + }, + }, + }, + ], + }, + ], + }, +}; +``` + +Result: `import all from "some-module"`. + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + moduleName: 'some-module', + list: { + name: 'all', + type: 'namespace', + }, + }, + }, + }, + ], + }, + ], + }, +}; +``` + +Result: `import * as all from "some-module"`. + +### wrapper + +Type: `String|Array` +Default: `undefined` + +Closes the module code in a function with a given `this` (`(function () { ... }).call(window);`). + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + moduleName: 'jquery', + list: '$', + }, + wrapper: ['window', 'document'], + }, + }, + ], + }, + ], + }, +}; +``` + +Result: + +```js +import $ from 'jquery'; + +(function () { + // code from example.js +}.call(window, document)); +``` + +### additionalCode + +Type: `String` +Default: `undefined` + +Adds custom code. + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + moduleName: 'jquery', + list: '$', + }, + additionalCode: 'var someVariable = 1;', + }, + }, + ], + }, + ], + }, +}; +``` + +Result: + +```js +import $ from 'jquery'; +var someVariable = 1; + +// code from example.js ``` For further hints on compatibility issues, check out [Shimming Modules](https://webpack.js.org/guides/shimming/) of the official docs. @@ -132,9 +536,15 @@ For further hints on compatibility issues, check out [Shimming Modules](https:// [npm]: https://img.shields.io/npm/v/imports-loader.svg [npm-url]: https://www.npmjs.com/package/imports-loader +[node]: https://img.shields.io/node/v/imports-loader.svg +[node-url]: https://nodejs.org [deps]: https://david-dm.org/webpack-contrib/imports-loader.svg [deps-url]: https://david-dm.org/webpack-contrib/imports-loader +[tests]: https://github.com/webpack-contrib/imports-loader/workflows/imports-loader/badge.svg +[tests-url]: https://github.com/webpack-contrib/imports-loader/actions +[cover]: https://codecov.io/gh/webpack-contrib/imports-loader/branch/master/graph/badge.svg +[cover-url]: https://codecov.io/gh/webpack-contrib/imports-loader [chat]: https://img.shields.io/badge/gitter-webpack%2Fwebpack-brightgreen.svg [chat-url]: https://gitter.im/webpack/webpack -[test]: http://img.shields.io/travis/webpack-contrib/imports-loader.svg -[test-url]: https://travis-ci.org/webpack-contrib/imports-loader +[size]: https://packagephobia.now.sh/badge?p=imports-loader +[size-url]: https://packagephobia.now.sh/result?p=imports-loader diff --git a/src/index.js b/src/index.js index b455528..3183e1e 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ Author Tobias Koppers @sokra */ +import { SourceNode, SourceMapConsumer } from 'source-map'; import { getOptions, getCurrentRequest } from 'loader-utils'; import validateOptions from 'schema-utils'; @@ -10,9 +11,6 @@ import schema from './options.json'; import renderImport from './utils'; -const { SourceNode } = require('source-map'); -const { SourceMapConsumer } = require('source-map'); - export default function loader(content, sourceMap) { const options = getOptions(this) || {}; @@ -26,6 +24,7 @@ export default function loader(content, sourceMap) { const moduleImport = options.imports; let moduleImports; + const imports = []; if (moduleImport) { @@ -36,25 +35,20 @@ export default function loader(content, sourceMap) { imports.push(renderImport(importEntry)); }); } catch (error) { - this.emitError(error); + callback(error, content, sourceMap); - callback(null, content, sourceMap); return; } } let prefix = ''; let postfix = ''; - const { wrapper } = options; - if (wrapper && wrapper.IIFE) { - prefix += '\n(function() {'; - postfix += `\n}(${wrapper.IIFE}));`; - } + const { wrapper } = options; - if (wrapper && wrapper.call) { + if (wrapper) { prefix += '\n(function() {'; - postfix += `\n}.call(${wrapper.call}));`; + postfix += `\n}.call(${wrapper.toString()}));`; } let importString = `/*** IMPORTS FROM imports-loader ***/\n${imports.join( @@ -67,17 +61,21 @@ export default function loader(content, sourceMap) { importString += `\n${additionalCode}`; } - if (sourceMap) { + if (this.sourceMap && sourceMap) { const node = SourceNode.fromStringWithSourceMap( content, new SourceMapConsumer(sourceMap) ); + node.prepend(`${importString}${prefix}\n`); node.add(postfix); + const result = node.toStringWithSourceMap({ file: getCurrentRequest(this), }); + callback(null, result.code, result.map.toJSON()); + return; } diff --git a/src/options.json b/src/options.json index 12085df..d3f8289 100644 --- a/src/options.json +++ b/src/options.json @@ -39,7 +39,14 @@ "type": "array", "minItems": 1, "items": { - "$ref": "#/definitions/NamesObjectPattern" + "anyOf": [ + { + "$ref": "#/definitions/StringPattern" + }, + { + "$ref": "#/definitions/NamesObjectPattern" + } + ] } } ] @@ -78,15 +85,18 @@ ] }, "wrapper": { - "type": "object", - "properties": { - "call": { + "anyOf": [ + { "$ref": "#/definitions/StringPattern" }, - "IIFE": { - "$ref": "#/definitions/StringPattern" + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/StringPattern" + } } - } + ] }, "additionalCode": { "$ref": "#/definitions/StringPattern" diff --git a/src/utils.js b/src/utils.js index f156e8a..8f20bd1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -7,15 +7,12 @@ function renderImport(params) { : { ...params }; let { list } = importEntry; + const { moduleName } = importEntry; // 1. Import-side-effect if (list === false) { - if (isCommonJs) { - throw new Error('Not enough data to commonjs import'); - } - - return `import "${moduleName}";`; + return isCommonJs ? `require("${moduleName}");` : `import "${moduleName}";`; } list = Array.isArray(list) ? list : [list]; @@ -31,6 +28,7 @@ function renderImport(params) { // 2. Default import if (normalizedEntry.type === 'default' && normalizedEntry.name) { defaultImport += `${normalizedEntry.name}`; + return; } @@ -39,7 +37,9 @@ function renderImport(params) { if (isCommonJs) { throw new Error('Commonjs not support namespace import'); } + namespaceImport += `* as ${normalizedEntry.name}`; + return; } @@ -61,7 +61,7 @@ function renderImport(params) { } if (!defaultImport && !notDefaultImport) { - throw new Error('Not enough data to import'); + throw new Error(`Not enough data to import \n${importEntry}`); } if (!isCommonJs) { diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 8945fd5..02587f5 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -1,54 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`loader should emit error when invalid arguments for import commonjs: errors 1`] = ` -Array [ - "ModuleError: Module Error (from \`replaced original path\`): -Not enough data to commonjs import", -] -`; - -exports[`loader should emit error when invalid arguments for import commonjs: result 1`] = ` -"var someCode = { - number: 123, - object: { existingSubProperty: 123 } -}; -" -`; - -exports[`loader should emit error when invalid arguments for import commonjs: warnings 1`] = `Array []`; - exports[`loader should emit error when invalid arguments for import: errors 1`] = ` Array [ - "ModuleError: Module Error (from \`replaced original path\`): -Not enough data to import", + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: Not enough data to import ", ] `; -exports[`loader should emit error when invalid arguments for import: result 1`] = ` -"var someCode = { - number: 123, - object: { existingSubProperty: 123 } -}; -" -`; - exports[`loader should emit error when invalid arguments for import: warnings 1`] = `Array []`; exports[`loader should emit error when try namespace import to commonjs: errors 1`] = ` Array [ - "ModuleError: Module Error (from \`replaced original path\`): -Commonjs not support namespace import", + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: Commonjs not support namespace import", ] `; -exports[`loader should emit error when try namespace import to commonjs: result 1`] = ` -"var someCode = { - number: 123, - object: { existingSubProperty: 123 } -}; -" -`; - exports[`loader should emit error when try namespace import to commonjs: warnings 1`] = `Array []`; exports[`loader should require when import option is array: errors 1`] = `Array []`; @@ -158,7 +125,7 @@ exports[`loader should require when named-imports: errors 1`] = `Array []`; exports[`loader should require when named-imports: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import { lib1_method } from \\"./lib_1\\"; -import { lib2_method_1, lib2_method_2 as lib_2_method_2_short } from \\"./lib_2\\"; +import lib2_default, { lib2_method_1, lib2_method_2 as lib_2_method_2_short } from \\"./lib_2\\"; var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -168,22 +135,6 @@ var someCode = { exports[`loader should require when named-imports: warnings 1`] = `Array []`; -exports[`loader should work IIFE: errors 1`] = `Array []`; - -exports[`loader should work IIFE: result 1`] = ` -"/*** IMPORTS FROM imports-loader ***/ - -(function() { -var someCode = { - number: 123, - object: { existingSubProperty: 123 } -}; - -}(window));" -`; - -exports[`loader should work IIFE: warnings 1`] = `Array []`; - exports[`loader should work additionalCode option: errors 1`] = `Array []`; exports[`loader should work additionalCode option: result 1`] = ` @@ -245,6 +196,20 @@ var someCode = { exports[`loader should work import, wrapper and additionalCode option: warnings 1`] = `Array []`; +exports[`loader should work pure require: errors 1`] = `Array []`; + +exports[`loader should work pure require: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ +require(\\"./lib_1\\"); +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; + +exports[`loader should work pure require: warnings 1`] = `Array []`; + exports[`loader should work require default: errors 1`] = `Array []`; exports[`loader should work require default: result 1`] = ` @@ -273,6 +238,22 @@ var someCode = { exports[`loader should work require: warnings 1`] = `Array []`; +exports[`loader should work wrapper array: errors 1`] = `Array []`; + +exports[`loader should work wrapper array: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +(function() { +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +}.call(window,document));" +`; + +exports[`loader should work wrapper array: warnings 1`] = `Array []`; + exports[`loader should work wrapper: errors 1`] = `Array []`; exports[`loader should work wrapper: result 1`] = ` diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 28b3a91..d9306d5 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -129,24 +129,30 @@ exports[`validate options should throw an error on the "unknown" option with "tr exports[`validate options should throw an error on the "wrapper" option with "[""]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be an object: - object { call?, IIFE?, … }" + - options.wrapper[0] should be an non-empty string." `; exports[`validate options should throw an error on the "wrapper" option with "[]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be an object: - object { call?, IIFE?, … }" + - options.wrapper should be an non-empty array." `; exports[`validate options should throw an error on the "wrapper" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be an object: - object { call?, IIFE?, … }" + - options.wrapper should be one of these: + non-empty string | [non-empty string, ...] (should not have fewer than 1 item) + Details: + * options.wrapper should be a non-empty string. + * options.wrapper should be an array: + [non-empty string, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "wrapper" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.wrapper should be an object: - object { call?, IIFE?, … }" + - options.wrapper should be one of these: + non-empty string | [non-empty string, ...] (should not have fewer than 1 item) + Details: + * options.wrapper should be a non-empty string. + * options.wrapper should be an array: + [non-empty string, ...] (should not have fewer than 1 item)" `; diff --git a/test/helpers/getCompiler.js b/test/helpers/getCompiler.js index 60810fa..320ab69 100644 --- a/test/helpers/getCompiler.js +++ b/test/helpers/getCompiler.js @@ -20,7 +20,7 @@ export default (fixture, loaderOptions = {}, config = {}) => { rules: [ { test: path.resolve(__dirname, '../fixtures', fixture), - rules: [ + use: [ { loader: path.resolve(__dirname, '../../src'), options: loaderOptions || {}, diff --git a/test/loader.test.js b/test/loader.test.js index 78046b6..62be48f 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -150,6 +150,7 @@ describe('loader', () => { { moduleName: './lib_2', list: [ + 'lib2_default', { name: 'lib2_method_1', }, @@ -186,11 +187,9 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); - it('should work IIFE', async () => { + it('should work wrapper', async () => { const compiler = getCompiler('some-library.js', { - wrapper: { - IIFE: 'window', - }, + wrapper: 'window', }); const stats = await compile(compiler); @@ -201,11 +200,9 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); - it('should work wrapper', async () => { + it('should work wrapper array', async () => { const compiler = getCompiler('some-library.js', { - wrapper: { - call: 'window', - }, + wrapper: ['window', 'document'], }); const stats = await compile(compiler); @@ -235,9 +232,7 @@ describe('loader', () => { moduleName: './lib_1', list: false, }, - wrapper: { - call: 'window', - }, + wrapper: 'window', additionalCode: 'var someVariable = 1;', }); const stats = await compile(compiler); @@ -345,7 +340,7 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); - it('should emit error when invalid arguments for import commonjs', async () => { + it('should work pure require', async () => { const compiler = getCompiler('some-library.js', { imports: { type: 'commonjs', @@ -379,9 +374,6 @@ describe('loader', () => { }); const stats = await compile(compiler); - expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( - 'result' - ); expect(getErrors(stats)).toMatchSnapshot('errors'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); @@ -401,9 +393,6 @@ describe('loader', () => { }); const stats = await compile(compiler); - expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( - 'result' - ); expect(getErrors(stats)).toMatchSnapshot('errors'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index a79571b..d28e50f 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -17,6 +17,7 @@ describe('validate options', () => { type: 'module', moduleName: 'jQuery', list: [ + 'lib', { name: 'lib', }, @@ -51,7 +52,7 @@ describe('validate options', () => { ], }, wrapper: { - success: [{ call: 'windows' }, { IIFE: 'windows' }], + success: ['window', ['window', 'document']], failure: [false, true, [], ['']], }, additionalCode: { From 77d3bc463af73b1325d287f38218dd4df8a04d85 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Tue, 9 Jun 2020 14:47:15 +0300 Subject: [PATCH 10/15] refactor: code --- src/index.js | 36 ++++++++++++++------------ src/options.json | 30 +++++++++++++-------- test/__snapshots__/loader.test.js.snap | 14 ++++++++++ 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/index.js b/src/index.js index 3183e1e..e1df11d 100644 --- a/src/index.js +++ b/src/index.js @@ -25,14 +25,14 @@ export default function loader(content, sourceMap) { let moduleImports; - const imports = []; + let imports = ''; if (moduleImport) { moduleImports = Array.isArray(moduleImport) ? moduleImport : [moduleImport]; try { moduleImports.forEach((importEntry) => { - imports.push(renderImport(importEntry)); + imports += `${renderImport(importEntry)}\n`; }); } catch (error) { callback(error, content, sourceMap); @@ -41,24 +41,22 @@ export default function loader(content, sourceMap) { } } - let prefix = ''; - let postfix = ''; + let importString = `/*** IMPORTS FROM imports-loader ***/\n${imports}`; - const { wrapper } = options; + const { additionalCode } = options; - if (wrapper) { - prefix += '\n(function() {'; - postfix += `\n}.call(${wrapper.toString()}));`; + if (additionalCode) { + importString += `\n${additionalCode}`; } - let importString = `/*** IMPORTS FROM imports-loader ***/\n${imports.join( - '\n' - )}`; + let codeBeforeModule = ''; + let codeAfterModule = ''; - const { additionalCode } = options; + const { wrapper } = options; - if (additionalCode) { - importString += `\n${additionalCode}`; + if (wrapper) { + codeBeforeModule += '\n(function() {'; + codeAfterModule += `\n}.call(${wrapper.toString()}));`; } if (this.sourceMap && sourceMap) { @@ -67,8 +65,8 @@ export default function loader(content, sourceMap) { new SourceMapConsumer(sourceMap) ); - node.prepend(`${importString}${prefix}\n`); - node.add(postfix); + node.prepend(`${importString}${codeBeforeModule}\n`); + node.add(codeAfterModule); const result = node.toStringWithSourceMap({ file: getCurrentRequest(this), @@ -79,5 +77,9 @@ export default function loader(content, sourceMap) { return; } - callback(null, `${importString}${prefix}\n${content}${postfix}`, sourceMap); + callback( + null, + `${importString}${codeBeforeModule}\n${content}${codeAfterModule}`, + sourceMap + ); } diff --git a/src/options.json b/src/options.json index d3f8289..efaa7a0 100644 --- a/src/options.json +++ b/src/options.json @@ -4,10 +4,10 @@ "type": "object", "properties": { "name": { - "$ref": "#/definitions/StringPattern" + "$ref": "#/definitions/ListStringPattern" }, "alias": { - "$ref": "#/definitions/StringPattern" + "$ref": "#/definitions/ListStringPattern" }, "type": { "enum": ["namespace", "default"] @@ -22,7 +22,8 @@ "enum": ["module", "commonjs"] }, "moduleName": { - "$ref": "#/definitions/StringPattern" + "type": "string", + "minLength": 1 }, "list": { "anyOf": [ @@ -30,7 +31,7 @@ "type": "boolean" }, { - "$ref": "#/definitions/StringPattern" + "$ref": "#/definitions/ListStringPattern" }, { "$ref": "#/definitions/NamesObjectPattern" @@ -41,7 +42,7 @@ "items": { "anyOf": [ { - "$ref": "#/definitions/StringPattern" + "$ref": "#/definitions/ListStringPattern" }, { "$ref": "#/definitions/NamesObjectPattern" @@ -53,7 +54,11 @@ } } }, - "StringPattern": { + "ImportsStringPattern": { + "type": "string", + "minLength": 1 + }, + "ListStringPattern": { "type": "string", "minLength": 1 } @@ -63,7 +68,7 @@ "imports": { "anyOf": [ { - "$ref": "#/definitions/StringPattern" + "$ref": "#/definitions/ImportsStringPattern" }, { "$ref": "#/definitions/ObjectPattern" @@ -74,7 +79,7 @@ "items": { "anyOf": [ { - "$ref": "#/definitions/StringPattern" + "$ref": "#/definitions/ImportsStringPattern" }, { "$ref": "#/definitions/ObjectPattern" @@ -87,19 +92,22 @@ "wrapper": { "anyOf": [ { - "$ref": "#/definitions/StringPattern" + "type": "string", + "minLength": 1 }, { "type": "array", "minItems": 1, "items": { - "$ref": "#/definitions/StringPattern" + "type": "string", + "minLength": 1 } } ] }, "additionalCode": { - "$ref": "#/definitions/StringPattern" + "type": "string", + "minLength": 1 } }, "additionalProperties": false diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 02587f5..ef764e1 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -24,6 +24,7 @@ exports[`loader should require when import option is array: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import lib_1 from \\"lib_1\\"; import lib_2 from \\"lib_2\\"; + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -38,6 +39,7 @@ exports[`loader should require when import option is filePath: errors 1`] = `Arr exports[`loader should require when import option is filePath: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import $ from \\"./lib_1\\"; + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -52,6 +54,7 @@ exports[`loader should require when import option is object: errors 1`] = `Array exports[`loader should require when import option is object: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import $ from \\"./lib_1\\"; + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -66,6 +69,7 @@ exports[`loader should require when import option is string: errors 1`] = `Array exports[`loader should require when import option is string: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import lib_1 from \\"lib_1\\"; + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -83,6 +87,7 @@ import lib_1 from \\"lib_1\\"; import lib_2 from \\"./lib_2.js\\"; import defaultExport, { lib_3_method as method } from \\"./lib_3.js\\"; import lib_4, * as lib_4_all from \\"./lib_4\\"; + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -97,6 +102,7 @@ exports[`loader should require when import-side-effect: errors 1`] = `Array []`; exports[`loader should require when import-side-effect: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import \\"./lib_1\\"; + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -111,6 +117,7 @@ exports[`loader should require when name-space-import: errors 1`] = `Array []`; exports[`loader should require when name-space-import: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import * as lib_1_all from \\"./lib_1\\"; + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -126,6 +133,7 @@ exports[`loader should require when named-imports: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import { lib1_method } from \\"./lib_1\\"; import lib2_default, { lib2_method_1, lib2_method_2 as lib_2_method_2_short } from \\"./lib_2\\"; + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -155,6 +163,7 @@ exports[`loader should work destructuring require: errors 1`] = `Array []`; exports[`loader should work destructuring require: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ var { lib2_method_1, lib2_method_2: lib_2_method_2_short } = require(\\"./lib_2\\"); + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -170,6 +179,7 @@ exports[`loader should work few require: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ var $ = require(\\"./lib_1\\"); var { lib2_method_1, lib2_method_2: lib_2_method_2_short } = require(\\"./lib_2\\"); + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -184,6 +194,7 @@ exports[`loader should work import, wrapper and additionalCode option: errors 1` exports[`loader should work import, wrapper and additionalCode option: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ import \\"./lib_1\\"; + var someVariable = 1; (function() { var someCode = { @@ -201,6 +212,7 @@ exports[`loader should work pure require: errors 1`] = `Array []`; exports[`loader should work pure require: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ require(\\"./lib_1\\"); + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -215,6 +227,7 @@ exports[`loader should work require default: errors 1`] = `Array []`; exports[`loader should work require default: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ var $ = require(\\"./lib_1\\"); + var someCode = { number: 123, object: { existingSubProperty: 123 } @@ -229,6 +242,7 @@ exports[`loader should work require: errors 1`] = `Array []`; exports[`loader should work require: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ var $ = require(\\"./lib_1\\"); + var someCode = { number: 123, object: { existingSubProperty: 123 } From be1b7be18b430652af32e66c8194e376f562c107 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Tue, 9 Jun 2020 18:03:43 +0300 Subject: [PATCH 11/15] refactor: code --- src/index.js | 37 +++---- src/utils.js | 147 ++++++++++++++++++------- test/__snapshots__/loader.test.js.snap | 21 +++- test/loader.test.js | 24 ++++ 4 files changed, 170 insertions(+), 59 deletions(-) diff --git a/src/index.js b/src/index.js index e1df11d..12645dd 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ import validateOptions from 'schema-utils'; import schema from './options.json'; -import renderImport from './utils'; +import { getImports, renderImports } from './utils'; export default function loader(content, sourceMap) { const options = getOptions(this) || {}; @@ -21,32 +21,27 @@ export default function loader(content, sourceMap) { const callback = this.async(); - const moduleImport = options.imports; + let imports; - let moduleImports; + try { + imports = getImports(options); + } catch (error) { + callback(error); - let imports = ''; - - if (moduleImport) { - moduleImports = Array.isArray(moduleImport) ? moduleImport : [moduleImport]; - - try { - moduleImports.forEach((importEntry) => { - imports += `${renderImport(importEntry)}\n`; - }); - } catch (error) { - callback(error, content, sourceMap); - - return; - } + return; } - let importString = `/*** IMPORTS FROM imports-loader ***/\n${imports}`; + const loaderContext = this; + const importsCode = imports.reduce((acc, importsEntry) => { + return `${acc}${renderImports(loaderContext, importsEntry)}\n`; + }, ''); + + let finalImportsCode = `/*** IMPORTS FROM imports-loader ***/\n${importsCode}`; const { additionalCode } = options; if (additionalCode) { - importString += `\n${additionalCode}`; + finalImportsCode += `\n${additionalCode}`; } let codeBeforeModule = ''; @@ -65,7 +60,7 @@ export default function loader(content, sourceMap) { new SourceMapConsumer(sourceMap) ); - node.prepend(`${importString}${codeBeforeModule}\n`); + node.prepend(`${finalImportsCode}${codeBeforeModule}\n`); node.add(codeAfterModule); const result = node.toStringWithSourceMap({ @@ -79,7 +74,7 @@ export default function loader(content, sourceMap) { callback( null, - `${importString}${codeBeforeModule}\n${content}${codeAfterModule}`, + `${finalImportsCode}${codeBeforeModule}\n${content}${codeAfterModule}`, sourceMap ); } diff --git a/src/utils.js b/src/utils.js index 8f20bd1..7e0c508 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,57 +1,115 @@ -function renderImport(params) { - const isCommonJs = params.type === 'commonjs'; +import { stringifyRequest } from 'loader-utils'; - const importEntry = - typeof params === 'string' - ? { moduleName: params, list: { name: params, type: 'default' } } - : { ...params }; +function getImports(options) { + const { imports, additionalCode, wrapper } = options; + const moduleImports = Array.isArray(imports) ? imports : [imports]; - let { list } = importEntry; + if (!imports && !additionalCode && !wrapper) { + throw new Error( + `You must fill out one of the options "imports", "wrapper" or "additionalCode"` + ); + } + + if (!imports) { + return []; + } + + const result = []; + + for (const params of moduleImports) { + const isCommonJs = params.type === 'commonjs'; + + const importsEntry = + typeof params === 'string' + ? { moduleName: params, list: { name: params, type: 'default' } } + : { ...params }; + + let { list } = importsEntry; + + if (list === false) { + result.push(importsEntry); + break; + } + + list = Array.isArray(list) ? list : [list]; - const { moduleName } = importEntry; + importsEntry.list = list.map((entry) => { + const normalizedEntry = + typeof entry === 'string' ? { name: entry, type: 'default' } : entry; + + // 2. Default import + if (normalizedEntry.type === 'default') { + if (!normalizedEntry.name) { + throw new Error(`Skipped the "name" option for default import`); + } + + return normalizedEntry; + } + + // 3. Namespace import + if (normalizedEntry.type === 'namespace') { + if (isCommonJs) { + throw new Error('Commonjs not support namespace import'); + } + + if (!normalizedEntry.name) { + throw new Error(`Skipped the "name" option for namespace import`); + } + + return normalizedEntry; + } + + // 4. Named import + if (!normalizedEntry.name) { + throw new Error(`Skipped the "name" option for named import`); + } + + return normalizedEntry; + }); + + result.push(importsEntry); + } + + return result; +} + +function renderImports(loaderContext, importsEntry) { + const isCommonJs = importsEntry.type === 'commonjs'; + const { list, moduleName } = importsEntry; // 1. Import-side-effect if (list === false) { - return isCommonJs ? `require("${moduleName}");` : `import "${moduleName}";`; + return isCommonJs + ? `require(${stringifyRequest(loaderContext, moduleName)});` + : `import ${stringifyRequest(loaderContext, moduleName)};`; } - list = Array.isArray(list) ? list : [list]; - let defaultImport = ''; let namespaceImport = ''; let namedImports = ''; list.forEach((entry) => { - const normalizedEntry = - typeof entry === 'string' ? { name: entry, type: 'default' } : entry; - // 2. Default import - if (normalizedEntry.type === 'default' && normalizedEntry.name) { - defaultImport += `${normalizedEntry.name}`; + if (entry.type === 'default') { + defaultImport += `${entry.name}`; return; } // 3. Namespace import - if (normalizedEntry.type === 'namespace' && normalizedEntry.name) { - if (isCommonJs) { - throw new Error('Commonjs not support namespace import'); - } - - namespaceImport += `* as ${normalizedEntry.name}`; + if (entry.type === 'namespace') { + namespaceImport += `* as ${entry.name}`; return; } // 4. Named import - if (normalizedEntry.name) { - const sep = isCommonJs ? ': ' : ' as '; - const comma = namedImports ? ', ' : ''; + const sep = isCommonJs ? ': ' : ' as '; + const comma = namedImports ? ', ' : ''; - namedImports += normalizedEntry.alias - ? `${comma}${normalizedEntry.name}${sep}${normalizedEntry.alias}` - : `${comma}${normalizedEntry.name}`; - } + namedImports += entry.alias + ? `${comma}${entry.name}${sep}${entry.alias}` + : `${comma}${entry.name}`; }); let notDefaultImport = namespaceImport; @@ -60,21 +118,36 @@ function renderImport(params) { notDefaultImport = `{ ${namedImports} }`; } - if (!defaultImport && !notDefaultImport) { - throw new Error(`Not enough data to import \n${importEntry}`); - } - if (!isCommonJs) { const comma = defaultImport && notDefaultImport ? ', ' : ''; - return `import ${defaultImport}${comma}${notDefaultImport} from "${moduleName}";`; + return `import ${defaultImport}${comma}${notDefaultImport} from ${stringifyRequest( + loaderContext, + moduleName + )};`; } + let commonjsImports = ''; + if (defaultImport) { - return `var ${defaultImport} = require("${moduleName}");`; + commonjsImports += `var ${defaultImport} = require(${stringifyRequest( + loaderContext, + moduleName + )});`; } - return `var { ${namedImports} } = require("${moduleName}");`; + if (!namedImports) { + return commonjsImports; + } + + commonjsImports += commonjsImports ? '\n' : ''; + + commonjsImports += `var { ${namedImports} } = require(${stringifyRequest( + loaderContext, + moduleName + )});`; + + return commonjsImports; } -export default renderImport; +export { getImports, renderImports }; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index ef764e1..9ee2965 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -3,12 +3,30 @@ exports[`loader should emit error when invalid arguments for import: errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): -Error: Not enough data to import ", +Error: Skipped the \\"name\\" option for named import", ] `; exports[`loader should emit error when invalid arguments for import: warnings 1`] = `Array []`; +exports[`loader should emit error when not arguments for import: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: You must fill out one of the options \\"imports\\", \\"wrapper\\" or \\"additionalCode\\"", +] +`; + +exports[`loader should emit error when not arguments for import: warnings 1`] = `Array []`; + +exports[`loader should emit error when skipped name to import-default: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: Skipped the \\"name\\" option for default import", +] +`; + +exports[`loader should emit error when skipped name to import-default: warnings 1`] = `Array []`; + exports[`loader should emit error when try namespace import to commonjs: errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): @@ -178,6 +196,7 @@ exports[`loader should work few require: errors 1`] = `Array []`; exports[`loader should work few require: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ var $ = require(\\"./lib_1\\"); +var lib_2_all = require(\\"./lib_2\\"); var { lib2_method_1, lib2_method_2: lib_2_method_2_short } = require(\\"./lib_2\\"); var someCode = { diff --git a/test/loader.test.js b/test/loader.test.js index 62be48f..82067d9 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -320,6 +320,7 @@ describe('loader', () => { type: 'commonjs', moduleName: './lib_2', list: [ + 'lib_2_all', { name: 'lib2_method_1', }, @@ -357,6 +358,21 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + it('should emit error when skipped name to import-default', async () => { + const compiler = getCompiler('some-library.js', { + imports: { + moduleName: './lib_2.js', + list: { + type: 'default', + }, + }, + }); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + it('should emit error when try namespace import to commonjs', async () => { const compiler = getCompiler('some-library.js', { imports: [ @@ -396,4 +412,12 @@ describe('loader', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + + it('should emit error when not arguments for import', async () => { + const compiler = getCompiler('some-library.js', {}); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); }); From fef7f3ed0c7823ac513f6a7f4229b595e026d338 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Tue, 9 Jun 2020 19:13:10 +0300 Subject: [PATCH 12/15] refactor: code --- README.md | 69 +++++++++++++++++++++++++++++++++++----------------- src/index.js | 5 ++-- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index e289d6a..4bd419c 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,38 @@ then you can inject the `jquery` variable into the module by configuring the imp **index.js** ```js -require('imports-loader?$=jquery!./example.js'); +require('./example.js'); +``` + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + imports: { + moduleName: 'jquery', + list: '$', + }, + }, + }, + ], + }, + ], + }, +}; ``` This simply prepends `import jquery from "jquery";` to `example.js`. +## Inline syntax + **index.js** ```js @@ -112,7 +139,7 @@ module.exports = { Type: `String|Object|Array` Default: `undefined` -- `String` +#### String **webpack.config.js** @@ -138,7 +165,7 @@ module.exports = { Result: `import jquery from "jquery";`. -- `Array` +#### Array **webpack.config.js** @@ -175,7 +202,7 @@ import angular from 'angular'; import $ from 'jquery'; ``` -- `Object` +#### Object **webpack.config.js** @@ -204,17 +231,16 @@ module.exports = { Result: `import $ from "jquery";`. -##### Type +Options: `type`, `moduleName`, `list` + +##### type Type: `String` Default: `module` The type of the module to import (`import` or `require`). -Possible values: - -- `module`, -- `commonjs` +Possible values: `module`, `commonjs` **webpack.config.js** @@ -244,21 +270,21 @@ module.exports = { Result: `var $ = require("jquery");` -##### ModuleName +##### moduleName Type: `String` Default: `undefined` The name of the module to import (`jquery`, `lodash`, `./example-file.js`). -##### List +##### list Type: `String|Boolean|Object|Array` Default: `undefined` Сonfigures import string. -- `Boolean` +###### Boolean Lets you make a namespace import or pure require @@ -289,11 +315,11 @@ module.exports = { Result: `import "some-module";` -- `String` +###### String Reduced to the object `{name: passedValue, type: 'default'}` -- `Array` +###### Array ```js module.exports = { @@ -329,9 +355,11 @@ module.exports = { Result: `import nameDefaultImport, { method_1, method_2 as method_2_alias } from "some-module"`. -- `Object` +###### Object + +Options: `name`, `alias`, `type` -###### name +> name Type: `String` Default: `undefined` @@ -341,7 +369,7 @@ Export name. `import { name as alias } from "some-module"`. `import { name } from "some-module"`. -###### alias +> alias Type: `String` Default: `undefined` @@ -350,17 +378,14 @@ Alias for export name. `import { name as alias } from "some-module"`. -###### type +> type Type: `String` Default: `undefined` Type export name -Possible values: - -- `default`, -- `namespace` +Possible values: `default`, `namespace` ```js module.exports = { diff --git a/src/index.js b/src/index.js index 12645dd..0bfe732 100644 --- a/src/index.js +++ b/src/index.js @@ -31,9 +31,8 @@ export default function loader(content, sourceMap) { return; } - const loaderContext = this; - const importsCode = imports.reduce((acc, importsEntry) => { - return `${acc}${renderImports(loaderContext, importsEntry)}\n`; + const importsCode = imports.reduce((acc, item) => { + return `${acc}${renderImports(this, item)}\n`; }, ''); let finalImportsCode = `/*** IMPORTS FROM imports-loader ***/\n${importsCode}`; From 24fcd3d2615604684fe78c6985f74a2eaae3098d Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Wed, 10 Jun 2020 20:22:29 +0300 Subject: [PATCH 13/15] refactor: code --- src/index.js | 17 +- src/options.json | 58 +--- src/utils.js | 243 +++++++++-------- test/__snapshots__/loader.test.js.snap | 97 +++++-- .../validate-options.test.js.snap | 67 +++-- test/fixtures/inline-broken.js | 1 + test/fixtures/inline.js | 1 + test/fixtures/inline2.js | 1 + test/fixtures/inline3.js | 2 + test/helpers/getCompiler.js | 33 ++- test/helpers/getModuleSource.js | 3 +- test/loader.test.js | 251 ++++++++++-------- test/validate-options.test.js | 40 ++- 13 files changed, 476 insertions(+), 338 deletions(-) create mode 100644 test/fixtures/inline-broken.js create mode 100644 test/fixtures/inline.js create mode 100644 test/fixtures/inline2.js create mode 100644 test/fixtures/inline3.js diff --git a/src/index.js b/src/index.js index 0bfe732..5c6f901 100644 --- a/src/index.js +++ b/src/index.js @@ -19,20 +19,31 @@ export default function loader(content, sourceMap) { baseDataPath: 'options', }); + const type = options.type || 'module'; const callback = this.async(); let imports; try { - imports = getImports(options); + imports = getImports(type, options); } catch (error) { callback(error); return; } - const importsCode = imports.reduce((acc, item) => { - return `${acc}${renderImports(this, item)}\n`; + const importsSorted = {}; + + for (const item of imports) { + if (!importsSorted[item.moduleName]) { + importsSorted[item.moduleName] = []; + } + + importsSorted[item.moduleName].push(item); + } + + const importsCode = Object.entries(importsSorted).reduce((acc, item) => { + return `${acc}${renderImports(this, type, item[1])}\n`; }, ''); let finalImportsCode = `/*** IMPORTS FROM imports-loader ***/\n${importsCode}`; diff --git a/src/options.json b/src/options.json index efaa7a0..831cb59 100644 --- a/src/options.json +++ b/src/options.json @@ -1,70 +1,36 @@ { "definitions": { - "NamesObjectPattern": { - "type": "object", - "properties": { - "name": { - "$ref": "#/definitions/ListStringPattern" - }, - "alias": { - "$ref": "#/definitions/ListStringPattern" - }, - "type": { - "enum": ["namespace", "default"] - } - } - }, "ObjectPattern": { "type": "object", "additionalProperties": false, "properties": { - "type": { - "enum": ["module", "commonjs"] + "syntax": { + "enum": ["default", "named", "namespace", "side-effect"] }, "moduleName": { "type": "string", "minLength": 1 }, - "list": { - "anyOf": [ - { - "type": "boolean" - }, - { - "$ref": "#/definitions/ListStringPattern" - }, - { - "$ref": "#/definitions/NamesObjectPattern" - }, - { - "type": "array", - "minItems": 1, - "items": { - "anyOf": [ - { - "$ref": "#/definitions/ListStringPattern" - }, - { - "$ref": "#/definitions/NamesObjectPattern" - } - ] - } - } - ] + "name": { + "type": "string", + "minLength": 1 + }, + "alias": { + "type": "string", + "minLength": 1 } } }, "ImportsStringPattern": { "type": "string", "minLength": 1 - }, - "ListStringPattern": { - "type": "string", - "minLength": 1 } }, "type": "object", "properties": { + "type": { + "enum": ["module", "commonjs"] + }, "imports": { "anyOf": [ { diff --git a/src/utils.js b/src/utils.js index 7e0c508..9e93a80 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,153 +1,180 @@ import { stringifyRequest } from 'loader-utils'; -function getImports(options) { - const { imports, additionalCode, wrapper } = options; - const moduleImports = Array.isArray(imports) ? imports : [imports]; - - if (!imports && !additionalCode && !wrapper) { - throw new Error( - `You must fill out one of the options "imports", "wrapper" or "additionalCode"` - ); - } - - if (!imports) { - return []; - } - - const result = []; - - for (const params of moduleImports) { - const isCommonJs = params.type === 'commonjs'; +function resolveImports(type, item) { + let result; - const importsEntry = - typeof params === 'string' - ? { moduleName: params, list: { name: params, type: 'default' } } - : { ...params }; + if (typeof item === 'string') { + const splittedItem = item.split(' '); - let { list } = importsEntry; + if (splittedItem.length > 4) { + throw new Error(`Invalid "${item}" for import`); + } - if (list === false) { - result.push(importsEntry); - break; + if (splittedItem.length === 1) { + result = { + type, + syntax: 'default', + moduleName: splittedItem[0], + name: splittedItem[0], + // eslint-disable-next-line no-undefined + alias: undefined, + }; + } else { + result = { + syntax: splittedItem[0], + moduleName: splittedItem[1], + name: splittedItem[2], + // eslint-disable-next-line no-undefined + alias: splittedItem[3] ? splittedItem[3] : undefined, + }; } + } else { + const defaultOptions = { + type: 'module', + syntax: 'default', + }; + + result = { ...defaultOptions, ...item }; + } - list = Array.isArray(list) ? list : [list]; + if ( + ['default', 'side-effect'].includes(result.syntax) && + typeof result.alias !== 'undefined' + ) { + throw new Error( + `The "${result.syntax}" syntax can't have "${result.alias}" alias in "${item}" value` + ); + } - importsEntry.list = list.map((entry) => { - const normalizedEntry = - typeof entry === 'string' ? { name: entry, type: 'default' } : entry; + if ( + ['side-effect'].includes(result.syntax) && + typeof result.name !== 'undefined' + ) { + throw new Error( + `The "${result.syntax}" syntax can't have "${result.name}" name in "${item}" value` + ); + } - // 2. Default import - if (normalizedEntry.type === 'default') { - if (!normalizedEntry.name) { - throw new Error(`Skipped the "name" option for default import`); - } + if (['namespace'].includes(result.syntax) && type === 'commonjs') { + throw new Error( + `The "commonjs" type not support "namespace" syntax import in "${item}" value` + ); + } - return normalizedEntry; - } + if ( + ['default', 'namespace', 'named'].includes(result.syntax) && + typeof result.name === 'undefined' + ) { + throw new Error( + `The "${result.syntax}" syntax should have "name" option in "${item}" value` + ); + } - // 3. Namespace import - if (normalizedEntry.type === 'namespace') { - if (isCommonJs) { - throw new Error('Commonjs not support namespace import'); - } + return result; +} - if (!normalizedEntry.name) { - throw new Error(`Skipped the "name" option for namespace import`); - } +function getImports(type, options) { + const { imports, additionalCode, wrapper } = options; - return normalizedEntry; - } + if (!imports && !additionalCode && !wrapper) { + throw new Error( + `You must fill out one of the options "imports", "wrapper" or "additionalCode"` + ); + } - // 4. Named import - if (!normalizedEntry.name) { - throw new Error(`Skipped the "name" option for named import`); - } + if (!imports) { + return []; + } - return normalizedEntry; - }); + let result = []; - result.push(importsEntry); + if (typeof imports === 'string') { + result.push(resolveImports(type, imports)); + } else { + result = [].concat(imports).map((item) => resolveImports(type, item)); } return result; } -function renderImports(loaderContext, importsEntry) { - const isCommonJs = importsEntry.type === 'commonjs'; - const { list, moduleName } = importsEntry; +function renderImports(loaderContext, type, imports) { + const [{ moduleName }] = imports; + const defaultImports = imports.filter((item) => item.syntax === 'default'); + const namedImports = imports.filter((item) => item.syntax === 'named'); + const namespaceImports = imports.filter( + (item) => item.syntax === 'namespace' + ); + const sideEffectImports = imports.filter( + (item) => item.syntax === 'side-effect' + ); // 1. Import-side-effect - if (list === false) { - return isCommonJs + if (sideEffectImports.length > 0) { + return type === 'commonjs' ? `require(${stringifyRequest(loaderContext, moduleName)});` : `import ${stringifyRequest(loaderContext, moduleName)};`; } - let defaultImport = ''; - let namespaceImport = ''; - let namedImports = ''; - - list.forEach((entry) => { - // 2. Default import - if (entry.type === 'default') { - defaultImport += `${entry.name}`; - - return; + let code = type === 'commonjs' ? '' : 'import'; + + // 2. Default import + if (defaultImports.length > 0) { + const [{ name }] = defaultImports; + + // eslint-disable-next-line default-case + switch (type) { + case 'commonjs': + code += `var ${name} = require(${stringifyRequest( + loaderContext, + moduleName + )});`; + break; + case 'module': + code += ` ${name}`; + break; } + } - // 3. Namespace import - if (entry.type === 'namespace') { - namespaceImport += `* as ${entry.name}`; - - return; + // 3. Namespace import + if (namespaceImports.length > 0) { + if (defaultImports.length > 0) { + code += `,`; } - // 4. Named import - const sep = isCommonJs ? ': ' : ' as '; - const comma = namedImports ? ', ' : ''; + const [{ name }] = namespaceImports; - namedImports += entry.alias - ? `${comma}${entry.name}${sep}${entry.alias}` - : `${comma}${entry.name}`; - }); - - let notDefaultImport = namespaceImport; - - if (namedImports) { - notDefaultImport = `{ ${namedImports} }`; + code += ` * as ${name}`; } - if (!isCommonJs) { - const comma = defaultImport && notDefaultImport ? ', ' : ''; + // 4. Named import + if (namedImports.length > 0) { + if (defaultImports.length > 0) { + code += type === 'commonjs' ? '\nvar { ' : ', { '; + } else { + code += type === 'commonjs' ? 'var { ' : ' { '; + } - return `import ${defaultImport}${comma}${notDefaultImport} from ${stringifyRequest( - loaderContext, - moduleName - )};`; - } + namedImports.forEach((namedImport, i) => { + const comma = i > 0 ? ', ' : ''; + const { name, alias } = namedImport; + const sep = type === 'commonjs' ? ': ' : ' as '; - let commonjsImports = ''; + code += alias ? `${comma}${name}${sep}${alias}` : `${comma}${name}`; + }); - if (defaultImport) { - commonjsImports += `var ${defaultImport} = require(${stringifyRequest( - loaderContext, - moduleName - )});`; + code += + type === 'commonjs' + ? ` } = require(${stringifyRequest(loaderContext, moduleName)});` + : ' }'; } - if (!namedImports) { - return commonjsImports; + if (type === 'commonjs') { + return code; } - commonjsImports += commonjsImports ? '\n' : ''; - - commonjsImports += `var { ${namedImports} } = require(${stringifyRequest( - loaderContext, - moduleName - )});`; + code += ` from ${stringifyRequest(loaderContext, moduleName)};`; - return commonjsImports; + return code; } export { getImports, renderImports }; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 9ee2965..8ceac80 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -1,9 +1,36 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`loader should emit error inline: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: Invalid \\"named lib_2 name alias something\\" for import", +] +`; + +exports[`loader should emit error inline: warnings 1`] = `Array []`; + +exports[`loader should emit error when alias don\`t need: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: The \\"side-effect\\" syntax can't have \\"some_alias\\" alias in \\"[object Object]\\" value", +] +`; + +exports[`loader should emit error when alias don\`t need: errors 2`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: The \\"side-effect\\" syntax can't have \\"some_name\\" name in \\"[object Object]\\" value", +] +`; + +exports[`loader should emit error when alias don\`t need: warnings 1`] = `Array []`; + +exports[`loader should emit error when alias don\`t need: warnings 2`] = `Array []`; + exports[`loader should emit error when invalid arguments for import: errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): -Error: Skipped the \\"name\\" option for named import", +Error: The \\"default\\" syntax can't have \\"lib_2_method_2_short\\" alias in \\"[object Object]\\" value", ] `; @@ -21,7 +48,7 @@ exports[`loader should emit error when not arguments for import: warnings 1`] = exports[`loader should emit error when skipped name to import-default: errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): -Error: Skipped the \\"name\\" option for default import", +Error: The \\"default\\" syntax should have \\"name\\" option in \\"[object Object]\\" value", ] `; @@ -30,7 +57,7 @@ exports[`loader should emit error when skipped name to import-default: warnings exports[`loader should emit error when try namespace import to commonjs: errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): -Error: Commonjs not support namespace import", +Error: The \\"commonjs\\" type not support \\"namespace\\" syntax import in \\"[object Object]\\" value", ] `; @@ -52,21 +79,6 @@ var someCode = { exports[`loader should require when import option is array: warnings 1`] = `Array []`; -exports[`loader should require when import option is filePath: errors 1`] = `Array []`; - -exports[`loader should require when import option is filePath: result 1`] = ` -"/*** IMPORTS FROM imports-loader ***/ -import $ from \\"./lib_1\\"; - -var someCode = { - number: 123, - object: { existingSubProperty: 123 } -}; -" -`; - -exports[`loader should require when import option is filePath: warnings 1`] = `Array []`; - exports[`loader should require when import option is object: errors 1`] = `Array []`; exports[`loader should require when import option is object: result 1`] = ` @@ -226,6 +238,55 @@ var someCode = { exports[`loader should work import, wrapper and additionalCode option: warnings 1`] = `Array []`; +exports[`loader should work inline 1: errors 1`] = `Array []`; + +exports[`loader should work inline 1: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ +import lib_1 from \\"lib_1\\"; +import lib_2 from \\"lib_2\\"; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; + +exports[`loader should work inline 1: warnings 1`] = `Array []`; + +exports[`loader should work inline 2: errors 1`] = `Array []`; + +exports[`loader should work inline 2: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ +var lib_2_all = require(\\"lib_2\\"); +var { lib_2_method: lib_2_method_alias } = require(\\"lib_2\\"); + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; + +exports[`loader should work inline 2: warnings 1`] = `Array []`; + +exports[`loader should work inline 3: errors 1`] = `Array []`; + +exports[`loader should work inline 3: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ +import $ from \\"lib_2\\"; + +(function() { +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +}.call(window));" +`; + +exports[`loader should work inline 3: warnings 1`] = `Array []`; + exports[`loader should work pure require: errors 1`] = `Array []`; exports[`loader should work pure require: result 1`] = ` diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index d9306d5..be9e11b 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -35,7 +35,7 @@ exports[`validate options should throw an error on the "imports" option with "" - options.imports should be an non-empty string." `; -exports[`validate options should throw an error on the "imports" option with "/test/" value 1`] = `"Cannot read property 'type' of undefined"`; +exports[`validate options should throw an error on the "imports" option with "/test/" value 1`] = `"The \\"default\\" syntax should have \\"name\\" option in \\"/test/\\" value"`; exports[`validate options should throw an error on the "imports" option with "[""]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. @@ -47,84 +47,115 @@ exports[`validate options should throw an error on the "imports" option with "[] - options.imports should be an non-empty array." `; +exports[`validate options should throw an error on the "imports" option with "{"syntax":"default","moduleName":"jQuery","name":"lib","alias":"lib_alias"}" value 1`] = `"The \\"default\\" syntax can't have \\"lib_alias\\" alias in \\"[object Object]\\" value"`; + exports[`validate options should throw an error on the "imports" option with "{"type":"string","moduleName":"jQuery","list":false}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options.imports.type should be one of these: - \\"module\\" | \\"commonjs\\"" + - options.imports should be one of these: + non-empty string | object { syntax?, moduleName?, name?, alias? } | [non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item) + Details: + * options.imports has an unknown property 'type'. These properties are valid: + object { syntax?, moduleName?, name?, alias? } + * options.imports has an unknown property 'list'. These properties are valid: + object { syntax?, moduleName?, name?, alias? }" `; -exports[`validate options should throw an error on the "imports" option with "{}" value 1`] = `"Cannot read property 'type' of undefined"`; +exports[`validate options should throw an error on the "imports" option with "{}" value 1`] = `"The \\"default\\" syntax should have \\"name\\" option in \\"[object Object]\\" value"`; exports[`validate options should throw an error on the "imports" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options.imports should be one of these: - non-empty string | object { type?, moduleName?, list? } | [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item) + non-empty string | object { syntax?, moduleName?, name?, alias? } | [non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item) Details: * options.imports should be a non-empty string. * options.imports should be an object: - object { type?, moduleName?, list? } + object { syntax?, moduleName?, name?, alias? } * options.imports should be an array: - [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "imports" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options.imports should be one of these: - non-empty string | object { type?, moduleName?, list? } | [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item) + non-empty string | object { syntax?, moduleName?, name?, alias? } | [non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item) Details: * options.imports should be a non-empty string. * options.imports should be an object: - object { type?, moduleName?, list? } + object { syntax?, moduleName?, name?, alias? } * options.imports should be an array: - [non-empty string | object { type?, moduleName?, list? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { syntax?, moduleName?, name?, alias? }, ...] (should not have fewer than 1 item)" +`; + +exports[`validate options should throw an error on the "type" option with "" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.type should be one of these: + \\"module\\" | \\"commonjs\\"" +`; + +exports[`validate options should throw an error on the "type" option with "[]" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.type should be one of these: + \\"module\\" | \\"commonjs\\"" +`; + +exports[`validate options should throw an error on the "type" option with "{}" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.type should be one of these: + \\"module\\" | \\"commonjs\\"" +`; + +exports[`validate options should throw an error on the "type" option with "string" value 1`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - options.type should be one of these: + \\"module\\" | \\"commonjs\\"" `; exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { imports?, wrapper?, additionalCode? }" + object { type?, imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { imports?, wrapper?, additionalCode? }" + object { type?, imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { imports?, wrapper?, additionalCode? }" + object { type?, imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { imports?, wrapper?, additionalCode? }" + object { type?, imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { imports?, wrapper?, additionalCode? }" + object { type?, imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { imports?, wrapper?, additionalCode? }" + object { type?, imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { imports?, wrapper?, additionalCode? }" + object { type?, imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { imports?, wrapper?, additionalCode? }" + object { type?, imports?, wrapper?, additionalCode? }" `; exports[`validate options should throw an error on the "wrapper" option with "[""]" value 1`] = ` diff --git a/test/fixtures/inline-broken.js b/test/fixtures/inline-broken.js new file mode 100644 index 0000000..e8d3ab0 --- /dev/null +++ b/test/fixtures/inline-broken.js @@ -0,0 +1 @@ +require('../../src/cjs.js?imports=named%20lib_2%20name%20alias%20something!./some-library.js'); diff --git a/test/fixtures/inline.js b/test/fixtures/inline.js new file mode 100644 index 0000000..12c0b59 --- /dev/null +++ b/test/fixtures/inline.js @@ -0,0 +1 @@ +require('../../src/cjs.js?imports[]=lib_1&imports[]=lib_2!./some-library.js'); diff --git a/test/fixtures/inline2.js b/test/fixtures/inline2.js new file mode 100644 index 0000000..5884ea9 --- /dev/null +++ b/test/fixtures/inline2.js @@ -0,0 +1 @@ +require('../../src/cjs.js?type=commonjs&imports[]=default%20lib_2%20lib_2_all&imports[]=named%20lib_2%20lib_2_method%20lib_2_method_alias!./some-library.js'); diff --git a/test/fixtures/inline3.js b/test/fixtures/inline3.js new file mode 100644 index 0000000..d8a3e41 --- /dev/null +++ b/test/fixtures/inline3.js @@ -0,0 +1,2 @@ +require('../../src/cjs.js?wrapper=window&imports=default%20lib_2%20$!./some-library.js'); + diff --git a/test/helpers/getCompiler.js b/test/helpers/getCompiler.js index 320ab69..89413b3 100644 --- a/test/helpers/getCompiler.js +++ b/test/helpers/getCompiler.js @@ -3,7 +3,26 @@ import path from 'path'; import webpack from 'webpack'; import { createFsFromVolume, Volume } from 'memfs'; -export default (fixture, loaderOptions = {}, config = {}) => { +export default ( + fixture, + loaderOptions = {}, + config = {}, + disableLoader = false +) => { + const loaders = []; + + if (!disableLoader) { + loaders.push({ + test: path.resolve(__dirname, '../fixtures', fixture), + use: [ + { + loader: path.resolve(__dirname, '../../src'), + options: loaderOptions || {}, + }, + ], + }); + } + const fullConfig = { mode: 'development', devtool: config.devtool || false, @@ -17,17 +36,7 @@ export default (fixture, loaderOptions = {}, config = {}) => { // libraryTarget: 'var', }, module: { - rules: [ - { - test: path.resolve(__dirname, '../fixtures', fixture), - use: [ - { - loader: path.resolve(__dirname, '../../src'), - options: loaderOptions || {}, - }, - ], - }, - ], + rules: loaders, }, plugins: [], resolve: { diff --git a/test/helpers/getModuleSource.js b/test/helpers/getModuleSource.js index 27e7cb9..610fc30 100644 --- a/test/helpers/getModuleSource.js +++ b/test/helpers/getModuleSource.js @@ -1,6 +1,7 @@ export default (name, stats) => { const { modules } = stats.toJson({ source: true }); - const module = modules.find((m) => m.name === name); + + const module = modules.find((m) => m.name.indexOf(name) !== -1); return module.source; }; diff --git a/test/loader.test.js b/test/loader.test.js index 82067d9..ca5e959 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -24,23 +24,7 @@ describe('loader', () => { const compiler = getCompiler('some-library.js', { imports: { moduleName: './lib_1', - list: '$', - }, - }); - const stats = await compile(compiler); - - expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( - 'result' - ); - expect(getErrors(stats)).toMatchSnapshot('errors'); - expect(getWarnings(stats)).toMatchSnapshot('warnings'); - }); - - it('should require when import option is filePath', async () => { - const compiler = getCompiler('some-library.js', { - imports: { - moduleName: './lib_1', - list: '$', + name: '$', }, }); const stats = await compile(compiler); @@ -70,37 +54,30 @@ describe('loader', () => { imports: [ 'lib_1', { + syntax: 'default', moduleName: './lib_2.js', - list: { - name: 'lib_2', - type: 'default', - }, + name: 'lib_2', + }, + { + syntax: 'default', + moduleName: './lib_3.js', + name: 'defaultExport', }, { + syntax: 'named', moduleName: './lib_3.js', - list: [ - { - name: 'defaultExport', - type: 'default', - }, - { - name: 'lib_3_method', - alias: 'method', - }, - ], + name: 'lib_3_method', + alias: 'method', + }, + { + syntax: 'default', + moduleName: './lib_4', + name: 'lib_4', }, { + syntax: 'namespace', moduleName: './lib_4', - list: [ - { - name: 'lib_4', - type: 'default', - }, - { - name: 'lib_4_all', - type: 'namespace', - }, - ], + name: 'lib_4_all', }, ], }); @@ -118,12 +95,8 @@ describe('loader', () => { imports: [ { moduleName: './lib_1', - list: [ - { - name: 'lib_1_all', - type: 'namespace', - }, - ], + name: 'lib_1_all', + syntax: 'namespace', }, ], }); @@ -140,25 +113,24 @@ describe('loader', () => { const compiler = getCompiler('some-library.js', { imports: [ { + syntax: 'named', moduleName: './lib_1', - list: [ - { - name: 'lib1_method', - }, - ], + name: 'lib1_method', + }, + { + moduleName: './lib_2', + name: 'lib2_default', }, { + syntax: 'named', moduleName: './lib_2', - list: [ - 'lib2_default', - { - name: 'lib2_method_1', - }, - { - name: 'lib2_method_2', - alias: 'lib_2_method_2_short', - }, - ], + name: 'lib2_method_1', + }, + { + syntax: 'named', + moduleName: './lib_2', + name: 'lib2_method_2', + alias: 'lib_2_method_2_short', }, ], }); @@ -175,7 +147,7 @@ describe('loader', () => { const compiler = getCompiler('some-library.js', { imports: { moduleName: './lib_1', - list: false, + syntax: 'side-effect', }, }); const stats = await compile(compiler); @@ -230,7 +202,7 @@ describe('loader', () => { const compiler = getCompiler('some-library.js', { imports: { moduleName: './lib_1', - list: false, + syntax: 'side-effect', }, wrapper: 'window', additionalCode: 'var someVariable = 1;', @@ -246,10 +218,10 @@ describe('loader', () => { it('should work require', async () => { const compiler = getCompiler('some-library.js', { + type: 'commonjs', imports: { - type: 'commonjs', moduleName: './lib_1', - list: '$', + name: '$', }, }); const stats = await compile(compiler); @@ -263,13 +235,11 @@ describe('loader', () => { it('should work require default', async () => { const compiler = getCompiler('some-library.js', { + type: 'commonjs', imports: { - type: 'commonjs', moduleName: './lib_1', - list: { - name: '$', - type: 'default', - }, + name: '$', + syntax: 'default', }, }); const stats = await compile(compiler); @@ -283,19 +253,18 @@ describe('loader', () => { it('should work destructuring require', async () => { const compiler = getCompiler('some-library.js', { + type: 'commonjs', imports: [ { - type: 'commonjs', + syntax: 'named', + moduleName: './lib_2', + name: 'lib2_method_1', + }, + { + syntax: 'named', moduleName: './lib_2', - list: [ - { - name: 'lib2_method_1', - }, - { - name: 'lib2_method_2', - alias: 'lib_2_method_2_short', - }, - ], + name: 'lib2_method_2', + alias: 'lib_2_method_2_short', }, ], }); @@ -310,25 +279,26 @@ describe('loader', () => { it('should work few require', async () => { const compiler = getCompiler('some-library.js', { + type: 'commonjs', imports: [ { - type: 'commonjs', moduleName: './lib_1', - list: '$', + name: '$', + }, + { + moduleName: './lib_2', + name: 'lib_2_all', }, { - type: 'commonjs', + syntax: 'named', moduleName: './lib_2', - list: [ - 'lib_2_all', - { - name: 'lib2_method_1', - }, - { - name: 'lib2_method_2', - alias: 'lib_2_method_2_short', - }, - ], + name: 'lib2_method_1', + }, + { + syntax: 'named', + moduleName: './lib_2', + name: 'lib2_method_2', + alias: 'lib_2_method_2_short', }, ], }); @@ -343,10 +313,10 @@ describe('loader', () => { it('should work pure require', async () => { const compiler = getCompiler('some-library.js', { + type: 'commonjs', imports: { - type: 'commonjs', + syntax: 'side-effect', moduleName: './lib_1', - list: false, }, }); const stats = await compile(compiler); @@ -358,13 +328,39 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + it('should emit error when alias don`t need', async () => { + const compiler = getCompiler('some-library.js', { + imports: { + moduleName: './lib_1', + syntax: 'side-effect', + alias: 'some_alias', + }, + }); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should emit error when alias don`t need', async () => { + const compiler = getCompiler('some-library.js', { + imports: { + moduleName: './lib_1', + syntax: 'side-effect', + name: 'some_name', + }, + }); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + it('should emit error when skipped name to import-default', async () => { const compiler = getCompiler('some-library.js', { imports: { moduleName: './lib_2.js', - list: { - type: 'default', - }, + syntax: 'default', }, }); const stats = await compile(compiler); @@ -375,16 +371,12 @@ describe('loader', () => { it('should emit error when try namespace import to commonjs', async () => { const compiler = getCompiler('some-library.js', { + type: 'commonjs', imports: [ { - type: 'commonjs', moduleName: './lib_4', - list: [ - { - name: 'lib_4_all', - type: 'namespace', - }, - ], + name: 'lib_4_all', + syntax: 'namespace', }, ], }); @@ -399,11 +391,7 @@ describe('loader', () => { imports: [ { moduleName: './lib_2', - list: [ - { - alias: 'lib_2_method_2_short', - }, - ], + alias: 'lib_2_method_2_short', }, ], }); @@ -420,4 +408,45 @@ describe('loader', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + + it('should work inline 1', async () => { + const compiler = getCompiler('inline.js', {}, {}, true); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should work inline 2', async () => { + const compiler = getCompiler('inline2.js', {}, {}, true); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should work inline 3', async () => { + const compiler = getCompiler('inline3.js', {}, {}, true); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should emit error inline', async () => { + const compiler = getCompiler('inline-broken.js', {}, {}, true); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index d28e50f..aeffc8e 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -2,6 +2,10 @@ import { getCompiler, compile } from './helpers'; describe('validate options', () => { const tests = { + type: { + success: ['module', 'commonjs'], + failure: ['string', '', {}, []], + }, imports: { success: [ 'lib_1', @@ -9,31 +13,19 @@ describe('validate options', () => { ['globalObject1'], ['globalObject1.foo'], { - type: 'commonjs', moduleName: 'jQuery', - list: '$', + name: '$', + }, + { + syntax: 'named', + moduleName: 'jQuery', + name: 'lib', + alias: 'lib_alias', }, { - type: 'module', + syntax: 'default', moduleName: 'jQuery', - list: [ - 'lib', - { - name: 'lib', - }, - { - name: 'lib', - alias: 'lib', - }, - { - name: 'lib', - type: 'default', - }, - { - alias: 'lib', - type: 'namespace', - }, - ], + name: 'lib', }, ], failure: [ @@ -49,6 +41,12 @@ describe('validate options', () => { moduleName: 'jQuery', list: false, }, + { + syntax: 'default', + moduleName: 'jQuery', + name: 'lib', + alias: 'lib_alias', + }, ], }, wrapper: { From 4aee959185a27a496112304307de60aff8ec8dca Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Thu, 11 Jun 2020 14:38:12 +0300 Subject: [PATCH 14/15] refactor: code --- README.md | 439 +++++++----------- src/utils.js | 19 +- test/__snapshots__/loader.test.js.snap | 44 +- .../validate-options.test.js.snap | 4 +- test/loader.test.js | 45 +- 5 files changed, 275 insertions(+), 276 deletions(-) diff --git a/README.md b/README.md index 4bd419c..97d41c6 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,15 @@ This is useful for third-party modules that rely on global variables like `$` or ## Getting Started -To begin, you'll need to install `copy-webpack-plugin`: +To begin, you'll need to install `imports-loader`: ```console -$ npm install imports-loader +$ npm install imports-loader --save-dev ``` Given you have this file `example.js` -```javascript +```js $('img').doSomeAwesomeJqueryPluginStuff(); ``` @@ -38,69 +38,65 @@ then you can inject the `jquery` variable into the module by configuring the imp **index.js** ```js -require('./example.js'); +require('imports-loader?imports=default%20jquery%20$!./example.js'); +// Adds the following code to the beginning of example.js: +// +// `import $ from "jquery";` to `example.js` ``` -**webpack.config.js** +> ⚠ By default loader generate ES module named syntax. -```js -module.exports = { - module: { - rules: [ - { - test: require.resolve('example.js'), - use: [ - { - loader: 'imports-loader', - options: { - imports: { - moduleName: 'jquery', - list: '$', - }, - }, - }, - ], - }, - ], - }, -}; -``` +### Inline -This simply prepends `import jquery from "jquery";` to `example.js`. +The `imports` have follow syntax: -## Inline syntax +``` +?imports=syntax%20moduleName%20name%20alias +``` -**index.js** +The space (`%20`) is the separator between import segments. -```js -require(`imports-loader?imports[]=jquery&imports[]=angular!./example.js`); -``` +> `syntax` is required. -Result: +A `syntax` can be omitted only if one segment is used. In this case, the `moduleName` and `name` will be equal to it. -```js -import jquery from 'jquery'; -import angular from 'angular'; -// code form example.js -``` +Description of string values can be found in the documentation below. + +#### Examples **index.js** ```js -require(`imports-loader?wrapper[]=window&wrapper[]=document!./example.js`); +require(`imports-loader?imports[]=default%20jquery%20$&imports[]=angular!./example.js`); +// Adds the following code to the beginning of example.js: +// +// import $ from "jquery"; +// import angular from "angular"; ``` -Result: +```js +require(`imports-loader?type=commonjsimports[]=default%20jquery%20$&imports[]=angular!./example.js`); +// Adds the following code to the beginning of example.js: +// +// var $ = require("jquery"); +// var angular = require("angular"); +``` ```js -(function () { - // code from example.js -}.call(window, document)); +require(`imports-loader?wrapper=window&imports[]=default%20jquery%20$&imports[]=angular!./example.js`); +// Adds the following code to the example.js: +// +// import $ from "jquery"; +// import angular from "angular"; +// +// (function () { +// code from example.js +// }.call(window)); ``` -To configure imports, use `webpack.config.js`: +Description of string values can be found in the documentation below. -## Options +### Using Configuration The loader's signature: @@ -116,11 +112,21 @@ module.exports = { { loader: 'imports-loader', options: { - imports: { - type: 'commonjs', - moduleName: 'jquery', - list: '$', - }, + type: 'commonjs', + imports: [ + 'default ./lib_1 $', + 'default ./lib_2 lib_2_default', + 'named ./lib_2 lib2_method_1', + 'named ./lib_2 lib2_method_2 lib_2_method_2_short', + 'default ./lib_3 lib_3_defaul', + 'namespace ./lib_3 lib_3_all', + 'side-effect ./lib_4', + 'default jquery $', + { + moduleName: 'angular', + name: 'angular', + }, + ], wrapper: { call: 'window', }, @@ -134,38 +140,25 @@ module.exports = { }; ``` -### Imports +And run `webpack` via your preferred method. -Type: `String|Object|Array` -Default: `undefined` +## Options -#### String +| Name | Type | Default | Description | +| :-----------------------: | :---------------------------------------: | :---------: | :-------------------------- | +| **[`type`](#type)** | `{String}` | `module` | Format of generated exports | +| **[`imports`](#imports)** | `{String\|Object\|Array}` | `undefined` | List of imports | -**webpack.config.js** +### Type -```js -module.exports = { - module: { - rules: [ - { - test: require.resolve('example.js'), - use: [ - { - loader: 'imports-loader', - options: { - imports: 'jquery', - }, - }, - ], - }, - ], - }, -}; -``` +Type: `String` +Default: `module` + +Format of generated exports. -Result: `import jquery from "jquery";`. +Possible values - `commonjs` (CommonJS module syntax) and `module` (ES module syntax). -#### Array +#### `commonjs` **webpack.config.js** @@ -175,34 +168,21 @@ module.exports = { rules: [ { test: require.resolve('example.js'), - use: [ - { - loader: 'imports-loader', - options: { - imports: [ - 'angular', - { - moduleName: 'jquery', - list: { name: '$', type: 'default' }, - }, - ], - }, - }, - ], + loader: 'imports-loader', + options: { + type: 'commonjs', + imports: 'Foo', + }, }, ], }, }; +// Adds the following code to the beginning of example.js: +// +// var Foo = require("Foo"); ``` -Result: - -```js -import angular from 'angular'; -import $ from 'jquery'; -``` - -#### Object +#### `module` **webpack.config.js** @@ -212,81 +192,57 @@ module.exports = { rules: [ { test: require.resolve('example.js'), - use: [ - { - loader: 'imports-loader', - options: { - imports: { - moduleName: 'jquery', - list: { name: '$', type: 'default' }, - }, - }, - }, - ], + loader: 'imports-loader', + options: { + type: 'module', + imports: 'Foo', + }, }, ], }, }; +// Adds the following code to the beginning of example.js: +// +// import Foo from "Foo"; ``` -Result: `import $ from "jquery";`. - -Options: `type`, `moduleName`, `list` - -##### type - -Type: `String` -Default: `module` - -The type of the module to import (`import` or `require`). +### Imports -Possible values: `module`, `commonjs` +Type: `String|Object|Array` +Default: `undefined` -**webpack.config.js** +List of imports. -```js -module.exports = { - module: { - rules: [ - { - test: require.resolve('example.js'), - use: [ - { - loader: 'imports-loader', - options: { - imports: { - type: 'commonjs', - moduleName: 'jquery', - list: '$', - }, - }, - }, - ], - }, - ], - }, -}; -``` +#### `String` -Result: `var $ = require("jquery");` +Allows to use a string to describe an export. -##### moduleName +##### `Syntax` -Type: `String` -Default: `undefined` +String values let you specify import syntax, moduleName, name, and alias. -The name of the module to import (`jquery`, `lodash`, `./example-file.js`). +String syntax - `[[syntax] [moduleName] [name] [alias]]`, where: -##### list +- `[syntax]` - can be `default`, `named`, `namespace` or `side-effect` +- `[moduleName]` - name of an imported module (**required**) +- `[name]` - name of an imported value (**required**) +- `[alias]` - alias of an imported value (**may be omitted**) -Type: `String|Boolean|Object|Array` -Default: `undefined` +Examples: -Сonfigures import string. +- `[Foo]` - generates `import Foo from "Foo";`. +- `[default Foo]` - generates `import Foo from "Foo";`. +- `[default ./my-lib Foo]` - generates `import Foo from "./my-lib";`. +- `[named Foo FooA]` - generates `import { FooA } from "Foo";`. +- `[named Foo FooA Bar]` - generates `import { FooA as Bar } from "Foo";`. +- `[[default Foo] [named Foo Bar BarA]]` - generates `import Foo, { Bar as BarA } from "Foo";`. +- `[namespace Foo FooA]` - generates `import * as FooA from "Foo";`. +- `[[default Foo] [namespace Foo FooA]]` - generates `import Foo, * as FooA from "Foo";`. +- `[side-effect Foo]` - generates `import "Foo";`. -###### Boolean +> ⚠ Aliases can't be used together with `default` or `side-effect` syntax. -Lets you make a namespace import or pure require +###### Examples **webpack.config.js** @@ -300,10 +256,8 @@ module.exports = { { loader: 'imports-loader', options: { - imports: { - moduleName: 'some-module', - list: false, - }, + type: 'commonjs', + imports: 'default jquery $', }, }, ], @@ -311,15 +265,26 @@ module.exports = { ], }, }; + +// Adds the following code to the beginning of example.js: +// +// import $ from "jquery"; ``` -Result: `import "some-module";` +#### `Object` -###### String +Allows to use an object to describe an import. -Reduced to the object `{name: passedValue, type: 'default'}` +Properties: -###### Array +- `[syntax]` - can be `default`, `named`, `namespace` or `side-effect` +- `moduleName` - name of an imported module (**required**) +- `name` - name of an exported value (**required**) +- `alias` - alias of an exported value (**may be omitted**) + +##### Examples + +**webpack.config.js** ```js module.exports = { @@ -331,18 +296,12 @@ module.exports = { { loader: 'imports-loader', options: { + type: 'commonjs', imports: { - moduleName: 'some-module', - list: [ - 'nameDefaultImport', - { - name: 'method_1', - }, - { - name: 'method_2', - alias: 'method_2_alias', - }, - ], + syntax: 'named', + moduleName: './lib_2', + name: 'lib2_method_2', + alias: 'lib_2_method_2_alias', }, }, }, @@ -351,41 +310,19 @@ module.exports = { ], }, }; -``` - -Result: `import nameDefaultImport, { method_1, method_2 as method_2_alias } from "some-module"`. - -###### Object - -Options: `name`, `alias`, `type` - -> name - -Type: `String` -Default: `undefined` - -Export name. - -`import { name as alias } from "some-module"`. -`import { name } from "some-module"`. - -> alias - -Type: `String` -Default: `undefined` - -Alias for export name. -`import { name as alias } from "some-module"`. +// Adds the following code to the beginning of example.js: +// +// import { lib2_method_2 as lib_2_method_2_alias } from "./lib_2"; +``` -> type +#### `Array` -Type: `String` -Default: `undefined` +Allow to specify multiple imports. Each item can be a [`string`](https://github.com/webpack-contrib/imports-loader#string) or an [`object`](https://github.com/webpack-contrib/imports-loader#object). -Type export name +##### Examples -Possible values: `default`, `namespace` +**webpack.config.js** ```js module.exports = { @@ -397,41 +334,23 @@ module.exports = { { loader: 'imports-loader', options: { - imports: { - moduleName: 'some-module', - list: { - name: 'all', - type: 'default', + type: 'commonjs', + imports: [ + { + moduleName: 'angular', }, - }, - }, - }, - ], - }, - ], - }, -}; -``` - -Result: `import all from "some-module"`. - -```js -module.exports = { - module: { - rules: [ - { - test: require.resolve('example.js'), - use: [ - { - loader: 'imports-loader', - options: { - imports: { - moduleName: 'some-module', - list: { - name: 'all', - type: 'namespace', + { + syntax: 'default', + moduleName: 'jquery', + name: '$', }, - }, + 'default ./lib_2 lib_2_default', + 'named ./lib_2 lib2_method_1', + 'named ./lib_2 lib2_method_2 lib_2_method_2_alias', + 'default ./lib_3 lib_3_default', + 'namespace ./lib_3 lib_3_all', + 'side-effect ./lib_4', + ], }, }, ], @@ -439,9 +358,15 @@ module.exports = { ], }, }; -``` -Result: `import * as all from "some-module"`. +// Adds the following code to the beginning of example.js: +// +// import angular from "angular"; +// import $ from "jquery"; +// import lib_2_default, { lib2_method_1, lib2_method_2 as lib_2_method_2_alias } from "./lib_2"; +// import lib_3_default, * as lib_3_all from "./lib_3"; +// import "./lib_4"; +``` ### wrapper @@ -462,7 +387,7 @@ module.exports = { options: { imports: { moduleName: 'jquery', - list: '$', + name: '$', }, wrapper: ['window', 'document'], }, @@ -472,16 +397,13 @@ module.exports = { ], }, }; -``` - -Result: - -```js -import $ from 'jquery'; - -(function () { - // code from example.js -}.call(window, document)); +// Adds the following code to the example.js: +// +// import $ from "jquery"; +// +// (function () { +// code from example.js +// }.call(window, document)); ``` ### additionalCode @@ -503,7 +425,7 @@ module.exports = { options: { imports: { moduleName: 'jquery', - list: '$', + name: '$', }, additionalCode: 'var someVariable = 1;', }, @@ -513,15 +435,10 @@ module.exports = { ], }, }; -``` - -Result: - -```js -import $ from 'jquery'; -var someVariable = 1; - -// code from example.js +// Adds the following code to the beginning of example.js: +// +// import $ from 'jquery'; +// var someVariable = 1; ``` For further hints on compatibility issues, check out [Shimming Modules](https://webpack.js.org/guides/shimming/) of the official docs. diff --git a/src/utils.js b/src/utils.js index 9e93a80..70b7956 100644 --- a/src/utils.js +++ b/src/utils.js @@ -29,12 +29,17 @@ function resolveImports(type, item) { }; } } else { - const defaultOptions = { - type: 'module', - syntax: 'default', - }; + result = { ...{ syntax: 'default' }, ...item }; - result = { ...defaultOptions, ...item }; + if (result.syntax === 'default' && !result.name) { + result.name = result.moduleName; + } + } + + if (!result.moduleName) { + throw new Error( + `The import should have "moduleName" option in "${item}" value` + ); } if ( @@ -62,7 +67,7 @@ function resolveImports(type, item) { } if ( - ['default', 'namespace', 'named'].includes(result.syntax) && + ['namespace', 'named'].includes(result.syntax) && typeof result.name === 'undefined' ) { throw new Error( @@ -78,7 +83,7 @@ function getImports(type, options) { if (!imports && !additionalCode && !wrapper) { throw new Error( - `You must fill out one of the options "imports", "wrapper" or "additionalCode"` + `You must fill out one of the options "imports", "wrapper" or "additionalCode" in "${options}" value` ); } diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 8ceac80..ae0e583 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -39,20 +39,20 @@ exports[`loader should emit error when invalid arguments for import: warnings 1` exports[`loader should emit error when not arguments for import: errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): -Error: You must fill out one of the options \\"imports\\", \\"wrapper\\" or \\"additionalCode\\"", +Error: You must fill out one of the options \\"imports\\", \\"wrapper\\" or \\"additionalCode\\" in \\"[object Object]\\" value", ] `; exports[`loader should emit error when not arguments for import: warnings 1`] = `Array []`; -exports[`loader should emit error when skipped name to import-default: errors 1`] = ` +exports[`loader should emit error when skipped name to import-named: errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): -Error: The \\"default\\" syntax should have \\"name\\" option in \\"[object Object]\\" value", +Error: The \\"named\\" syntax should have \\"name\\" option in \\"[object Object]\\" value", ] `; -exports[`loader should emit error when skipped name to import-default: warnings 1`] = `Array []`; +exports[`loader should emit error when skipped name to import-named: warnings 1`] = `Array []`; exports[`loader should emit error when try namespace import to commonjs: errors 1`] = ` Array [ @@ -332,6 +332,42 @@ var someCode = { exports[`loader should work require: warnings 1`] = `Array []`; +exports[`loader should work string syntax when commonjs type: errors 1`] = `Array []`; + +exports[`loader should work string syntax when commonjs type: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ +var $ = require(\\"./lib_1\\"); +var lib_2_all = require(\\"./lib_2\\"); +var { lib2_method_1, lib2_method_2: lib_2_method_2_short } = require(\\"./lib_2\\"); +require(\\"./lib_3\\"); + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; + +exports[`loader should work string syntax when commonjs type: warnings 1`] = `Array []`; + +exports[`loader should work string syntax when module type: errors 1`] = `Array []`; + +exports[`loader should work string syntax when module type: result 1`] = ` +"/*** IMPORTS FROM imports-loader ***/ +import $ from \\"./lib_1\\"; +import lib_2_all, { lib2_method_1, lib2_method_2 as lib_2_method_2_short } from \\"./lib_2\\"; +import lib_3_defaul, * as lib_3_all from \\"./lib_3\\"; +import \\"./lib_4\\"; + +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; +" +`; + +exports[`loader should work string syntax when module type: warnings 1`] = `Array []`; + exports[`loader should work wrapper array: errors 1`] = `Array []`; exports[`loader should work wrapper array: result 1`] = ` diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index be9e11b..33effef 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -35,7 +35,7 @@ exports[`validate options should throw an error on the "imports" option with "" - options.imports should be an non-empty string." `; -exports[`validate options should throw an error on the "imports" option with "/test/" value 1`] = `"The \\"default\\" syntax should have \\"name\\" option in \\"/test/\\" value"`; +exports[`validate options should throw an error on the "imports" option with "/test/" value 1`] = `"The import should have \\"moduleName\\" option in \\"/test/\\" value"`; exports[`validate options should throw an error on the "imports" option with "[""]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. @@ -60,7 +60,7 @@ exports[`validate options should throw an error on the "imports" option with "{" object { syntax?, moduleName?, name?, alias? }" `; -exports[`validate options should throw an error on the "imports" option with "{}" value 1`] = `"The \\"default\\" syntax should have \\"name\\" option in \\"[object Object]\\" value"`; +exports[`validate options should throw an error on the "imports" option with "{}" value 1`] = `"The import should have \\"moduleName\\" option in \\"[object Object]\\" value"`; exports[`validate options should throw an error on the "imports" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. diff --git a/test/loader.test.js b/test/loader.test.js index ca5e959..6f260f7 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -328,6 +328,47 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + it('should work string syntax when commonjs type', async () => { + const compiler = getCompiler('some-library.js', { + type: 'commonjs', + imports: [ + 'default ./lib_1 $', + 'default ./lib_2 lib_2_all', + 'named ./lib_2 lib2_method_1', + 'named ./lib_2 lib2_method_2 lib_2_method_2_short', + 'side-effect ./lib_3', + ], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should work string syntax when module type', async () => { + const compiler = getCompiler('some-library.js', { + imports: [ + 'default ./lib_1 $', + 'default ./lib_2 lib_2_all', + 'named ./lib_2 lib2_method_1', + 'named ./lib_2 lib2_method_2 lib_2_method_2_short', + 'default ./lib_3 lib_3_defaul', + 'namespace ./lib_3 lib_3_all', + 'side-effect ./lib_4', + ], + }); + const stats = await compile(compiler); + + expect(getModuleSource('./some-library.js', stats)).toMatchSnapshot( + 'result' + ); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + it('should emit error when alias don`t need', async () => { const compiler = getCompiler('some-library.js', { imports: { @@ -356,11 +397,11 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); - it('should emit error when skipped name to import-default', async () => { + it('should emit error when skipped name to import-named', async () => { const compiler = getCompiler('some-library.js', { imports: { moduleName: './lib_2.js', - syntax: 'default', + syntax: 'named', }, }); const stats = await compile(compiler); From c19daa4a9cae10bec1881ab06aca7a45d612fa5a Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Thu, 11 Jun 2020 17:08:01 +0300 Subject: [PATCH 15/15] refactor: code --- src/index.js | 53 ++++------- src/options.json | 5 ++ src/utils.js | 88 +++++++++++-------- test/__snapshots__/loader.test.js.snap | 29 +++++- .../validate-options.test.js.snap | 80 +++++++++++++---- test/loader.test.js | 30 +++++++ test/validate-options.test.js | 15 +++- 7 files changed, 209 insertions(+), 91 deletions(-) diff --git a/src/index.js b/src/index.js index 5c6f901..f2048a3 100644 --- a/src/index.js +++ b/src/index.js @@ -22,46 +22,33 @@ export default function loader(content, sourceMap) { const type = options.type || 'module'; const callback = this.async(); - let imports; - - try { - imports = getImports(type, options); - } catch (error) { - callback(error); + let importsCode = `/*** IMPORTS FROM imports-loader ***/\n`; - return; - } + let imports; - const importsSorted = {}; + if (options.imports) { + try { + imports = getImports(type, options.imports); + } catch (error) { + callback(error); - for (const item of imports) { - if (!importsSorted[item.moduleName]) { - importsSorted[item.moduleName] = []; + return; } - importsSorted[item.moduleName].push(item); + importsCode += Object.entries(imports).reduce((acc, item) => { + return `${acc}${renderImports(this, type, item[1])}\n`; + }, ''); } - const importsCode = Object.entries(importsSorted).reduce((acc, item) => { - return `${acc}${renderImports(this, type, item[1])}\n`; - }, ''); - - let finalImportsCode = `/*** IMPORTS FROM imports-loader ***/\n${importsCode}`; - - const { additionalCode } = options; - - if (additionalCode) { - finalImportsCode += `\n${additionalCode}`; + if (options.additionalCode) { + importsCode += `\n${options.additionalCode}`; } - let codeBeforeModule = ''; let codeAfterModule = ''; - const { wrapper } = options; - - if (wrapper) { - codeBeforeModule += '\n(function() {'; - codeAfterModule += `\n}.call(${wrapper.toString()}));`; + if (options.wrapper) { + importsCode += '\n(function() {'; + codeAfterModule += `\n}.call(${options.wrapper.toString()}));`; } if (this.sourceMap && sourceMap) { @@ -70,7 +57,7 @@ export default function loader(content, sourceMap) { new SourceMapConsumer(sourceMap) ); - node.prepend(`${finalImportsCode}${codeBeforeModule}\n`); + node.prepend(`${importsCode}\n`); node.add(codeAfterModule); const result = node.toStringWithSourceMap({ @@ -82,9 +69,5 @@ export default function loader(content, sourceMap) { return; } - callback( - null, - `${finalImportsCode}${codeBeforeModule}\n${content}${codeAfterModule}`, - sourceMap - ); + callback(null, `${importsCode}\n${content}${codeAfterModule}`, sourceMap); } diff --git a/src/options.json b/src/options.json index 831cb59..501ab28 100644 --- a/src/options.json +++ b/src/options.json @@ -76,5 +76,10 @@ "minLength": 1 } }, + "anyOf": [ + { "required": ["imports"] }, + { "required": ["wrapper"] }, + { "required": ["additionalCode"] } + ], "additionalProperties": false } diff --git a/src/utils.js b/src/utils.js index 70b7956..00e7af7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -29,7 +29,7 @@ function resolveImports(type, item) { }; } } else { - result = { ...{ syntax: 'default' }, ...item }; + result = { syntax: 'default', ...item }; if (result.syntax === 'default' && !result.name) { result.name = result.moduleName; @@ -78,19 +78,7 @@ function resolveImports(type, item) { return result; } -function getImports(type, options) { - const { imports, additionalCode, wrapper } = options; - - if (!imports && !additionalCode && !wrapper) { - throw new Error( - `You must fill out one of the options "imports", "wrapper" or "additionalCode" in "${options}" value` - ); - } - - if (!imports) { - return []; - } - +function getImports(type, imports) { let result = []; if (typeof imports === 'string') { @@ -99,7 +87,41 @@ function getImports(type, options) { result = [].concat(imports).map((item) => resolveImports(type, item)); } - return result; + const sortedResults = {}; + + for (const item of result) { + if (!sortedResults[item.moduleName]) { + sortedResults[item.moduleName] = []; + } + + sortedResults[item.moduleName].push(item); + } + + for (const item of Object.entries(sortedResults)) { + const defaultImports = item[1].filter( + (entry) => entry.syntax === 'default' + ); + const namespaceImports = item[1].filter( + (entry) => entry.syntax === 'namespace' + ); + const sideEffectImports = item[1].filter( + (entry) => entry.syntax === 'side-effect' + ); + + [defaultImports, namespaceImports, sideEffectImports].forEach( + (importsSyntax) => { + if (importsSyntax.length > 1) { + const [{ syntax }] = importsSyntax; + + throw new Error( + `The "${syntax}" syntax format can't have multiple import in "${item}" value` + ); + } + } + ); + } + + return sortedResults; } function renderImports(loaderContext, type, imports) { @@ -112,32 +134,27 @@ function renderImports(loaderContext, type, imports) { const sideEffectImports = imports.filter( (item) => item.syntax === 'side-effect' ); + const isModule = type === 'module'; // 1. Import-side-effect if (sideEffectImports.length > 0) { - return type === 'commonjs' - ? `require(${stringifyRequest(loaderContext, moduleName)});` - : `import ${stringifyRequest(loaderContext, moduleName)};`; + return isModule + ? `import ${stringifyRequest(loaderContext, moduleName)};` + : `require(${stringifyRequest(loaderContext, moduleName)});`; } - let code = type === 'commonjs' ? '' : 'import'; + let code = isModule ? 'import' : ''; // 2. Default import if (defaultImports.length > 0) { const [{ name }] = defaultImports; - // eslint-disable-next-line default-case - switch (type) { - case 'commonjs': - code += `var ${name} = require(${stringifyRequest( + code += isModule + ? ` ${name}` + : `var ${name} = require(${stringifyRequest( loaderContext, moduleName )});`; - break; - case 'module': - code += ` ${name}`; - break; - } } // 3. Namespace import @@ -154,26 +171,25 @@ function renderImports(loaderContext, type, imports) { // 4. Named import if (namedImports.length > 0) { if (defaultImports.length > 0) { - code += type === 'commonjs' ? '\nvar { ' : ', { '; + code += isModule ? ', { ' : '\nvar { '; } else { - code += type === 'commonjs' ? 'var { ' : ' { '; + code += isModule ? ' { ' : 'var { '; } namedImports.forEach((namedImport, i) => { const comma = i > 0 ? ', ' : ''; const { name, alias } = namedImport; - const sep = type === 'commonjs' ? ': ' : ' as '; + const sep = isModule ? ' as ' : ': '; code += alias ? `${comma}${name}${sep}${alias}` : `${comma}${name}`; }); - code += - type === 'commonjs' - ? ` } = require(${stringifyRequest(loaderContext, moduleName)});` - : ' }'; + code += isModule + ? ' }' + : ` } = require(${stringifyRequest(loaderContext, moduleName)});`; } - if (type === 'commonjs') { + if (!isModule) { return code; } diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index ae0e583..53ee8ed 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -36,10 +36,37 @@ Error: The \\"default\\" syntax can't have \\"lib_2_method_2_short\\" alias in \ exports[`loader should emit error when invalid arguments for import: warnings 1`] = `Array []`; +exports[`loader should emit error when multiple default import: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: The \\"default\\" syntax format can't have multiple import in \\"./lib_2,[object Object],[object Object]\\" value", +] +`; + +exports[`loader should emit error when multiple default import: warnings 1`] = `Array []`; + +exports[`loader should emit error when multiple namespace import: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: The \\"namespace\\" syntax format can't have multiple import in \\"./lib_2,[object Object],[object Object]\\" value", +] +`; + +exports[`loader should emit error when multiple namespace import: warnings 1`] = `Array []`; + +exports[`loader should emit error when multiple side-effect import: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: The \\"side-effect\\" syntax format can't have multiple import in \\"./lib_2,[object Object],[object Object]\\" value", +] +`; + +exports[`loader should emit error when multiple side-effect import: warnings 1`] = `Array []`; + exports[`loader should emit error when not arguments for import: errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): -Error: You must fill out one of the options \\"imports\\", \\"wrapper\\" or \\"additionalCode\\" in \\"[object Object]\\" value", +ValidationError: Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.", ] `; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 33effef..9441de6 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -112,50 +112,98 @@ exports[`validate options should throw an error on the "type" option with "strin exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options has an unknown property 'unknown'. These properties are valid: - object { type?, imports?, wrapper?, additionalCode? }" + - options should be one of these: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + Details: + * options has an unknown property 'unknown'. These properties are valid: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + * options misses the property 'imports' | should be any non-object. + * options misses the property 'wrapper' | should be any non-object. + * options misses the property 'additionalCode' | should be any non-object." `; exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options has an unknown property 'unknown'. These properties are valid: - object { type?, imports?, wrapper?, additionalCode? }" + - options should be one of these: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + Details: + * options has an unknown property 'unknown'. These properties are valid: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + * options misses the property 'imports' | should be any non-object. + * options misses the property 'wrapper' | should be any non-object. + * options misses the property 'additionalCode' | should be any non-object." `; exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options has an unknown property 'unknown'. These properties are valid: - object { type?, imports?, wrapper?, additionalCode? }" + - options should be one of these: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + Details: + * options has an unknown property 'unknown'. These properties are valid: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + * options misses the property 'imports' | should be any non-object. + * options misses the property 'wrapper' | should be any non-object. + * options misses the property 'additionalCode' | should be any non-object." `; exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options has an unknown property 'unknown'. These properties are valid: - object { type?, imports?, wrapper?, additionalCode? }" + - options should be one of these: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + Details: + * options has an unknown property 'unknown'. These properties are valid: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + * options misses the property 'imports' | should be any non-object. + * options misses the property 'wrapper' | should be any non-object. + * options misses the property 'additionalCode' | should be any non-object." `; exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options has an unknown property 'unknown'. These properties are valid: - object { type?, imports?, wrapper?, additionalCode? }" + - options should be one of these: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + Details: + * options has an unknown property 'unknown'. These properties are valid: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + * options misses the property 'imports' | should be any non-object. + * options misses the property 'wrapper' | should be any non-object. + * options misses the property 'additionalCode' | should be any non-object." `; exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options has an unknown property 'unknown'. These properties are valid: - object { type?, imports?, wrapper?, additionalCode? }" + - options should be one of these: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + Details: + * options has an unknown property 'unknown'. These properties are valid: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + * options misses the property 'imports' | should be any non-object. + * options misses the property 'wrapper' | should be any non-object. + * options misses the property 'additionalCode' | should be any non-object." `; exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options has an unknown property 'unknown'. These properties are valid: - object { type?, imports?, wrapper?, additionalCode? }" + - options should be one of these: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + Details: + * options has an unknown property 'unknown'. These properties are valid: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + * options misses the property 'imports' | should be any non-object. + * options misses the property 'wrapper' | should be any non-object. + * options misses the property 'additionalCode' | should be any non-object." `; exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` "Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. - - options has an unknown property 'unknown'. These properties are valid: - object { type?, imports?, wrapper?, additionalCode? }" + - options should be one of these: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + Details: + * options has an unknown property 'unknown'. These properties are valid: + object { imports, … } | object { wrapper, … } | object { additionalCode, … } + * options misses the property 'imports' | should be any non-object. + * options misses the property 'wrapper' | should be any non-object. + * options misses the property 'additionalCode' | should be any non-object." `; exports[`validate options should throw an error on the "wrapper" option with "[""]" value 1`] = ` diff --git a/test/loader.test.js b/test/loader.test.js index 6f260f7..73bc5dc 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -442,6 +442,36 @@ describe('loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); }); + it('should emit error when multiple default import', async () => { + const compiler = getCompiler('some-library.js', { + imports: ['default ./lib_2 lib_2', 'default ./lib_2 lib_3'], + }); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should emit error when multiple namespace import', async () => { + const compiler = getCompiler('some-library.js', { + imports: ['namespace ./lib_2 lib_2', 'namespace ./lib_2 lib_3'], + }); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + + it('should emit error when multiple side-effect import', async () => { + const compiler = getCompiler('some-library.js', { + imports: ['side-effect ./lib_2', 'side-effect ./lib_2'], + }); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + it('should emit error when not arguments for import', async () => { const compiler = getCompiler('some-library.js', {}); const stats = await compile(compiler); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index aeffc8e..b72f93a 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -78,9 +78,18 @@ describe('validate options', () => { it(`should ${ type === 'success' ? 'successfully validate' : 'throw an error on' } the "${key}" option with "${stringifyValue(value)}" value`, async () => { - const compiler = getCompiler('some-library.js', { - [key]: value, - }); + let compiler; + + if (key === 'type') { + compiler = getCompiler('some-library.js', { + [key]: value, + wrapper: 'window', + }); + } else { + compiler = getCompiler('some-library.js', { + [key]: value, + }); + } let stats;