Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(export-static): add --export-static option #33

Merged
merged 30 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bfc7a82
feat: add exporter
Himenon Jan 30, 2020
22780b3
fix: add process.setMaxListeners
Himenon Jan 30, 2020
04136a4
feat: success assets load
Himenon Jan 30, 2020
da9457a
feat: generate svg elements
Himenon Jan 30, 2020
fddc0a2
feat: update router
Himenon Jan 31, 2020
d9fba54
feat: update react router hoc
Himenon Feb 3, 2020
27139d8
feat: add useHistory
Himenon Feb 3, 2020
381157e
feat: change query pathname
Himenon Feb 3, 2020
0995b52
feat: restore element by reopen window
Himenon Feb 3, 2020
af0d05d
feat: seperate project view
Himenon Feb 4, 2020
393e68c
refactor: use flat state
Himenon Feb 4, 2020
485541b
feat: start csr after page loaded
Himenon Feb 4, 2020
fb3e1d7
feat: change output directory to project
Himenon Feb 4, 2020
8c50ca6
feat: force reload static link
Himenon Feb 4, 2020
e17068c
fix: current pathname title
Himenon Feb 4, 2020
8b6a330
feat: open current directory child paths
Himenon Feb 4, 2020
24aefa5
feat: add error boundary
Himenon Feb 4, 2020
8124e22
fix: Error: Invariant failed
Himenon Feb 4, 2020
b7f7f66
feat: update routing
Himenon Feb 5, 2020
4d775c5
feat: update routing
Himenon Feb 5, 2020
ab61c47
feat: add invalidUrl check
Himenon Feb 5, 2020
0f340ec
feat: update async loop
Himenon Feb 5, 2020
cdf5ca4
feat: add assets base path
Himenon Feb 5, 2020
7fa1f34
fix: cruise path
Himenon Feb 5, 2020
de2d124
feat: add dry run
Himenon Feb 5, 2020
2e67036
refactor: path option
Himenon Feb 5, 2020
6efc144
refactor: rename props key names
Himenon Feb 5, 2020
d0492b7
refactor(cli): change routing path to const
Himenon Feb 5, 2020
a52ca70
docs: update document
Himenon Feb 5, 2020
9796b39
chore: add generate docs script
Himenon Feb 5, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new !

.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