@bouygues-telecom/staticjs 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ import fs from "fs";
2
+ import crypto from "node:crypto";
3
+ import path from "path";
4
+ const getDefaultExportFunctionName = (code) => {
5
+ const defaultExportRegex = /export\s+default\s+function\s+(\w+)/;
6
+ const match = code.match(defaultExportRegex);
7
+ if (match && match[1])
8
+ return match[1];
9
+ const defaultVarExportRegex = /export\s+default\s+(\w+)/;
10
+ const varMatch = code.match(defaultVarExportRegex);
11
+ if (varMatch && varMatch[1])
12
+ return varMatch[1];
13
+ return null;
14
+ };
15
+ export const addHydrationCodePlugin = (entries) => {
16
+ return {
17
+ name: "add-hydration-code",
18
+ transform(code, id) {
19
+ if (!Object.values(entries).includes(id))
20
+ return null;
21
+ const componentName = getDefaultExportFunctionName(code);
22
+ if (!componentName) {
23
+ console.warn(`No default export found in ${id}`);
24
+ return null;
25
+ }
26
+ const importReactDOM = `import ReactDOM from 'react-dom/client';`;
27
+ const importApp = `import { App } from "@/app";`;
28
+ const rootId = crypto
29
+ .createHash("sha256")
30
+ .update(`app-${id}`)
31
+ .digest("hex")
32
+ .slice(0, 10);
33
+ const initialDatasId = crypto
34
+ .createHash("sha256")
35
+ .update(`initial-data-${id}`)
36
+ .digest("hex")
37
+ .slice(0, 10);
38
+ const additionalCode = `
39
+ export const rootId = 'app-${rootId}';
40
+ export const initialDatasId = 'initial-data-${initialDatasId}';
41
+
42
+ if (typeof document !== 'undefined') {
43
+ document.addEventListener('DOMContentLoaded', () => {
44
+ const initialDataScript = document.getElementById(initialDatasId);
45
+ const initialData = initialDataScript ? JSON.parse(initialDataScript.textContent || '{}') : {title: ''};
46
+ ReactDOM.hydrateRoot(document.getElementById(rootId), App({Component:${componentName}, props: {data: initialData}}));
47
+ });
48
+ }
49
+ `;
50
+ const transformedCode = importReactDOM + "\n" + importApp + "\n" + code + "\n" + additionalCode;
51
+ return {
52
+ code: transformedCode,
53
+ map: null,
54
+ };
55
+ },
56
+ };
57
+ };
58
+ export const noJsPlugin = (entries) => {
59
+ return {
60
+ name: "no-js-plugin",
61
+ buildStart() {
62
+ const excludedFiles = [];
63
+ Object.entries(entries).forEach(([name, path]) => {
64
+ const content = fs.readFileSync(path, "utf8");
65
+ const firstLine = content.split("\n")[0];
66
+ if (firstLine.includes("no scripts")) {
67
+ delete entries[name];
68
+ excludedFiles.push(name);
69
+ console.log(`Excluding ${name} from build due to "no scripts" directive.`);
70
+ }
71
+ });
72
+ const cacheDir = path.resolve(process.cwd(), "cache");
73
+ const excludedFilePath = path.resolve(cacheDir, "excludedFiles.json");
74
+ if (!fs.existsSync(cacheDir)) {
75
+ fs.mkdirSync(cacheDir, { recursive: true });
76
+ }
77
+ fs.writeFileSync(excludedFilePath, JSON.stringify(excludedFiles, null, 2), "utf8");
78
+ },
79
+ };
80
+ };
@@ -2,7 +2,7 @@ import fs from "fs";
2
2
  import crypto from "node:crypto";
3
3
  import path from "path";
4
4
 
