feat(rocket-preset-extend-lion-docs): initial release

This commit is contained in:
Thomas Allmer 2021-05-28 15:48:48 +02:00
parent 0bda0fbff3
commit 0ca860315b
53 changed files with 1523 additions and 567 deletions

View file

@ -0,0 +1,13 @@
---
'babel-plugin-extend-docs': minor
---
Work with package entry points (exports) and internal imports.
This simplified the internal logic a lot. For more details please see [package entry points](https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#packages_exports) in the node documentation.
BREAKING CHANGES:
- we no longer support relative import paths in demos
- no need to pass on a `rootPath` or `\_\_filePath`` anymore
- option `throwOnNonExistingPathToFiles` and `throwOnNonExistingRootPath` got removed

View file

@ -0,0 +1,5 @@
---
'rocket-preset-extend-lion-docs': minor
---
Initial release

View file

@ -2,4 +2,4 @@
'@lion/combobox': minor '@lion/combobox': minor
--- ---
Add a new \_getTextboxValueFromOption method on LionCombobox, to be able to overide how the modelValue is displayed in the textbox Add a new \_getTextboxValueFromOption method on LionCombobox, to be able to override how the modelValue is displayed in the textbox

View file

@ -10,25 +10,32 @@ npm i -D babel-plugin-extend-docs
``` ```
We want to only execute `babel-plugin-extend-docs` on the actual files we want to modify/extend. We want to only execute `babel-plugin-extend-docs` on the actual files we want to modify/extend.
We recommend using [babel overrides](https://babeljs.io/docs/en/options#overrides) for it.
👉 _babel.config.js_ You may also consider using [babel overrides](https://babeljs.io/docs/en/options#overrides).
👉 _web-dev-server.config.js_
```js ```js
import path from 'path';
import { fromRollup } from '@web/dev-server-rollup';
import rollupBabel from '@rollup/plugin-babel';
const extendDocsConfig = { const extendDocsConfig = {
rootPath: process.cwd(), // or `path.resolve('./')` as plugin needs to know the rootPath of your project
changes: [ changes: [
// possible changes as described below // possible changes as described below
], ],
}; };
module.exports = { // note that you need to use `.default` for babel
overrides: [ const babel = fromRollup(rollupBabel.default);
{
// plugin will only be executed on files that match this pattern export default {
test: ['./node_modules/source-library/demos/**/*.js'], nodeResolve: true,
plugins: [['babel-plugin-extend-docs', extendDocsConfig]], plugins: [
}, babel({
include: ['./glob/to/files/**/*.js'],
plugins: [[path.resolve('./'), extendDocsConfig]],
}),
], ],
}; };
``` ```
@ -63,14 +70,11 @@ changes: [
### Paths ### Paths
Both `variable` and `tag` are required to have a `paths` array which defines how to remap import paths. As demos can use multiple ways to import all of them needs to be written down in the config. Both `variable` and `tag` are required to have a `paths` array which defines how to remap import paths. Generally it should be a single entry.
```js ```js
paths: [ paths: [
{ from: './index.js', to: './my-extension/index.js' }, { from: 'source-pkg/counter', to: 'extension-pkg/counter' },
{ from: '../index.js', to: '../my-extension/index.js' },
{ from: './src/MyCounter.js', to: './my-extension/index.js' },
{ from: '../src/MyCounter.js', to: '../my-extension/index.js' },
], ],
``` ```
@ -80,28 +84,28 @@ We have an existing demo code which we want to reuse.
```js ```js
import { LitElement, html } from '@lion/core'; import { LitElement, html } from '@lion/core';
import './my-counter.js'; import 'source-pkg/counter/define';
class MyApp extends LitElement { class MyApp extends LitElement {
render() { render() {
return html` return html`
<h1>Example App</h1> <h1>Example App</h1>
<my-counter></my-counter> <source-counter></source-counter>
`; `;
} }
} }
customElements.define('my-app', MyApp); customElements.define('my-app', MyApp);
``` ```
We created a "better" version of `<my-counter>` so we would like to use that in the demo. We created a "better" version of `<source-counter>` so we would like to use that in the demo.
Our extension is called `<my-extension>` and is available in `./my-extension/my-extension.js`. Our extension is called `<extension-counter>` and is available via `extension-pkg/counter/define`.
Within `babel-plugin-extend-docs` we can define to replace the tag + it's import. Within `babel-plugin-extend-docs` we can define to replace the tag + it's import.
```js ```js
tag: { tag: {
from: 'my-counter', from: 'source-counter',
to: 'my-extension', to: 'extension-counter',
paths: [{ from: './my-counter.js', to: './my-extension/my-extension.js' }], paths: [{ from: 'source-pkg/counter/define', to: 'extension-pkg/counter/define' }],
} }
``` ```
@ -109,12 +113,12 @@ tag: {
```js ```js
import { LitElement, html } from '@lion/core'; import { LitElement, html } from '@lion/core';
import './my-extension/my-extension.js'; import 'extension-pkg/counter/define';
class MyApp extends LitElement { class MyApp extends LitElement {
render() { render() {
return html` return html`
<h1>Example App</h1> <h1>Example App</h1>
<my-extension></my-extension> <extension-counter></extension-counter>
`; `;
} }
} }
@ -127,13 +131,14 @@ We have an existing demo code which we want to reuse.
```js ```js
import { LitElement, html } from '@lion/core'; import { LitElement, html } from '@lion/core';
import { MyCounter } from './src/MyCounter.js'; import { SourceCounter } from 'source-pkg/counter';
class TenCounter extends MyCounter { class TenCounter extends SourceCounter {
inc() { inc() {
this.count += 10; this.count += 10;
} }
} }
customElements.define('ten-counter', TenCounter); customElements.define('ten-counter', TenCounter);
class MyApp extends LitElement { class MyApp extends LitElement {
render() { render() {
return html` return html`
@ -145,16 +150,16 @@ class MyApp extends LitElement {
customElements.define('my-app', MyApp); customElements.define('my-app', MyApp);
``` ```
We created a "better" version of `MyCounter` so we would like that `TenCounter` now extends it instead. We created a "better" version of `SourceCounter` so we would like that `TenCounter` now extends it instead.
Within `babel-plugin-extend-docs` we can define to replace the class + it's import. Within `babel-plugin-extend-docs` we can define to replace the class + it's import.
```js ```js
variable: { variable: {
from: 'MyCounter', from: 'SourceCounter',
to: 'MyExtension', to: 'ExtensionCounter',
paths: [ paths: [
{ from: './src/MyCounter.js', to: './my-extension/index.js' }, { from: 'source-pkg/counter', to: 'extension-pkg/counter' },
], ],
}, },
``` ```
@ -163,8 +168,8 @@ variable: {
```js ```js
import { LitElement, html } from '@lion/core'; import { LitElement, html } from '@lion/core';
import { MyExtension } from './my-extension/index.js'; import { SourceCounter } from 'extension-pkg/counter';
class TenCounter extends MyExtension { class TenCounter extends SourceCounter {
inc() { inc() {
this.count += 10; this.count += 10;
} }
@ -184,41 +189,45 @@ customElements.define('my-app', MyApp);
## Full Demo & Api Example ## Full Demo & Api Example
You can run the example locally via `npm run start` or look at its [source code](https://github.com/ing-bank/lion/tree/master/packages-node/babel-plugin-extend-docs/demo/). You can run the example locally via `npm run start` or look at its [source code](https://github.com/ing-bank/lion/tree/master/packages-node/babel-plugin-extend-docs/demo/).
_Note we are configuring babel via the [server.config.js](https://github.com/ing-bank/lion/tree/master/packages-node/babel-plugin-extend-docs/demo/server.config.js)_ _Note we are configuring babel via the [server.config.mjs](https://github.com/ing-bank/lion/tree/master/packages-node/babel-plugin-extend-docs/demo/server.config.mjs)_
👉 _babel.config.js_ 👉 _server.config.mjs_
```js ```js
const path = require('path'); import path from 'path';
import { fromRollup } from '@web/dev-server-rollup';
import rollupBabel from '@rollup/plugin-babel';
const extendDocsConfig = { const extendDocsConfig = {
rootPath: path.resolve('./demo'),
changes: [ changes: [
{ {
name: 'MyCounter', name: 'SourceCounter',
variable: { variable: {
from: 'MyCounter', from: 'SourceCounter',
to: 'MyExtension', to: 'ExtensionCounter',
paths: [ paths: [{ from: '#source/counter', to: '#extension/counter' }],
{ from: './index.js', to: './my-extension/index.js' },
{ from: './src/MyCounter.js', to: './my-extension/index.js' },
],
}, },
tag: { tag: {
from: 'my-counter', from: 'source-counter',
to: 'my-extension', to: 'extension-counter',
paths: [{ from: './my-counter.js', to: './my-extension/my-extension.js' }], paths: [{ from: '#source/counter/define', to: '#extension/counter/define' }],
}, },
}, },
], ],
}; };
module.exports = { // note that you need to use `.default` for babel
overrides: [ const babel = fromRollup(rollupBabel.default);
{
test: ['./node_modules/@lion/*/README.md', './node_modules/@lion/*/docs/**/*.md', export default {
plugins: [['babel-plugin-docs-extend', extendDocsConfig]], nodeResolve: true,
}, watch: true,
open: 'demo/',
plugins: [
babel({
include: ['./demo/**/*.demo.js'],
plugins: [[path.resolve('./'), extendDocsConfig]],
}),
], ],
}; };
``` ```

View file

@ -0,0 +1 @@
# Node Tools >> Extend Docs ||5

View file

@ -0,0 +1,248 @@
# Node Tools >> Extend Docs >> Overview ||10
When maintaining your own extension layer of lion you most likely want to maintain a similar documentation.
Copying and rewriting imports/tags the markdown files works but also means that whenever something change you need copy and rewrite again.
To do this automatically you can use this preset for [rocket](https://rocket.modern-web.dev/).
## Features
- Import lion documentation and adjust it using [remark-extend](../remark-extend/overview.md)
- Renames named imports and all it's usage
- Adjusts import paths
- Replace tags in template literals
## Installation
```bash
npm i -D rocket-preset-extend-lion-docs
```
👉 _rocket.config.js_
```js
import { rocketLaunch } from '@rocket/launch';
import { extendLionDocs } from 'rocket-preset-extend-lion-docs';
const extendLionDocsInstance = await extendLionDocs({
classPrefix: 'Wolf',
classBareImport: '@wolf/',
tagPrefix: 'wolf-',
tagBareImport: '@wolf/',
});
export default {
presets: [rocketLaunch(), extendLionDocsInstance],
};
```
## Reusing documentation
To take an existing documentation you can "import" it using [remark-extend](../remark-extend/overview.md).
As an example you can create `docs/components/tabs/overview.md`.
In it you do
- write a headline
- use importSmallBlockContent to reuse content from lion
- add your own installation instruction
````md
# Content >> Tabs >> Overview ||10
```js ::importSmallBlockContent('@lion/tabs/docs/overview.md', '# Content >> Tabs >> Overview ||10')
```
## Installation
```bash
npm i --save @wolf/tabs
```
```js
import { WolfTabs } from '@wolf/tabs';
```
````
## How does it work
1. `importSmallBlockContent`` will import the content from lion
2. all code blocks will then be precessed to use `@wolf` instead of `@lion`
3. all links which are absolute to github will be processed to be local links
as an example this is a part of the lion docs for tabs
````md
```js script
import { LitElement, html } from '@lion/core';
import '@lion/tabs/define';
```
```js preview-story
export const main = () => html`
<lion-tabs>
<button slot="tab">Info</button>
<p slot="panel">Info page with lots of information about us.</p>
<button slot="tab">Work</button>
<p slot="panel">Work page that showcases our work.</p>
</lion-tabs>
`;
```
````
After all replacements the output that will be used for markdown rendering will be
````md
# Content >> Tabs >> Overview ||10
```js script
import { LitElement, html } from '@wolf/core';
import '@wolf/tabs/define';
```
```js preview-story
export const main = () => html`
<wolf-tabs>
<button slot="tab">Info</button>
<p slot="panel">Info page with lots of information about us.</p>
<button slot="tab">Work</button>
<p slot="panel">Work page that showcases our work.</p>
</wolf-tabs>
`;
```
## Installation
```bash
npm i --save @wolf/tabs
```
```js
import { WolfTabs } from '@wolf/tabs';
```
````
Doing so means you can focus on writing what is specific to your design system extension and you don't need to rewrite all the examples and explanations of lion but you can import them while still using your components.
## Use with a monorepo
The above setup assumes that you have the same "system" of exports in `@wolf` as we have in `@lion`.
So your users are able to do
```js
// provide classes as side effect free imports
import { WolfTabs } from '@wolf/tabs';
// register web components via `/define`
import '@wolf/tabs/define';
```
This means you need to have to define the following package entry points for you tabs extension.
👉 _tabs/package.json_
```json
"exports": {
".": "./src/index.js",
"./define": "./src/define/tabs.js",
}
```
## Use as a single repo
Often its easier for your users to have one package to work with instead of a big list of individual packages.
If you are distributing one package then your exports/imports will be different.
So your users are able to do
```js
// provide classes as side effect free imports
import { WolfTabs } from 'wolf-web/tabs';
// register web components via `/define`
import 'wolf-web/tabs/define';
```
👉 _package.json_
```json
"exports": {
".": "./index.js",
"./tabs/define": "./define/tabs.js",
}
```
The configuration for that is
👉 _rocket.config.js_
```js
import { rocketLaunch } from '@rocket/launch';
import { extendLionDocs } from 'rocket-preset-extend-lion-docs';
const extendLionDocsInstance = await extendLionDocs({
classPrefix: 'Wolf',
classBareImport: 'wolf-web/',
tagPrefix: 'wolf-',
tagBareImport: 'wolf-web/',
});
export default {
presets: [rocketLaunch(), extendLionDocsInstance],
};
```
## Do not distribute side effects
When distributing you may choose to stay side effect free. This means no definitions of custom elements. You may want to do this to "force" usage of a scoped registry in order to support multiple mayor version of a component in a single application. We would recommend [Scoped Elements](https://open-wc.org/docs/development/scoped-elements/) for that.
So your users are able to do
```js
// provide classes as side effect free imports
import { WolfTabs } from 'wolf-web/tabs';
// NOTE: there no way to import a definition as a user
```
For demos it's still useful/needed to have those definitions. To have them but not exposing them you can use private imports which are only available to the package itself. This features is called [Subpath imports](https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#packages_subpath_imports) in node.
To enable it you can set the following settings
👉 _rocket.config.js_
```js
import { rocketLaunch } from '@rocket/launch';
import { extendLionDocs } from 'rocket-preset-extend-lion-docs';
const extendLionDocsInstance = await extendLionDocs({
classPrefix: 'Wolf',
classBareImport: 'wolf-web/',
tagPrefix: 'wolf-',
tagBareImport: '#',
});
export default {
presets: [rocketLaunch(), extendLionDocsInstance],
};
```
This rewrites the custom element definition side effects to
```js
// from
import '@lion/tabs/define';
// to
import '#tabs/define';
```
In order for such imports to work you need to define them
👉 _package.json_
```json
"imports": {
"#tabs/define": "./__element-definitions/wolf-tabs.js",
}
```

View file

@ -92,7 +92,7 @@
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.0.5", "prettier": "^2.0.5",
"prettier-package-json": "^2.1.3", "prettier-package-json": "^2.1.3",
"remark-html": "^11.0.1", "remark-html": "^13.0.1",
"rimraf": "^2.6.3", "rimraf": "^2.6.3",
"rollup": "^2.0.0", "rollup": "^2.0.0",
"sinon": "^7.2.2", "sinon": "^7.2.2",

View file

@ -1,9 +1,10 @@
/* eslint-disable import/extensions */
import { LitElement, html } from '@lion/core'; import { LitElement, html } from '@lion/core';
import { MyCounter } from './src/MyCounter.js'; import { SourceCounter } from '#source/counter';
import './my-counter.js'; import '#source/counter/define';
class TenCounter extends MyCounter { class TenCounter extends SourceCounter {
inc() { inc() {
this.count += 10; this.count += 10;
} }
@ -15,7 +16,7 @@ class MyApp extends LitElement {
return html` return html`
<h1>Example App</h1> <h1>Example App</h1>
<hr /> <hr />
<my-counter></my-counter> <source-counter></source-counter>
<hr /> <hr />
<ten-counter></ten-counter> <ten-counter></ten-counter>
`; `;

View file

@ -1,3 +0,0 @@
import { MyCounter } from './src/MyCounter.js';
customElements.define('my-counter', MyCounter);

View file

@ -1,7 +1,7 @@
import { html, css } from '@lion/core'; import { html, css } from '@lion/core';
import { MyCounter } from '../src/MyCounter.js'; import { SourceCounter } from '../src/SourceCounter.js';
export class MyExtension extends MyCounter { export class ExtensionCounter extends SourceCounter {
static get styles() { static get styles() {
return [ return [
...super.styles, ...super.styles,
@ -14,6 +14,6 @@ export class MyExtension extends MyCounter {
} }
_renderHeader() { _renderHeader() {
return html`<h2>I am MyExtension</h2> `; return html`<h2>I am ExtensionCounter</h2> `;
} }
} }

View file

@ -0,0 +1,3 @@
import { ExtensionCounter } from './ExtensionCounter.js';
customElements.define('extension-counter', ExtensionCounter);

View file

@ -1,3 +0,0 @@
import { MyExtension } from './MyExtension.js';
customElements.define('my-extension', MyExtension);

View file

@ -1,38 +0,0 @@
const path = require('path');
const extendDocsConfig = {
changes: [
{
name: 'MyCounter',
variable: {
from: 'MyCounter',
to: 'MyExtension',
paths: [
{ from: './index.js', to: './my-extension/index.js' },
{ from: './src/MyCounter.js', to: './my-extension/index.js' },
],
},
tag: {
from: 'my-counter',
to: 'my-extension',
paths: [{ from: './my-counter.js', to: './my-extension/my-extension.js' }],
},
},
],
rootPath: path.resolve('./demo'),
};
module.exports = {
nodeResolve: true,
watch: true,
open: 'packages-node/babel-plugin-extend-docs/demo/',
babel: true,
babelConfig: {
overrides: [
{
test: ['./demo/**/*.demo.js'],
plugins: [[path.resolve('./'), extendDocsConfig]],
},
],
},
};

View file

@ -0,0 +1,38 @@
import path from 'path';
import { fromRollup } from '@web/dev-server-rollup';
import rollupBabel from '@rollup/plugin-babel';
const extendDocsConfig = {
changes: [
{
name: 'SourceCounter',
variable: {
from: 'SourceCounter',
to: 'ExtensionCounter',
paths: [
{ from: '#source/counter', to: '#extension/counter' },
],
},
tag: {
from: 'source-counter',
to: 'extension-counter',
paths: [{ from: '#source/counter/define', to: '#extension/counter/define' }],
},
},
],
};
// note that you need to use `.default` for babel
const babel = fromRollup(rollupBabel.default);
export default {
nodeResolve: true,
watch: true,
open: 'demo/',
plugins: [
babel({
include: ['./demo/**/*.demo.js'],
plugins: [[path.resolve('./'), extendDocsConfig]],
}),
],
};

View file

@ -1,5 +0,0 @@
module.exports = {
nodeResolve: true,
watch: true,
open: 'packages-node/babel-plugin-extend-docs/demo/',
};

View file

@ -0,0 +1,5 @@
export default {
nodeResolve: true,
watch: true,
open: 'demo/',
};

View file

@ -1,73 +0,0 @@
import { LitElement, html, css } from '@lion/core';
export class MyCounter extends LitElement {
static get properties() {
return {
count: { type: Number },
};
}
static get styles() {
return css`
:host {
display: block;
width: 220px;
margin: 0 auto;
}
button,
span {
font-size: 200%;
}
span {
width: 4rem;
display: inline-block;
text-align: center;
}
button {
width: 64px;
height: 64px;
border: none;
border-radius: 10px;
background-color: seagreen;
color: white;
}
h3 {
text-align: center;
}
`;
}
constructor() {
super();
this.count = 0;
}
inc() {
this.count += 1;
}
dec() {
this.count -= 1;
}
_renderHeader() {
return html`<h3>I am MyCounter</h3> `;
}
_renderIncButton() {
return html`<button @click="${this.inc}">+</button> `;
}
render() {
return html`
${this._renderHeader()}
<button @click="${this.dec}">-</button>
<span>${this.count}</span>
${this._renderIncButton()}
`;
}
}

View file

@ -0,0 +1,75 @@
import { LitElement, html, css } from '@lion/core';
export class SourceCounter extends LitElement {
static get properties() {
return {
count: { type: Number },
};
}
static get styles() {
return [
css`
:host {
display: block;
width: 220px;
margin: 0 auto;
}
button,
span {
font-size: 200%;
}
span {
width: 4rem;
display: inline-block;
text-align: center;
}
button {
width: 64px;
height: 64px;
border: none;
border-radius: 10px;
background-color: seagreen;
color: white;
}
h3 {
text-align: center;
}
`,
];
}
constructor() {
super();
this.count = 0;
}
inc() {
this.count += 1;
}
dec() {
this.count -= 1;
}
_renderHeader() {
return html`<h3>I am SourceCounter</h3> `;
}
_renderIncButton() {
return html`<button @click="${this.inc}">+</button> `;
}
render() {
return html`
${this._renderHeader()}
<button @click="${this.dec}">-</button>
<span>${this.count}</span>
${this._renderIncButton()}
`;
}
}

View file

@ -0,0 +1,3 @@
import { SourceCounter } from './SourceCounter.js';
customElements.define('source-counter', SourceCounter);

View file

@ -19,8 +19,8 @@
"scripts": { "scripts": {
"publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../", "publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../",
"prepublishOnly": "npm run publish-docs", "prepublishOnly": "npm run publish-docs",
"start": "es-dev-server -c demo/server.config.js --root-dir ../../", "start": "web-dev-server -c demo/server.config.mjs",
"start:no-babel": "es-dev-server -c demo/server.no-babel.config.js --root-dir ../../", "start:no-babel": "web-dev-server -c demo/server.no-babel.config.mjs",
"test": "npm run test:node", "test": "npm run test:node",
"test:node": "mocha test-node", "test:node": "mocha test-node",
"test:watch": "mocha test-node --watch" "test:watch": "mocha test-node --watch"
@ -35,5 +35,11 @@
"exports": { "exports": {
".": "./index.js", ".": "./index.js",
"./docs/": "./docs/" "./docs/": "./docs/"
},
"imports": {
"#source/counter": "./demo/src/SourceCounter.js",
"#source/counter/define": "./demo/src/define.js",
"#extension/counter": "./demo/my-extension/ExtensionCounter.js",
"#extension/counter/define": "./demo/my-extension/define.js"
} }
} }

View file

@ -30,12 +30,6 @@ function insertImportStatements({ imports, path }) {
module.exports = ({ types: t }) => ({ module.exports = ({ types: t }) => ({
visitor: { visitor: {
ImportDeclaration(path, state) { ImportDeclaration(path, state) {
// If a filePath is not passed explicitly by the user, take the filename provided by babel
// and subtract the rootpath from it, to get the desired filePath relative to the root.
state.filePath = state.opts.__filePath
? state.opts.__filePath
: state.file.opts.filename.replace(state.opts.rootPath, '');
if (path.node.specifiers.length > 0) { if (path.node.specifiers.length > 0) {
renameAndStoreImports({ path, state, opts: state.opts, types: t }); renameAndStoreImports({ path, state, opts: state.opts, types: t });
} else { } else {
@ -52,7 +46,6 @@ module.exports = ({ types: t }) => ({
validateOptions(state.opts); validateOptions(state.opts);
state.importedStorage = []; state.importedStorage = [];
state.filePath = '';
}, },
exit: (path, state) => { exit: (path, state) => {
const imports = generateImportStatements({ state, types: t }); const imports = generateImportStatements({ state, types: t });

View file

@ -1,15 +1,5 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
const { joinPaths } = require('./helpers.js');
/**
* -1 because filepath is an absolute path starting with '/' and we turn it into a relative path without a '/' at the start
* @param {*} filePath
*/
function getFolderDepth(filePath) {
return [...filePath.match(new RegExp(/\/|\\/, 'g'))].length - 1;
}
function getImportAs(specifier, newImportName) { function getImportAs(specifier, newImportName) {
if (specifier.local && specifier.local.name && specifier.local.name !== specifier.imported.name) { if (specifier.local && specifier.local.name && specifier.local.name !== specifier.imported.name) {
return specifier.local.name; return specifier.local.name;
@ -23,12 +13,10 @@ function renameAndStoreImports({ path, state, opts, types: t }) {
if (t.isIdentifier(specifier.imported) && specifier.type === 'ImportSpecifier') { if (t.isIdentifier(specifier.imported) && specifier.type === 'ImportSpecifier') {
for (const change of opts.changes) { for (const change of opts.changes) {
if (specifier.imported.name === change.variable.from) { if (change.variable && specifier.imported.name === change.variable.from) {
for (const { from, to } of change.variable.paths) { for (const { from, to } of change.variable.paths) {
if (managed === false && from === path.node.source.value) { if (managed === false && from === path.node.source.value) {
const relativePart = '../'.repeat(getFolderDepth(state.filePath));
const importAs = getImportAs(specifier, change.variable.to); const importAs = getImportAs(specifier, change.variable.to);
const newPath = joinPaths(relativePart, to);
// rename so it replaces all occurrences // rename so it replaces all occurrences
path.scope.rename(specifier.local.name, importAs); path.scope.rename(specifier.local.name, importAs);
@ -38,7 +26,7 @@ function renameAndStoreImports({ path, state, opts, types: t }) {
state.importedStorage.push({ state.importedStorage.push({
action: 'change', action: 'change',
specifier, specifier,
path: newPath, path: to,
}); });
managed = true; managed = true;
} }
@ -75,14 +63,12 @@ function generateImportStatements({ state, types: t }) {
return res; return res;
} }
function replaceTagImports({ path, state, opts, types: t }) { function replaceTagImports({ path, opts, types: t }) {
for (const change of opts.changes) { for (const change of opts.changes) {
if (change.tag && Array.isArray(change.tag.paths) && change.tag.paths.length > 0) { if (change.tag && Array.isArray(change.tag.paths) && change.tag.paths.length > 0) {
for (const { from, to } of change.tag.paths) { for (const { from, to } of change.tag.paths) {
if (from === path.node.source.value) { if (from === path.node.source.value) {
const relativePart = '../'.repeat(getFolderDepth(state.filePath)); path.node.source = t.stringLiteral(to);
const updatedPath = joinPaths(relativePart, to);
path.node.source = t.stringLiteral(updatedPath);
} }
} }
} }

View file

@ -1,19 +0,0 @@
const path = require('path');
function joinPaths(a, b) {
let joinMe = b;
if (a && a === b.substring(0, a.length)) {
joinMe = b.substring(a.length + 1);
}
// Normalize for windows
const updatedPath = path.posix.join(a, joinMe);
if (a === '' && b.startsWith('./')) {
return `./${updatedPath}`;
}
return updatedPath;
}
module.exports = {
joinPaths,
};

View file

@ -1,16 +1,12 @@
const fs = require('fs');
const { joinPaths } = require('./helpers.js');
const tagExample = [ const tagExample = [
'Should be example:', 'Should be example:',
' {', ' {',
" from: 'my-counter',", " from: 'source-counter',",
" to: 'my-extension',", " to: 'extension-counter',",
' paths: [', ' paths: [',
' {', ' {',
" from: './my-counter.js',", " from: '@source/counter/define',",
" to: './my-extension/my-extension.js'", " to: 'extension/counter/define'",
' }', ' }',
' ]', ' ]',
' }', ' }',
@ -19,12 +15,12 @@ const tagExample = [
const variableExample = [ const variableExample = [
'Should be example:', 'Should be example:',
' {', ' {',
" from: 'MyCounter',", " from: 'SourceCounter',",
" to: 'MyExtension',", " to: 'ExtensionCounter',",
' paths: [', ' paths: [',
' {', ' {',
" from: './index.js',", " from: '@source/counter',",
" to: './my-extension/index.js'", " to: 'extension/counter'",
' }', ' }',
' ]', ' ]',
' }', ' }',
@ -37,7 +33,7 @@ function formatJsonErrorMessage(json) {
return `\n ${JSON.stringify(json, null, 2).split('\n').join('\n ')}`; return `\n ${JSON.stringify(json, null, 2).split('\n').join('\n ')}`;
} }
function validatePaths(paths, given, intro, example, options) { function validatePaths(paths, given, intro, example) {
if (!Array.isArray(paths) || (Array.isArray(paths) && paths.length === 0)) { if (!Array.isArray(paths) || (Array.isArray(paths) && paths.length === 0)) {
const errorMsg = [ const errorMsg = [
intro, intro,
@ -60,18 +56,11 @@ function validatePaths(paths, given, intro, example, options) {
} }
if (typeof pathObj.to !== 'string' || !pathObj.to) { if (typeof pathObj.to !== 'string' || !pathObj.to) {
throw new Error(errorMsg); throw new Error(errorMsg);
} else if (options.throwOnNonExistingPathToFiles === true) {
const filePath = joinPaths(options.rootPath, pathObj.to);
if (!(fs.existsSync(filePath) && fs.lstatSync(filePath).isFile())) {
throw new Error(
`babel-plugin-extend-docs: Rewriting import from "${pathObj.from}" to "${pathObj.to}" but we could not find a file at "${filePath}".`,
);
}
} }
} }
} }
function validateChanges(changes, options) { function validateChanges(changes) {
if (!Array.isArray(changes) || (Array.isArray(changes) && changes.length === 0)) { if (!Array.isArray(changes) || (Array.isArray(changes) && changes.length === 0)) {
const errorMsg = [ const errorMsg = [
'babel-plugin-extend-docs: The required changes array is missing.', 'babel-plugin-extend-docs: The required changes array is missing.',
@ -92,7 +81,7 @@ function validateChanges(changes, options) {
throw new Error(errorMsg); throw new Error(errorMsg);
} }
validatePaths(tag.paths, tag, intro, tagExample, options); validatePaths(tag.paths, tag, intro, tagExample);
} }
if (change.variable) { if (change.variable) {
@ -109,39 +98,16 @@ function validateChanges(changes, options) {
if (typeof variable.to !== 'string' || !variable.to) { if (typeof variable.to !== 'string' || !variable.to) {
throw new Error(errorMsg); throw new Error(errorMsg);
} }
validatePaths(variable.paths, variable, intro, variableExample);
validatePaths(variable.paths, variable, intro, variableExample, options);
} }
} }
} }
function validateOptions(_options) { function validateOptions(_options) {
const options = { const options = {
throwOnNonExistingPathToFiles: true,
throwOnNonExistingRootPath: true,
..._options, ..._options,
}; };
if (options.throwOnNonExistingRootPath) { validateChanges(options.changes);
if (!options.rootPath) {
throw new Error(
`babel-plugin-extend-docs: You need to provide a rootPath option (string)\nExample: rootPath: path.resolve('.')`,
);
}
if (!fs.existsSync(options.rootPath)) {
throw new Error(
`babel-plugin-extend-docs: The provided rootPath "${options.rootPath}" does not exist.`,
);
}
if (!fs.lstatSync(options.rootPath).isDirectory()) {
throw new Error(
`babel-plugin-extend-docs: The provided rootPath "${options.rootPath}" is not a directory.`,
);
}
}
validateChanges(options.changes, {
throwOnNonExistingPathToFiles: options.throwOnNonExistingPathToFiles,
rootPath: options.rootPath,
});
} }
module.exports = { module.exports = {

View file

@ -4,64 +4,47 @@ const { executeBabel, baseConfig } = require('./helpers.js');
const testConfig = { const testConfig = {
...baseConfig, ...baseConfig,
throwOnNonExistingPathToFiles: false,
throwOnNonExistingRootPath: false,
__filePath: '/node_module/@lion/input/README.md',
}; };
describe('babel-plugin-extend-docs', () => { describe('babel-plugin-extend-docs', () => {
it('replaces local src class imports (1)', () => { it('replaces src class imports (1)', () => {
const code = `import { LionInput } from './src/LionInput.js';`; const code = `import { LionInput } from '@lion/input';`;
const output = `import { WolfInput } from "../../../index.js";`; const output = `import { WolfInput } from "wolf-web/input";`;
expect(executeBabel(code, testConfig)).to.equal(output); expect(executeBabel(code, testConfig)).to.equal(output);
}); });
it('renames classes everywhere', () => { it('renames classes everywhere', () => {
const code = [ const code = [
`import { LionInput } from './src/LionInput.js';`, `import { LionInput } from '@lion/input';`,
`class Foo extends LionInput {}`, `class Foo extends LionInput {}`,
].join('\n'); ].join('\n');
const output = [ const output = [
`import { WolfInput } from "../../../index.js";`, `import { WolfInput } from "wolf-web/input";`,
'', '',
`class Foo extends WolfInput {}`, `class Foo extends WolfInput {}`,
].join('\n'); ].join('\n');
expect(executeBabel(code, testConfig)).to.equal(output); expect(executeBabel(code, testConfig)).to.equal(output);
}); });
it('replaces local src class imports (2)', () => { it('replaces src class imports (2)', () => {
const code = `import { LionInput } from './src/LionInput.js';`;
const output = `import { WolfInput } from "../../../../index.js";`;
const config = {
...testConfig,
__filePath: '/node_module/@lion/input/docs/README.md',
};
expect(executeBabel(code, config)).to.equal(output);
});
it('replaces local src class imports (3)', () => {
const code = `import { LionInput as Foo } from './src/LionInput.js';`;
const output = `import { WolfInput as Foo } from "../../../index.js";`;
expect(executeBabel(code, testConfig)).to.equal(output);
});
it('replaces local src class imports (4)', () => {
const code = [ const code = [
`import someDefaultHelper, { LionInput, someHelper } from './src/LionInput.js';`, `import someDefaultHelper, { LionInput, someHelper } from '@lion/input';`,
`import { LionButton } from '@lion/button';`, `import { LionButton } from '@lion/button';`,
].join('\n'); ].join('\n');
const output = [ const output = [
`import someDefaultHelper, { someHelper } from "./src/LionInput.js";`, `import someDefaultHelper, { someHelper } from "@lion/input";`,
`import { WolfInput, WolfButton } from "../../../index.js";`, `import { WolfInput } from "wolf-web/input";`,
`import { WolfButton } from "wolf-web/button";`,
].join('\n'); ].join('\n');
expect(executeBabel(code, testConfig)).to.equal(output); expect(executeBabel(code, testConfig)).to.equal(output);
}); });
it('replaces local src class imports (5)', () => { it('replaces src class imports (3)', () => {
const code = `import { LionInput, LionFoo, LionBar, someHelper } from '@lion/input';`; const code = `import { LionInput, LionFoo, LionBar, someHelper } from '@lion/input';`;
const output = [ const output = [
`import { WolfInput, WolfFoo } from "../../../index.js";`, `import { WolfInput } from "wolf-web/input";`,
`import { WolfBar } from "../../../somewhere-else.js";`, `import { WolfFoo } from "./index.js";`,
`import { WolfBar } from "./somewhere-else.js";`,
`import { someHelper } from "@lion/input";`, `import { someHelper } from "@lion/input";`,
].join('\n'); ].join('\n');
const config = { const config = {
@ -95,7 +78,6 @@ describe('babel-plugin-extend-docs', () => {
}, },
}, },
], ],
__filePath: '/node_module/@lion/input/README.md',
}; };
expect(executeBabel(code, config)).to.equal(output); expect(executeBabel(code, config)).to.equal(output);
}); });
@ -106,91 +88,27 @@ describe('babel-plugin-extend-docs', () => {
import { LionInput } from '@lion/input'; import { LionInput } from '@lion/input';
`; `;
const output = [ const output = [
`import { localize } from "../../../localize.js";`, `import { localize } from "wolf-web/localize";`,
`import { WolfInput } from "../../../index.js";`, `import { WolfInput } from "wolf-web/input";`,
].join('\n'); ].join('\n');
expect(executeBabel(code, testConfig)).to.equal(output); expect(executeBabel(code, testConfig)).to.equal(output);
}); });
it('allows separate import paths of managed imports', () => {
const code1 = `import { LionInput } from '@lion/input';`;
const code2 = `import { LionInput } from './src/LionInput.js';`;
const output1 = `import { WolfInput } from "../../../index.js";`;
const output2 = `import { WolfInput } from "../../../packages/input/src/WolfInput.js";`;
const config = {
...testConfig,
changes: [
{
description: 'LionInput',
variable: {
from: 'LionInput',
to: 'WolfInput',
paths: [
{
from: '@lion/input',
to: './index.js',
},
{
from: './src/LionInput.js',
to: './packages/input/src/WolfInput.js',
},
],
},
},
],
__filePath: '/node_module/@lion/input/README.md',
};
expect(executeBabel(code1, config)).to.equal(output1);
expect(executeBabel(code2, config)).to.equal(output2);
});
it('replaces local index.js class imports (1)', () => {
const code = `import { LionInput } from './index.js';`;
const output = `import { WolfInput } from "../../../index.js";`;
expect(executeBabel(code, testConfig)).to.equal(output);
});
it('replaces local index.js class imports (2)', () => {
const code = `import { LionInput } from './index.js';`;
const output = `import { WolfInput } from "../../../../index.js";`;
const config = {
...testConfig,
__filePath: '/node_module/@lion/input/docs/README.md',
};
expect(executeBabel(code, config)).to.equal(output);
});
it('works with local index.js class imports with an empty relative path', () => {
const code = `import { LionInput } from './index.js';`;
const output = `import { WolfInput } from "./index.js";`;
const config = {
...testConfig,
__filePath: './README.md',
};
expect(executeBabel(code, config)).to.equal(output);
});
it('replaces `@lion` class imports', () => { it('replaces `@lion` class imports', () => {
const code = `import { LionInput } from '@lion/input';`; const code = `import { LionInput } from '@lion/input';`;
const output = `import { WolfInput } from "../../../index.js";`; const output = `import { WolfInput } from "wolf-web/input";`;
expect(executeBabel(code, testConfig)).to.equal(output); expect(executeBabel(code, testConfig)).to.equal(output);
}); });
it('does NOT replace imports no in the config', () => { it('does NOT replace imports not in the config', () => {
const code = `import { FooInput } from '@lion/input';`; const code = `import { FooInput } from '@lion/calendar';`;
const output = `import { FooInput } from "@lion/input";`; const output = `import { FooInput } from "@lion/calendar";`;
expect(executeBabel(code, testConfig)).to.equal(output);
});
it('replaces local tag imports', () => {
const code = `import './lion-input.js';`;
const output = `import "../../../__element-definitions/wolf-input.js";`;
expect(executeBabel(code, testConfig)).to.equal(output); expect(executeBabel(code, testConfig)).to.equal(output);
}); });
it('replaces `@lion` tag imports', () => { it('replaces `@lion` tag imports', () => {
const code = `import '@lion/input/define';`; const code = `import '@lion/input/define';`;
const output = `import "../../../__element-definitions/wolf-input.js";`; const output = `import "#input/define";`;
expect(executeBabel(code, testConfig)).to.equal(output); expect(executeBabel(code, testConfig)).to.equal(output);
}); });
@ -346,7 +264,7 @@ describe('babel-plugin-extend-docs', () => {
const code = `import * as all from '@lion/input';`; const code = `import * as all from '@lion/input';`;
const output = ` const output = `
import { notRenameHelper } from "@lion/input"; import { notRenameHelper } from "@lion/input";
import { WolfInput } from "../../../index.js"; import { WolfInput } from "wolf-web/input";
const all = { LionInput: WolfInput, someHelper }; const all = { LionInput: WolfInput, someHelper };
`; `;
expect(executeBabel(code, testConfig)).to.equal(output); expect(executeBabel(code, testConfig)).to.equal(output);

View file

@ -17,17 +17,9 @@ const baseConfig = {
from: 'LionInput', from: 'LionInput',
to: 'WolfInput', to: 'WolfInput',
paths: [ paths: [
{
from: './index.js',
to: './index.js',
},
{
from: './src/LionInput.js',
to: './index.js',
},
{ {
from: '@lion/input', from: '@lion/input',
to: './index.js', to: 'wolf-web/input',
}, },
], ],
}, },
@ -35,13 +27,9 @@ const baseConfig = {
from: 'lion-input', from: 'lion-input',
to: 'wolf-input', to: 'wolf-input',
paths: [ paths: [
{
from: './lion-input.js',
to: './__element-definitions/wolf-input.js',
},
{ {
from: '@lion/input/define', from: '@lion/input/define',
to: './__element-definitions/wolf-input.js', to: '#input/define',
}, },
], ],
}, },
@ -52,17 +40,9 @@ const baseConfig = {
from: 'LionButton', from: 'LionButton',
to: 'WolfButton', to: 'WolfButton',
paths: [ paths: [
{
from: './index.js',
to: './index.js',
},
{
from: './src/LionButton.js',
to: './index.js',
},
{ {
from: '@lion/button', from: '@lion/button',
to: './index.js', to: 'wolf-web/button',
}, },
], ],
}, },
@ -70,13 +50,9 @@ const baseConfig = {
from: 'lion-button', from: 'lion-button',
to: 'wolf-button', to: 'wolf-button',
paths: [ paths: [
{
from: './lion-button.js',
to: './__element-definitions/wolf-button.js',
},
{ {
from: '@lion/button/define', from: '@lion/button/define',
to: './__element-definitions/wolf-button.js', to: '#button/define',
}, },
], ],
}, },
@ -87,17 +63,9 @@ const baseConfig = {
from: 'LionCheckbox', from: 'LionCheckbox',
to: 'WolfCheckbox', to: 'WolfCheckbox',
paths: [ paths: [
{
from: './index.js',
to: './index.js',
},
{
from: './src/LionCheckbox.js',
to: './index.js',
},
{ {
from: '@lion/checkbox-group', from: '@lion/checkbox-group',
to: './index.js', to: 'wolf-web/checkbox-group',
}, },
], ],
}, },
@ -106,12 +74,8 @@ const baseConfig = {
to: 'wolf-checkbox', to: 'wolf-checkbox',
paths: [ paths: [
{ {
from: './lion-checkbox.js', from: '@lion/checkbox-group/define',
to: './__element-definitions/wolf-checkbox.js', to: '#checkbox-group/define',
},
{
from: '@lion/checkbox-group/lion-checkbox.js',
to: './__element-definitions/wolf-checkbox.js',
}, },
], ],
}, },
@ -122,17 +86,9 @@ const baseConfig = {
from: 'localize', from: 'localize',
to: 'localize', to: 'localize',
paths: [ paths: [
{
from: './index.js',
to: './localize.js',
},
{
from: './src/localize.js',
to: './localize.js',
},
{ {
from: '@lion/localize', from: '@lion/localize',
to: './localize.js', to: 'wolf-web/localize',
}, },
], ],
}, },

View file

@ -1,5 +1,4 @@
const { expect } = require('chai'); const { expect } = require('chai');
const path = require('path');
const { executeBabel } = require('./helpers.js'); const { executeBabel } = require('./helpers.js');
const extendDocsConfig = { const extendDocsConfig = {
@ -9,27 +8,22 @@ const extendDocsConfig = {
variable: { variable: {
from: 'MyCounter', from: 'MyCounter',
to: 'MyExtension', to: 'MyExtension',
paths: [ paths: [{ from: 'source/counter', to: 'extension/counter' }],
{ from: './index.js', to: './my-extension/index.js' },
{ from: './src/MyCounter.js', to: './my-extension/index.js' },
],
}, },
tag: { tag: {
from: 'my-counter', from: 'my-counter',
to: 'my-extension', to: 'my-extension',
paths: [{ from: './my-counter.js', to: './my-extension/my-extension.js' }], paths: [{ from: 'source/counter/define', to: '#counter/define' }],
}, },
}, },
], ],
rootPath: path.resolve('./demo'),
__filePath: '/my-app.demo.js',
}; };
describe('babel-plugin-extend-docs: integration tests', () => { describe('babel-plugin-extend-docs: integration tests', () => {
it('works for the demo', () => { it('works for the demo', () => {
const code = `import { LitElement, html } from '@lion/core'; const code = `import { LitElement, html } from '@lion/core';
import { MyCounter } from './src/MyCounter.js'; import { MyCounter } from 'source/counter';
import './my-counter.js'; import 'source/counter/define';
class TenCounter extends MyCounter { class TenCounter extends MyCounter {
inc() { inc() {
@ -53,8 +47,8 @@ class MyApp extends LitElement {
customElements.define('my-app', MyApp); customElements.define('my-app', MyApp);
`; `;
const output = `import { LitElement, html } from "@lion/core"; const output = `import { LitElement, html } from "@lion/core";
import { MyExtension } from "./my-extension/index.js"; import { MyExtension } from "extension/counter";
import "./my-extension/my-extension.js"; import "#counter/define";
class TenCounter extends MyExtension { class TenCounter extends MyExtension {
inc() { inc() {

View file

@ -1,6 +1,6 @@
const { expect } = require('chai'); const { expect } = require('chai');
const path = require('path'); const path = require('path');
const { executeBabel, baseConfig } = require('./helpers.js'); const { executeBabel } = require('./helpers.js');
function formatJsonErrorMessage(json) { function formatJsonErrorMessage(json) {
if (!json) { if (!json) {
@ -10,30 +10,6 @@ function formatJsonErrorMessage(json) {
} }
describe('babel-plugin-extend-docs: validateOptions', () => { describe('babel-plugin-extend-docs: validateOptions', () => {
it('throws if no rootPath string is provided', () => {
expect(() => executeBabel('', { ...baseConfig })).to.throw(
`babel-plugin-extend-docs: You need to provide a rootPath option (string)\nExample: rootPath: path.resolve('.')`,
);
});
it('throws if rootPath does not exist', () => {
expect(() => executeBabel('', { ...baseConfig, rootPath: 'something' })).to.throw(
`babel-plugin-extend-docs: The provided rootPath "something" does not exist.`,
);
});
it('throws if rootPath is not a directory', () => {
const rootPath = path.resolve('./index.js');
expect(() => {
executeBabel('', {
...baseConfig,
rootPath,
});
}).to.throw(
`babel-plugin-extend-docs: The provided rootPath "${rootPath}" is not a directory.`,
);
});
it('throws if no changes array is provided', () => { it('throws if no changes array is provided', () => {
expect(() => { expect(() => {
executeBabel('', { executeBabel('', {
@ -45,12 +21,12 @@ describe('babel-plugin-extend-docs: validateOptions', () => {
`Given: ${formatJsonErrorMessage(undefined)}`, `Given: ${formatJsonErrorMessage(undefined)}`,
'Should be example:', 'Should be example:',
' {', ' {',
" from: 'my-counter',", " from: 'source-counter',",
" to: 'my-extension',", " to: 'extension-counter',",
' paths: [', ' paths: [',
' {', ' {',
" from: './my-counter.js',", " from: '@source/counter/define',",
" to: './my-extension/my-extension.js'", " to: 'extension/counter/define'",
' }', ' }',
' ]', ' ]',
' }', ' }',
@ -72,12 +48,12 @@ describe('babel-plugin-extend-docs: validateOptions', () => {
`Given: ${formatJsonErrorMessage(tag)}`, `Given: ${formatJsonErrorMessage(tag)}`,
'Should be example:', 'Should be example:',
' {', ' {',
" from: 'my-counter',", " from: 'source-counter',",
" to: 'my-extension',", " to: 'extension-counter',",
' paths: [', ' paths: [',
' {', ' {',
" from: './my-counter.js',", " from: '@source/counter/define',",
" to: './my-extension/my-extension.js'", " to: 'extension/counter/define'",
' }', ' }',
' ]', ' ]',
' }', ' }',
@ -126,12 +102,12 @@ describe('babel-plugin-extend-docs: validateOptions', () => {
`Given: ${formatJsonErrorMessage(variable)}`, `Given: ${formatJsonErrorMessage(variable)}`,
'Should be example:', 'Should be example:',
' {', ' {',
" from: 'MyCounter',", " from: 'SourceCounter',",
" to: 'MyExtension',", " to: 'ExtensionCounter',",
' paths: [', ' paths: [',
' {', ' {',
" from: './index.js',", " from: '@source/counter',",
" to: './my-extension/index.js'", " to: 'extension/counter'",
' }', ' }',
' ]', ' ]',
' }', ' }',
@ -166,33 +142,6 @@ describe('babel-plugin-extend-docs: validateOptions', () => {
variableThrowsErrorFor({ ...pathSetup, paths: [{ from: '', to: './index.js' }] }, pathMsg); variableThrowsErrorFor({ ...pathSetup, paths: [{ from: '', to: './index.js' }] }, pathMsg);
}); });
it('throws if "to path" could not be found on file system', () => {
expect(() => {
executeBabel('', {
changes: [
{
tag: {
from: 'lion-input',
to: 'wolf-input',
paths: [
{
from: './lion-input.js',
to: './non-existing/wolf-input.js',
},
],
},
},
],
rootPath: path.resolve('./'),
});
}).to.throw(
[
'babel-plugin-extend-docs: Rewriting import from "./lion-input.js" to "./non-existing/wolf-input.js" but we ',
`could not find a file at "${path.resolve('./')}/non-existing/wolf-input.js".`,
].join(''),
);
});
it('does NOT throws if "to path" could be found on file system', () => { it('does NOT throws if "to path" could be found on file system', () => {
expect(() => { expect(() => {
executeBabel('', { executeBabel('', {

View file

@ -0,0 +1,3 @@
# Rocket Preset Extend Lion Docs
[=> See Source <=](../../docs/docs/node-tools/rocket-preset-extend-lion-docs/overview.md)

View file

@ -0,0 +1,3 @@
# Rocket Preset Extend Lion Docs
[=> See Source <=](../../docs/docs/node-tools/rocket-preset-extend-lion-docs/overview.md)

View file

@ -0,0 +1 @@
export { extendLionDocs } from './preset/extendLionDocs.js';

View file

@ -0,0 +1,47 @@
{
"name": "rocket-preset-extend-lion-docs",
"version": "0.0.0",
"description": "A rocket preset to reuse lion documentation inside your design system extension",
"license": "MIT",
"author": "ing-bank",
"homepage": "https://github.com/ing-bank/lion/",
"repository": {
"type": "git",
"url": "https://github.com/ing-bank/lion.git",
"directory": "packages-node/rocket-preset-extend-lion-docs"
},
"type": "module",
"main": "index.js",
"files": [
"*.js",
"docs",
"preset",
"src",
"test"
],
"scripts": {
"publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../",
"prepublishOnly": "npm run publish-docs",
"test": "npm run test:node",
"test:node": "mocha test-node",
"test:watch": "mocha test-node --watch"
},
"dependencies": {
"@babel/core": "^7.10.1",
"es-module-lexer": "^0.3.6",
"plugins-manager": "^0.2.1",
"remark-extend": "0.4.2",
"unist-util-visit": "^2.0.2"
},
"keywords": [
"docs",
"lion",
"rocket"
],
"publishConfig": {
"access": "public"
},
"exports": {
".": "./index.js"
}
}

View file

@ -0,0 +1,69 @@
import path from 'path';
import { fileURLToPath } from 'url';
import { addPlugin } from 'plugins-manager';
// @ts-ignore
import remarkExtendPkg from 'remark-extend';
import { remarkExtendLionDocsTransformJs } from '../src/remarkExtendLionDocsTransformJs.js';
import { remarkUrlToLocal } from '../src/remarkUrlToLocal.js';
import { generateExtendDocsConfig } from '../src/generateExtendDocsConfig.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
* @param {object} opts
* @param {string} [opts.rootDir]
* @param {string} [opts.nodeModulesDir]
* @param {string} opts.classPrefix
* @param {string} opts.classBareImport
* @param {string} opts.tagPrefix
* @param {string} opts.tagBareImport
* @returns
*/
export async function extendLionDocs({
rootDir,
nodeModulesDir,
classPrefix,
classBareImport,
tagPrefix,
tagBareImport,
}) {
const changes = await generateExtendDocsConfig({
nodeModulesDir,
classPrefix,
classBareImport,
tagPrefix,
tagBareImport,
});
const extendDocsConfig = {
changes,
};
const _rootDir = rootDir || path.resolve('.');
return {
path: path.resolve(__dirname),
setupUnifiedPlugins: [
addPlugin({
name: 'remark-extend',
plugin: remarkExtendPkg.remarkExtend,
location: 'markdown',
}),
addPlugin({
name: 'github-urls-to-local',
plugin: remarkUrlToLocal,
location: 'remark-extend',
options: {
gitHubUrl: 'https://github.com/ing-bank/lion/',
rootDir: _rootDir,
},
}),
addPlugin({
name: 'remark-extend-lion-docs-transform-js',
plugin: remarkExtendLionDocsTransformJs,
location: 'remark-extend',
options: { extendDocsConfig },
}),
],
};
}

View file

@ -0,0 +1,171 @@
import fs from 'fs';
import path from 'path';
// @ts-ignore
import { init, parse } from 'es-module-lexer/dist/lexer.js';
/**
* @param {string} src
* @returns
*/
function getImportNames(src) {
const [imports] = parse(src);
/** @type {string[]} */
const names = [];
for (const importObj of imports) {
const full = src.substring(importObj.ss, importObj.se);
if (full.includes('{')) {
const namesString = full.substring(full.indexOf('{') + 1, full.indexOf('}'));
namesString.split(',').forEach(name => {
names.push(name.trim());
});
}
}
return names;
}
/**
* @param {object} opts
* @param {string} opts.className
* @param {string} opts.pkgName
* @param {string} opts.classPrefix
* @param {string} opts.classBareImport
* @param {string} [opts.sourceClassPrefix]
* @param {string} [opts.sourceBareImport]
* @returns
*/
function generateVariableChange({
className,
pkgName,
classPrefix,
classBareImport,
sourceClassPrefix = 'Lion',
sourceBareImport = '@lion/',
}) {
const pureClassName = className.replace(sourceClassPrefix, '');
const purePkgName = pkgName.replace(sourceBareImport, '');
return {
name: `${pkgName} - ${className}`,
variable: {
from: `${sourceClassPrefix}${pureClassName}`,
to: `${classPrefix}${pureClassName}`,
paths: [
{
from: `${sourceBareImport}${purePkgName}`,
to: `${classBareImport}${purePkgName}`,
},
],
},
};
}
/**
* @param {object} opts
* @param {string} opts.tagName
* @param {string} opts.pkgName
* @param {string} opts.definePath
* @param {string} opts.tagPrefix
* @param {string} opts.tagBareImport
* @param {string} [opts.sourceTagPrefix]
* @param {string} [opts.sourceBareImport]
* @returns
*/
function generateTagChange({
tagName,
pkgName,
definePath,
tagPrefix,
tagBareImport,
sourceTagPrefix = 'lion-',
sourceBareImport = '@lion/',
}) {
const pureTagName = tagName.replace(sourceTagPrefix, '');
const purePkgName = pkgName.replace(sourceBareImport, '');
return {
name: `${pkgName}${definePath}`,
tag: {
from: `${sourceTagPrefix}${pureTagName}`,
to: `${tagPrefix}${pureTagName}`,
paths: [
{
from: `${sourceBareImport}${purePkgName}${definePath}`,
to: `${tagBareImport}${purePkgName}${definePath}`,
},
],
},
};
}
/**
* @param {object} opts
* @param {string} [opts.nodeModulesDir]
* @param {string} [opts.npmScope]
* @param {string} opts.classPrefix
* @param {string} opts.classBareImport
* @param {string} opts.tagPrefix
* @param {string} opts.tagBareImport
* @returns
*/
export async function generateExtendDocsConfig({
nodeModulesDir,
npmScope = '@lion',
classPrefix,
classBareImport,
tagPrefix,
tagBareImport,
}) {
const _nodeModulesDir = nodeModulesDir || path.resolve('./node_modules');
await init;
const options = { classPrefix, classBareImport, tagPrefix, tagBareImport };
const folderToCheck = path.join(_nodeModulesDir, npmScope);
const packages = fs
.readdirSync(folderToCheck)
.filter(dir => fs.statSync(path.join(folderToCheck, dir)).isDirectory())
.map(dir => `${npmScope}/${dir}`);
const changes = [];
for (const pkgName of packages) {
const pkgPath = path.join(_nodeModulesDir, ...pkgName.split('/'));
const pkgJsonPath = path.join(pkgPath, 'package.json');
const pkgJsonString = await fs.promises.readFile(pkgJsonPath, 'utf8');
const pkgJson = JSON.parse(pkgJsonString);
const pkgExports = pkgJson.exports;
for (const pkgExportName of Object.keys(pkgExports)) {
const pkgExportPath = pkgExports[pkgExportName];
const entryPointFilePath = path.join(pkgPath, pkgExportPath);
if (pkgExportName === '.') {
const src = await fs.promises.readFile(entryPointFilePath, 'utf8');
const importNames = getImportNames(src);
for (const importName of importNames) {
changes.push(generateVariableChange({ className: importName, pkgName, ...options }));
}
}
if (pkgExportName.startsWith('./define')) {
const src = await fs.promises.readFile(entryPointFilePath, 'utf8');
const definePath = `/${pkgExportName.substr(2)}`;
if (src.includes('.define(')) {
const matches = src.match(/define\(['"](.*)['"]/);
if (matches && matches[1]) {
const tagName = matches[1];
changes.push(generateTagChange({ tagName, pkgName, definePath, ...options }));
}
} else {
changes.push(
generateTagChange({ tagName: 'xxx-workaround-xxx', pkgName, definePath, ...options }),
);
}
}
}
}
return changes;
}

View file

@ -0,0 +1,56 @@
/* eslint-disable no-param-reassign */
import babelPkg from '@babel/core';
import visit from 'unist-util-visit';
const { transformSync } = babelPkg;
/** @typedef {import('vfile').VFileOptions} VFileOptions */
/** @typedef {import('unist').Node} Node */
/**
* @typedef {Object} CodeProperties
* @property {string} [value]
* @property {string} [lang]
* @property {string} [meta]
*/
/** @typedef {Node & CodeProperties} CodeNode */
/**
* @param {object} opts
* @param {object} opts.extendDocsConfig
* @returns
*/
export function remarkExtendLionDocsTransformJs({ extendDocsConfig }) {
/**
* @param {CodeNode} node
*/
const visitor = node => {
if (
node.type === 'code' &&
node.lang === 'js' &&
(node.meta === 'story' || node.meta === 'preview-story' || node.meta === 'script') &&
node.value
) {
const processed = transformSync(node.value, {
plugins: [['babel-plugin-extend-docs', extendDocsConfig]],
});
if (processed && processed.code) {
node.value = processed.code;
}
}
return node;
};
/**
* @param {Node} tree
*/
function transformer(tree) {
// @ts-ignore
visit(tree, visitor);
return tree;
}
return transformer;
}

View file

@ -0,0 +1,58 @@
/* eslint-disable no-param-reassign */
import path from 'path';
import visit from 'unist-util-visit';
/** @typedef {import('vfile').VFileOptions} VFileOptions */
/** @typedef {import('unist').Node} Node */
/**
* @typedef {Object} UrlProperty
* @property {string} url
*/
/** @typedef {Node & UrlProperty} UrlNode */
/**
* @param {object} opts
* @param {string} opts.gitHubUrl
* @param {object} opts.page
* @param {string} opts.page.inputPath
* @param {string} opts.rootDir
* @returns
*/
export function remarkUrlToLocal({ gitHubUrl, page, rootDir }) {
/**
* @param {UrlNode} node
*/
const visitor = node => {
if (node.type === 'link' || node.type === 'image') {
if (node.url.startsWith(gitHubUrl)) {
const urlPart = node.url.substring(gitHubUrl.length);
const urlParts = urlPart.split('/');
if (urlParts[0] === 'blob') {
urlParts.shift();
urlParts.shift();
const fullUrlPath = path.join(rootDir, urlParts.join('/'));
const fullInputPath =
page.inputPath[0] === '/' ? page.inputPath : path.join(rootDir, page.inputPath);
const newPath = path.relative(path.dirname(fullInputPath), fullUrlPath);
node.url = newPath;
}
}
}
return node;
};
/**
* @param {Node} tree
*/
function transformer(tree) {
// @ts-ignore
visit(tree, visitor);
return tree;
}
return transformer;
}

View file

@ -0,0 +1 @@
export { LionAccordion } from './src/LionAccordion.js';

View file

@ -0,0 +1,3 @@
import { LionAccordion } from './src/LionAccordion.js';
customElements.define('lion-accordion', LionAccordion);

View file

@ -0,0 +1,55 @@
{
"name": "@lion/accordion",
"version": "0.5.0",
"description": "Vertically stacked list of invokers that can be clicked to reveal or hide content associated with them.",
"license": "MIT",
"author": "ing-bank",
"homepage": "https://github.com/ing-bank/lion/",
"repository": {
"type": "git",
"url": "https://github.com/ing-bank/lion.git",
"directory": "packages/accordion"
},
"main": "index.js",
"module": "index.js",
"files": [
"*.d.ts",
"*.js",
"custom-elements.json",
"docs",
"src",
"test",
"test-helpers",
"translations",
"types"
],
"scripts": {
"custom-elements-manifest": "custom-elements-manifest analyze --litelement --exclude \"docs/**/*\" \"test-helpers/**/*\"",
"debug": "cd ../../ && npm run debug -- --group accordion",
"debug:firefox": "cd ../../ && npm run debug:firefox -- --group accordion",
"debug:webkit": "cd ../../ && npm run debug:webkit -- --group accordion",
"publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../",
"prepublishOnly": "npm run publish-docs && npm run custom-elements-manifest",
"test": "cd ../../ && npm run test:browser -- --group accordion"
},
"sideEffects": [
"lion-accordion.js"
],
"dependencies": {
"@lion/core": "0.17.0"
},
"keywords": [
"accordion",
"lion",
"web-components"
],
"publishConfig": {
"access": "public"
},
"customElementsManifest": "custom-elements.json",
"exports": {
".": "./index.js",
"./define": "./lion-accordion.js",
"./docs/": "./docs/"
}
}

View file

@ -0,0 +1 @@
export class LionAccordion extends HTMLElement {}

View file

@ -0,0 +1,3 @@
import '@lion/checkbox-group/define-checkbox';
import '@lion/checkbox-group/define-checkbox-group';
import '@lion/checkbox-group/define-checkbox-indeterminate';

View file

@ -0,0 +1,3 @@
export { LionCheckboxGroup } from './src/LionCheckboxGroup.js';
export { LionCheckboxIndeterminate } from './src/LionCheckboxIndeterminate.js';
export { LionCheckbox } from './src/LionCheckbox.js';

View file

@ -0,0 +1,3 @@
import { LionCheckboxGroup } from './src/LionCheckboxGroup.js';
customElements.define('lion-checkbox-group', LionCheckboxGroup);

View file

@ -0,0 +1,3 @@
import { LionCheckboxIndeterminate } from './src/LionCheckboxIndeterminate.js';
customElements.define('lion-checkbox-indeterminate', LionCheckboxIndeterminate);

View file

@ -0,0 +1,3 @@
import { LionCheckbox } from './src/LionCheckbox.js';
customElements.define('lion-checkbox', LionCheckbox);

View file

@ -0,0 +1,63 @@
{
"name": "@lion/checkbox-group",
"version": "0.17.0",
"description": "A container for multiple checkboxes",
"license": "MIT",
"author": "ing-bank",
"homepage": "https://github.com/ing-bank/lion/",
"repository": {
"type": "git",
"url": "https://github.com/ing-bank/lion.git",
"directory": "packages/checkbox-group"
},
"main": "index.js",
"module": "index.js",
"files": [
"*.d.ts",
"*.js",
"custom-elements.json",
"docs",
"src",
"test",
"test-helpers",
"translations",
"types"
],
"scripts": {
"custom-elements-manifest": "custom-elements-manifest analyze --litelement --exclude \"docs/**/*\" \"test-helpers/**/*\"",
"debug": "cd ../../ && npm run debug -- --group checkbox-group",
"debug:firefox": "cd ../../ && npm run debug:firefox -- --group checkbox-group",
"debug:webkit": "cd ../../ && npm run debug:webkit -- --group checkbox-group",
"publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../",
"prepublishOnly": "npm run publish-docs && npm run custom-elements-manifest",
"test": "cd ../../ && npm run test:browser -- --group checkbox-group"
},
"sideEffects": [
"define.js",
"lion-checkbox.js",
"lion-checkbox-group.js",
"lion-checkbox-indeterminate.js"
],
"dependencies": {
"@lion/core": "0.17.0",
"@lion/form-core": "0.12.0",
"@lion/input": "0.14.0"
},
"keywords": [
"checkbox-group",
"lion",
"web-components"
],
"publishConfig": {
"access": "public"
},
"customElementsManifest": "custom-elements.json",
"exports": {
".": "./index.js",
"./define-checkbox": "./lion-checkbox.js",
"./define-checkbox-group": "./lion-checkbox-group.js",
"./define-checkbox-indeterminate": "./lion-checkbox-indeterminate.js",
"./define": "./define.js",
"./docs/": "./docs/"
}
}

View file

@ -0,0 +1 @@
export class LionCheckbox extends HTMLElement {}

View file

@ -0,0 +1 @@
export class LionCheckboxGroup extends HTMLElement {}

View file

@ -0,0 +1 @@
export class LionCheckboxIndeterminate extends HTMLElement {}

View file

@ -0,0 +1,212 @@
/* eslint-disable import/no-extraneous-dependencies */
import path from 'path';
import { fileURLToPath } from 'url';
import chai from 'chai';
import { generateExtendDocsConfig } from '../src/generateExtendDocsConfig.js';
const { expect } = chai;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
* @param {string} input
* @param {object} [options]
* @param {string} [options.nodeModulesDir]
* @param {string} [options.npmScope]
* @param {string} [options.classPrefix]
* @param {string} [options.classBareImport]
* @param {string} [options.tagPrefix]
* @param {string} [options.tagBareImport]
* @returns
*/
async function execute(input, options = {}) {
const nodeModulesDir = path.join(__dirname, input);
const result = await generateExtendDocsConfig({
// used tsc version does not recognize optional jsdoc params
// @ts-ignore
nodeModulesDir,
// @ts-ignore
classPrefix: 'Ing',
// @ts-ignore
classBareImport: 'ing-web/',
// @ts-ignore
tagPrefix: 'ing-',
// @ts-ignore
tagBareImport: '#',
// @ts-ignore
...options,
});
return result;
}
describe('generateExtendDocsConfig', () => {
it('works for packages with a single class and tag export', async () => {
const result = await execute('fixtures/accordion');
expect(result).to.deep.equal([
{
name: '@lion/accordion - LionAccordion',
variable: {
from: 'LionAccordion',
to: 'IngAccordion',
paths: [
{
from: '@lion/accordion',
to: 'ing-web/accordion',
},
],
},
},
{
name: '@lion/accordion/define',
tag: {
from: 'lion-accordion',
to: 'ing-accordion',
paths: [
{
from: '@lion/accordion/define',
to: '#accordion/define',
},
],
},
},
]);
});
it('can customize the target', async () => {
const result = await execute('fixtures/accordion', {
classPrefix: 'Wolf',
classBareImport: '@wolf-web/',
tagPrefix: 'wolf-',
tagBareImport: '@wolf-web/',
});
expect(result).to.deep.equal([
{
name: '@lion/accordion - LionAccordion',
variable: {
from: 'LionAccordion',
to: 'WolfAccordion',
paths: [
{
from: '@lion/accordion',
to: '@wolf-web/accordion',
},
],
},
},
{
name: '@lion/accordion/define',
tag: {
from: 'lion-accordion',
to: 'wolf-accordion',
paths: [
{
from: '@lion/accordion/define',
to: '@wolf-web/accordion/define',
},
],
},
},
]);
});
it('works for packages with multiple class and tag exports', async () => {
const result = await execute('fixtures/checkbox-group');
expect(result).to.deep.equal([
{
name: '@lion/checkbox-group - LionCheckboxGroup',
variable: {
from: 'LionCheckboxGroup',
to: 'IngCheckboxGroup',
paths: [
{
from: '@lion/checkbox-group',
to: 'ing-web/checkbox-group',
},
],
},
},
{
name: '@lion/checkbox-group - LionCheckboxIndeterminate',
variable: {
from: 'LionCheckboxIndeterminate',
to: 'IngCheckboxIndeterminate',
paths: [
{
from: '@lion/checkbox-group',
to: 'ing-web/checkbox-group',
},
],
},
},
{
name: '@lion/checkbox-group - LionCheckbox',
variable: {
from: 'LionCheckbox',
to: 'IngCheckbox',
paths: [
{
from: '@lion/checkbox-group',
to: 'ing-web/checkbox-group',
},
],
},
},
{
name: '@lion/checkbox-group/define-checkbox',
tag: {
from: 'lion-checkbox',
to: 'ing-checkbox',
paths: [
{
from: '@lion/checkbox-group/define-checkbox',
to: '#checkbox-group/define-checkbox',
},
],
},
},
{
name: '@lion/checkbox-group/define-checkbox-group',
tag: {
from: 'lion-checkbox-group',
to: 'ing-checkbox-group',
paths: [
{
from: '@lion/checkbox-group/define-checkbox-group',
to: '#checkbox-group/define-checkbox-group',
},
],
},
},
{
name: '@lion/checkbox-group/define-checkbox-indeterminate',
tag: {
from: 'lion-checkbox-indeterminate',
to: 'ing-checkbox-indeterminate',
paths: [
{
from: '@lion/checkbox-group/define-checkbox-indeterminate',
to: '#checkbox-group/define-checkbox-indeterminate',
},
],
},
},
{
name: '@lion/checkbox-group/define',
tag: {
from: 'lion-xxx-workaround-xxx',
to: 'ing-xxx-workaround-xxx',
paths: [
{
from: '@lion/checkbox-group/define',
to: '#checkbox-group/define',
},
],
},
},
]);
});
});

View file

@ -0,0 +1,135 @@
/* eslint-disable import/no-extraneous-dependencies */
import path from 'path';
import { fileURLToPath } from 'url';
import chai from 'chai';
import { mdjsProcess } from '@mdjs/core';
import { addPlugin } from 'plugins-manager';
import { remarkExtendLionDocsTransformJs } from '../src/remarkExtendLionDocsTransformJs.js';
const { expect } = chai;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
* @param {string} input
*/
async function execute(input) {
const rootDir = path.join(__dirname, '../../../');
const extendDocsConfig = {
rootPath: rootDir,
__filePath: 'fake',
changes: [
{
name: '@lion/accordion - LionAccordion',
variable: {
from: 'LionAccordion',
to: 'IngAccordion',
paths: [
{
from: '@lion/accordion',
to: 'ing-web/accordion',
},
],
},
},
{
name: '@lion/accordion/define',
tag: {
from: 'lion-accordion',
to: 'ing-accordion',
paths: [
{
from: '@lion/accordion/define',
to: '#accordion/define',
},
],
},
},
],
};
// @ts-ignore
const result = await mdjsProcess(input, {
setupUnifiedPlugins: [
addPlugin({
name: 'remark-extend-lion-docs-transform-js',
plugin: remarkExtendLionDocsTransformJs,
location: 'markdown',
options: { extendDocsConfig },
}),
],
});
return result;
}
describe('remarkExtendLionDocsTransformJs', () => {
it('processes all instance of code and code snippets', async () => {
const result = await execute(
[
'',
'```js script',
"import { html } from '@lion/core';",
"import '@lion/accordion/define';",
'```',
'',
'```js preview-story',
'export const main = () => html`',
' <lion-accordion></lion-accordion>',
'`;',
'```',
].join('\n'),
);
expect(result.html).to.include('ing-accordion');
expect(result.html).to.equal(
[
'<mdjs-preview mdjs-story-name="main">',
'',
'',
'',
'<pre class="language-js"><code class="language-js"><span class="token keyword module">export</span> <span class="token keyword">const</span> <span class="token function-variable function">main</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token html language-html">',
' <span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>ing-accordion</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>ing-accordion</span><span class="token punctuation">></span></span>',
'</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>',
'</code></pre>',
'',
'',
'',
'</mdjs-preview>',
].join('\n'),
);
expect(result.jsCode).to.include('ing-accordion');
expect(result.jsCode).to.include('#accordion/define');
expect(result.jsCode).to.equal(
[
'/** script code **/',
'import { html } from "@lion/core";',
'import "#accordion/define";',
'/** stories code **/',
'export const main = () => html`',
' <ing-accordion></ing-accordion>',
'`;',
'/** stories setup code **/',
'const rootNode = document;',
"const stories = [{ key: 'main', story: main }];",
'let needsMdjsElements = false;',
'for (const story of stories) {',
// eslint-disable-next-line no-template-curly-in-string
' const storyEl = rootNode.querySelector(`[mdjs-story-name="${story.key}"]`);',
' if (storyEl) {',
' storyEl.story = story.story;',
' storyEl.key = story.key;',
' needsMdjsElements = true;',
' Object.assign(storyEl, {});',
' }',
'};',
'if (needsMdjsElements) {',
" if (!customElements.get('mdjs-preview')) { import('@mdjs/mdjs-preview/define'); }",
" if (!customElements.get('mdjs-story')) { import('@mdjs/mdjs-story/define'); }",
'}',
].join('\n'),
);
});
});

View file

@ -0,0 +1,72 @@
/* eslint-disable import/no-extraneous-dependencies */
import path from 'path';
import { fileURLToPath } from 'url';
import chai from 'chai';
import unified from 'unified';
import markdown from 'remark-parse';
import mdStringify from 'remark-html';
import { remarkUrlToLocal } from '../src/remarkUrlToLocal.js';
const { expect } = chai;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
* @param {string} input
* @returns
*/
async function execute(input) {
const rootDir = path.join(__dirname, '../../../');
const parser = unified()
//
.use(markdown)
.use(remarkUrlToLocal, {
gitHubUrl: 'https://github.com/ing-bank/lion/',
rootDir,
page: {
inputPath: path.join(rootDir, 'docs/components/inputs/form/overview/index.md'),
},
})
.use(mdStringify);
const result = await parser.process(input);
return result.contents.toString().trim();
}
describe('remarkUrlToLocal', () => {
it('convert urls to local', async () => {
const result = await execute(
'Since it extends from [fieldset](https://github.com/ing-bank/lion/blob/6f2b6f940a0875091f1d940f45f0cd32dffce9ac/docs/components/inputs/fieldset/overview.md)',
);
expect(result).to.equal(
'<p>Since it extends from <a href="../../fieldset/overview.md">fieldset</a></p>',
);
});
it('does not touch issue urls', async () => {
const result = await execute('see [explanation](https://github.com/ing-bank/lion/issues/591)');
expect(result).to.equal(
'<p>see <a href="https://github.com/ing-bank/lion/issues/591">explanation</a></p>',
);
});
it('does not touch urls to the repo', async () => {
const result = await execute('see [explanation](https://github.com/ing-bank/lion/)');
expect(result).to.equal(
'<p>see <a href="https://github.com/ing-bank/lion/">explanation</a></p>',
);
});
it('works with images', async () => {
const result = await execute(
`see ![Standard flow](https://github.com/ing-bank/lion/blob/6f2b6f940a0875091f1d940f45f0cd32dffce9ac/docs/docs/systems/form/assets/FormatMixinDiagram-1.svg 'Standard flow')`,
);
expect(result).to.equal(
'<p>see <img src="../../../../docs/systems/form/assets/FormatMixinDiagram-1.svg" alt="Standard flow" title="Standard flow"></p>',
);
});
});

View file

@ -1526,9 +1526,9 @@
rimraf "^2.5.2" rimraf "^2.5.2"
"@mdjs/core@^0.7.1": "@mdjs/core@^0.7.1":
version "0.7.1" version "0.7.2"
resolved "https://registry.yarnpkg.com/@mdjs/core/-/core-0.7.1.tgz#115681f1f24d68c042c9765f16ead87aee3ec726" resolved "https://registry.yarnpkg.com/@mdjs/core/-/core-0.7.2.tgz#2d94c8440f81860cc8d9fef8d516e329463bfbd7"
integrity sha512-iHIXl230X3c0lsWAE+MUtd6lKmARj2bsh2yfS3UBfWbZiX7rMrK5AAb9Yh+5/aXaBOg3Gm2dHSeiSueen3kEBg== integrity sha512-0vaiM3l01TC6PCmMZ/Mgun3xHaTzg8A3tSEVewkSQ3unfoB1053GrrqDb8Y8K/CXM5c3CPG1cFJEYOTk4mhuyA==
dependencies: dependencies:
"@mdjs/mdjs-preview" "^0.4.2" "@mdjs/mdjs-preview" "^0.4.2"
"@mdjs/mdjs-story" "^0.2.0" "@mdjs/mdjs-story" "^0.2.0"
@ -4179,11 +4179,6 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
collapse-white-space@^1.0.0:
version "1.0.6"
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287"
integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==
color-convert@^1.9.0, color-convert@^1.9.1: color-convert@^1.9.0, color-convert@^1.9.1:
version "1.9.3" version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -5122,13 +5117,6 @@ destroy@^1.0.4, destroy@~1.0.4:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
detab@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43"
integrity sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==
dependencies:
repeat-string "^1.5.4"
detect-indent@^6.0.0: detect-indent@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd"
@ -6808,10 +6796,10 @@ hast-util-raw@^6.1.0:
xtend "^4.0.0" xtend "^4.0.0"
zwitch "^1.0.0" zwitch "^1.0.0"
hast-util-sanitize@^2.0.0: hast-util-sanitize@^3.0.0:
version "2.0.3" version "3.0.2"
resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-2.0.3.tgz#3cf4a1f5adb7d3c0b1fbb5dc1b1930fab6574856" resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-3.0.2.tgz#b0b783220af528ba8fe6999f092d138908678520"
integrity sha512-RILqWHmzU0Anmfw1KEP41LbCsJuJUVM0lQWAbTDk9+0bWqzRFXDaMdqIoRocLlOfR5NfcWyhFfZw/mGsuftwYA== integrity sha512-+2I0x2ZCAyiZOO/sb4yNLFmdwPBnyJ4PBkVTUMKMqBwYNA+lXSgOmoRXlJFazoyid9QPogRRKgKhVEodv181sA==
dependencies: dependencies:
xtend "^4.0.0" xtend "^4.0.0"
@ -8565,13 +8553,6 @@ maximatch@^0.1.0:
arrify "^1.0.0" arrify "^1.0.0"
minimatch "^3.0.0" minimatch "^3.0.0"
mdast-util-definitions@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-2.0.1.tgz#2c931d8665a96670639f17f98e32c3afcfee25f3"
integrity sha512-Co+DQ6oZlUzvUR7JCpP249PcexxygiaKk9axJh+eRzHDZJk2julbIdKB4PXHVxdBuLzvJ1Izb+YDpj2deGMOuA==
dependencies:
unist-util-visit "^2.0.0"
mdast-util-definitions@^4.0.0: mdast-util-definitions@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2"
@ -8641,7 +8622,7 @@ mdast-util-gfm@^0.1.0:
mdast-util-gfm-task-list-item "^0.1.0" mdast-util-gfm-task-list-item "^0.1.0"
mdast-util-to-markdown "^0.6.1" mdast-util-to-markdown "^0.6.1"
mdast-util-to-hast@^10.2.0: mdast-util-to-hast@^10.0.0, mdast-util-to-hast@^10.2.0:
version "10.2.0" version "10.2.0"
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz#61875526a017d8857b71abc9333942700b2d3604" resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz#61875526a017d8857b71abc9333942700b2d3604"
integrity sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ== integrity sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ==
@ -8655,21 +8636,6 @@ mdast-util-to-hast@^10.2.0:
unist-util-position "^3.0.0" unist-util-position "^3.0.0"
unist-util-visit "^2.0.0" unist-util-visit "^2.0.0"
mdast-util-to-hast@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-8.2.0.tgz#adf9f824defcd382e53dd7bace4282a45602ac67"
integrity sha512-WjH/KXtqU66XyTJQ7tg7sjvTw1OQcVV0hKdFh3BgHPwZ96fSBCQ/NitEHsN70Mmnggt+5eUUC7pCnK+2qGQnCA==
dependencies:
collapse-white-space "^1.0.0"
detab "^2.0.0"
mdast-util-definitions "^2.0.0"
mdurl "^1.0.0"
trim-lines "^1.0.0"
unist-builder "^2.0.0"
unist-util-generated "^1.0.0"
unist-util-position "^3.0.0"
unist-util-visit "^2.0.0"
mdast-util-to-markdown@^0.6.0, mdast-util-to-markdown@^0.6.1, mdast-util-to-markdown@~0.6.0: mdast-util-to-markdown@^0.6.0, mdast-util-to-markdown@^0.6.1, mdast-util-to-markdown@~0.6.0:
version "0.6.5" version "0.6.5"
resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz#b33f67ca820d69e6cc527a93d4039249b504bebe" resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz#b33f67ca820d69e6cc527a93d4039249b504bebe"
@ -10100,9 +10066,9 @@ plugin-error@^1.0.1:
extend-shallow "^3.0.2" extend-shallow "^3.0.2"
plugins-manager@^0.2.0, plugins-manager@^0.2.1: plugins-manager@^0.2.0, plugins-manager@^0.2.1:
version "0.2.1" version "0.2.2"
resolved "https://registry.yarnpkg.com/plugins-manager/-/plugins-manager-0.2.1.tgz#fe42857f9dd9326eccdeb2e112f5f4e7fe9f261d" resolved "https://registry.yarnpkg.com/plugins-manager/-/plugins-manager-0.2.2.tgz#1cae861e47f9806767fcaf1b25cbb2dcc149d016"
integrity sha512-ir2R5Jt1XH9/oFKEiyOhRFoGfFEToru8NinhebaBMm1iZdaL51k0hubjuVKPqMkA+T3FguB7FFd+x3iKSWOvAg== integrity sha512-Yiqkl9DARga3182tk4x/iY0nf4A2mAnUMQp5xsCDz+3em9bevx102jLZrwhapw1BIN+fJK/igfuvOfqqjHnaBg==
plur@^3.1.1: plur@^3.1.1:
version "3.1.1" version "3.1.1"
@ -10868,15 +10834,14 @@ remark-gfm@^1.0.0:
mdast-util-gfm "^0.1.0" mdast-util-gfm "^0.1.0"
micromark-extension-gfm "^0.3.0" micromark-extension-gfm "^0.3.0"
remark-html@^11.0.1: remark-html@^13.0.1:
version "11.0.2" version "13.0.1"
resolved "https://registry.yarnpkg.com/remark-html/-/remark-html-11.0.2.tgz#76f6f7c8981c736f01cb65f8853dbe5c2e546dfa" resolved "https://registry.yarnpkg.com/remark-html/-/remark-html-13.0.1.tgz#d5b2d8be01203e61fc37403167ca7584879ad675"
integrity sha512-U7qPKZq6Aai+UTpH5YrblLvqvdSUCRA4YmZYRTtbtknm/WUGmNUI0dvThbSuTNSf6TtC8btmbbScWi1wtUIxnw== integrity sha512-K5KQCXWVz+harnyC+UVM/J9eJWCgjYRqFeZoZf2NgP0iFbuuw/RgMZv3MA34b/OEpGnstl3oiOUtZzD3tJ+CBw==
dependencies: dependencies:
hast-util-sanitize "^2.0.0" hast-util-sanitize "^3.0.0"
hast-util-to-html "^7.0.0" hast-util-to-html "^7.0.0"
mdast-util-to-hast "^8.2.0" mdast-util-to-hast "^10.0.0"
xtend "^4.0.1"
remark-parse@^9.0.0: remark-parse@^9.0.0:
version "9.0.0" version "9.0.0"
@ -10930,7 +10895,7 @@ remove-trailing-separator@^1.0.1:
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
repeat-string@^1.0.0, repeat-string@^1.5.2, repeat-string@^1.5.4: repeat-string@^1.0.0, repeat-string@^1.5.2:
version "1.6.1" version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
@ -12364,11 +12329,6 @@ tree-kill@^1.2.2:
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
trim-lines@^1.0.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.3.tgz#839514be82428fd9e7ec89e35081afe8f6f93115"
integrity sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA==
trim-newlines@^1.0.0: trim-newlines@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@ -13431,7 +13391,7 @@ xmlhttprequest-ssl@~1.6.2:
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6"
integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==