diff --git a/README.md b/README.md index 07e4d0f..97d41c6 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,444 @@ -[![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 `imports-loader`: -

Usage

+```console +$ npm install imports-loader --save-dev +``` Given you have this file `example.js` -```javascript +```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: + +**index.js** + +```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` +``` + +> ⚠ By default loader generate ES module named syntax. + +### Inline + +The `imports` have follow syntax: + +``` +?imports=syntax%20moduleName%20name%20alias +``` + +The space (`%20`) is the separator between import segments. -```javascript -require('imports-loader?$=jquery!./example.js'); +> `syntax` is required. + +A `syntax` can be omitted only if one segment is used. In this case, the `moduleName` and `name` will be equal to it. + +Description of string values can be found in the documentation below. + +#### Examples + +**index.js** + +```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"; ``` -This simply prepends `var $ = require("jquery");` to `example.js`. +```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"); +``` -### Syntax +```js +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)); +``` + +Description of string values can be found in the documentation below. -| 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);` | +### Using Configuration -### Multiple values +The loader's signature: -Multiple values are separated by comma `,`: +**webpack.config.js** -```javascript -require('imports-loader?$=jquery,angular,config=>{size:50}!./file.js'); +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + 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', + }, + additionalCode: 'var someVariable = 1;', + }, + }, + ], + }, + ], + }, +}; ``` -### webpack.config.js +And run `webpack` via your preferred method. + +## Options + +| Name | Type | Default | Description | +| :-----------------------: | :---------------------------------------: | :---------: | :-------------------------- | +| **[`type`](#type)** | `{String}` | `module` | Format of generated exports | +| **[`imports`](#imports)** | `{String\|Object\|Array}` | `undefined` | List of imports | + +### Type + +Type: `String` +Default: `module` + +Format of generated exports. + +Possible values - `commonjs` (CommonJS module syntax) and `module` (ES module syntax). + +#### `commonjs` + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + loader: 'imports-loader', + options: { + type: 'commonjs', + imports: 'Foo', + }, + }, + ], + }, +}; +// Adds the following code to the beginning of example.js: +// +// var Foo = require("Foo"); +``` -As always, you should rather configure this in your `webpack.config.js`: +#### `module` -```javascript -// ./webpack.config.js +**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'), + loader: 'imports-loader', + options: { + type: 'module', + imports: 'Foo', + }, + }, + ], + }, }; +// Adds the following code to the beginning of example.js: +// +// import Foo from "Foo"; ``` -

Typical Use Cases

+### Imports -### jQuery plugins +Type: `String|Object|Array` +Default: `undefined` -`imports-loader?$=jquery` +List of imports. -### Custom Angular modules +#### `String` -`imports-loader?angular` +Allows to use a string to describe an export. -### Disable AMD +##### `Syntax` -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. +String values let you specify import syntax, moduleName, name, and alias. -Then you can easily disable the AMD path by writing +String syntax - `[[syntax] [moduleName] [name] [alias]]`, where: -```javascript -imports-loader?define=>false +- `[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**) + +Examples: + +- `[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";`. + +> ⚠ Aliases can't be used together with `default` or `side-effect` syntax. + +###### Examples + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + type: 'commonjs', + imports: 'default jquery $', + }, + }, + ], + }, + ], + }, +}; + +// Adds the following code to the beginning of example.js: +// +// import $ from "jquery"; +``` + +#### `Object` + +Allows to use an object to describe an import. + +Properties: + +- `[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 = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + type: 'commonjs', + imports: { + syntax: 'named', + moduleName: './lib_2', + name: 'lib2_method_2', + alias: 'lib_2_method_2_alias', + }, + }, + }, + ], + }, + ], + }, +}; + +// Adds the following code to the beginning of example.js: +// +// import { lib2_method_2 as lib_2_method_2_alias } from "./lib_2"; +``` + +#### `Array` + +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). + +##### Examples + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: require.resolve('example.js'), + use: [ + { + loader: 'imports-loader', + options: { + type: 'commonjs', + imports: [ + { + moduleName: 'angular', + }, + { + 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', + ], + }, + }, + ], + }, + ], + }, +}; + +// 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 + +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', + name: '$', + }, + wrapper: ['window', 'document'], + }, + }, + ], + }, + ], + }, +}; +// Adds the following code to the example.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', + name: '$', + }, + additionalCode: 'var someVariable = 1;', + }, + }, + ], + }, + ], + }, +}; +// 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. @@ -132,9 +478,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 cb51833..f2048a3 100644 --- a/src/index.js +++ b/src/index.js @@ -3,74 +3,71 @@ Author Tobias Koppers @sokra */ +import { SourceNode, SourceMapConsumer } from 'source-map'; import { getOptions, getCurrentRequest } from 'loader-utils'; -// import validateOptions from 'schema-utils'; -// -// import schema from './options.json'; +import validateOptions from 'schema-utils'; -const { SourceNode } = require('source-map'); -const { SourceMapConsumer } = require('source-map'); +import schema from './options.json'; -const HEADER = '/*** IMPORTS FROM imports-loader ***/\n'; +import { getImports, renderImports } from './utils'; export default function loader(content, sourceMap) { const options = getOptions(this) || {}; - // validateOptions(schema, options, 'Loader'); + validateOptions(schema, options, { + name: 'Imports loader', + baseDataPath: 'options', + }); + const type = options.type || 'module'; const callback = this.async(); - if (this.cacheable) this.cacheable(); - const query = options; - const imports = []; - 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};`); + let importsCode = `/*** IMPORTS FROM imports-loader ***/\n`; + + let imports; + + if (options.imports) { + try { + imports = getImports(type, options.imports); + } catch (error) { + callback(error); + + return; } - }); - const prefix = `${HEADER}${imports.join('\n')}\n\n`; - const postfix = `\n${postfixes.join('\n')}`; - if (sourceMap) { + + importsCode += Object.entries(imports).reduce((acc, item) => { + return `${acc}${renderImports(this, type, item[1])}\n`; + }, ''); + } + + if (options.additionalCode) { + importsCode += `\n${options.additionalCode}`; + } + + let codeAfterModule = ''; + + if (options.wrapper) { + importsCode += '\n(function() {'; + codeAfterModule += `\n}.call(${options.wrapper.toString()}));`; + } + + if (this.sourceMap && sourceMap) { const node = SourceNode.fromStringWithSourceMap( content, new SourceMapConsumer(sourceMap) ); - node.prepend(prefix); - node.add(postfix); + + node.prepend(`${importsCode}\n`); + node.add(codeAfterModule); + const result = node.toStringWithSourceMap({ file: getCurrentRequest(this), }); + callback(null, result.code, result.map.toJSON()); + return; } - callback(null, `${prefix}${content}${postfix}`, sourceMap); + callback(null, `${importsCode}\n${content}${codeAfterModule}`, sourceMap); } diff --git a/src/options.json b/src/options.json index 4a7c07d..501ab28 100644 --- a/src/options.json +++ b/src/options.json @@ -1,5 +1,85 @@ { + "definitions": { + "ObjectPattern": { + "type": "object", + "additionalProperties": false, + "properties": { + "syntax": { + "enum": ["default", "named", "namespace", "side-effect"] + }, + "moduleName": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "alias": { + "type": "string", + "minLength": 1 + } + } + }, + "ImportsStringPattern": { + "type": "string", + "minLength": 1 + } + }, "type": "object", - "properties": {}, + "properties": { + "type": { + "enum": ["module", "commonjs"] + }, + "imports": { + "anyOf": [ + { + "$ref": "#/definitions/ImportsStringPattern" + }, + { + "$ref": "#/definitions/ObjectPattern" + }, + { + "type": "array", + "minItems": 1, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/ImportsStringPattern" + }, + { + "$ref": "#/definitions/ObjectPattern" + } + ] + } + } + ] + }, + "wrapper": { + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + ] + }, + "additionalCode": { + "type": "string", + "minLength": 1 + } + }, + "anyOf": [ + { "required": ["imports"] }, + { "required": ["wrapper"] }, + { "required": ["additionalCode"] } + ], "additionalProperties": false } diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..00e7af7 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,201 @@ +import { stringifyRequest } from 'loader-utils'; + +function resolveImports(type, item) { + let result; + + if (typeof item === 'string') { + const splittedItem = item.split(' '); + + if (splittedItem.length > 4) { + throw new Error(`Invalid "${item}" for import`); + } + + 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 { + result = { syntax: 'default', ...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 ( + ['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` + ); + } + + 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` + ); + } + + if (['namespace'].includes(result.syntax) && type === 'commonjs') { + throw new Error( + `The "commonjs" type not support "namespace" syntax import in "${item}" value` + ); + } + + if ( + ['namespace', 'named'].includes(result.syntax) && + typeof result.name === 'undefined' + ) { + throw new Error( + `The "${result.syntax}" syntax should have "name" option in "${item}" value` + ); + } + + return result; +} + +function getImports(type, imports) { + let result = []; + + if (typeof imports === 'string') { + result.push(resolveImports(type, imports)); + } else { + result = [].concat(imports).map((item) => resolveImports(type, item)); + } + + 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) { + 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' + ); + const isModule = type === 'module'; + + // 1. Import-side-effect + if (sideEffectImports.length > 0) { + return isModule + ? `import ${stringifyRequest(loaderContext, moduleName)};` + : `require(${stringifyRequest(loaderContext, moduleName)});`; + } + + let code = isModule ? 'import' : ''; + + // 2. Default import + if (defaultImports.length > 0) { + const [{ name }] = defaultImports; + + code += isModule + ? ` ${name}` + : `var ${name} = require(${stringifyRequest( + loaderContext, + moduleName + )});`; + } + + // 3. Namespace import + if (namespaceImports.length > 0) { + if (defaultImports.length > 0) { + code += `,`; + } + + const [{ name }] = namespaceImports; + + code += ` * as ${name}`; + } + + // 4. Named import + if (namedImports.length > 0) { + if (defaultImports.length > 0) { + code += isModule ? ', { ' : '\nvar { '; + } else { + code += isModule ? ' { ' : 'var { '; + } + + namedImports.forEach((namedImport, i) => { + const comma = i > 0 ? ', ' : ''; + const { name, alias } = namedImport; + const sep = isModule ? ' as ' : ': '; + + code += alias ? `${comma}${name}${sep}${alias}` : `${comma}${name}`; + }); + + code += isModule + ? ' }' + : ` } = require(${stringifyRequest(loaderContext, moduleName)});`; + } + + if (!isModule) { + return code; + } + + code += ` from ${stringifyRequest(loaderContext, moduleName)};`; + + return code; +} + +export { getImports, renderImports }; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 7496454..53ee8ed 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -1,21 +1,428 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`loader should set a variable: errors 1`] = `Array []`; +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: The \\"default\\" syntax can't have \\"lib_2_method_2_short\\" alias in \\"[object Object]\\" value", +] +`; + +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\`): +ValidationError: Invalid options object. Imports loader has been initialized using an options object that does not match the API schema.", +] +`; + +exports[`loader should emit error when not arguments for import: warnings 1`] = `Array []`; + +exports[`loader should emit error when skipped name to import-named: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: The \\"named\\" syntax should have \\"name\\" option in \\"[object Object]\\" value", +] +`; + +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 [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: The \\"commonjs\\" type not support \\"namespace\\" syntax import in \\"[object Object]\\" value", +] +`; + +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`] = ` +"/*** 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 require when import option is array: 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, + object: { existingSubProperty: 123 } +}; +" +`; + +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_default, { 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 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 } +}; +" +`; + +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 lib_2_all = require(\\"./lib_2\\"); +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 set a variable: result 1`] = ` +exports[`loader should work import, wrapper and additionalCode option: result 1`] = ` "/*** IMPORTS FROM imports-loader ***/ +import \\"./lib_1\\"; + var someVariable = 1; -var anotherVariable = 2; -var someVariable = (someVariable || {}); -someVariable.someProperty = someVariable.someProperty || {}; -someVariable.someProperty.someSubProperty = 1; +(function() { +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +}.call(window));" +`; + +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 set a variable: warnings 1`] = `Array []`; +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`] = ` +"/*** 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`] = ` +"/*** 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`] = ` +"/*** 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 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`] = ` +"/*** 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`] = ` +"/*** IMPORTS FROM imports-loader ***/ + +(function() { +var someCode = { + number: 123, + object: { existingSubProperty: 123 } +}; + +}.call(window));" +`; + +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 new file mode 100644 index 0000000..9441de6 --- /dev/null +++ b/test/__snapshots__/validate-options.test.js.snap @@ -0,0 +1,237 @@ +// 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 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 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 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 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 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 non-empty string." +`; + +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.imports should be an non-empty string." +`; + +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. + - options.imports[0] should be an non-empty string." +`; + +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.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 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`] = `"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. + - 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 should be a non-empty string. + * options.imports should be an object: + object { syntax?, moduleName?, name?, alias? } + * options.imports should be an array: + [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 { 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 { syntax?, moduleName?, name?, alias? } + * options.imports should be an array: + [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 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 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 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 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 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 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 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 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`] = ` +"Invalid options object. Imports loader has been initialized using an options object that does not match the API schema. + - 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 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 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 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/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/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/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..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,19 +36,17 @@ export default (fixture, loaderOptions = {}, config = {}) => { // libraryTarget: 'var', }, module: { - rules: [ - { - test: /.js/i, - rules: [ - { - loader: path.resolve(__dirname, '../../src'), - options: loaderOptions || {}, - }, - ], - }, - ], + rules: loaders, }, 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/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 25ffa69..73bc5dc 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', + imports: 'lib_1', }); const stats = await compile(compiler); @@ -21,4 +19,505 @@ 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', { + imports: { + moduleName: './lib_1', + name: '$', + }, + }); + 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', { + imports: ['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', { + imports: [ + 'lib_1', + { + syntax: 'default', + moduleName: './lib_2.js', + name: 'lib_2', + }, + { + syntax: 'default', + moduleName: './lib_3.js', + name: 'defaultExport', + }, + { + syntax: 'named', + moduleName: './lib_3.js', + name: 'lib_3_method', + alias: 'method', + }, + { + syntax: 'default', + moduleName: './lib_4', + name: 'lib_4', + }, + { + syntax: 'namespace', + moduleName: './lib_4', + name: 'lib_4_all', + }, + ], + }); + 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', { + imports: [ + { + moduleName: './lib_1', + name: 'lib_1_all', + syntax: '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 require when named-imports', async () => { + const compiler = getCompiler('some-library.js', { + imports: [ + { + syntax: 'named', + moduleName: './lib_1', + name: 'lib1_method', + }, + { + moduleName: './lib_2', + name: 'lib2_default', + }, + { + syntax: 'named', + moduleName: './lib_2', + name: 'lib2_method_1', + }, + { + syntax: 'named', + moduleName: './lib_2', + 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', { + imports: { + moduleName: './lib_1', + syntax: 'side-effect', + }, + }); + 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', + }); + 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 array', async () => { + const compiler = getCompiler('some-library.js', { + wrapper: ['window', 'document'], + }); + 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', { + 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'); + }); + + it('should work import, wrapper and additionalCode option', async () => { + const compiler = getCompiler('some-library.js', { + imports: { + moduleName: './lib_1', + syntax: 'side-effect', + }, + 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'); + }); + + it('should work require', async () => { + const compiler = getCompiler('some-library.js', { + type: 'commonjs', + imports: { + moduleName: './lib_1', + name: '$', + }, + }); + 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 require default', async () => { + const compiler = getCompiler('some-library.js', { + type: 'commonjs', + imports: { + moduleName: './lib_1', + name: '$', + syntax: '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', { + type: 'commonjs', + imports: [ + { + syntax: 'named', + moduleName: './lib_2', + name: 'lib2_method_1', + }, + { + syntax: 'named', + moduleName: './lib_2', + 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', { + type: 'commonjs', + imports: [ + { + moduleName: './lib_1', + name: '$', + }, + { + moduleName: './lib_2', + name: 'lib_2_all', + }, + { + syntax: 'named', + moduleName: './lib_2', + name: 'lib2_method_1', + }, + { + syntax: 'named', + moduleName: './lib_2', + 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 pure require', async () => { + const compiler = getCompiler('some-library.js', { + type: 'commonjs', + imports: { + syntax: 'side-effect', + moduleName: './lib_1', + }, + }); + 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 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: { + 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-named', async () => { + const compiler = getCompiler('some-library.js', { + imports: { + moduleName: './lib_2.js', + syntax: 'named', + }, + }); + 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', { + type: 'commonjs', + imports: [ + { + moduleName: './lib_4', + name: 'lib_4_all', + syntax: 'namespace', + }, + ], + }); + const stats = await compile(compiler); + + 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: [ + { + moduleName: './lib_2', + alias: 'lib_2_method_2_short', + }, + ], + }); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + 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); + + 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 new file mode 100644 index 0000000..b72f93a --- /dev/null +++ b/test/validate-options.test.js @@ -0,0 +1,129 @@ +import { getCompiler, compile } from './helpers'; + +describe('validate options', () => { + const tests = { + type: { + success: ['module', 'commonjs'], + failure: ['string', '', {}, []], + }, + imports: { + success: [ + 'lib_1', + 'globalObject1.foo', + ['globalObject1'], + ['globalObject1.foo'], + { + moduleName: 'jQuery', + name: '$', + }, + { + syntax: 'named', + moduleName: 'jQuery', + name: 'lib', + alias: 'lib_alias', + }, + { + syntax: 'default', + moduleName: 'jQuery', + name: 'lib', + }, + ], + failure: [ + false, + true, + /test/, + '', + [], + [''], + {}, + { + type: 'string', + moduleName: 'jQuery', + list: false, + }, + { + syntax: 'default', + moduleName: 'jQuery', + name: 'lib', + alias: 'lib_alias', + }, + ], + }, + wrapper: { + success: ['window', ['window', 'document']], + failure: [false, true, [], ['']], + }, + 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 () => { + let compiler; + + if (key === 'type') { + compiler = getCompiler('some-library.js', { + [key]: value, + wrapper: 'window', + }); + } else { + 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); + } + } + } +});