Skip to content

Commit

Permalink
Merge pull request #33 from Himenon/feature/export-static
Browse files Browse the repository at this point in the history
feat(export-static): add `--export-static` option
  • Loading branch information
Himenon authored Feb 5, 2020
2 parents e2ec392 + 9796b39 commit 543e380
Show file tree
Hide file tree
Showing 52 changed files with 945 additions and 230 deletions.
10 changes: 10 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
"program": "${workspaceFolder}/packages/cli/bin/code-dependency.js",
"outFiles": ["${workspaceFolder}/**/*.js"],
"args": ["--source", "src"]
},
{
"type": "node",
"request": "launch",
"name": "export static",
"skipFiles": ["<node_internals>/**"],
"cwd": "${workspaceFolder}/packages/cli",
"program": "${workspaceFolder}/packages/cli/bin/code-dependency.js",
"outFiles": ["${workspaceFolder}/**/*.js"],
"args": ["--source", "src", "--export-static", "${workspaceFolder}/packages/cli/output"]
}
]
}
8 changes: 2 additions & 6 deletions lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@
"publish": {
"access": "public",
"allowBranch": "master",
"ignoreChanges": [
"CHANGELOG.md"
]
"ignoreChanges": ["CHANGELOG.md"]
}
},
"packages": [
"packages/*"
],
"packages": ["packages/*"],
"version": "0.3.6",
"npmClient": "yarn",
"registry": "https://registry.npmjs.org/",
Expand Down
1 change: 1 addition & 0 deletions packages/cli/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ package-lock.json
private_npm_cache
data
dist
output
23 changes: 23 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,29 @@ cruise ignore pattern (default: "node_modules"). [see](https://github.com/sverwe
code-dependency --source ./src --exclude node_modules
```

**--export-static**

generate static site.

```bash
code-dependency --source ./src --exclude node_modules --export-static ./docs
```

**--public-path**

```bash
code-dependency --source ./src --exclude node_modules --export-static ./docs --public-path /docs
code-dependency --source ./src --exclude node_modules --export-static ./docs --public-path https://himenon.github.io/code-dependency/
```

**--dry-run** (experimental)

if failed generate static file and retry generate static file only unfinished path.

```bash
code-dependency --source ./src --exclude node_modules --export-static ./docs --dry-run
```

## License

@code-dependency/cli is [MIT licensed](https://github.com/Himenon/code-dependency/blob/master/LICENSE).
11 changes: 8 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
"build:webpack": "webpack --config ./scripts/build.ts",
"clean": "rimraf ./lib ./build ./dist",
"develop": "cross-env NODE_ENV=development yarn build:webpack --watch",
"export:static": "node ./bin/code-dependency.js --source ./src --exclude node_modules --export-static ./output --public-path http://localhost:5000/output",
"generate:docs": "node ./bin/code-dependency.js --source ./src --exclude node_modules --export-static ../../docs --public-path https://himenon.github.io/code-dependency/",
"server": "nodemon ./bin/code-dependency.js --source ./src --exclude node_modules",
"start": "run-p develop server",
"test": "jest -c jest.config.json",
"test": "jest -c jest.config.json && yarn export:static",
"test:depcruise": "depcruise --validate .dependency-cruiser.json src",
"ts": "ts-node -P tsconfig.json"
},
Expand All @@ -56,7 +58,9 @@
"react-router-dom": "^5.1.2",
"recursive-readdir": "^2.2.2",
"resolve-pkg": "^2.0.0",
"tsconfig": "^7.0.0"
"rimraf": "^3.0.1",
"tsconfig": "^7.0.0",
"urljoin": "^0.1.5"
},
"devDependencies": {
"@types/compression": "^1.0.1",
Expand All @@ -65,7 +69,8 @@
"@types/fs-extra": "^8.0.1",
"@types/pretty": "^2.0.0",
"@types/react": "^16.9.17",
"@types/react-dom": "^16.9.4"
"@types/react-dom": "^16.9.4",
"@types/rimraf": "^2.0.3"
},
"publishConfig": {
"access": "public"
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/scripts/webpack.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as path from "path";
import * as webpack from "webpack";
import { CleanWebpackPlugin } from "clean-webpack-plugin";

const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
Expand Down Expand Up @@ -47,6 +48,7 @@ export const generateConfig = ({ isProduction }: Option): webpack.Configuration[
plugins: [
new ProgressBarPlugin(),
new FriendlyErrorsWebpackPlugin(),
new CleanWebpackPlugin(),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("production"),
"process.env.isProduction": JSON.stringify(isProduction),
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ interface CLIArguments {
tsConfig?: PathFactory.Type;
exclude?: string;
webpackConfig?: PathFactory.Type;
exportStatic?: PathFactory.Type;
publicPath?: string;
dryRun: boolean;
}

export const validateCliArguments = (args: commander.Command): CLIArguments => {
Expand All @@ -25,6 +28,9 @@ export const validateCliArguments = (args: commander.Command): CLIArguments => {
tsConfig: args["tsConfig"] && PathFactory.create({ source: args["tsConfig"] }),
exclude: args["exclude"],
webpackConfig: args["webpackConfig"] && PathFactory.create({ source: args["webpackConfig"] }),
exportStatic: args["exportStatic"] && PathFactory.create({ source: args["exportStatic"] }),
publicPath: args["publicPath"],
dryRun: !!args["dryRun"],
};
};

Expand All @@ -36,6 +42,9 @@ export const executeCommandLine = (): CLIArguments => {
.option("--ts-config [path]", "tsconfig.json path", undefined)
.option("--webpack-config [path]", "webpack.config.js path (only js file)")
.option("--exclude [string pattern]", "cruise exclude pattern", "node_modules")
.option("--export-static [static directory]", "static file hosting directory")
.option("--public-path [host public path]", "the base path for all the assets")
.option("--dry-run", "only use --export-static option")
.parse(process.argv);
return validateCliArguments(commander);
};
11 changes: 11 additions & 0 deletions packages/cli/src/constants/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const routes = {
project: {
path: "/project",
},
assets: {
path: "/assets",
},
api: {
path: "/api",
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { find } from "../utils";

export const create = () => {
const router = express.Router();
router.use("/", express.static(find("@code-dependency/view/dist/stylesheets")));
router.use("/", express.static(find("@code-dependency/view/dist/")));
return router;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import express from "express";
import ReactDOMServer from "react-dom/server";
import { IndexView } from "../view";
import { ProjectView } from "../view";
import * as Service from "../service";
import * as Config from "../config";

Expand All @@ -9,8 +9,16 @@ export const create = (service: Service.Type, config: Config.Type) => {
router.get("/", async (req, res) => {
const serverUrl = `${req.protocol}://${req.hostname}:${config.server.port}`;
try {
const props: IndexView.Props = { serverUrl, url: req.url, context: {}, service, filePathList: config.filePathList };
const html = ReactDOMServer.renderToString(await IndexView.create(props));
const props: ProjectView.Props = {
serverUrl,
publicPath: serverUrl,
url: req.url,
pathname: req.query.pathname,
context: {},
service,
filePathList: config.filePathList,
};
const html = ReactDOMServer.renderToString(await ProjectView.create(props));
res.send(html);
res.end();
} catch (error) {
Expand Down
14 changes: 7 additions & 7 deletions packages/cli/src/controller/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import express from "express";
import * as Service from "../service";
import * as Config from "../config";

import * as IndexController from "./IndexController";
import * as ScriptController from "./ScriptController";
import * as StylesheetsController from "./StylesheetsController";
import * as ProjectController from "./ProjectController";
import * as AssetController from "./AssetController";
import * as ApiController from "./ApiController";

import { routes } from "../constants/router";

export const create = (app: express.Application, service: Service.Type, config: Config.Type) => {
app.use("/", IndexController.create(service, config));
app.use("/scripts", ScriptController.create());
app.use("/stylesheets", StylesheetsController.create());
app.use("/api", ApiController.create(service, config));
app.use(routes.project.path, ProjectController.create(service, config));
app.use(routes.assets.path, AssetController.create());
app.use(routes.api.path, ApiController.create(service, config));
};
34 changes: 34 additions & 0 deletions packages/cli/src/exporter/DryRun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as rimraf from "rimraf";
import * as fs from "fs";

export interface DryRunCache {
[pathname: string]: "done" | "pending";
}

export const create = (filename: string, dryRun: boolean) => {
const getDryRunCache = (): DryRunCache => {
if (dryRun && fs.existsSync(filename)) {
return JSON.parse(fs.readFileSync(filename, { encoding: "utf-8" }));
} else {
return {};
}
};

const updateDryRunCache = (cache: DryRunCache) => {
if (dryRun) {
fs.writeFileSync(filename, JSON.stringify(cache, null, 2), { encoding: "utf-8" });
}
};

const deleteDryRunCache = () => {
if (dryRun) {
rimraf.sync(filename);
}
};

return {
getDryRunCache,
updateDryRunCache,
deleteDryRunCache,
};
};
79 changes: 79 additions & 0 deletions packages/cli/src/exporter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as ReactDOM from "react-dom/server";
import * as Service from "../service";
import * as Config from "../config";
import * as fs from "fs";
import * as path from "path";
import * as View from "./view";
import { find } from "../utils";
import { routes } from "../constants/router";
import * as DryRun from "./DryRun";
import manifest from "@code-dependency/view/dist/manifest.json";

export const create = (service: Service.Type, config: Config.Type, isDryRun: boolean) => {
process.setMaxListeners(config.filePathList.length);
const generateStaticHtml = async (pathname: string, publicPath: string, assets: View.Assets): Promise<string> => {
const url = path.join("/", pathname.replace(path.extname(pathname), ""));
const dotSource = service.dependencyCruiser.getDependenciesDot(path.join(config.absoluteRootDirPath, pathname));
const view = await View.create(url, publicPath, pathname, dotSource, config.filePathList, assets);
return "<!DOCTYPE html>" + ReactDOM.renderToStaticMarkup(view);
};

const writePage = (dist: string, html: string) => {
if (!fs.existsSync(path.dirname(dist))) {
fs.mkdirSync(path.dirname(dist), { recursive: true });
}
fs.writeFileSync(dist, html, { encoding: "utf-8" });
};

const copyAssets = async (distDir: string): Promise<View.Assets> => {
const assets: View.Assets = JSON.parse(JSON.stringify(manifest));
const promises = Object.entries(manifest).map(([key, assetsPath]) => {
if (path.extname(assetsPath) === ".html") {
return;
}
return new Promise((resolve, reject) => {
const src = find("@code-dependency/view/dist/" + assetsPath, false);
const dist = path.join(distDir, assetsPath);
if (!fs.existsSync(path.dirname(dist))) {
fs.mkdirSync(path.dirname(dist), { recursive: true });
}
fs.copyFile(src, dist, error => {
if (error) {
reject(error);
} else {
resolve();
}
});
assets[key] = path.join(routes.assets.path, assetsPath);
});
});
await Promise.all(promises);
return assets;
};

return {
generateStaticHtml: async (publicPath: string, outputBaseDir: string) => {
const assets = await copyAssets(path.join(outputBaseDir, routes.assets.path));
const dryRunCacheFilename = path.join(outputBaseDir, ".code-dependency.json");
const dryRun = DryRun.create(dryRunCacheFilename, isDryRun);
const dryRunCache = dryRun.getDryRunCache();
for await (const filePath of config.filePathList) {
const pathname = filePath.source;
if (dryRunCache[pathname] === "done") {
continue;
}
if (dryRunCache[pathname] === "pending") {
continue;
} else {
dryRunCache[pathname] = "pending";
}
dryRun.updateDryRunCache(dryRunCache);
const outputFilePath = path.join(outputBaseDir, "project", pathname).replace(path.extname(pathname), ".html");
const html = await generateStaticHtml(filePath.source, publicPath, assets);
writePage(outputFilePath, html);
dryRunCache[pathname] = "done";
}
dryRun.deleteDryRunCache();
},
};
};
Loading

0 comments on commit 543e380

Please sign in to comment.