Repository: styleguidist/react-styleguidist
Pull request #1116 (2018-08-30 14:28:52)

Feat: Do not put component into global namespace
BREAKING CHANGE:

Style guide componets are no longer accessible on window. Only current component is accessible in the example context (not on window). You need to explicitly import any other component.

tizmagik: I really like this change! Will help a lot of large styleguides. 👍 

Patch:
diff --git a/.eslintrc b/.eslintrc
index 853c77c0..5ecce2ef 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -17,7 +17,7 @@
     }
   },
   "rules": {
-    "no-invalid-this": 0,
+    "valid-jsdoc": 0,
     "compat/compat": "error",
     "import/no-unresolved": ["error", {
       "commonjs": true,
diff --git a/docs/Documenting.md b/docs/Documenting.md
index a22ecf1f..2e3a3a53 100644
--- a/docs/Documenting.md
+++ b/docs/Documenting.md
@@ -146,7 +146,7 @@ MyComponent.propTypes = {
 
 ## Defining custom component names
 
-Use @visibleName JSDoc tag to define component names that are used in the Styleguidist UI:
+Use `@visibleName` JSDoc tag to define component names that are used in the Styleguidist UI:
 
 ```javascript
 /**
@@ -221,20 +221,29 @@ class Button extends React.Component {
 
 ## Writing code examples
 
-Code examples in Markdown use ES6+JSX syntax. All components covered by the style guide can be used in all examples:
+Code examples in Markdown use ES6+JSX syntax. You can use the current component without explicitly importing it:
 
-```jsx
-<Panel>
+````jsx
+// ```jsx inside Button/Readme.md or Button.md
+<Button>Push Me</Button>
+````
+
+> **Note:** Styleguidist uses [Bublé](https://buble.surge.sh/guide/) to run ES6 code on the frontend, it supports [most of the ES6 features](https://buble.surge.sh/guide/#unsupported-features).
+
+To use other components, you need to explicitly `import` them:
+
+````jsx
+// ```jsx inside Panel/Readme.md or Panel.md
+import Button from '../Button'
+;<Panel>
   <p>
     Using the Button component in the example of the Panel component:
   </p>
   <Button>Push Me</Button>
 </Panel>
-```
-
-> **Note:** Styleguidist uses [Bublé](https://buble.surge.sh/guide/) to run ES6 code on the frontend, it supports [most of the ES6 features](https://buble.surge.sh/guide/#unsupported-features).
+````
 
-You can also `import` other modules (like mock data for unit tests):
+You can also `import` other modules, like mock data:
 
 ````jsx
 // ```jsx inside Markdown
@@ -246,7 +255,8 @@ import mockData from './mocks'
 
 Each example has its own state that you can access as `state` variable and change with `setState()` function. Default state is `{}` and can be set with `initialState`.
 
-```jsx
+````jsx
+// ```jsx inside Markdown
 initialState = { isOpen: false }
 ;<div>
   <button onClick={() => setState({ isOpen: true })}>Open</button>
@@ -255,11 +265,12 @@ initialState = { isOpen: false }
     <button onClick={() => setState({ isOpen: false })}>Close</button>
   </Modal>
 </div>
-```
+````
 
 `initialState`, `state` and `setState()` helpers are good to show components in different states, but to let users copy-paste your example code without modifications into their React app you may want to use `React.Component` instead. We can rewrite the example above like this:
 
-```jsx
+````jsx
+// ```jsx inside Markdown
 class ModalExample extends React.Component {
   constructor() {
     super()
@@ -286,7 +297,7 @@ class ModalExample extends React.Component {
   }
 }
 ;<ModalExample />
-```
+````
 
 > **Note:** If you need a more complex demo it’s often a good idea to define it in a separate JavaScript file and `import` it in Markdown.
 
diff --git a/examples/basic/src/components/Button/Readme.md b/examples/basic/src/components/Button/Readme.md
index 16ebd3c9..f93fdce5 100644
--- a/examples/basic/src/components/Button/Readme.md
+++ b/examples/basic/src/components/Button/Readme.md
@@ -44,6 +44,22 @@ Fenced blocks with other languages are rendered as highlighted code:
 <h1>Hello world</h1>
 ```
 
+Current component (like `Button` in this example) is always accessible by its name in the example code. If you want to use other components, you need to explicitly import them:
+
+```jsx
+import Placeholder from '../Placeholder'
+<Button><Placeholder /></Button>
+```
+
+Or you can explicitly import everything:
+
+```jsx
+import React from 'react'
+import Button from '../Button'
+import Placeholder from '../Placeholder'
+<Button><Placeholder /></Button>
+```
+
 Each example has its own state that you can access at the `state` variable and change with the `setState` function. Default state is `{}`:
 
 ```jsx
diff --git a/examples/basic/src/components/CounterButton/Readme.md b/examples/basic/src/components/CounterButton/Readme.md
index 28422384..a80420f6 100644
--- a/examples/basic/src/components/CounterButton/Readme.md
+++ b/examples/basic/src/components/CounterButton/Readme.md
@@ -1,4 +1,5 @@
 ```js
+import Button from '../Button'
 let ref
 ;<div>
   <CounterButton ref={r => (ref = r)} />
diff --git a/examples/sections/docs/Two.md b/examples/sections/docs/Two.md
index 1fc137f2..361a89c5 100644
--- a/examples/sections/docs/Two.md
+++ b/examples/sections/docs/Two.md
@@ -1,3 +1,8 @@
 That big pink button again:
 
-    <Button size="large" color="deeppink">Lick Me</Button>
+```jsx
+import Button from '../src/components/Button'
+;<Button size="large" color="deeppink">
+  Lick Me
+</Button>
+```
diff --git a/examples/sections/src/components/Placeholder/Example.md b/examples/sections/src/components/Placeholder/Example.md
index d576b033..8bec6a3f 100644
--- a/examples/sections/src/components/Placeholder/Example.md
+++ b/examples/sections/src/components/Placeholder/Example.md
@@ -1,5 +1,6 @@
 Extra example file linked via `@example` doclet:
 
 ```jsx
+import Placeholder from '../Placeholder'
 <Placeholder type="bear" />
 ```
diff --git a/package-lock.json b/package-lock.json
index a0b696ab..8ae6c435 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5088,12 +5088,6 @@
         }
       }
     },
-    "eslint-plugin-es5": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-es5/-/eslint-plugin-es5-1.3.1.tgz",
-      "integrity": "sha512-2oq0R/3XoAsiMwllwsHmnk0gA7+KSvRHUAiJ7Pa/iKrClywVd1q/G7LDZHXU5YD+9kVNxlK155ElXrmX8uLW8A==",
-      "dev": true
-    },
     "eslint-plugin-import": {
       "version": "2.14.0",
       "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz",
@@ -11206,6 +11200,18 @@
         "prepend-http": "^1.0.0",
         "query-string": "^4.1.0",
         "sort-keys": "^1.0.0"
+      },
+      "dependencies": {
+        "query-string": {
+          "version": "4.3.4",
+          "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
+          "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+          "dev": true,
+          "requires": {
+            "object-assign": "^4.1.0",
+            "strict-uri-encode": "^1.0.0"
+          }
+        }
       }
     },
     "npm-path": {
@@ -12507,16 +12513,6 @@
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
       "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
     },
-    "query-string": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
-      "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
-      "dev": true,
-      "requires": {
-        "object-assign": "^4.1.0",
-        "strict-uri-encode": "^1.0.0"
-      }
-    },
     "querystring": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
