leanweb 2.0.5 → 3.0.1

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,49 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ const __filename = fileURLToPath(import.meta.url);
4
+ const __dirname = path.dirname(__filename);
5
+ import { createRequire } from "module";
6
+ const require = createRequire(import.meta.url);
7
+
8
+ import fs from 'fs';
9
+ import * as utils from './utils.js';
10
+
11
+ (async () => {
12
+ const args = process.argv;
13
+ if (args.length < 3) {
14
+ console.error('Usage: lw addpage page names');
15
+ return;
16
+ }
17
+
18
+ const leanwebJSONPath = `${process.cwd()}/${utils.dirs.src}/leanweb.json`;
19
+ const leanwebJSON = require(leanwebJSONPath);
20
+ const pages = args.slice(2);
21
+
22
+ const projectName = path.basename(path.resolve());
23
+
24
+ leanwebJSON.pages = leanwebJSON.pages ?? [];
25
+ for (const pageJSON of leanwebJSON.pages) {
26
+ for (const page of pages) {
27
+ if (pageJSON === page) {
28
+ console.error(`Error: page ${pageJSON} existed.`);
29
+ return;
30
+ }
31
+ }
32
+ }
33
+
34
+ leanwebJSON.pages.push(...pages);
35
+ fs.writeFileSync(leanwebJSONPath, JSON.stringify(leanwebJSON, null, 2));
36
+
37
+ for (const page of pages) {
38
+ const pageName = utils.getComponentName(page);
39
+ const pagePath = `${utils.dirs.src}/${utils.getComponentPath(page)}`;
40
+ fs.mkdirSync(pagePath, { recursive: true });
41
+ if (!fs.existsSync(`${pagePath}/${pageName}.html`)) {
42
+ let htmlString = fs.readFileSync(`${__dirname}/../templates/page.html`, 'utf8');
43
+ htmlString = htmlString.replace(/\$\{project\.name\}/g, projectName);
44
+ htmlString = htmlString.replace(/\$\{page\.name\}/g, pageName);
45
+ htmlString = htmlString.replace(/\$\{pathLevels\}/g, utils.getPathLevels(page));
46
+ fs.writeFileSync(`${pagePath}/${pageName}.html`, htmlString);
47
+ }
48
+ }
49
+ })();
package/commands/build.js CHANGED
@@ -10,95 +10,77 @@ import fse from 'fs-extra';
10
10
  import { minify } from 'html-minifier';
11
11
  import * as utils from './utils.js';
12
12
  import * as parser from '../lib/lw-html-parser.js';
13
- import CleanCSS from 'clean-css';
14
13
 
