ipsos-components
Version:
Material Design components for Angular
212 lines (184 loc) • 7.41 kB
text/typescript
import {task, src, dest} from 'gulp';
import {Dgeni} from 'dgeni';
import * as path from 'path';
import {buildConfig} from 'material2-build-tools';
import {apiDocsPackage} from '../../dgeni/index';
// There are no type definitions available for these imports.
const markdown = require('gulp-markdown');
const transform = require('gulp-transform');
const highlight = require('gulp-highlight-files');
const rename = require('gulp-rename');
const flatten = require('gulp-flatten');
const htmlmin = require('gulp-htmlmin');
const hljs = require('highlight.js');
const dom = require('gulp-dom');
const {outputDir, packagesDir} = buildConfig;
const DIST_DOCS = path.join(outputDir, 'docs');
// Our docs contain comments of the form `<!-- example(...) -->` which serve as placeholders where
// example code should be inserted. We replace these comments with divs that have a
// `material-docs-example` attribute which can be used to locate the divs and initialize the example
// viewer.
const EXAMPLE_PATTERN = /<!--\W*example\(([^)]+)\)\W*-->/g;
// Markdown files can contain links to other markdown files.
// Most of those links don't work in the Material docs, because the paths are invalid in the
// documentation page. Using a RegExp to rewrite links in HTML files to work in the docs.
const LINK_PATTERN = /(<a[^>]*) href="([^"]*)"/g;
// HTML tags in the markdown generated files that should receive a .docs-markdown-${tagName} class
// for styling purposes.
const MARKDOWN_TAGS_TO_CLASS_ALIAS = [
'a',
'h1',
'h2',
'h3',
'h4',
'h5',
'li',
'ol',
'p',
'table',
'tbody',
'td',
'th',
'tr',
'ul',
'pre',
'code',
];
// Options for the html-minifier that minifies the generated HTML files.
const htmlMinifierOptions = {
collapseWhitespace: true,
removeComments: true,
caseSensitive: true,
removeAttributeQuotes: false
};
const markdownOptions = {
// Add syntax highlight using highlight.js
highlight: (code: string, language: string): string => {
if (language) {
// highlight.js expects "typescript" written out, while Github supports "ts".
let lang = language.toLowerCase() === 'ts' ? 'typescript' : language;
return hljs.highlight(lang, code).value;
}
return code;
}
};
/** Generate all docs content. */
task('docs', [
'markdown-docs',
'markdown-docs-cdk',
'highlight-examples',
'api-docs',
'minified-api-docs',
'build-examples-module',
'stackblitz-example-assets',
]);
/** Generates html files from the markdown overviews and guides for material. */
task('markdown-docs', () => {
// Extend the renderer for custom heading anchor rendering
markdown.marked.Renderer.prototype.heading = (text: string, level: number): string => {
if (level === 3 || level === 4) {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
return `
<h${level} id="${escapedText}" class="docs-header-link">
<span header-link="${escapedText}"></span>
${text}
</h${level}>
`;
} else {
return `<h${level}>${text}</h${level}>`;
}
};
return src(['src/lib/**/!(README).md', 'guides/*.md'])
.pipe(rename({prefix: 'material-'}))
.pipe(markdown(markdownOptions))
.pipe(transform(transformMarkdownFiles))
.pipe(dom(createTagNameAliaser('docs-markdown')))
.pipe(dest('dist/docs/markdown'));
});
// TODO(jelbourn): figure out how to avoid duplicating this task w/ material while still
// disambiguating the output.
/** Generates html files from the markdown overviews and guides for the cdk. */
task('markdown-docs-cdk', () => {
return src(['src/cdk/**/!(README).md'])
.pipe(rename({prefix: 'cdk-'}))
.pipe(markdown(markdownOptions))
.pipe(transform(transformMarkdownFiles))
.pipe(dom(createTagNameAliaser('docs-markdown')))
.pipe(dest('dist/docs/markdown'));
});
/**
* Creates syntax-highlighted html files from the examples to be used for the source view of
* live examples on the docs site.
*/
task('highlight-examples', () => {
// rename files to fit format: [filename]-[filetype].html
const renameFile = (filePath: any) => {
const extension = filePath.extname.slice(1);
filePath.basename = `${filePath.basename}-${extension}`;
};
return src('src/material-examples/**/*.+(html|css|ts)')
.pipe(flatten())
.pipe(rename(renameFile))
.pipe(highlight())
.pipe(dest('dist/docs/examples'));
});
/** Generates API docs from the source JsDoc using dgeni. */
task('api-docs', () => {
const docs = new Dgeni([apiDocsPackage]);
return docs.generate();
});
/** Generates minified html api docs. */
task('minified-api-docs', ['api-docs'], () => {
return src('dist/docs/api/*.html')
.pipe(htmlmin(htmlMinifierOptions))
.pipe(dest('dist/docs/api/'));
});
/** Copies example sources to be used as stackblitz assets for the docs site. */
task('stackblitz-example-assets', () => {
src(path.join(packagesDir, 'material-examples', '**/*'))
.pipe(dest(path.join(DIST_DOCS, 'stackblitz', 'examples')));
});
/** Updates the markdown file's content to work inside of the docs app. */
function transformMarkdownFiles(buffer: Buffer, file: any): string {
let content = buffer.toString('utf-8');
// Replace <!-- example(..) --> comments with HTML elements.
content = content.replace(EXAMPLE_PATTERN, (_match: string, name: string) =>
`<div material-docs-example="${name}"></div>`
);
// Replace the URL in anchor elements inside of compiled markdown files.
content = content.replace(LINK_PATTERN, (_match: string, head: string, link: string) =>
// The head is the first match of the RegExp and is necessary to ensure that the RegExp matches
// an anchor element. The head will be then used to re-create the existing anchor element.
// If the head is not prepended to the replaced value, then the first match will be lost.
`${head} href="${fixMarkdownDocLinks(link, file.path)}"`
);
// Finally, wrap the entire generated in a doc in a div with a specific class.
return `<div class="docs-markdown">${content}</div>`;
}
/** Fixes paths in the markdown files to work in the material-docs-io. */
function fixMarkdownDocLinks(link: string, filePath: string): string {
// As for now, only markdown links that are relative and inside of the guides/ directory
// will be rewritten.
if (!filePath.includes(path.normalize('guides/')) || link.startsWith('http')) {
return link;
}
let baseName = path.basename(link, path.extname(link));
// Temporary link the file to the /guide URL because that's the route where the
// guides can be loaded in the Material docs.
return `guide/${baseName}`;
}
/**
* Returns a function to be called with an HTML document as its context that aliases HTML tags by
* adding a class consisting of a prefix + the tag name.
* @param classPrefix The prefix to use for the alias class.
*/
function createTagNameAliaser(classPrefix: string) {
return function() {
MARKDOWN_TAGS_TO_CLASS_ALIAS.forEach(tag => {
for (let el of this.querySelectorAll(tag)) {
el.classList.add(`${classPrefix}-${tag}`);
}
});
return this;
};
}