diff --git a/package.json b/package.json
index 44b23925..b721cd85 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
     "prismjs": "^1.15.0",
     "prop-types": "^15.6.2",
     "q-i": "^2.0.1",
+    "querystringify": "^2.0.0",
     "react-dev-utils": "^5.0.1",
     "react-docgen": "3.0.0-beta12",
     "react-docgen-annotation-resolver": "^1.0.0",
@@ -113,7 +114,6 @@
     "eslint": "^5.4.0",
     "eslint-config-tamia": "^6.0.2",
     "eslint-plugin-compat": "^2.5.1",
-    "eslint-plugin-es5": "^1.3.1",
     "eslint-plugin-import": "^2.14.0",
     "eslint-plugin-react": "^7.11.1",
     "file-loader": "^1.1.11",
diff --git a/src/client/rsg-components/Preview/Preview.js b/src/client/rsg-components/Preview/Preview.js
index 7ad4e078..26b77bb6 100644
--- a/src/client/rsg-components/Preview/Preview.js
+++ b/src/client/rsg-components/Preview/Preview.js
@@ -5,8 +5,6 @@ import PlaygroundError from 'rsg-components/PlaygroundError';
 
 import ReactExample from '../ReactExample';
 
-/* eslint-disable no-invalid-this */
-
 const Fragment = React.Fragment ? React.Fragment : 'div';
 
 export default class Preview extends Component {
diff --git a/src/client/utils/globalizeComponent.js b/src/client/utils/globalizeComponent.js
deleted file mode 100644
index ba00a03f..00000000
--- a/src/client/utils/globalizeComponent.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import getComponent from './getComponent';
-
-/**
- * Expose component as global variables.
- *
- * @param {Object} component
- */
-export default function globalizeComponent(component) {
-	if (!component.name) {
-		return;
-	}
-
-	global[component.name] = getComponent(component.module, component.name);
-}
diff --git a/src/client/utils/globalizeComponents.js b/src/client/utils/globalizeComponents.js
deleted file mode 100644
index 57518656..00000000
--- a/src/client/utils/globalizeComponents.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import globalizeComponent from './globalizeComponent';
-
-/**
- * Expose all components in all sections as global variables.
- *
- * @param {array} sections
- */
-export default function globalizeComponents(sections) {
-	sections.forEach(section => {
-		if (section.components) {
-			section.components.forEach(globalizeComponent);
-		}
-		if (section.sections) {
-			globalizeComponents(section.sections);
-		}
-	});
-}
diff --git a/src/client/utils/renderStyleguide.js b/src/client/utils/renderStyleguide.js
index 18d4c544..a0d26d0e 100644
--- a/src/client/utils/renderStyleguide.js
+++ b/src/client/utils/renderStyleguide.js
@@ -3,7 +3,6 @@ import slots from 'rsg-components/slots';
 import StyleGuide from 'rsg-components/StyleGuide';
 import getPageTitle from './getPageTitle';
 import getRouteData from './getRouteData';
-import globalizeComponents from './globalizeComponents';
 import processSections from './processSections';
 
 /**
@@ -23,10 +22,6 @@ export default function renderStyleguide(
 ) {
 	const allSections = processSections(styleguide.sections);
 
-	// Globalize all components, not just ones we see on the screen, to make
-	// all components accessible to all examples
-	globalizeComponents(allSections);
-
 	const { title, pagePerSection } = styleguide.config;
 	const { sections, displayMode } = getRouteData(allSections, loc.hash, pagePerSection);
 
diff --git a/src/loaders/.eslintrc b/src/loaders/.eslintrc
index 1d5f79c5..6b760c04 100644
--- a/src/loaders/.eslintrc
+++ b/src/loaders/.eslintrc
@@ -1,9 +1,6 @@
 {
 	"extends": "tamia",
-	"parserOptions": {
-		"sourceType": "script"
-	},
 	"rules": {
-		"no-invalid-this": 0
+		"valid-jsdoc": 0
 	}
 }
diff --git a/src/loaders/examples-loader.js b/src/loaders/examples-loader.js
index 6fd85c91..ebfbf70c 100644
--- a/src/loaders/examples-loader.js
+++ b/src/loaders/examples-loader.js
@@ -13,16 +13,16 @@ const requireIt = require('./utils/requireIt');
 
 const absolutize = filepath => path.resolve(__dirname, filepath);
 
+const REQUIRE_IN_RUNTIME_PATH = absolutize('utils/client/requireInRuntime');
+const EVAL_IN_CONTEXT_PATH = absolutize('utils/client/evalInContext');
+
 function examplesLoader(source) {
-	const query = loaderUtils.getOptions(this) || {};
 	const config = this._styleguidist;
-
-	// Append React to context modules, since it’s required for JSX
-	const fullContext = Object.assign({ React: 'react' }, config.context);
+	const { file, displayName, shouldShowDefaultExample } = loaderUtils.getOptions(this) || {};
 
 	// Replace placeholders (__COMPONENT__) with the passed-in component name
-	if (query.componentName) {
-		source = expandDefaultComponent(source, query.componentName);
+	if (shouldShowDefaultExample) {
+		source = expandDefaultComponent(source, displayName);
 	}
 
 	const updateExample = config.updateExample
@@ -41,18 +41,36 @@ function examplesLoader(source) {
 		return requires.concat(getRequires(example.content));
 	}, []);
 
-	// React + context requires + example requires
-	const allRequires = requiresFromExamples.concat(values(fullContext));
+	// Auto imported modules.
+	// We don't need to do anything here to support explicit imports: they will
+	// work because both imports (generated below and by rewrite-imports) will
+	// be eventually transpiled to `var x = require('x')`, so we'll just have two
+	// of them in the same scope, which is fine in non-strict mode
+	const fullContext = {
+		// Modules, provied by the user
+		...config.context,
+		// Append React, because it’s required for JSX
+		React: 'react',
+		// Append the current component module to make it accessible in examples
+		// without an explicit import
+		// TODO: Do not leak absolute path
+		...(displayName ? { [displayName]: file } : {}),
+	};
+
+	// All required or imported modules, either explicitly in examples code
+	// or implicitly (React, current component and context config option)
+	const allModules = [...requiresFromExamples, ...values(fullContext)];
 
 	// “Prerequire” modules required in Markdown examples and context so they
 	// end up in a bundle and be available at runtime
-	const allRequiresCode = allRequires.reduce((requires, requireRequest) => {
+	const allModulesCode = allModules.reduce((requires, requireRequest) => {
 		requires[requireRequest] = requireIt(requireRequest);
 		return requires;
 	}, {});
 
 	// Require context modules so they are available in an example
 	const requireContextCode = b.program(
+		// var name = require(path)
 		map(fullContext, (requireRequest, name) =>
 			b.variableDeclaration('var', [
 				b.variableDeclarator(b.identifier(name), requireIt(requireRequest).toAST()),
@@ -73,10 +91,10 @@ if (module.hot) {
 	module.hot.accept([])
 }
 
-var requireMap = ${generate(toAst(allRequiresCode))};
-var requireInRuntimeBase = require(${JSON.stringify(absolutize('utils/client/requireInRuntime'))});
+var requireMap = ${generate(toAst(allModulesCode))};
+var requireInRuntimeBase = require(${JSON.stringify(REQUIRE_IN_RUNTIME_PATH)}).default;
 var requireInRuntime = requireInRuntimeBase.bind(null, requireMap);
-var evalInContextBase = require(${JSON.stringify(absolutize('utils/client/evalInContext'))});
+var evalInContextBase = require(${JSON.stringify(absolutize(EVAL_IN_CONTEXT_PATH))}).default;
 var evalInContext = evalInContextBase.bind(null, ${JSON.stringify(
 		generate(requireContextCode)
 	)}, requireInRuntime);
diff --git a/src/loaders/props-loader.js b/src/loaders/props-loader.js
index b0fe0343..c56bf651 100644
--- a/src/loaders/props-loader.js
+++ b/src/loaders/props-loader.js
@@ -68,7 +68,7 @@ module.exports = function(source) {
 
 	// Examples from Markdown file
 	const examplesFile = config.getExampleFilename(file);
-	docs.examples = getExamples(examplesFile, docs.displayName, config.defaultExample);
+	docs.examples = getExamples(file, docs.displayName, examplesFile, config.defaultExample);
 
 	if (config.updateDocs) {
 		docs = config.updateDocs(docs, file);
diff --git a/src/loaders/utils/client/.eslintrc b/src/loaders/utils/client/.eslintrc
deleted file mode 100644
index b885fc84..00000000
--- a/src/loaders/utils/client/.eslintrc
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-	"extends": [
-		"tamia/legacy",
-		"plugin:es5/no-es2015"
-	],
-	"rules": {
-		"no-var": 0
-	}
-}
diff --git a/src/loaders/utils/client/evalInContext.js b/src/loaders/utils/client/evalInContext.js
index 1992d5d0..0bff6be7 100644
--- a/src/loaders/utils/client/evalInContext.js
+++ b/src/loaders/utils/client/evalInContext.js
@@ -1,6 +1,3 @@
-// WARNING: This function’s source is returned by a loader without transpilation.
-// Do not use any unsupported by IE11+ features.
-
 /**
  * Eval example code in a custom context:
  * - `require()` that allows you to require modules from Markdown examples (won’t work dinamically becasue we need
@@ -15,9 +12,11 @@
  * @param {string} code
  * @return {Function}
  */
-module.exports = function evalInContext(header, require, code) {
-	var func = new Function('require', 'state', 'setState', header + code); // eslint-disable-line no-new-func
+export default function evalInContext(header, require, code) {
+	// eslint-disable-next-line no-new-func
+	const func = new Function('require', 'state', 'setState', header + code);
 
-	// Bind the `require` function, other context arguments will be passed from the frontend
+	// Bind the `require` function, other context arguments will be passed from
+	// the frontend
 	return func.bind(null, require);
-};
+}
diff --git a/src/loaders/utils/client/requireInRuntime.js b/src/loaders/utils/client/requireInRuntime.js
index 65775e9d..b30dccc7 100644
--- a/src/loaders/utils/client/requireInRuntime.js
+++ b/src/loaders/utils/client/requireInRuntime.js
@@ -1,6 +1,3 @@
-// WARNING: This function’s source is returned by a loader without transpilation.
-// Do not use any unsupported by IE11+ features.
-
 /**
  * Return module from a given map (like {react: require('react')}) or throw.
  * We alllow to require modules only from Markdown examples (won’t work dinamically becasue we need to know all required
@@ -10,13 +7,12 @@
  * @param {string} filepath
  * @return {object}
  */
-module.exports = function requireInRuntime(requireMap, filepath) {
+export default function requireInRuntime(requireMap, filepath) {
 	if (!(filepath in requireMap)) {
 		throw new Error(
-			'require() statements can be added only by editing a Markdown example file: require("' +
-				filepath +
-				'")'
+			`import or require() statements can be added only by editing a Markdown example file: ${filepath}`
 		);
 	}
-	return requireMap[filepath];
-};
+	const mod = requireMap[filepath];
+	return mod.default || mod;
+}
diff --git a/src/loaders/utils/getExamples.js b/src/loaders/utils/getExamples.js
index fa899422..de9a91c4 100644
--- a/src/loaders/utils/getExamples.js
+++ b/src/loaders/utils/getExamples.js
@@ -1,25 +1,30 @@
-const fs = require('fs');
+// @flow
 const path = require('path');
+const qs = require('querystringify');
 const requireIt = require('./requireIt');
 
 const examplesLoader = path.resolve(__dirname, '../examples-loader.js');
 
 /**
  * Get require statement for examples file if it exists, or for default examples if it was defined.
- *
- * @param {string} examplesFile
- * @param {string} displayName
- * @param {string} defaultExample
- * @returns {object|Array}
  */
-module.exports = function getExamples(examplesFile, displayName, defaultExample) {
-	if (examplesFile && fs.existsSync(examplesFile)) {
-		return requireIt(`!!${examplesLoader}!${examplesFile}`);
+module.exports = function getExamples(
+	file: string,
+	displayName: string,
+	examplesFile: string,
+	defaultExample: string
+): {} {
+	const examplesFileToLoad = examplesFile || defaultExample;
+	if (!examplesFileToLoad) {
+		return null;
 	}
 
-	if (defaultExample) {
-		return requireIt(`!!${examplesLoader}?componentName=${displayName}!${defaultExample}`);
-	}
+	const relativePath = `./${path.relative(path.dirname(examplesFileToLoad), file)}`;
 
-	return [];
+	const query = {
+		displayName,
+		file: relativePath,
+		shouldShowDefaultExample: !examplesFile && !!defaultExample,
+	};
+	return requireIt(`!!${examplesLoader}?${qs.stringify(query)}!${examplesFileToLoad}`);
 };


Test patch:
diff --git a/src/client/utils/__tests__/globalizeComponent.spec.js b/src/client/utils/__tests__/globalizeComponent.spec.js
deleted file mode 100644
index 6c4079fa..00000000
--- a/src/client/utils/__tests__/globalizeComponent.spec.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import globalizeComponent from '../globalizeComponent';
-
-const component = { module: 'someModule', name: 'SomeName' };
-
-describe('globalizeComponent', () => {
-	afterEach(() => {
-		delete global[component.name];
-	});
-
-	it('should not add anything as a global variable if there is no component name', () => {
-		expect(global[component.name]).toBeUndefined();
-		globalizeComponent({});
-		expect(global[component.name]).toBeUndefined();
-	});
-
-	it('should set the return value of getComponent as a global variable', () => {
-		expect(global[component.name]).toBeUndefined();
-		globalizeComponent(component);
-		expect(global[component.name]).toBe(component.module);
-	});
-});
diff --git a/src/client/utils/__tests__/globalizeComponents.spec.js b/src/client/utils/__tests__/globalizeComponents.spec.js
deleted file mode 100644
index fe8269a8..00000000
--- a/src/client/utils/__tests__/globalizeComponents.spec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import globalizeComponents from '../globalizeComponents';
-
-afterEach(() => {
-	delete global.Foo;
-	delete global.Bar;
-});
-
-describe('globalizeComponents', () => {
-	it('should set all components’ modules as a global variables', () => {
-		const globalsCount = Object.keys(global).length;
-		globalizeComponents([
-			{
-				components: [
-					{
-						name: 'Foo',
-						props: {},
-						module: 13,
-					},
-				],
-				sections: [
-					{
-						components: [
-							{
-								name: 'Bar',
-								props: {},
-								module: 14,
-							},
-						],
-					},
-				],
-			},
-		]);
-		expect(Object.keys(global).length).toBe(globalsCount + 2);
-		expect(global.Foo).toBe(13);
-		expect(global.Bar).toBe(14);
-	});
-});
diff --git a/src/client/utils/__tests__/renderStyleguide.spec.js b/src/client/utils/__tests__/renderStyleguide.spec.js
index 391d8a7d..ccbf7145 100644
--- a/src/client/utils/__tests__/renderStyleguide.spec.js
+++ b/src/client/utils/__tests__/renderStyleguide.spec.js
@@ -48,18 +48,6 @@ describe('renderStyleguide', () => {
 		expect(result).toMatchSnapshot();
 	});
 
-	it('should globalize all components', () => {
-		renderStyleguide(styleguide, codeRevision, location, doc, history);
-		expect(global.Button).toBe('ButtonModule');
-		expect(global.Image).toBe('ImageModule');
-	});
-
-	it('should globalize all components in isolated mode', () => {
-		renderStyleguide(styleguide, codeRevision, { hash: '#!/Button' }, doc, history);
-		expect(global.Button).toBe('ButtonModule');
-		expect(global.Image).toBe('ImageModule');
-	});
-
 	it('should change document title', () => {
 		const title = jest.fn();
 		const location = { hash: '' };
diff --git a/src/loaders/__tests__/examples-loader.spec.js b/src/loaders/__tests__/examples-loader.spec.js
index 3db68436..ce59e418 100644
--- a/src/loaders/__tests__/examples-loader.spec.js
+++ b/src/loaders/__tests__/examples-loader.spec.js
@@ -1,5 +1,16 @@
+import qs from 'querystringify';
 import examplesLoader from '../examples-loader';
 
+/* eslint-disable no-new-func */
+
+const query = {
+	file: '../foo.js',
+	displayName: 'FooComponent',
+	shouldShowDefaultExample: false,
+};
+
+const getQuery = (options = {}) => `?${qs.stringify({ ...query, ...options })}`;
+
 it('should return valid, parsable JS', () => {
 	const exampleMarkdown = `
 # header
@@ -14,18 +25,17 @@ text
 `;
 	const result = examplesLoader.call(
 		{
+			query: getQuery(),
 			_styleguidist: {},
 		},
 		exampleMarkdown
 	);
 
 	expect(result).toBeTruthy();
-	expect(() => new Function(result)).not.toThrowError(SyntaxError); // eslint-disable-line no-new-func
+	expect(() => new Function(result)).not.toThrowError(SyntaxError);
 });
 
-// componentName query option
-
-it('should replace all occurrences of __COMPONENT__ with provided query.componentName', () => {
+it('should replace all occurrences of __COMPONENT__ with provided query.displayName', () => {
 	const exampleMarkdown = `
 <div>
 	<__COMPONENT__>
@@ -38,14 +48,34 @@ it('should replace all occurrences of __COMPONENT__ with provided query.componen
 
 	const result = examplesLoader.call(
 		{
-			query: '?componentName=FooComponent',
+			query: getQuery({ shouldShowDefaultExample: true }),
 			_styleguidist: {},
 		},
 		exampleMarkdown
 	);
 	expect(result).not.toMatch(/__COMPONENT__/);
-	expect(result).toMatch(/FooComponent/);
-	expect(result.match(/FooComponent/g).length).toBe(4);
+	expect(result.match(/<div>(.*?)<\/div>/)[0]).toMatchInlineSnapshot(`
+
+<div>
+  \\n\\t
+  <FooComponent>
+    \\n\\t\\t
+    <span>
+      text
+    </span>
+    \\n\\t\\t
+    <span>
+      Name of component: FooComponent
+    </span>
+    \\n\\t
+  </FooComponent>
+  \\n\\t
+  <FooComponent>
+  </FooComponent>
+  \\n
+</div>
+
+`);
 });
 
 it('should pass updateExample function from config to chunkify', () => {
@@ -57,7 +87,7 @@ it('should pass updateExample function from config to chunkify', () => {
 	const updateExample = jest.fn(props => props);
 	examplesLoader.call(
 		{
-			query: '?componentName=FooComponent',
+			query: getQuery(),
 			resourcePath: '/path/to/foo/examples/file',
 			_styleguidist: {
 				updateExample,
@@ -75,7 +105,7 @@ it('should pass updateExample function from config to chunkify', () => {
 	);
 });
 
-it('should generate require map', () => {
+it('should generate require map when require() is used', () => {
 	const exampleMarkdown = `
 One:
 
@@ -88,13 +118,35 @@ Two:
 `;
 	const result = examplesLoader.call(
 		{
+			query: getQuery(),
+			_styleguidist: {},
+		},
+		exampleMarkdown
+	);
+
+	expect(result).toBeTruthy();
+	expect(() => new Function(result)).not.toThrowError(SyntaxError);
+	expect(result).toMatch(`'lodash': require('lodash')`);
+	expect(result).toMatch(`'react': require('react')`);
+});
+
+it('should generate require map when import is used', () => {
+	const exampleMarkdown = `
+One:
+
+    import _ from 'lodash';
+	<X/>
+`;
+	const result = examplesLoader.call(
+		{
+			query: getQuery(),
 			_styleguidist: {},
 		},
 		exampleMarkdown
 	);
 
 	expect(result).toBeTruthy();
-	expect(() => new Function(result)).not.toThrowError(SyntaxError); // eslint-disable-line no-new-func
+	expect(() => new Function(result)).not.toThrowError(SyntaxError);
 	expect(result).toMatch(`'lodash': require('lodash')`);
 	expect(result).toMatch(`'react': require('react')`);
 });
@@ -107,13 +159,14 @@ it('should work with multiple JSX element on the root level', () => {
 `;
 	const result = examplesLoader.call(
 		{
+			query: getQuery(),
 			_styleguidist: {},
 		},
 		exampleMarkdown
 	);
 
 	expect(result).toBeTruthy();
-	expect(() => new Function(result)).not.toThrowError(SyntaxError); // eslint-disable-line no-new-func
+	expect(() => new Function(result)).not.toThrowError(SyntaxError);
 	expect(result).toMatch(`'lodash': require('lodash')`);
 });
 
@@ -121,12 +174,66 @@ it('should prepend example code with React require()', () => {
 	const exampleMarkdown = `<X/>`;
 	const result = examplesLoader.call(
 		{
+			query: getQuery(),
+			_styleguidist: {},
+		},
+		exampleMarkdown
+	);
+
+	expect(result).toBeTruthy();
+	expect(() => new Function(result)).not.toThrowError(SyntaxError);
+	expect(result).toMatch(`var React = require('react');`);
+});
+
+it('should prepend example code with component require()', () => {
+	const exampleMarkdown = `<X/>`;
+	const result = examplesLoader.call(
+		{
+			query: getQuery(),
+			_styleguidist: {},
+		},
+		exampleMarkdown
+	);
+
+	expect(result).toBeTruthy();
+	expect(() => new Function(result)).not.toThrowError(SyntaxError);
+	expect(result).toMatch(`var FooComponent = require('../foo.js');`);
+});
+
+it('should allow explicit import of React and component module', () => {
+	const exampleMarkdown = `
+    import React from 'react';
+    import FooComponent from '../foo.js';
+    <FooComponent/>`;
+	const result = examplesLoader.call(
+		{
+			query: getQuery(),
+			_styleguidist: {},
+		},
+		exampleMarkdown
+	);
+
+	expect(result).toBeTruthy();
+	expect(() => new Function(result)).not.toThrowError(SyntaxError);
+	expect(result).toMatch(`var React = require('react');`);
+	expect(result).toMatch(`var FooComponent = require('../foo.js');`);
+});
+
+it('should works for any Markdown file, without a current component', () => {
+	const exampleMarkdown = `
+    import React from 'react';
+    import FooComponent from '../foo.js';
+    <FooComponent/>`;
+	const result = examplesLoader.call(
+		{
+			query: '',
 			_styleguidist: {},
 		},
 		exampleMarkdown
 	);
 
 	expect(result).toBeTruthy();
-	expect(() => new Function(result)).not.toThrowError(SyntaxError); // eslint-disable-line no-new-func
+	expect(() => new Function(result)).not.toThrowError(SyntaxError);
 	expect(result).toMatch(`var React = require('react');`);
+	expect(result).not.toMatch('undefined');
 });
diff --git a/src/loaders/__tests__/props-loader.spec.js b/src/loaders/__tests__/props-loader.spec.js
index d5380e44..ac6047bb 100644
--- a/src/loaders/__tests__/props-loader.spec.js
+++ b/src/loaders/__tests__/props-loader.spec.js
@@ -158,7 +158,7 @@ it('should attach examples from Markdown file', () => {
 
 	expect(() => new vm.Script(result)).not.toThrow();
 	expect(result).toMatch(
-		/require\('!!.*?\/loaders\/examples-loader\.js!test\/components\/Button\/Readme.md'\)/
+		/require\('!!.*?\/loaders\/examples-loader\.js\?displayName=Button&file=\.%2FButton\.js&shouldShowDefaultExample=false!test\/components\/Button\/Readme\.md'\)/
 	);
 });
 
diff --git a/src/loaders/utils/__tests__/getExamples.spec.js b/src/loaders/utils/__tests__/getExamples.spec.js
index 97b8e835..37fb50b9 100644
--- a/src/loaders/utils/__tests__/getExamples.spec.js
+++ b/src/loaders/utils/__tests__/getExamples.spec.js
@@ -1,28 +1,26 @@
+import deabsDeep from 'deabsdeep';
 import getExamples from '../getExamples';
 
-it('getExamples() should return require with examples-loader is component ha example file', () => {
-	const examplesFile = __filename;
-	const result = getExamples(examplesFile);
+const file = '../pizza.js';
+const displayName = 'Pizza';
+const examplesFile = './Pizza.md';
+const defaultExample = './Default.md';
 
-	expect(result.require.includes(examplesFile)).toBe(true);
-	expect(result.require.includes('componentName=')).toBe(false);
+test('require an example file if component has example file', () => {
+	const result = getExamples(file, displayName, examplesFile);
+	expect(deabsDeep(result).require).toMatchInlineSnapshot(
+		`"!!~/src/loaders/examples-loader.js?displayName=Pizza&file=.%2F..%2Fpizza.js&shouldShowDefaultExample=false!./Pizza.md"`
+	);
 });
 
-it('getExamples() should return require with examples-loader is component has examples', () => {
-	const examplesFile = 'foo';
-	const fallbackName = 'Baz';
-	const defaultExample = 'foo.js';
-	const result = getExamples(examplesFile, fallbackName, defaultExample);
-
-	expect(result.require.includes(__filename)).toBe(false);
-	expect(result.require.includes(fallbackName)).toBe(true);
-	expect(result.require.includes(defaultExample)).toBe(true);
-	expect(result.require.includes('componentName=')).toBe(true);
+test('require default example has no example file', () => {
+	const result = getExamples(file, displayName, false, defaultExample);
+	expect(deabsDeep(result).require).toMatchInlineSnapshot(
+		`"!!~/src/loaders/examples-loader.js?displayName=Pizza&file=.%2F..%2Fpizza.js&shouldShowDefaultExample=true!./Default.md"`
+	);
 });
 
-it('getExamples() should return an empty array if component has no example file', () => {
-	const examplesFile = 'foo';
-	const result = getExamples(examplesFile);
-
-	expect(result).toEqual([]);
+test('return null if component has no example file or default example', () => {
+	const result = getExamples(file, displayName);
+	expect(result).toEqual(null);
 });