Skip to content
This repository has been archived by the owner on Apr 11, 2024. It is now read-only.

Commit

Permalink
feat(middleware): Add 'symbolMiddleware' and 'symbolInstanceMiddlewar…
Browse files Browse the repository at this point in the history
…e' (#52)
  • Loading branch information
mnikkane authored and markdalgleish committed Jul 2, 2018
1 parent fe80e6a commit 5818868
Show file tree
Hide file tree
Showing 14 changed files with 3,425 additions and 104 deletions.
177 changes: 103 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,48 @@ $ html-sketchapp --viewports.HigherDensity [email protected] --viewports.Retina 1024x

If no scaling factor is provided, a default of `1` will be used.

### Config file

All options can be provided via an `html-sketchapp.config.js` file.

```js
module.exports = {
file: 'sketch.html',
outDir: 'dist/sketch',
viewports: {
Desktop: '1024x768',
Mobile: '320x568'
},
puppeteerArgs: '--no-sandbox --disable-setuid-sandbox',
puppeteerExecutablePath: 'google-chrome-unstable'
};
```

You can provide an alternate config file path with the `--config` option.

```bash
$ html-sketchapp --config example.config.js
```

### Importing into Sketch

Once this command has successfully run, the following files will be generated in the output directory.

- `document.asketch.json`
- `page.asketch.json`

These need to be imported into Sketch via html-sketchapp's corresponding Sketch plugin. To ease the install process, you can run the following command.

```bash
$ html-sketchapp install
```

Then, open a new Sketch document and, from the menu, select `Plugins > From *Almost* Sketch to Sketch`. In the file picker, select both `document.asketch.json` and `page.asketch.json`, and click `Choose`.

Congratulations! You should now have your symbols, text styles and document colors available within Sketch! 💎🎉

## Advanced Usage

### Debug mode