15
- (async () => {
16
- let env;
17
- const args = process.argv;
18
- if (args.length >= 3) {
19
- env = args[2];
20
- }
14
+ let env;
15
+ const args = process.argv;
16
+ if (args.length >= 3) {
17
+ env = args[2];
18
+ }
21
19
 
22
- const leanwebPackageJSON = require(`${__dirname}/../package.json`);
20
+ const leanwebPackageJSON = require(`${__dirname}/../package.json`);
23
21
 
24
- const buildModule = (projectPath) => {
22
+ const buildModule = (projectPath) => {
25
23
 
26
- const project = require(`${projectPath}/${utils.dirs.src}/leanweb.json`);
24
+ const project = require(`${projectPath}/${utils.dirs.src}/leanweb.json`);
27
25
 
28
- fs.mkdirSync(utils.dirs.build, { recursive: true });
26
+ fs.mkdirSync(utils.dirs.build, { recursive: true });
29
27
 
30
- const copySrc = () => {
31
- fse.copySync(`${projectPath}/${utils.dirs.src}/`, utils.dirs.build, { filter: utils.copyFilter });
32
- };
28
+ const copySrc = () => {
29
+ fse.copySync(`${projectPath}/${utils.dirs.src}/`, utils.dirs.build, { filter: utils.copyFilter });
30
+ };
33
31
 
34
- const copyEnv = () => {
35
- if (env) {
36
- fse.copySync(`${utils.dirs.build}/env/${env}.js`, `${utils.dirs.build}/env.js`, { filter: utils.copyFilter });
37
- }
38
- };
32
+ const copyEnv = () => {
33
+ if (env) {
34
+ fse.copySync(`${utils.dirs.build}/env/${env}.js`, `${utils.dirs.build}/env.js`, { filter: utils.copyFilter });
35
+ }
36
+ };
39
37
 
40
- const buildJS = () => {
41
- const jsString = project.components.reduce((acc, cur) => {
42
- const cmpName = utils.getComponentName(cur);
43
- let importString = `import './components/${cur}/${cmpName}.js';`;
44
- return acc + importString + '\n';
45
- }, '');
46
- utils.writeIfChanged(`${utils.dirs.build}/${project.name}.js`, jsString);
47
- };
38
+ const buildJS = () => {
39
+ const jsString = project.components.reduce((acc, cur) => {
40
+ const cmpName = utils.getComponentName(cur);
41
+ let importString = `import './components/${cur}/${cmpName}.js';`;
42
+ return acc + importString + '\n';
43
+ }, '');
44
+ utils.writeIfChanged(`${utils.dirs.build}/${project.name}.js`, jsString);
45
+ };
48
46
 
49
- const buildHTML = () => {
50
- project.components.forEach(cmp => {
51
- const cmpName = utils.getComponentName(cmp);
52
- const htmlFilename = `${utils.dirs.build}/components/${cmp}/${cmpName}.html`;
53
- const htmlFileExists = fs.existsSync(htmlFilename);
54
- if (htmlFileExists) {
55
- const scssFilename = `${utils.dirs.build}/components/${cmp}/${cmpName}.scss`;
56
- const scssFileExists = fs.existsSync(scssFilename);
57
- let cssString = '';
58
- if (scssFileExists) {
59
- let scssString = `@use "global-styles.scss";\n`;
60
- scssString += fs.readFileSync(scssFilename, 'utf8');
61
- scssString += '\n[lw-false],[lw-for]{display:none !important;}\n';
62
- cssString = utils.buildCSS(scssString, utils.dirs.build, `${utils.dirs.build}/components/${cmp}`);
63
- }
64
- const htmlString = fs.readFileSync(htmlFilename, 'utf8');
65
- const minifiedHtml = minify(htmlString, {
66
- caseSensitive: true,
67
- collapseWhitespace: true,
68
- minifyCSS: true,
69
- minifyJS: true,
70
- removeComments: true,
71
- });
72
- const ast = parser.parse(minifiedHtml);
73
- const minifiedCss = new CleanCSS({}).minify(cssString ?? '');
74
- ast.css = minifiedCss.styles ?? '';
75
- ast.componentFullName = project.name + '-' + cmp.replace(/\//g, '-');
76
- ast.runtimeVersion = project.version;
77
- ast.builderVersion = leanwebPackageJSON.version;
78
- utils.writeIfChanged(`${utils.dirs.build}/components/${cmp}/ast.js`, `export default ${JSON.stringify(ast, null, 0)};`);
47
+ const buildHTML = () => {
48
+ project.components.forEach(cmp => {
49
+ const cmpName = utils.getComponentName(cmp);
50
+ const htmlFilename = `${utils.dirs.build}/components/${cmp}/${cmpName}.html`;
51
+ const htmlFileExists = fs.existsSync(htmlFilename);
52
+ if (htmlFileExists) {
53
+ const cssFilename = `${utils.dirs.build}/components/${cmp}/${cmpName}.css`;
54
+ const cssFileExists = fs.existsSync(cssFilename);
55
+ let cssString = `@import "global-styles.css";\n`;
56
+ if (cssFileExists) {
57
+ cssString += fs.readFileSync(cssFilename, 'utf8');
79
58
  }
80
- });
81
- };
82
-
83
- const buildSCSS = () => {
84
- const projectScssFilename = `${projectPath}/${utils.dirs.src}/${project.name}.scss`;
85
- let projectCssString = '';
86
- if (fs.existsSync(projectScssFilename)) {
87
- const projectScssString = fs.readFileSync(projectScssFilename, 'utf8');
88
- projectCssString += utils.buildCSS(projectScssString, utils.dirs.build);
59
+ cssString += '\n[lw-false],[lw-for]{display:none !important;}\n';
60
+ const htmlString = fs.readFileSync(htmlFilename, 'utf8');
61
+ const minifiedHtml = minify(htmlString, {
62
+ caseSensitive: true,
63
+ collapseWhitespace: true,
64
+ minifyCSS: true,
65
+ minifyJS: true,
66
+ removeComments: true,
67
+ });
68
+ const ast = parser.parse(minifiedHtml);
69
+ ast.css = cssString;
70
+ ast.componentFullName = project.name + '-' + cmp.replace(/\//g, '-');
71
+ ast.runtimeVersion = project.version;
72
+ ast.builderVersion = leanwebPackageJSON.version;
73
+ utils.writeIfChanged(`${utils.dirs.build}/components/${cmp}/ast.js`, `export default ${JSON.stringify(ast, null, 0)};`);
89
74
  }
90
- utils.writeIfChanged(`${utils.dirs.build}/${project.name}.css`, projectCssString);
91
- };
92
-
93
- copySrc();
94
- copyEnv();
95
- buildJS();
96
- buildSCSS();
97
- buildHTML();
98
-
99
- return project.name;
75
+ });
100
76
  };
101
77
 
102
- buildModule(process.cwd());
78
+ copySrc();
79
+ copyEnv();
80
+ buildJS();
81
+ buildHTML();
82
+
83
+ return project.name;
84
+ };
103
85
 
104
- })();
86
+ buildModule(process.cwd());
package/commands/clean.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import fs from 'fs';
2
2
  import * as utils from './utils.js';
3
3
 
4
- (async () => {
5
- fs.rmSync(utils.dirs.build + '/', { recursive: true, force: true });
6
- fs.rmSync(utils.dirs.dist + '/', { recursive: true, force: true });
7
- })();
4
+ fs.rmSync(utils.dirs.build + '/', { recursive: true, force: true });
5
+ fs.rmSync(utils.dirs.dist + '/', { recursive: true, force: true });
package/commands/dist.js CHANGED
@@ -5,7 +5,6 @@ import * as utils from './utils.js';
5
5
  import fs from 'fs';
6
6
  import fse from 'fs-extra';
7
7
  import { minify } from 'html-minifier';
8
- import CleanCSS from 'clean-css';
9
8
 
10
9
  import esbuild from 'esbuild';
11
10
 
@@ -17,51 +16,59 @@ if (args.length >= 3) {
17
16
 
18
17
  const verbose = process.env.verbose || false;
19
18
 
20
- (async () => {
21
- const project = require(`${process.cwd()}/${utils.dirs.src}/leanweb.json`);
19
+ const project = require(`${process.cwd()}/${utils.dirs.src}/leanweb.json`);
22
20
 
23
- await utils.exec(`npx leanweb clean`);
24
- await utils.exec(`npx leanweb build ${env}`);
21
+ utils.exec(`npx leanweb clean`);
22
+ utils.exec(`npx leanweb build ${env}`);
25
23
 
26
- fs.mkdirSync(utils.dirs.dist, { recursive: true });
27
- const result = await esbuild.build({
28
- entryPoints: [`./${utils.dirs.build}/${project.name}.js`],
29
- bundle: true,
30
- minify: true,
31
- sourcemap: true,
32
- format: 'esm',
33
- outfile: `./${utils.dirs.dist}/${project.name}.js`,
34
- metafile: !!verbose,
35
- });
36
- if (verbose) {
37
- const text = await esbuild.analyzeMetafile(result.metafile);
38
- console.log(text);
39
- }
24
+ fs.mkdirSync(utils.dirs.dist, { recursive: true });
25
+ const result = await esbuild.build({
26
+ entryPoints: [`./${utils.dirs.build}/${project.name}.js`],
27
+ bundle: true,
28
+ minify: true,
29
+ sourcemap: true,
30
+ format: 'esm',
31
+ outfile: `./${utils.dirs.dist}/${project.name}.js`,
32
+ metafile: !!verbose,
33
+ });
34
+ if (verbose) {
35
+ const text = await esbuild.analyzeMetafile(result.metafile);
36
+ console.log(text);
37
+ }
40
38
 
41
- const indexHTML = fs.readFileSync(`./${utils.dirs.build}/index.html`, 'utf8');
42
- const minifiedIndexHtml = minify(indexHTML, {
39
+ const minimizePage = page => {
40
+ const html = fs.readFileSync(`./${utils.dirs.build}/${page}.html`, 'utf8');
41
+ const minifiedIndexHtml = minify(html, {
43
42
  caseSensitive: true,
44
43
  collapseWhitespace: true,
45
44
  minifyCSS: true,
46
45
  minifyJS: true,
47
46
  removeComments: true,
48
47
  });
49
- fs.writeFileSync(`./${utils.dirs.dist}/index.html`, minifiedIndexHtml);
50
48
 
51
- const appCSS = fs.readFileSync(`./${utils.dirs.build}/${project.name}.css`, 'utf8');
52
- const minifiedAppCss = new CleanCSS({}).minify(appCSS);
53
- fs.writeFileSync(`./${utils.dirs.dist}/${project.name}.css`, minifiedAppCss.styles);
49
+ const pageName = utils.getComponentName(page);
50
+ const pagePath = `${utils.dirs.dist}/${utils.getComponentPath(page)}`;
51
+ fs.mkdirSync(pagePath, { recursive: true });
52
+ fs.writeFileSync(`${pagePath}/${pageName}.html`, minifiedIndexHtml);
53
+ };
54
54
 
55
- fse.copySync(`./${utils.dirs.build}/favicon.svg`, `./${utils.dirs.dist}/favicon.svg`);
56
- project.resources?.forEach(resource => {
57
- const source = `./${utils.dirs.build}/${resource}`;
58
- if (fs.existsSync(source)) {
59
- fse.copySync(source, `./${utils.dirs.dist}/${resource}`, { dereference: true });
60
- }
61
- });
55
+ project.pages ??= [];
56
+ project.pages.push('index');
57
+ project.pages.forEach(minimizePage);
58
+
59
+ const appCSS = fs.readFileSync(`./${utils.dirs.build}/${project.name}.css`, 'utf8');
60
+ fs.writeFileSync(`./${utils.dirs.dist}/${project.name}.css`, appCSS);
62
61
 
63
- const postDistFile = './post-dist';
64
- if (fs.existsSync(postDistFile) && fs.statSync(postDistFile).isFile()) {
65
- await utils.exec(postDistFile);
62
+ fse.copySync(`./${utils.dirs.build}/favicon.svg`, `./${utils.dirs.dist}/favicon.svg`);
63
+ fse.copySync(`./${utils.dirs.build}/global-styles.css`, `./${utils.dirs.dist}/global-styles.css`);
64
+ project.resources?.forEach(resource => {
65
+ const source = `./${utils.dirs.build}/${resource}`;
66
+ if (fs.existsSync(source)) {
67
+ fse.copySync(source, `./${utils.dirs.dist}/${resource}`, { dereference: true });
66
68
  }
67
- })();
69
+ });
70
+
71
+ const postDistFile = './post-dist';
72
+ if (fs.existsSync(postDistFile) && fs.statSync(postDistFile).isFile()) {
73
+ utils.exec(postDistFile);
74
+ }
@@ -50,8 +50,8 @@ import * as utils from './utils.js';
50
50
  fs.writeFileSync(`${cmpPath}/${cmpName}.html`, `<div>${leanwebJSON.name}-${cmpName} works!</div>`);
51
51
  }
52
52
 
53
- if (!fs.existsSync(`${cmpPath}/${cmpName}.scss`)) {
54
- fs.writeFileSync(`${cmpPath}/${cmpName}.scss`, '');
53
+ if (!fs.existsSync(`${cmpPath}/${cmpName}.css`)) {
54
+ fs.writeFileSync(`${cmpPath}/${cmpName}.css`, '');
55
55
  }
56
56
  }
57
57
  })();
package/commands/init.js CHANGED
@@ -23,7 +23,7 @@ import * as utils from './utils.js';
23
23
  return;
24
24
  }
25
25
 
26
- let projectName = path.basename(path.resolve());
26
+ const projectName = path.basename(path.resolve());
27
27
 
28
28
  if (args.length >= 3) {
29
29
  projectName = args[2];
@@ -38,20 +38,20 @@ import * as utils from './utils.js';
38
38
  ],
39
39
  };
40
40
 
41
- const projectScss = `// html,
42
- // body {
43
- // height: 100%;
44
- // width: 100%;
45
- // margin: 0 auto;
46
- // padding: 0;
41
+ const projectCss = `/* html,
42
+ body {
43
+ height: 100%;
44
+ width: 100%;
45
+ margin: 0 auto;
46
+ padding: 0;
47
47
 
48
- // font-family: "Roboto", "Helvetica", "Arial", sans-serif;
49
- // }
48
+ font-family: "Roboto", "Helvetica", "Arial", sans-serif;
49
+ } */
50
50
  `
51
51
 
52
- const globalScss = `// div {
53
- // color: tomato;
54
- // }
52
+ const globalCss = `/* div {
53
+ color: tomato;
54
+ } */
55
55
  `;
56
56
 
57
57
  fs.mkdirSync(`${utils.dirs.src}/resources/`, { recursive: true });
@@ -64,8 +64,8 @@ import * as utils from './utils.js';
64
64
  let htmlString = fs.readFileSync(`${__dirname}/../templates/index.html`, 'utf8');
65
65
  htmlString = htmlString.replace(/\$\{project\.name\}/g, projectName);
66
66
  fs.writeFileSync(`./${utils.dirs.src}/index.html`, htmlString);
67
- fs.writeFileSync(`./src/${projectName}.scss`, projectScss);
68
- fs.writeFileSync(`./${utils.dirs.src}/global-styles.scss`, globalScss);
67
+ fs.writeFileSync(`./src/${projectName}.css`, projectCss);
68
+ fs.writeFileSync(`./${utils.dirs.src}/global-styles.css`, globalCss);
69
69
  fse.copySync(`${__dirname}/../templates/favicon.svg`, `./${utils.dirs.src}/favicon.svg`);
70
70
  fse.copySync(`${__dirname}/../templates/env.js`, `./${utils.dirs.src}/env.js`);
71
71
  fse.copySync(`${__dirname}/../templates/env/`, `./${utils.dirs.src}/env/`);
package/commands/serve.js CHANGED
@@ -13,28 +13,25 @@ const host = process.env.host || '127.0.0.1';
13
13
  let port = process.env.port || 2020;
14
14
  const noopen = process.env.noopen || false;
15
15
 
16
- (async () => {
17
-
18
- const build = async (eventType, filename) => {
19
- // console.log(eventType + ': ', filename);
20
- await utils.exec(`npx leanweb build ${env}`);
21
- };
22
-
23
- const throttledBuild = utils.throttle(build);
24
- watch(process.cwd() + `/${utils.dirs.src}/`, { recursive: true }, (eventType, filename) => {
25
- throttledBuild(eventType, filename);
26
- });
27
-
28
- build();
29
-
30
- const params = {
31
- port,
32
- host,
33
- root: utils.dirs.build,
34
- open: !noopen,
35
- file: 'index.html',
36
- wait: 1000,
37
- logLevel: 1,
38
- };
39
- liveServer.start(params);
40
- })();
16
+ const build = (eventType, filename) => {
17
+ // console.log(eventType + ': ', filename);
18
+ utils.exec(`npx leanweb build ${env}`);
19
+ };
20
+
21
+ const throttledBuild = utils.throttle(build);
22
+ watch(process.cwd() + `/${utils.dirs.src}/`, { recursive: true }, (eventType, filename) => {
23
+ throttledBuild(eventType, filename);
24
+ });
25
+
26
+ build();
27
+
28
+ const params = {
29
+ port,
30
+ host,
31
+ root: utils.dirs.build,
32
+ open: !noopen,
33
+ file: 'index.html',
34
+ wait: 1000,
35
+ logLevel: 1,
36
+ };
37
+ liveServer.start(params);
package/commands/utils.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { execSync } from 'child_process';
2
- import sass from 'sass';
3
2
  import path from 'path';
4
3
  import net from 'net';
5
4
  import fs from 'fs';
@@ -37,15 +36,6 @@ export const writeIfChanged = (file, string) => {
37
36
 
38
37
  export const exec = command => execSync(command, { encoding: 'utf8', stdio: 'inherit' });
39
38
 
40
- export const buildCSS = (scssString, ...currentPaths) => {
41
- if (scssString.trim()) {
42
- const loadPaths = [...currentPaths, path.resolve(process.cwd(), dirs.build)];
43
- const cssResult = sass.compileString(scssString, { loadPaths });
44
- return cssResult.css.toString().trim();
45
- }
46
- return '';
47
- };
48
-
49
39
  export const getComponentName = cmp => {
50
40
  const indexOfLastSlash = cmp.lastIndexOf('/');
51
41
  if (indexOfLastSlash > -1) {
@@ -54,6 +44,14 @@ export const getComponentName = cmp => {
54
44
  return cmp;
55
45
  };
56
46
 
47
+ export const getComponentPath = cmp => {
48
+ const indexOfLastSlash = cmp.lastIndexOf('/');
49
+ if (indexOfLastSlash > -1) {
50
+ return cmp.substring(0, indexOfLastSlash);
51
+ }
52
+ return '';
53
+ };
54
+
57
55
  export const getPathLevels = filePath => {
58
56
  filePath = path.normalize(filePath);
59
57
  const numSlashes = filePath.replace(/[^\/]/g, '').length;
@@ -115,12 +113,12 @@ will be created. demo-root web component contains 3 files:
115
113
 
116
114
  root.html
117
115
  root.js
118
- root.scss
116
+ root.css
119
117
 
120
- Under src/ directory, global-styles.scss is created for global styling.
118
+ Under src/ directory, global-styles.css is created for global styling.
121
119
  `;
122
120
 
123
- const generateNote = `Usage: leanweb generate component-name
121
+ const generateNote = `Usage: leanweb generate component-names
124
122
  For example leanweb g login will create demo-login web component in
125
123
  src/components directory. The leanweb.json will be updated to look like:
126
124
 
@@ -140,12 +138,14 @@ demo-login web component will contain 3 files:
140
138
 
141
139
  login.html
142
140
  login.js
143
- login.scss
141
+ login.css
144
142
 
145
143
  Now, the demo-login component can be added in root.html as follows:
146
144
  <demo-login></demo-login>
147
145
  `;
148
146
 
147
+ const addpageNote = `Usage: leanweb addpage page-names`;
148
+
149
149
  const serveNote = `Usage: leabweb [env] serve or lw s [env]
150
150
  Running this command will start the dev server and open the app in a new
151
151
  browser window. Any chances to the source code will cause the dev server to
@@ -189,6 +189,7 @@ Print version information for leanweb.`;
189
189
 
190
190
  export const targets = {
191
191
  'init': { file: 'init.js', note: initNote },
192
+ 'addpage': { file: 'addpage.js', note: addpageNote },
192
193
  'generate': { file: 'generate.js', note: generateNote },
193
194
  'serve': { file: 'serve.js', note: serveNote },
194
195
  'build': { file: 'build.js', note: buildNote },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leanweb",
3
- "version": "2.0.5",
3
+ "version": "3.0.1",
4
4
  "description": "Builds framework agnostic web components.",
5
5
  "bin": {
6
6
  "leanweb": "leanweb.js",
@@ -20,17 +20,15 @@
20
20
  "author": "Qian Chen",
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
- "@babel/parser": "^7.21.1",
24
- "clean-css": "^5.3.2",
25
- "esbuild": "^0.17.10",
26
- "fs-extra": "^11.1.0",
27
- "globby": "^13.1.3",
23
+ "@babel/parser": "^7.24.1",
24
+ "esbuild": "^0.20.2",
25
+ "fs-extra": "^11.2.0",
26
+ "globby": "^14.0.1",
28
27
  "html-minifier": "^4.0.0",
29
- "isomorphic-git": "^1.21.0",
28
+ "isomorphic-git": "^1.25.6",
30
29
  "live-server": "^1.2.2",
31
- "node-watch": "^0.7.3",
30
+ "node-watch": "^0.7.4",
32
31
  "parse5": "^7.1.2",
33
- "sass": "^1.58.3",
34
- "semver": "^7.3.8"
32
+ "semver": "^7.6.0"
35
33
  }
36
- }
34
+ }
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>${page.name} - ${project.name}</title>
6
+ <script type="module" src="${pathLevels}${project.name}.js"></script>
7
+ <link rel="stylesheet" href="${pathLevels}${project.name}.css">
8
+ <link rel="icon" type="image/svg+xml" href="${pathLevels}favicon.svg">
9
+ </head>
10
+ <body style="opacity: 0;" onload="document.body.style.opacity=1">
11
+ ${page.name} works!
12
+ </body>
13
+ </html>