5
- const getDefaultExportFunctionName = (code) => {
5
+ const getDefaultExportFunctionName = (code: string) => {
6
6
  const defaultExportRegex = /export\s+default\s+function\s+(\w+)/;
7
7
  const match = code.match(defaultExportRegex);
8
8
  if (match && match[1]) return match[1];
@@ -12,10 +12,10 @@ const getDefaultExportFunctionName = (code) => {
12
12
  return null;
13
13
  };
14
14
 
15
- export const addHydrationCodePlugin = (entries) => {
15
+ export const addHydrationCodePlugin = (entries: { [key: string]: string }) => {
16
16
  return {
17
17
  name: "add-hydration-code",
18
- transform(code, id) {
18
+ transform(code: string, id: string) {
19
19
  if (!Object.values(entries).includes(id)) return null;
20
20
  const componentName = getDefaultExportFunctionName(code);
21
21
 
@@ -63,11 +63,11 @@ export const addHydrationCodePlugin = (entries) => {
63
63
  };
64
64
  };
65
65
 
66
- export const noJsPlugin = (entries) => {
66
+ export const noJsPlugin = (entries: { [key: string]: string }) => {
67
67
  return {
68
68
  name: "no-js-plugin",
69
69
  buildStart() {
70
- const excludedFiles = [];
70
+ const excludedFiles: string[] = [];
71
71
 
72
72
  Object.entries(entries).forEach(([name, path]) => {
73
73
  const content = fs.readFileSync(path, "utf8");
@@ -76,7 +76,9 @@ export const noJsPlugin = (entries) => {
76
76
  if (firstLine.includes("no scripts")) {
77
77
  delete entries[name];
78
78
  excludedFiles.push(name);
79
- console.log(`Excluding ${name} from build due to "no scripts" directive.`);
79
+ console.log(
80
+ `Excluding ${name} from build due to "no scripts" directive.`
81
+ );
80
82
  }
81
83
  });
82
84
 
@@ -11,8 +11,8 @@ let entries;
11
11
 
12
12
  if (args.length > 0) {
13
13
  entries = args
14
- .filter((arg) => arg.endsWith(".tsx"))
15
- .reduce((obj, tsxFile) => {
14
+ .filter((arg: string) => arg.endsWith(".tsx"))
15
+ .reduce((obj: { [key: string]: string }, tsxFile) => {
16
16
  console.log(`Processing arg: ${tsxFile}`);
17
17
  const relativePathWithoutExtension = tsxFile.replace(/\.tsx$/, "");
18
18
  const fullPath = path.resolve(pagesDir, tsxFile);
@@ -5,6 +5,16 @@ import ReactDOMServer from "react-dom/server";
5
5
 
6
6
  const outputDir = path.resolve(process.cwd(), "dist");
7
7
 
8
+ interface IcreatePage {
9
+ data:any,
10
+ AppComponent: React.FC<{ Component: React.FC; props: {} }>,
11
+ PageComponent: () => React.JSX.Element,
12
+ initialDatasId: string,
13
+ rootId:string,
14
+ pageName:string,
15
+ JSfileName:string | false,
16
+ }
17
+
8
18
  export const createPage = ({
9
19
  data,
10
20
  AppComponent,
@@ -13,7 +23,7 @@ export const createPage = ({
13
23
  rootId,
14
24
  pageName,
15
25
  JSfileName,
16
- }) => {
26
+ }: IcreatePage) => {
17
27
  const template = `
18
28
  <div id=app-{{rootId}}>{{html}}
19
29
  ${data ? `<script id=initial-data-{{initialDatasId}} type="application/json">${JSON.stringify(data)}</script>` : ""}
@@ -0,0 +1,26 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { readPages } from "./readPages.js";
4
+ const pagesDir = path.resolve(process.cwd(), "src/pages");
5
+ const args = process.argv.slice(2);
6
+ const cacheDir = path.resolve(process.cwd(), "cache");
7
+ const cacheFilePath = path.resolve(cacheDir, "pagesCache.json");
8
+ let entries;
9
+ if (args.length > 0) {
10
+ entries = args
11
+ .filter((arg) => arg.endsWith(".tsx"))
12
+ .reduce((obj, tsxFile) => {
13
+ console.log(`Processing arg: ${tsxFile}`);
14
+ const relativePathWithoutExtension = tsxFile.replace(/\.tsx$/, "");
15
+ const fullPath = path.resolve(pagesDir, tsxFile);
16
+ obj[relativePathWithoutExtension] = fullPath;
17
+ return obj;
18
+ }, {});
19
+ }
20
+ else {
21
+ entries = readPages(pagesDir);
22
+ }
23
+ if (!fs.existsSync(cacheDir))
24
+ fs.mkdirSync(cacheDir, { recursive: true });
25
+ fs.writeFileSync(cacheFilePath, JSON.stringify(entries, null, 2), "utf8");
26
+ console.log("Pages cached successfully.");
@@ -0,0 +1,23 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import React from "react";
4
+ import ReactDOMServer from "react-dom/server";
5
+ const outputDir = path.resolve(process.cwd(), "dist");
6
+ export const createPage = ({ data, AppComponent, PageComponent, initialDatasId, rootId, pageName, JSfileName, }) => {
7
+ const template = `
8
+ <div id=app-{{rootId}}>{{html}}
9
+ ${data ? `<script id=initial-data-{{initialDatasId}} type="application/json">${JSON.stringify(data)}</script>` : ""}
10
+ ${JSfileName ? `<script type="module" src="{{scriptPath}}"></script>` : ""}
11
+ </div>
12
+ `;
13
+ const component = React.createElement(AppComponent, {
14
+ Component: PageComponent,
15
+ props: { data },
16
+ });
17
+ const htmlContent = template
18
+ .replace("{{initialDatasId}}", initialDatasId)
19
+ .replace("{{rootId}}", rootId)
20
+ .replace("{{html}}", ReactDOMServer.renderToString(component))
21
+ .replace("{{scriptPath}}", `${JSfileName}.js`);
22
+ fs.writeFileSync(path.join(outputDir, `${pageName}.html`), htmlContent);
23
+ };
@@ -0,0 +1,19 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export function readPages(dir, baseDir = dir) {
4
+ let result = {};
5
+ const files = fs.readdirSync(dir);
6
+ for (const file of files) {
7
+ const fullPath = path.join(dir, file);
8
+ const stat = fs.statSync(fullPath);
9
+ if (stat.isDirectory()) {
10
+ const nested = readPages(fullPath, baseDir);
11
+ result = { ...result, ...nested };
12
+ }
13
+ else if (file.endsWith(".tsx")) {
14
+ const relPath = path.relative(baseDir, fullPath).replace(/\.tsx$/, "");
15
+ result[relPath] = fullPath;
16
+ }
17
+ }
18
+ return result;
19
+ }
@@ -1,8 +1,8 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
 
4
- export function readPages(dir, baseDir = dir) {
5
- let result = {};
4
+ export function readPages(dir: string, baseDir = dir) {
5
+ let result: { [key: string]: string } = {};
6
6
  const files = fs.readdirSync(dir);
7
7
 
8
8
  for (const file of files) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bouygues-telecom/staticjs",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "config",
@@ -14,10 +14,12 @@
14
14
  "bt-staticjs": "./scripts/cli.js"
15
15
  },
16
16
  "scripts": {
17
- "build": "cd templates/react && rimraf dist && rimraf cache && node ../../helpers/cachePages.js && vite build --config ../../vite.config.js && tsx ../../scripts/build-html.js",
17
+ "prebuild": "tsc -p ./tsconfig.json && mv dist/config config && mv dist/helpers helpers && mv dist/scripts scripts",
18
+ "build": "cd templates/react && rimraf dist && rimraf cache && node ../../dist/helpers/cachePages.js && vite build --config ../../vite.config.js && tsx ../../dist/scripts/build-html.js",
18
19
  "start": "cd templates/react && npm start"
19
20
  },
20
21
  "dependencies": {
22
+ "@types/node": "^20",
21
23
  "cli-spinner": "^0.2.10",
22
24
  "commander": "^14.0.0",
23
25
  "giget": "^2.0.0",
@@ -25,12 +27,17 @@
25
27
  "path": "^0.12.7",
26
28
  "readline": "^1.3.0",
27
29
  "rimraf": "^6.0.1",
28
- "tsx": "^4.19.4"
30
+ "tsx": "^4.19.4",
31
+ "typescript": "^5"
29
32
  },
30
33
  "volta": {
31
34
  "node": "24.1.0"
32
35
  },
33
36
  "workspaces": [
34
37
  "templates/react"
35
- ]
38
+ ],
39
+ "devDependencies": {
40
+ "@types/cli-spinner": "^0.2.3",
41
+ "@types/express": "^5.0.3"
42
+ }
36
43
  }
@@ -7,7 +7,7 @@ import { createPage } from "../helpers/createPage.js";
7
7
 
8
8
  const rootDir = path.resolve(process.cwd(), "./src");
9
9
 
10
- async function loadJson(filePath) {
10
+ async function loadJson(filePath: string) {
11
11
  const data = await fs.readFile(filePath, "utf-8");
12
12
  return JSON.parse(data);
13
13
  }
@@ -20,7 +20,7 @@ async function main() {
20
20
  path.join(path.resolve(process.cwd()), "./cache/pagesCache.json")
21
21
  );
22
22
 
23
- const processPage = async (page) => {
23
+ const processPage = async (page: { path: string; pageName: string }) => {
24
24
  try {
25
25
  let data;
26
26
  const absolutePath = page.path;
@@ -54,22 +54,25 @@ async function main() {
54
54
 
55
55
  if (getStaticProps && getStaticPaths) {
56
56
  const { paths } = await getStaticPaths();
57
- return paths.forEach(async (param) => {
58
- const slug = param.params[fileName.replace(/[\[\]]/g, "")];
59
- const { props } = await getStaticProps(param);
60
- const pageName = page.pageName.replace(/\[.*?\]/, slug);
61
- const JSfileName = injectJS && fileName.replace(/\[(.*?)\]/g, "_$1_");
62
-
63
- createPage({
64
- data: props.data,
65
- AppComponent,
66
- PageComponent,
67
- initialDatasId,
68
- rootId,
69
- pageName,
70
- JSfileName,
71
- });
72
- });
57
+ return paths.forEach(
58
+ async (param: { params: { [x: string]: string } }) => {
59
+ const slug = param.params[fileName.replace(/[\[\]]/g, "")];
60
+ const { props } = await getStaticProps(param);
61
+ const pageName = page.pageName.replace(/\[.*?\]/, slug);
62
+ const JSfileName =
63
+ injectJS && fileName.replace(/\[(.*?)\]/g, "_$1_");
64
+
65
+ createPage({
66
+ data: props.data,
67
+ AppComponent,
68
+ PageComponent,
69
+ initialDatasId,
70
+ rootId,
71
+ pageName,
72
+ JSfileName: JSfileName,
73
+ });
74
+ }
75
+ );
73
76
  }
74
77
 
75
78
  if (getStaticProps) {
@@ -94,8 +97,8 @@ async function main() {
94
97
  };
95
98
 
96
99
  const pages = Object.entries(files).map(([pageName, path]) => ({
97
- pageName,
98
- path,
100
+ pageName: pageName as string,
101
+ path: path as string,
99
102
  }));
100
103
 
101
104
  pages.forEach(processPage);
@@ -10,7 +10,7 @@ const rl = readline.createInterface({
10
10
  output: process.stdout,
11
11
  });
12
12
 
13
- async function createReactPowerStatic(projectName) {
13
+ async function createReactPowerStatic(projectName: string) {
14
14
  const dest = path.join(process.cwd(), projectName);
15
15
  const spinner = new Spinner("Creating react project... %s");
16
16
  spinner.setSpinnerString("|/-\\");
@@ -1,11 +1,12 @@
1
1
  import { exec } from "child_process";
2
+ import { Request, Response } from "express";
2
3
  import { dirname } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import path from "path";
5
6
 
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
8
 
8
- export const revalidate = (req, res) => {
9
+ export const revalidate = (req: Request, res: Response) => {
9
10
  try {
10
11
  const paths = req?.body?.paths || [];
11
12
  const pathsArg = paths.length > 0 ? paths.join(" ") : "";
@@ -0,0 +1,82 @@
1
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
2
+ import fs from "fs/promises";
3
+ import crypto from "node:crypto";
4
+ import path from "path";
5
+ import { createPage } from "../helpers/createPage.js";
6
+ const rootDir = path.resolve(process.cwd(), "./src");
7
+ async function loadJson(filePath) {
8
+ const data = await fs.readFile(filePath, "utf-8");
9
+ return JSON.parse(data);
10
+ }
11
+ async function main() {
12
+ const excludedJSFiles = await loadJson(path.join(path.resolve(process.cwd()), "./cache/excludedFiles.json"));
13
+ const files = await loadJson(path.join(path.resolve(process.cwd()), "./cache/pagesCache.json"));
14
+ const processPage = async (page) => {
15
+ try {
16
+ let data;
17
+ const absolutePath = page.path;
18
+ const pageModule = await import(absolutePath);
19
+ const appModule = await import(`${rootDir}/app.tsx`);
20
+ const fileName = path.basename(page.path, path.extname(page.path));
21
+ const AppComponent = appModule.App;
22
+ const PageComponent = pageModule.default;
23
+ const getStaticProps = pageModule?.getStaticProps;
24
+ const getStaticPaths = pageModule?.getStaticPaths;
25
+ const injectJS = !excludedJSFiles.includes(page.pageName);
26
+ const rootId = crypto
27
+ .createHash("sha256")
28
+ .update(`app-${absolutePath}`)
29
+ .digest("hex")
30
+ .slice(0, 10);
31
+ const initialDatasId = crypto
32
+ .createHash("sha256")
33
+ .update(`initial-data-${absolutePath}`)
34
+ .digest("hex")
35
+ .slice(0, 10);
36
+ if (!PageComponent) {
37
+ throw new Error(`Failed to import PageComponent from ${page.pageName}.tsx`);
38
+ }
39
+ if (getStaticProps && getStaticPaths) {
40
+ const { paths } = await getStaticPaths();
41
+ return paths.forEach(async (param) => {
42
+ const slug = param.params[fileName.replace(/[\[\]]/g, "")];
43
+ const { props } = await getStaticProps(param);
44
+ const pageName = page.pageName.replace(/\[.*?\]/, slug);
45
+ const JSfileName = injectJS && fileName.replace(/\[(.*?)\]/g, "_$1_");
46
+ createPage({
47
+ data: props.data,
48
+ AppComponent,
49
+ PageComponent,
50
+ initialDatasId,
51
+ rootId,
52
+ pageName,
53
+ JSfileName: JSfileName,
54
+ });
55
+ });
56
+ }
57
+ if (getStaticProps) {
58
+ const { props } = await getStaticProps();
59
+ data = props.data;
60
+ }
61
+ createPage({
62
+ data,
63
+ AppComponent,
64
+ PageComponent,
65
+ initialDatasId,
66
+ rootId,
67
+ pageName: page.pageName,
68
+ JSfileName: injectJS && fileName,
69
+ });
70
+ console.log(`Successfully wrote: dist/${page.pageName}.html`);
71
+ }
72
+ catch (error) {
73
+ console.error(`Error processing ${page.pageName}:`, error);
74
+ }
75
+ };
76
+ const pages = Object.entries(files).map(([pageName, path]) => ({
77
+ pageName: pageName,
78
+ path: path,
79
+ }));
80
+ pages.forEach(processPage);
81
+ }
82
+ main();
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import { execSync } from "node:child_process";
4
+ import { dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import path from "path";
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ program
9
+ .command("build")
10
+ .description("Build with static.js configuration")
11
+ .action(() => {
12
+ try {
13
+ const cachePagesPath = path.resolve(__dirname, "../helpers/cachePages.js");
14
+ const htmlConfig = path.resolve(__dirname, "./build-html.js");
15
+ const dest = process.cwd();
16
+ console.log("Executing static.js config...");
17
+ execSync("rimraf dist", {
18
+ cwd: dest,
19
+ stdio: "inherit",
20
+ });
21
+ execSync(`rimraf cache && node ${cachePagesPath}`, {
22
+ cwd: dest,
23
+ stdio: "inherit",
24
+ });
25
+ execSync(`vite build && tsx ${htmlConfig}`, {
26
+ cwd: dest,
27
+ stdio: "inherit",
28
+ });
29
+ console.log("Build completed successfully");
30
+ }
31
+ catch (error) {
32
+ console.error("Build failed:", error);
33
+ process.exit(1);
34
+ }
35
+ });
36
+ program.parse(process.argv);
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { Spinner } from "cli-spinner";
3
+ import { downloadTemplate } from "giget";
4
+ import path from "path";
5
+ import readline from "readline";
6
+ const rl = readline.createInterface({
7
+ input: process.stdin,
8
+ output: process.stdout,
9
+ });
10
+ async function createReactPowerStatic(projectName) {
11
+ const dest = path.join(process.cwd(), projectName);
12
+ const spinner = new Spinner("Creating react project... %s");
13
+ spinner.setSpinnerString("|/-\\");
14
+ spinner.start();
15
+ try {
16
+ await downloadTemplate(`github:BouyguesTelecom/static.js/templates/react`, {
17
+ force: true,
18
+ provider: "github",
19
+ cwd: dest,
20
+ dir: `.`,
21
+ });
22
+ spinner.stop(true);
23
+ console.log("React project created successfully in " + dest);
24
+ }
25
+ catch (error) {
26
+ spinner.stop(true);
27
+ console.error("Error can't create react project:", error);
28
+ process.exit(1);
29
+ }
30
+ finally {
31
+ rl.close();
32
+ }
33
+ }
34
+ rl.question("Enter the name for your new project: ", (projectName) => {
35
+ createReactPowerStatic(projectName);
36
+ });
@@ -0,0 +1,31 @@
1
+ import { exec } from "child_process";
2
+ import { dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import path from "path";
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ export const revalidate = (req, res) => {
7
+ try {
8
+ const paths = req?.body?.paths || [];
9
+ const pathsArg = paths.length > 0 ? paths.join(" ") : "";
10
+ const cachePages = path.resolve(__dirname, "../helpers/cachePages.js");
11
+ const buildHtmlConfig = path.resolve(__dirname, "./build-html.js");
12
+ const buildCommand = `NODE_TLS_REJECT_UNAUTHORIZED=0 node ${cachePages} ${pathsArg && `${pathsArg}`} && npx tsx ${buildHtmlConfig}`;
13
+ exec(buildCommand, (error, stdout, stderr) => {
14
+ if (error) {
15
+ console.error(`Exec error: ${error}`);
16
+ return;
17
+ }
18
+ if (!error) {
19
+ console.log(`stdout: ${stdout}`);
20
+ console.error(`stderr: ${stderr}`);
21
+ }
22
+ });
23
+ return res
24
+ .status(200)
25
+ .send(`Revalidation triggered, paths: ${paths.length > 0 ? paths.join(", ") : "all pages"} built!`);
26
+ }
27
+ catch (error) {
28
+ console.error("Revalidation error:", error);
29
+ res.status(500).send("Error during revalidation.");
30
+ }
31
+ };
File without changes