import * as chokidar from 'chokidar'; import * as fs from 'fs-extra'; import * as path from 'path'; import * as portfinder from 'portfinder'; import * as prettier from 'prettier'; import * as urlJoin from 'url-join'; import * as webpack from 'webpack'; import * as yargs from 'yargs'; import { vai } from '../../node'; import { analyseProject } from '../../utils/analyse-project'; import { spinner } from '../../utils/log'; import { prettierConfig } from '../../utils/prettier-config'; import { docsPath, tempPath } from '../../utils/structor-config'; import text from '../../utils/text'; import { runWebpackDevServer } from '../../utils/webpack-dev-server'; import { WrapContent } from '../../utils/webpack-plugin-wrap-content'; import { bundleDlls, dllMainfestName, dllOutPath, libraryStaticPath } from '../command-dev/dll'; interface IResult { projectAnalyseDocs: { docs: Array<{ file: path.ParsedPath; }>; }; } export default async (instance: typeof vai) => { instance.commands.registerCommand({ name: 'docs', description: text.commander.docs.description, action: async () => { await devDocs(instance, docsPath.dir); } }); }; export async function devDocs(instance: typeof vai, realDocsPath: string) { const docsEntryPath = path.join(instance.projectRootPath, tempPath.dir, 'docs-entry.tsx'); prepare(instance, realDocsPath, docsEntryPath); await instance.project.lint(false); await instance.project.ensureProjectFiles(); await instance.project.checkProjectFiles(); // Anaylse project for init. await spinner('Analyse project', async () => { return analyseProject(); }); await bundleDlls(); chokidar .watch(path.join(instance.projectRootPath, realDocsPath, '/**'), { ignored: /(^|[\/\\])\../, ignoreInitial: true }) .on('add', async filePath => { await analyseProject(); }) .on('unlink', async filePath => { await analyseProject(); }) .on('unlinkDir', async filePath => { await analyseProject(); }); // Serve docs const freePort = await portfinder.getPortPromise(); await runWebpackDevServer({ publicPath: '/', entryPath: docsEntryPath, devServerPort: freePort, htmlTemplatePath: path.join(__dirname, '../../../template-project.ejs'), htmlTemplateArgs: { appendBody: ` ` }, pipeConfig: config => { const dllHttpPath = urlJoin( `${instance.projectConfig.useHttps ? 'https' : 'http'}://127.0.0.1:${freePort}`, libraryStaticPath ); config.plugins.push( new WrapContent( ` var dllScript = document.createElement("script"); dllScript.src = "${dllHttpPath}"; dllScript.onload = runEntry; document.body.appendChild(dllScript); function runEntry() { `, `}` ) ); return config; } }); } function prepare(instance: typeof vai, realDocsPath: string, docsEntryPath: string) { instance.build.pipeTsInclude(paths => { paths.push(path.join(instance.projectRootPath, realDocsPath)); return paths; }); instance.build.pipeLessInclude(paths => { paths.push(path.join(instance.projectRootPath, realDocsPath)); return paths; }); instance.build.pipeSassInclude(paths => { paths.push(path.join(instance.projectRootPath, realDocsPath)); return paths; }); instance.build.pipeConfig(config => { if (!instance.isDevelopment) { return config; } config.plugins.push( new webpack.DllReferencePlugin({ context: '.', manifest: require(path.join(dllOutPath, dllMainfestName)) }) ); return config; }); instance.project.onAnalyseProject(files => { const result = { projectAnalyseDocs: { docs: files .filter(file => { const relativePath = path.relative(instance.projectRootPath, path.join(file.dir, file.name)); if (!relativePath.startsWith(realDocsPath)) { return false; } if (file.isDir) { return false; } if (['.tsx'].indexOf(file.ext) === -1) { return false; } return true; }) .map(file => { return { file }; }) } } as IResult; // Create entry file for docs const docList: string[] = []; const docsEntryContent = prettier.format( ` import * as React from "react" import * as ReactDOM from 'react-dom' import { hot, setConfig } from 'react-hot-loader' const DocsWrapper = require("${path.join(__dirname, 'docs-wrapper')}").default ${(() => { const docFiles = result.projectAnalyseDocs.docs; return docFiles .map((docFile, index) => { const docFilePathWithoutPrefix = path.join(docFile.file.dir, docFile.file.name); const docImportPath = path.relative(path.parse(docsEntryPath).dir, docFilePathWithoutPrefix); const fileName = `Doc${index}`; docList.push(` { name: "${docFile.file.name}", element: ${fileName}, text: ${fileName}Text } `); return ` import * as ${fileName} from '${docImportPath}' const ${fileName}Text = require('-!raw-loader!${docImportPath}') `; }) .join('\n'); })()} const DocComponents: any[] = [${docList.join(',')}] class Docs extends React.PureComponent { public render() { return ( ) } } const ROOT_ID = 'root'; setConfig({ pureSFC: true }) const HotDocs = hot(module)(Docs); // Create entry div if not exist. if (!document.getElementById(ROOT_ID)) { const rootDiv = document.createElement('div'); rootDiv.id = ROOT_ID; document.body.appendChild(rootDiv); } ReactDOM.render(, document.getElementById(ROOT_ID)); `, { ...prettierConfig, parser: 'typescript' } ); fs.outputFileSync(docsEntryPath, docsEntryContent); return result; }); }