If you need to see what Puppeteer is doing, you can provide the `--debug` flag which will do the following things:
Expand All @@ -134,59 +176,75 @@ For example:
$ html-sketchapp --url http://localhost:3000 --out-dir dist --debug
```

### Symbol Layer Middleware
### Puppeteer args

Symbol Layer Middleware allows you to call out to any APIs that may be exposed on the underlying html-sketchapp layer.
If you need to provide command line arguments to the browser instance via [Puppeteer](https://github.com/GoogleChrome/puppeteer), you can provide the `puppeteer-args` option.

The current usecase for this is the new `layer.setResizingConstraint` API which allows you to configure how a layer should behave when a symbol is resized.
Since Puppeteer uses [Chromium](https://www.chromium.org/Home) internally, you can refer to the [List of Chromium Command Line Switches](https://peter.sh/experiments/chromium-command-line-switches) for available options.

#### Requiring a file
For example, if you'd like to disable the browser sandbox:

The below uses the string argument to `require` in a file that defines what resizing a layer should have applied to it. In the below case, fixing the layer to the top and left.
```bash
$ html-sketchapp --puppeteer-args="--no-sandbox --disable-setuid-sandbox" --file sketch.html --out-dir dist
```

*Note: Because Puppeteer args are prefixed with hyphens, you **must** use an equals sign and quotes when providing this option via the command line (as seen above).*

### Chromium executable

If you'd like to override the Chromium used by Puppeteer, you can provide a path to the executable with the `puppeteer-executable-path` option.

```bash
$ html-sketchapp --symbol-layer-middleware symbol.layer.middleware.js
$ html-sketchapp --puppeteer-executable-path google-chrome-unstable --file sketch.html --out-dir dist
```

```js
module.exports = (args) => {
const { layer, RESIZING_CONSTRAINTS } = args;
### Middleware

layer.setResizingConstraint(RESIZING_CONSTRAINTS.LEFT, RESIZING_CONSTRAINTS.TOP);
If you need to call out to lower-level html-sketchapp APIs, you can provide middleware functions that allow you to modify the underlying Sketch classes as they're generated.

It's recommended that you provide middleware via a [config file](#config-file) as inline functions, for example:

```js
module.exports = {
symbolLayerMiddleware: (args) => { ... }
};
```

#### Inline function

If you use the config file you can provide an inline function and avoid creating a separate file:
Alternatively, you can also provide middleware as standalone JavaScript files, configured via the command line:

```bash
$ html-sketchapp --config config.js
$ html-sketchapp --symbol-layer-middleware symbol.layer.middleware.js
```

```js
module.exports = {
symbolLayerMiddleware: (args) => {
const { layer, RESIZING_CONSTRAINTS } = args;
This assumes that your middleware is a JavaScript module that exports the function:

layer.setResizingConstraint(RESIZING_CONSTRAINTS.LEFT, RESIZING_CONSTRAINTS.TOP);
}
};
```js
module.exports = (args) => { ... };
```

#### Symbol layer middleware arguments
However, in order to keep the documentation streamlined, all examples will use the config file notation.

#### Symbol Layer Middleware

This middleware is executed for every layer within a symbol.

The function that is called has several arguments passed into it so you can provide different resizing options for different types of layers.
The typical use case for this is html-sketchapp's `layer.setResizingConstraint` API which allows you to configure how a layer should behave when a symbol is resized.

The following things are passed into symbol
```js
module.exports = {
symbolLayerMiddleware: args => { ... }
};
```

The following arguments are passed into your middleware function:
- layer: the html-sketchapp layer instance
- SVG: The SVG class for type checking of layer
- Text: The Text class for type checking of layer
- Rectangle: The Rectangle class for type checking of layer
- ShapeGroup: The ShapeGroup class for type checking of layer
- RESIZING_CONSTRAINTS: contains friendly names for `setResizingConstraint` API.
- RESIZING_CONSTRAINTS: Object containing constants for the `setResizingConstraint` API

Handling SVGs differently from other layers:
For example, when handling SVGs differently from other layers:

```js
module.exports = {
Expand All @@ -203,67 +261,38 @@ module.exports = {

```

### Puppeteer args

If you need to provide command line arguments to the browser instance via [Puppeteer](https://github.com/GoogleChrome/puppeteer), you can provide the `puppeteer-args` option.

Since Puppeteer uses [Chromium](https://www.chromium.org/Home) internally, you can refer to the [List of Chromium Command Line Switches](https://peter.sh/experiments/chromium-command-line-switches) for available options.

For example, if you'd like to disable the browser sandbox:

```bash
$ html-sketchapp --puppeteer-args="--no-sandbox --disable-setuid-sandbox" --file sketch.html --out-dir dist
```

*Note: Because Puppeteer args are prefixed with hyphens, you **must** use an equals sign and quotes when providing this option via the command line (as seen above).*

### Chromium executable
#### Symbol Middleware

If you'd like to override the Chromium used by Puppeteer, you can provide a path to the executable with the `puppeteer-executable-path` option.

```bash
$ html-sketchapp --puppeteer-executable-path google-chrome-unstable --file sketch.html --out-dir dist
```

### Config file

All options can be provided via an `html-sketchapp.config.js` file.
This middleware is executed for every symbol within a document.

```js
module.exports = {
file: 'sketch.html',
outDir: 'dist/sketch',
viewports: {
Desktop: '1024x768',
Mobile: '320x568'
},
puppeteerArgs: '--no-sandbox --disable-setuid-sandbox',
puppeteerExecutablePath: 'google-chrome-unstable'
symbolMiddleware: args => { ... }
};
```

You can provide an alternate config file path with the `--config` option.
The following arguments are passed into your middleware function:
- symbol: The current symbol master
- node: The source HTML node
- suffix: The symbol name suffix (e.g. `/Desktop`)
- RESIZING_CONSTRAINTS: Object containing constants for the `setResizingConstraint` API

```bash
$ html-sketchapp --config example.config.js
```

## Importing into Sketch

Once this command has successfully run, the following files will be generated in the output directory.

- `document.asketch.json`
- `page.asketch.json`
#### Symbol Instance Middleware

These need to be imported into Sketch via html-sketchapp's corresponding Sketch plugin. To ease the install process, you can run the following command.
This middleware is executed for every symbol instance within a document.

```bash
$ html-sketchapp install
```js
module.exports = {
symbolInstanceMiddleware: args => { ... }
};
```

Then, open a new Sketch document and, from the menu, select `Plugins > From *Almost* Sketch to Sketch`. In the file picker, select both `document.asketch.json` and `page.asketch.json`, and click `Choose`.

Congratulations! You should now have your symbols, text styles and document colors available within Sketch! 💎🎉
The following arguments are passed into your middleware function:
- symbolInstance: The current symbol instance
- symbolMaster: The symbol master that the symbol instance is referencing
- node: The source HTML node
- RESIZING_CONSTRAINTS: Object containing constants for the `setResizingConstraint` API

## Contributing

Expand Down
23 changes: 11 additions & 12 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ const makeServer = async (relativePath, port) => {
return server;
};

const resolveMiddleware = configValue => {
return (typeof configValue === 'string') ?
require(path.resolve(process.cwd(), configValue))
: configValue;
};

require('yargs')
.config(config)
.config('config', 'Path to JavaScript config file', customConfigPath => require(customConfigPath))
Expand Down Expand Up @@ -80,6 +86,10 @@ require('yargs')
const symbolsUrl = argv.serve ? urlJoin(`http://localhost:${String(port)}`, argv.url || '/') : url;
const debug = argv.debug;

const symbolLayerMiddleware = resolveMiddleware(argv.symbolLayerMiddleware);
const symbolMiddleware = resolveMiddleware(argv.symbolMiddleware);
const symbolInstanceMiddleware = resolveMiddleware(argv.symbolInstanceMiddleware);

const launchArgs = {
args: argv.puppeteerArgs ? argv.puppeteerArgs.split(' ') : [],
executablePath: argv.puppeteerExecutablePath,
Expand All @@ -88,17 +98,6 @@ require('yargs')

const browser = await puppeteer.launch(launchArgs);

const { symbolLayerMiddleware: argSLM } = argv;
let symbolLayerMiddleware;

if (argSLM) {
if (typeof argSLM === 'string') {
symbolLayerMiddleware = require(path.resolve(process.cwd(), argSLM));
} else if (typeof argSLM === 'function') {
symbolLayerMiddleware = argSLM;
}
}

try {
const page = await browser.newPage();

Expand Down Expand Up @@ -138,7 +137,7 @@ require('yargs')
const deviceScaleFactor = typeof scale === 'undefined' ? 1 : parseFloat(scale);
await page.setViewport({ width, height, deviceScaleFactor });
await page.evaluate(`generateAlmostSketch.snapshotTextStyles({ suffix: "${hasViewports ? `/${viewportName}` : ''}" })`);
await page.evaluate(`generateAlmostSketch.snapshotSymbols({ suffix: "${hasViewports ? `/${viewportName}` : ''}", symbolLayerMiddleware: ${symbolLayerMiddleware} })`);
await page.evaluate(`generateAlmostSketch.snapshotSymbols({ suffix: "${hasViewports ? `/${viewportName}` : ''}", symbolLayerMiddleware: ${symbolLayerMiddleware}, symbolMiddleware: ${symbolMiddleware}, symbolInstanceMiddleware: ${symbolInstanceMiddleware} })`);
}
}

Expand Down
35 changes: 18 additions & 17 deletions script/generateAlmostSketch.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ const {
ShapeGroup
} = htmlSketchapp;

const getAllLayers = (item, symbolMastersByName = {}) => {
const itemAndChildren = [item, ...item.querySelectorAll('*')];
const getAllLayers = (rootNode, symbolMastersByName = {}, symbolInstanceMiddleware = {}) => {
const rootNodeAndChildren = [rootNode, ...rootNode.querySelectorAll('*')];

const symbolInstanceChildren = new Set([
...item.querySelectorAll('[data-sketch-symbol-instance] *')
...rootNode.querySelectorAll('[data-sketch-symbol-instance] *')
]);

const layers = Array.from(itemAndChildren).map(node => {
const layers = Array.from(rootNodeAndChildren).map(node => {
if (node.dataset.sketchSymbolInstance) {
const symbolName = node.dataset.sketchSymbolInstance;

Expand All @@ -33,6 +33,7 @@ const getAllLayers = (item, symbolMastersByName = {}) => {
const symbolInstance = symbolMaster.getSymbolInstance({ x, y, width, height });

symbolInstance.setName(symbolName);
symbolInstanceMiddleware({symbolInstance, symbolMaster, node, RESIZING_CONSTRAINTS});

return [symbolInstance];
} else if (symbolInstanceChildren.has(node)) {
Expand All @@ -51,20 +52,20 @@ const doc = new Document();

export function snapshotColorStyles() {
Array.from(document.querySelectorAll('[data-sketch-color]'))
.forEach(item => {
const color = item.dataset.sketchColor;
.forEach(node => {
const color = node.dataset.sketchColor;

doc.addColor(color);
});
}

export function snapshotTextStyles({ suffix = '' }) {
Array.from(document.querySelectorAll('[data-sketch-text]'))
.forEach(item => {
getAllLayers(item)
.forEach(node => {
getAllLayers(node)
.filter(layer => layer instanceof Text)
.forEach(layer => {
const name = item.dataset.sketchText;
const name = node.dataset.sketchText;

layer.setName(`${name}${suffix}`);
doc.addTextStyle(layer);
Expand All @@ -85,26 +86,26 @@ export function setupSymbols({ name }) {
page.setName(name);
}

export function snapshotSymbols({ suffix = '', symbolLayerMiddleware = () => {} }, ) {
export function snapshotSymbols({ suffix = '', symbolLayerMiddleware = () => {}, symbolMiddleware = () => {}, symbolInstanceMiddleware = () => {} },) {
const nodes = Array.from(document.querySelectorAll('[data-sketch-symbol]'));

const symbolMastersByName = nodes.reduce((obj, item) => {
const name = item.dataset.sketchSymbol;
const { left: x, top: y } = item.getBoundingClientRect();
const symbolMastersByName = nodes.reduce((obj, node) => {
const name = node.dataset.sketchSymbol;
const { left: x, top: y } = node.getBoundingClientRect();

const symbol = new SymbolMaster({ x, y });
symbol.setName(`${name}${suffix}`);

symbolMiddleware({symbol, node, suffix, RESIZING_CONSTRAINTS});
obj[name] = symbol;

return obj;
}, {});

const symbols = nodes.map(item => {
const name = item.dataset.sketchSymbol;
const symbols = nodes.map(node => {
const name = node.dataset.sketchSymbol;
const symbol = symbolMastersByName[name];

const layers = getAllLayers(item, symbolMastersByName);
const layers = getAllLayers(node, symbolMastersByName, symbolInstanceMiddleware);

layers
.filter(layer => layer !== null)
Expand Down
4 changes: 3 additions & 1 deletion test/_utils/dirContentsToObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const path = require('path');
const readFileAsync = promisify(require('fs').readFile);
const traverse = require('traverse');

const guidRegex = /[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/;

module.exports = async dir => {
const document = await readFileAsync(path.join(dir, 'document.asketch.json'));
const page = await readFileAsync(path.join(dir, 'page.asketch.json'));
Expand All @@ -14,7 +16,7 @@ module.exports = async dir => {

// Scrub out GUIDs or they'll fail the snapshot tests
traverse(output).forEach(function() {
if (/ID$/.test(this.path)) {
if (guidRegex.test(this.node) ) {
this.update('__GUID__');
}
});
Expand Down
Loading

0 comments on commit 5818868

Please sign in to comment.